Merge branch 'rbac' of ssh://git.infini.ltd:64221/infini/console into rbac

This commit is contained in:
medcl 2022-05-09 20:40:06 +08:00
commit 99433c0fc4
26 changed files with 2786 additions and 80 deletions

274
config/map.json Normal file
View File

@ -0,0 +1,274 @@
{
"DELETE-/_ingest/pipeline/:id": "ingest.delete_pipeline",
"DELETE-/_scripts/:id": "script.delete",
"DELETE-/_search/scroll": "scroll.delete",
"DELETE-/_search/scroll/:scroll_id":"scroll.delete",
"DELETE-/_snapshot/:repository": "snapshot.delete_repository",
"DELETE-/_snapshot/:repository/:snapshot": "snapshot.delete",
"DELETE-/_template/:name": "indices.delete_template",
"DELETE-/:index": "indices.delete",
"DELETE-/:index/_alias/:name": "indices.delete_alias",
"DELETE-/:index/_aliases/:name": "indices.delete_alias",
"DELETE-/:index/_doc/:id": "doc.delete",
"GET-/": "cluster.info",
"GET-/_alias": "indices.get_alias",
"GET-/_alias/:name": "indices.get_alias",
"GET-/_analyze": "indices.analyze",
"GET-/_cat": "cat.help",
"GET-/_cat/aliases": "cat.aliases",
"GET-/_cat/aliases/:name": "cat.aliases",
"GET-/_cat/allocation": "cat.allocation",
"GET-/_cat/allocation/:node_id": "cat.allocation",
"GET-/_cat/count": "cat.count",
"GET-/_cat/count/:index": "cat.count",
"GET-/_cat/fielddata": "cat.fielddata",
"GET-/_cat/fielddata/:fields": "cat.fielddata",
"GET-/_cat/health": "cat.health",
"GET-/_cat/indices": "cat.indices",
"GET-/_cat/indices/:index": "cat.indices",
"GET-/_cat/master": "cat.master",
"GET-/_cat/nodeattrs": "cat.nodeattrs",
"GET-/_cat/nodes": "cat.nodes",
"GET-/_cat/pending_tasks": "cat.pending_tasks",
"GET-/_cat/plugins": "cat.plugins",
"GET-/_cat/recovery": "cat.recovery",
"GET-/_cat/recovery/:index": "cat.recovery",
"GET-/_cat/repositories": "cat.repositories",
"GET-/_cat/segments": "cat.segments",
"GET-/_cat/segments/:index": "cat.segments",
"GET-/_cat/shards": "cat.shards",
"GET-/_cat/shards/:index": "cat.shards",
"GET-/_cat/snapshots": "cat.snapshots",
"GET-/_cat/snapshots/:repository": "cat.snapshots",
"GET-/_cat/tasks": "cat.tasks",
"GET-/_cat/templates": "cat.templates",
"GET-/_cat/templates/:name": "cat.templates",
"GET-/_cat/thread_pool": "cat.thread_pool",
"GET-/_cat/thread_pool/:thread_pool_patterns": "cat.thread_pool",
"GET-/_cluster/allocation/explain": "cluster.allocation_explain",
"GET-/_cluster/health": "cluster.health",
"GET-/_cluster/health/:index": "cluster.health",
"GET-/_cluster/nodes/hot_threads": "nodes.hot_threads",
"GET-/_cluster/nodes/hotthreads": "nodes.hot_threads",
"GET-/_cluster/nodes/:node_id/hot_threads": "nodes.hot_threads",
"GET-/_cluster/nodes/:node_id/hotthreads": "nodes.hot_threads",
"GET-/_cluster/pending_tasks": "cluster.pending_tasks",
"GET-/_cluster/settings": "cluster.get_settings",
"GET-/_cluster/state": "cluster.state",
"GET-/_cluster/state/:metric": "cluster.state",
"GET-/_cluster/state/:metric/:index": "cluster.state",
"GET-/_cluster/stats": "cluster.stats",
"GET-/_cluster/stats/nodes/:node_id": "cluster.stats",
"GET-/_count": "cluster.count",
"GET-/_flush": "indices.flush",
"GET-/_flush/synced": "indices.flush_synced",
"GET-/_ingest/pipeline": "ingest.get_pipeline",
"GET-/_ingest/pipeline/_simulate": "ingest.simulate",
"GET-/_ingest/pipeline/:id": "ingest.get_pipeline",
"GET-/_ingest/pipeline/:id/_simulate": "ingest.simulate",
"GET-/_ingest/processor/grok": "ingest.processor_grok",
"GET-/_mapping": "indices.get_mapping",
"GET-/_mget": "cluster.mget",
"GET-/_msearch": "cluster.msearch",
"GET-/_msearch/template": "cluster.msearch_template",
"GET-/_mtermvectors": "cluster.mtermvectors",
"GET-/_nodes": "nodes.info",
"GET-/_nodes/hot_threads": "nodes.hot_threads",
"GET-/_nodes/hotthreads": "nodes.hot_threads",
"GET-/_nodes/stats": "nodes.stats",
"GET-/_nodes/stats/:metric": "nodes.stats",
"GET-/_nodes/stats/:metric/:index_metric": "nodes.stats",
"GET-/_nodes/usage": "nodes.usage",
"GET-/_nodes/usage/:metric": "nodes.usage",
"GET-/_nodes/:metric": "nodes.info",
"GET-/_nodes/:node_id": "nodes.info",
"GET-/_nodes/:node_id/hot_threads": "nodes.hot_threads",
"GET-/_nodes/:node_id/hotthreads": "nodes.hot_threads",
"GET-/_nodes/:node_id/stats": "nodes.stats",
"GET-/_nodes/:node_id/stats/:metric": "nodes.stats",
"GET-/_nodes/:node_id/stats/:metric/:index_metric": "nodes.stats",
"GET-/_nodes/:node_id/usage": "nodes.usage",
"GET-/_nodes/:node_id/usage/:metric": "nodes.usage",
"GET-/_nodes/:node_id/:metric": "nodes.info",
"GET-/_rank_eval": "cluster.rank_eval",
"GET-/_recovery": "indices.recovery",
"GET-/_refresh": "indices.refresh",
"GET-/_remote/info": "cluster.remote_info",
"GET-/_render/template": "render_search_template",
"GET-/_render/template/:id": "render_search_template",
"GET-/_scripts/painless/_execute": "scripts.painless_execute",
"GET-/_scripts/:id": "scripts.get",
"GET-/_search": "cluster.search",
"GET-/_search/scroll": "scroll.get",
"GET-/_search/scroll/:scroll_id": "scroll.get",
"GET-/_search/template": "search_template",
"GET-/_search_shards": "cluster.search_shards",
"GET-/_segments": "indices.segments",
"GET-/_settings": "indices.get_settings",
"GET-/_settings/:name": "indices.get_settings",
"GET-/_shard_stores": "indices.shard_stores",
"GET-/_snapshot": "snapshot.get_repository",
"GET-/_snapshot/_status": "snapshot.status",
"GET-/_snapshot/:repository": "snapshot.get_repository",
"GET-/_snapshot/:repository/_status": "snapshot.status",
"GET-/_snapshot/:repository/:snapshot": "snapshot.get",
"GET-/_snapshot/:repository/:snapshot/_status": "snapshot.status",
"GET-/_stats": "indices.stats",
"GET-/_stats/:metric": "indices.stats",
"GET-/_tasks": "tasks.list",
"GET-/_tasks/:task_id": "tasks.get",
"GET-/_template": "indices.get_template",
"GET-/_template/:name": "indices.get_template",
"GET-/_upgrade": "indices.get_upgrade",
"GET-/_validate/query": "indices.validate_query",
"GET-/:index": "indices.get",
"GET-/:index/_alias": "indices.get_alias",
"GET-/:index/_alias/:name": "indices.get_alias",
"GET-/:index/_analyze": "indices.analyze",
"GET-/:index/_count": "indices.count",
"GET-/:index/_doc/:id": "doc.get",
"GET-/:index/_field_caps": "indices.field_caps",
"GET-/:index/_flush": "indices.flush",
"GET-/:index/_flush/synced": "indices.flush_synced",
"GET-/:index/_mapping": "indices.get_mapping",
"GET-/:index/_mappings": "indices.get_mapping",
"GET-/:index/_mapping/field/:fields": "indices.get_field_mapping",
"GET-/:index/_mget": "indices.mget",
"GET-/:index/_msearch": "indices.msearch",
"GET-/:index/_msearch/template": "indices.msearch_template",
"GET-/:index/_mtermvectors": "indices.mtermvectors",
"GET-/:index/_rank_eval": "indices.rank_eval",
"GET-/:index/_recovery": "indices.recovery",
"GET-/:index/_refresh": "indices.refresh",
"GET-/:index/_search": "indices.search",
"GET-/:index/_search/template": "indices.search_template",
"GET-/:index/_search_shards": "indices.search_shards",
"GET-/:index/_segments": "indices.segments",
"GET-/:index/_settings": "indices.get_settings",
"GET-/:index/_settings/:name": "indices.get_settings",
"GET-/:index/_shard_stores": "indices.shard_stores",
"GET-/:index/_stats": "indices.stats",
"GET-/:index/_stats/:metric": "indices.stats",
"GET-/:index/_upgrade": "indices.get_upgrade",
"GET-/:index/_validate/query": "indices.validate_query",
"HEAD-/": "cluster.info",
"HEAD-/_alias/:name": "indices.exists_alias",
"HEAD-/_template/:name": "indices.exists_template",
"HEAD-/:index": "indices.exists",
"HEAD-/:index/_alias/:name": "indices.exists_alias",
"HEAD-/:index/_analyze": "indices.analyze",
"HEAD-/:index/_mapping/{type}": "indices.exists_type",
"HEAD-/:index/_doc/{id}": "doc.exists",
"HEAD-/:index/_doc/{id}/_source": "doc.exists_source",
"POST-/_aliases": "indices.update_aliases",
"POST-/_analyze": "indices.analyze",
"POST-/_bulk": "cluster.bulk",
"POST-/_cache/clear": "indices.clear_cache",
"POST-/_cluster/allocation/explain": "cluster.allocation_explain",
"POST-/_cluster/reroute": "cluster.reroute",
"POST-/_count": "cluster.count",
"POST-/_delete_by_query/:task_id/_rethrottle": "reindex_rethrottle",
"POST-/_field_caps": "cluster.field_caps",
"POST-/_flush": "indices.flush",
"POST-/_flush/synced": "indices.flush_synced",
"POST-/_forcemerge": "indices.forcemerge",
"POST-/_ingest/pipeline/_simulate": "ingest.simulate",
"POST-/_ingest/pipeline/:id/_simulate": "ingest.simulate",
"POST-/_mget": "cluster.mget",
"POST-/_msearch": "cluster.msearch",
"POST-/_msearch/template": "cluster.msearch_template",
"POST-/_mtermvectors": "cluster.mtermvectors",
"POST-/_nodes/reload_secure_settings": "nodes.reload_secure_settings",
"POST-/_nodes/:node_id/reload_secure_settings": "nodes.reload_secure_settings",
"POST-/_rank_eval": "cluster.rank_eval",
"POST-/_refresh": "indices.refresh",
"POST-/_reindex": "reindex",
"POST-/_reindex/:task_id/_rethrottle": "reindex_rethrottle",
"POST-/_render/template": "render_search_template.create",
"POST-/_render/template/:id": "render_search_template.get",
"POST-/_scripts/painless/_execute": "scripts_painless_execute",
"POST-/_scripts/:id": "scripts.put",
"POST-/_scripts/:id/:context": "scripts.put",
"POST-/_search/scroll": "scroll.create",
"POST-/_search/scroll/:scroll_id": "scroll.create",
"POST-/_search/template": "search_template",
"POST-/_snapshot/:repository": "snapshot.create_repository",
"POST-/_snapshot/:repository/_verify": "snapshot.verify_repository",
"POST-/_snapshot/:repository/:snapshot": "snapshot.create",
"POST-/_snapshot/:repository/:snapshot/_restore": "snapshot.restore",
"POST-/_tasks/_cancel": "tasks.cancel",
"POST-/_tasks/:task_id/_cancel": "tasks.cancel",
"POST-/_template/:name": "indices.put_template",
"POST-/_update_by_query/:task_id/_rethrottle": "reindex_rethrottle",
"POST-/_upgrade": "indices.upgrade",
"POST-/_validate/query": "indices.validate_query",
"POST-/:alias/_rollover": "indices.rollover",
"POST-/:alias/_rollover/:new_index": "indices.rollover",
"POST-/:index/_alias/:name": "indices.put_alias",
"POST-/:index/_aliases/:name": "indices.put_alias",
"POST-/:index/_analyze": "indices.analyze",
"POST-/:index/_bulk": "indices.bulk",
"POST-/:index/_cache/clear": "indices.clear_cache",
"POST-/:index/_close": "indices.close",
"POST-/:index/_count": "indices.count",
"POST-/:index/_delete_by_query": "indices.delete_by_query",
"POST-/:index/_doc": "doc.create",
"POST-/:index/_doc/:id": "doc.update",
"POST-/:index/_doc/:id/_update": "doc.update",
"POST-/:index/_field_caps": "indices.field_caps",
"POST-/:index/_flush": "indices.flush",
"POST-/:index/_flush/synced": "indices.flush_synced",
"POST-/:index/_forcemerge": "indices.forcemerge",
"POST-/:index/_mget": "indices.mget",
"POST-/:index/_msearch": "indices.msearch",
"POST-/:index/_msearch/template": "indices.msearch_template",
"POST-/:index/_mtermvectors": "indices.mtermvectors",
"POST-/:index/_open": "indices.open",
"POST-/:index/_rank_eval": "indices.rank_eval",
"POST-/:index/_refresh": "indices.refresh",
"POST-/:index/_search": "indices.search",
"POST-/:index/_search/template": "indices.search_template",
"POST-/:index/_search_shards": "indices.search_shards",
"POST-/:index/_shrink/:target": "indices.shrink",
"POST-/:index/_split/:target": "indices.split",
"POST-/:index/_update_by_query": "indices.update_by_query",
"POST-/:index/_upgrade": "indices.upgrade",
"POST-/:index/_validate/query": "indices.validate_query",
"POST-/:index/_mapping": "indices.put_mapping",
"POST-/:index/_mappings": "indices.put_mapping",
"PUT-/_bulk": "cluster.bulk",
"PUT-/_cluster/settings": "cluster.put_settings",
"PUT-/_ingest/pipeline/:id": "ingest.put_pipeline",
"PUT-/_scripts/:id": "scripts.put",
"PUT-/_scripts/:id/:context": "scripts.put",
"PUT-/_settings": "cluster.settings_put",
"PUT-/_snapshot/:repository": "snapshot.create_repository",
"PUT-/_snapshot/:repository/:snapshot": "snapshot.create",
"PUT-/_template/:name": "indices.put_template",
"PUT-/:index": "indices.put",
"PUT-/:index/_alias/:name": "indices.put_alias",
"PUT-/:index/_aliases/:name": "indices.put_alias",
"PUT-/:index/_bulk": "indices.put_bulk",
"PUT-/:index/_doc/:id": "doc.put",
"PUT-/:index/_settings": "indices.put_settings",
"PUT-/:index/_shrink/:target": "indices.shrink",
"PUT-/:index/_split/:target": "indices.split",
"PUT-/:index/_mapping": "indices.put_mapping",
"PUT-/:index/_mappings": "indices.put_mapping"
}

186
config/permission.json Normal file
View File

@ -0,0 +1,186 @@
{
"cat": [
"cat.*",
"cat.indices",
"cat.help",
"cat.repositories",
"cat.pending_tasks",
"cat.tasks",
"cat.allocation",
"cat.count",
"cat.shards",
"cat.aliases",
"cat.nodeattrs",
"cat.templates",
"cat.thread_pool",
"cat.health",
"cat.recovery",
"cat.fielddata",
"cat.nodes",
"cat.plugins",
"cat.segments",
"cat.snapshots",
"cat.master"
],
"cluster": [
"cluster.*",
"cluster.health",
"cluster.get_settings",
"cluster.pending_tasks",
"cluster.stats",
"cluster.remote_info",
"cluster.allocation_explain",
"cluster.put_settings",
"cluster.reroute",
"cluster.count",
"cluster.state",
"cluster.info",
"cluster.bulk",
"cluster.mget",
"cluster.ping",
"cluster.msearch",
"cluster.msearch_template",
"cluster.mtermvectors",
"cluster.rank_eval",
"cluster.search",
"cluster.search_shards"
],
"doc": [
"doc.*",
"doc.update",
"doc.create",
"doc.delete",
"doc.get",
"doc.exists",
"doc.count",
"doc.exists_source",
"doc.bulk",
"doc.explain",
"doc.mget",
"doc.msearch",
"doc.msearch_template",
"doc.mtermvectors",
"doc.search"
],
"indices": [
"indices.*",
"indices.exists_alias",
"indices.get_alias",
"indices.recovery",
"indices.delete",
"indices.put",
"indices.clear_cache",
"indices.update_by_query",
"indices.shrink",
"indices.forcemerge",
"indices.put_alias",
"indices.create",
"indices.split",
"indices.flush",
"indices.get_mapping",
"indices.upgrade",
"indices.validate_query",
"indices.exists_template",
"indices.get_upgrade",
"indices.update_aliases",
"indices.analyze",
"indices.exists",
"indices.close",
"indices.delete_template",
"indices.get_field_mapping",
"indices.delete_alias",
"indices.exists_type",
"indices.get_template",
"indices.put_template",
"indices.refresh",
"indices.segments",
"indices.termvectors",
"indices.flush_synced",
"indices.put_mapping",
"indices.get",
"indices.get_settings",
"indices.open",
"indices.put_settings",
"indices.stats",
"indices.delete_by_query",
"indices.rollover",
"indices.count",
"indices.shard_stores",
"indices.bulk",
"indices.mget",
"indices.msearch",
"indices.msearch_template",
"indices.mtermvectors",
"indices.rank_eval",
"indices.search",
"indices.search_shards",
"indices.field_caps"
],
"ingest": [
"ingest.*",
"ingest.delete_pipeline",
"ingest.put_pipeline",
"ingest.simulate",
"ingest.get_pipeline",
"ingest.processor_grok"
],
"nodes": [
"nodes.*",
"nodes.info",
"nodes.stats",
"nodes.reload_secure_settings",
"nodes.usage",
"nodes.hot_threads"
],
"reindex": [
"reindex.*",
"reindex.rethrottle"
],
"render_search_template": [
"render_search_template.*",
"render_search_template.create",
"render_search_template.*"
],
"scripts": [
"scripts.*",
"scripts.get",
"scripts.put",
"scripts.delete",
"scripts.painless_execute"
],
"scroll": [
"scroll.*",
"scroll.delete",
"scroll.get",
"scroll.create"
],
"snapshot": [
"snapshot.*",
"snapshot.get_repository",
"snapshot.create_repository",
"snapshot.create",
"snapshot.restore",
"snapshot.status",
"snapshot.delete",
"snapshot.delete_repository",
"snapshot.verify_repository",
"snapshot.get"
],
"tasks": [
"tasks.*",
"tasks.list",
"tasks.cancel",
"tasks.get"
]
}

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

@ -0,0 +1,219 @@
package biz
import (
"errors"
"github.com/golang-jwt/jwt"
"github.com/mitchellh/mapstructure"
"golang.org/x/crypto/bcrypt"
"infini.sh/console/internal/dto"
"infini.sh/console/model/rbac"
"infini.sh/framework/core/event"
"infini.sh/framework/core/global"
"infini.sh/framework/core/orm"
"infini.sh/framework/core/util"
"time"
)
type UserClaims struct {
*jwt.RegisteredClaims
*User
}
type User struct {
Username string `json:"username"`
UserId string `json:"user_id"`
Roles []string `json:"roles"`
}
type Account struct {
ID string `json:"id,omitempty" `
Created string `json:"created,omitempty" `
Updated string `json:"updated,omitempty" `
Username string `json:"username" elastic_mapping:"username:{type:keyword}"`
Password string `json:"password" elastic_mapping:"password:{type:text}"`
Name string `json:"name" elastic_mapping:"name:{type:keyword}"`
Phone string `json:"phone" elastic_mapping:"phone:{type:keyword}"`
Email string `json:"email" elastic_mapping:"email:{type:keyword}"`
Tags []string `json:"tags" elastic_mapping:"tags:{type:text}"`
Roles []rbac.UserRole `json:"roles"`
}
const Secret = "console"
func authenticateUser(username string, password string) (user Account, err error) {
err, result := orm.GetBy("name", username, rbac.User{})
if err != nil {
err = ErrNotFound
return
}
if result.Total == 0 {
err = errors.New("user not found")
return
}
err = mapstructure.Decode(result.Result[0], &user)
if err != nil {
return
}
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
if err == bcrypt.ErrMismatchedHashAndPassword {
err = errors.New("password incorrect")
return
}
return
}
func authenticateAdmin(username string, password string) (user Account, 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
}
user.ID = username
user.Username = username
user.Roles = []rbac.UserRole{{
ID: "admin", Name: "admin",
}}
return user, nil
}
func authorize(user Account) (m map[string]interface{}, err error) {
var roles, privilege []string
for _, v := range user.Roles {
role := RoleMap[v.Name]
roles = append(roles, v.Name)
privilege = append(privilege, role.Privilege.Platform...)
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, UserClaims{
User: &User{
Username: user.Username,
UserId: user.ID,
Roles: roles,
},
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": user.Username,
"id": user.ID,
"expire_in": 86400,
"roles": roles,
"privilege": privilege,
}
return
}
func Login(username string, password string) (m map[string]interface{}, err error) {
var user Account
if username == "admin" {
user, err = authenticateAdmin(username, password)
if err != nil {
return nil, err
}
} else {
user, err = authenticateUser(username, password)
if err != nil {
return nil, err
}
}
m, err = authorize(user)
if err != nil {
return
}
TokenMap[user.ID] = Token{ExpireIn: time.Now().Unix() + 86400}
err = orm.Save(GenerateEvent(event.ActivityMetadata{
Category: "platform",
Group: "rbac",
Name: "login",
Type: "create",
Labels: util.MapStr{
"username": username,
"password": password,
},
User: util.MapStr{
"id": user.ID,
"name": user.Username,
},
}, nil, nil))
return
}
func UpdatePassword(localUser *User, req dto.UpdatePassword) (err error) {
user := rbac.User{}
user.ID = localUser.UserId
_, err = orm.Get(&user)
if err != nil {
return
}
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.OldPassword))
if err == bcrypt.ErrMismatchedHashAndPassword {
err = errors.New("old password is not correct")
return
}
hash, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost)
if err != nil {
return
}
user.Password = string(hash)
err = orm.Save(&user)
if err != nil {
return
}
err = orm.Save(GenerateEvent(event.ActivityMetadata{
Category: "platform",
Group: "rbac",
Name: "user",
Type: "update",
Labels: util.MapStr{
"old_password": req.OldPassword,
"new_password": req.NewPassword,
},
User: util.MapStr{
"id": user.ID,
"name": user.Name,
},
}, nil, nil))
return
}
func UpdateProfile(localUser *User, req dto.UpdateProfile) (err error) {
user := rbac.User{}
user.ID = localUser.UserId
_, err = orm.Get(&user)
if err != nil {
return
}
user.Name = req.Name
user.Email = req.Email
user.Phone = req.Phone
err = orm.Save(&user)
if err != nil {
return
}
err = orm.Save(GenerateEvent(event.ActivityMetadata{
Category: "platform",
Group: "rbac",
Name: "user",
Type: "update",
Labels: util.MapStr{
"name": req.Name,
"email": req.Email,
"phone": req.Phone,
},
User: util.MapStr{
"id": user.ID,
"name": user.Name,
},
}, nil, nil))
return
}

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

@ -0,0 +1,23 @@
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
}

192
internal/biz/enum/const.go Normal file
View File

@ -0,0 +1,192 @@
package enum
import (
"time"
)
var PermissionMap = make(map[string][]string)
const (
UserRead = "system.user:read"
UserAll = "system.user:all"
RoleRead = "system.role:read"
RoleAll = "system.role:all"
ClusterAll = "system.cluster:all"
ClusterRead = "system.cluster:read"
CommandAll = "system.command:all"
CommandRead = "system.command:read"
InstanceRead = "gateway.instance:read"
InstanceAll = "gateway.instance:all"
EntryAll = "gateway.entry:all"
EntryRead = "gateway.entry:read"
RouterRead = "gateway.router:read"
RouterAll = "gateway.router:all"
FlowRead = "gateway.flow:read"
FlowAll = "gateway.flow:all"
IndexAll = "data.index:all"
IndexRead = "data.index:read"
ViewsAll = "data.view:all"
ViewsRead = "data.view:read"
DiscoverAll = "data.discover:all"
DiscoverRead = "data.discover:read"
RuleRead = "alerting.rule:read"
RuleAll = "alerting.rule:all"
AlertRead = "alerting.alert:read"
AlertAll = "alerting.alert:all"
ChannelRead = "alerting.channel:read"
ChannelAll = "alerting.channel:all"
ClusterOverviewRead = "cluster.overview:read"
ClusterOverviewAll = "cluster.overview:all"
MonitoringRead = "cluster.monitoring:read"
MonitoringAll = "cluster.monitoring:all"
ActivitiesRead = "cluster.activities:read"
ActivitiesAll = "cluster.activities:all"
)
const (
PermissionUserRead string = "user:read"
PermissionUserWrite = "user:write"
PermissionRoleRead = "role:read"
PermissionRoleWrite = "role:write"
PermissionCommandRead = "command:read"
PermissionCommandWrite = "command:write"
PermissionElasticsearchClusterRead = "es.cluster:read"
PermissionElasticsearchClusterWrite = "es.cluster:write" // es cluster
PermissionElasticsearchIndexRead = "es.index:read"
PermissionElasticsearchIndexWrite = "es.index:write" // es index metadata
PermissionElasticsearchNodeRead = "es.node:read" //es node metadata
PermissionActivityRead = "activity:read"
PermissionActivityWrite = "activity:write"
PermissionAlertRuleRead = "alert.rule:read"
PermissionAlertRuleWrite = "alert.rule:write"
PermissionAlertHistoryRead = "alert.history:read"
PermissionAlertHistoryWrite = "alert.history:write"
PermissionAlertChannelRead = "alert.channel:read"
PermissionAlertChannelWrite = "alert.channel:write"
PermissionViewRead = "view:read"
PermissionViewWrite = "view:write"
PermissionGatewayInstanceRead = "gateway.instance:read"
PermissionGatewayInstanceWrite = "gateway.instance:write"
PermissionGatewayEntryRead = "gateway.entry:read"
PermissionGatewayEntryWrite = "gateway.entry:write"
PermissionGatewayRouterRead = "gateway.router:read"
PermissionGatewayRouterWrite = "gateway.router:write"
PermissionGatewayFlowRead = "gateway.flow:read"
PermissionGatewayFlowWrite = "gateway.flow:write"
PermissionElasticsearchMetricRead = "es.metric:read"
)
var (
UserReadPermission = []string{PermissionUserRead}
UserAllPermission = []string{PermissionUserRead, PermissionUserWrite,PermissionRoleRead}
RoleReadPermission = []string{PermissionRoleRead}
RoleAllPermission = []string{PermissionRoleRead, PermissionRoleWrite}
ClusterReadPermission = []string{PermissionElasticsearchClusterRead}
ClusterAllPermission = []string{PermissionElasticsearchClusterRead, PermissionElasticsearchClusterWrite}
CommandReadPermission = []string{PermissionCommandRead}
CommandAllPermission = []string{PermissionCommandRead, PermissionCommandWrite}
InstanceReadPermission = []string{PermissionGatewayInstanceRead}
InstanceAllPermission = []string{PermissionGatewayInstanceRead,PermissionGatewayInstanceWrite}
EntryReadPermission = []string{PermissionGatewayEntryRead}
EntryAllPermission = []string{PermissionGatewayEntryRead, PermissionGatewayEntryWrite}
RouterReadPermission = []string{PermissionGatewayRouterRead}
RouterAllPermission = []string{PermissionGatewayRouterRead, PermissionGatewayRouterWrite}
FlowReadPermission = []string{PermissionGatewayFlowRead}
FlowAllPermission = []string{PermissionGatewayFlowRead, PermissionGatewayFlowWrite}
IndexAllPermission = []string{"index:read"}
IndexReadPermission = []string{"index:read", "index:write"}
ViewsAllPermission = []string{PermissionViewRead}
ViewsReadPermission = []string{PermissionViewRead, PermissionViewWrite}
DiscoverReadPermission = []string{PermissionViewRead}
DiscoverAllPermission = []string{PermissionViewRead}
RuleReadPermission = []string{PermissionAlertRuleRead}
RuleAllPermission = []string{PermissionAlertRuleRead, PermissionAlertRuleWrite}
AlertReadPermission = []string{PermissionAlertHistoryRead}
AlertAllPermission = []string{PermissionAlertHistoryRead, PermissionAlertHistoryWrite}
ChannelReadPermission = []string{PermissionAlertChannelRead}
ChannelAllPermission = []string{PermissionAlertChannelRead, PermissionAlertChannelWrite}
ClusterOverviewReadPermission = []string{PermissionElasticsearchClusterRead, PermissionElasticsearchIndexRead, PermissionElasticsearchNodeRead, PermissionElasticsearchMetricRead}
ClusterOverviewAllPermission = ClusterOverviewReadPermission
MonitoringReadPermission = ClusterOverviewAllPermission
ActivitiesReadPermission = []string{PermissionActivityRead}
ActivitiesAllPermission = []string{PermissionActivityRead, PermissionActivityWrite}
)
var AdminPrivilege = []string{
UserAll, RoleAll, ClusterAll, CommandAll,
InstanceAll, EntryAll, RouterAll, FlowAll,
IndexAll, ViewsAll, DiscoverAll,
RuleAll, AlertAll, ChannelAll,
ClusterOverviewAll, MonitoringAll, ActivitiesAll,
}
var BuildRoles = make(map[string]map[string]interface{}, 0)
func init() {
BuildRoles["admin"] = map[string]interface{}{
"id": "admin",
"name": "管理员",
"type": "platform",
"platform": AdminPrivilege,
"builtin": true,
"description": "is admin",
"created": time.Now(),
}
PermissionMap = map[string][]string{
UserRead: UserReadPermission,
UserAll: UserAllPermission,
RoleRead: RoleReadPermission,
RoleAll: RoleAllPermission,
ClusterRead: ClusterReadPermission,
ClusterAll: ClusterAllPermission,
CommandRead: CommandReadPermission,
CommandAll: CommandAllPermission,
InstanceRead: InstanceReadPermission,
InstanceAll: InstanceAllPermission,
EntryRead: EntryReadPermission,
EntryAll: EntryAllPermission,
RouterRead: RouterReadPermission,
RouterAll: RouterAllPermission,
FlowRead: FlowReadPermission,
FlowAll: FlowAllPermission,
IndexAll: IndexAllPermission,
IndexRead: IndexReadPermission,
ViewsAll: ViewsAllPermission,
ViewsRead: ViewsReadPermission,
DiscoverRead: DiscoverReadPermission,
DiscoverAll: DiscoverAllPermission,
RuleRead: RuleReadPermission,
RuleAll: RuleAllPermission,
AlertRead: AlertReadPermission,
AlertAll: AlertAllPermission,
ChannelRead: ChannelReadPermission,
ChannelAll: ChannelAllPermission,
ClusterOverviewRead: ClusterOverviewReadPermission,
ClusterOverviewAll: ClusterOverviewAllPermission,
MonitoringAll: MonitoringReadPermission,
MonitoringRead: MonitoringReadPermission,
ActivitiesAll: ActivitiesAllPermission,
ActivitiesRead: ActivitiesReadPermission,
}
}

18
internal/biz/event.go Normal file
View File

@ -0,0 +1,18 @@
package biz
import (
"infini.sh/framework/core/event"
"infini.sh/framework/core/util"
"time"
)
func GenerateEvent(metadata event.ActivityMetadata, fields util.MapStr, changeLog interface{}) *event.Activity {
return &event.Activity{
ID: util.GetUUID(),
Timestamp: time.Now(),
Metadata: metadata,
Fields: fields,
Changelog: changeLog,
}
}

View File

@ -0,0 +1,40 @@
package biz
import (
"infini.sh/console/model/rbac"
)
var ClusterApis = make(map[string][]string)
var IndexApis = make([]string, 50)
var RoleMap = make(map[string]rbac.Role)
type Token struct {
JwtStr string `json:"jwt_str"`
Value string `json:"value"`
ExpireIn int64 `json:"expire_in"`
}
var TokenMap = make(map[string]Token)
type RolePermission struct {
Platform []string `json:"platform,omitempty"`
Cluster []string `json:"cluster"`
ClusterPrivilege []string `json:"cluster_privilege"`
IndexPrivilege map[string][]string `json:"index_privilege"`
}
func ListElasticsearchPermission() interface{} {
list := ElasticsearchPermission{
ClusterPrivileges: ClusterApis,
IndexPrivileges: IndexApis,
}
return list
}
type ElasticsearchPermission struct {
IndexPrivileges []string `json:"index_privileges"`
ClusterPrivileges map[string][]string `json:"cluster_privileges"`
}

186
internal/biz/role.go Normal file
View File

@ -0,0 +1,186 @@
package biz
import (
"errors"
"fmt"
"infini.sh/console/internal/biz/enum"
"infini.sh/console/model/rbac"
"infini.sh/framework/core/event"
"infini.sh/framework/core/orm"
"infini.sh/framework/core/util"
log "src/github.com/cihub/seelog"
"strings"
"time"
)
type RoleType = string
const (
Platform RoleType = "platform"
Elastisearch RoleType = "elasticsearch"
)
func UpdateRole(localUser *User, role *rbac.Role) (err error) {
model, err := GetRole(role.ID)
if err != nil {
return err
}
role.Type = model.Type
role.Created = model.Created
changeLog, _ := util.DiffTwoObject(model, role)
role.Updated = time.Now()
err = orm.Save(role)
if err != nil {
return
}
RoleMap[model.Name] = model
err = orm.Save(GenerateEvent(event.ActivityMetadata{
Category: "platform",
Group: "rbac",
Name: "role",
Type: "update",
Labels: util.MapStr{
"id": model.ID,
"description": model.Description,
"privilege": role.Privilege,
"updated": model.Updated,
},
User: util.MapStr{
"userid": localUser.UserId,
"username": localUser.Username,
},
}, nil, changeLog))
return
}
func CreateRole(localUser *User, role *rbac.Role) (id string, err error) {
if role.Name == "" {
err = errors.New("role name is require")
return
}
if _, ok := enum.BuildRoles[role.Name]; ok {
err = fmt.Errorf("role name %s already exists", role.Name)
return
}
q := orm.Query{Size: 1}
q.Conds = orm.And(orm.Eq("name", role.Name))
err, result := orm.Search(rbac.Role{}, &q)
if err != nil {
return
}
if result.Total > 0 {
err = fmt.Errorf("role name %s already exists", role.Name)
return
}
role.ID = util.GetUUID()
role.Created = time.Now()
role.Updated = time.Now()
err = orm.Save(role)
if err != nil {
return
}
id = role.ID
RoleMap[role.Name] = *role
err = orm.Save(GenerateEvent(event.ActivityMetadata{
Category: "platform",
Group: "rbac",
Name: "role",
Type: "create",
Labels: util.MapStr{
"id": id,
"name": role.Name,
"description": role.Description,
"privilege": role.Privilege,
"type": role.Type,
},
User: util.MapStr{
"userid": localUser.UserId,
"username": localUser.Username,
},
}, nil, nil))
if err != nil {
log.Error(err)
}
return
}
func DeleteRole(localUser *User, id string) (err error) {
role := rbac.Role{}
role.ID = id
roleName := role.Name
_, err = orm.Get(&role)
if err != nil {
return
}
err = orm.Delete(&role)
if err != nil {
return
}
delete(RoleMap, roleName)
err = orm.Save(GenerateEvent(event.ActivityMetadata{
Category: "platform",
Group: "rbac",
Name: "role",
Type: "delete",
Labels: util.MapStr{
"id": id,
},
User: util.MapStr{
"userid": localUser.UserId,
"username": localUser.Username,
},
}, util.MapStr{
"id": id,
"name": role.Name,
"description": role.Description,
"type": role.Type,
"created": role.Created.Format("2006-01-02 15:04:05"),
"updated": role.Updated.Format("2006-01-02 15:04:05"),
}, nil))
return
}
func GetRole(id string) (role rbac.Role, err error) {
role.ID = id
_, err = orm.Get(&role)
if err != nil {
return
}
return
}
func SearchRole(keyword string, from, size int) (roles orm.Result, err error) {
query := orm.Query{}
queryDSL := `{"query":{"bool":{"must":[%s]}}, "from": %d,"size": %d}`
mustBuilder := &strings.Builder{}
if keyword != "" {
mustBuilder.WriteString(fmt.Sprintf(`{"query_string":{"default_field":"*","query": "%s"}}`, keyword))
}
queryDSL = fmt.Sprintf(queryDSL, mustBuilder.String(), from, size)
query.RawQuery = []byte(queryDSL)
err, roles = orm.Search(rbac.Role{}, &query)
return
}
func IsAllowRoleType(roleType string) (err error) {
if roleType != Platform && roleType != Elastisearch {
err = fmt.Errorf("invalid role type %s ", roleType)
return
}
return
}

279
internal/biz/user.go Normal file
View File

@ -0,0 +1,279 @@
package biz
import (
"fmt"
"golang.org/x/crypto/bcrypt"
"infini.sh/console/internal/dto"
"infini.sh/console/model/rbac"
"infini.sh/framework/core/event"
"infini.sh/framework/core/orm"
"infini.sh/framework/core/util"
"strings"
"time"
)
var ErrNotFound = fmt.Errorf("not found")
func DeleteUser(localUser *User, id string) (err error) {
user := rbac.User{}
user.ID = id
_, err = orm.Get(&user)
if err != nil {
return
}
err = orm.Delete(user)
if err != nil {
return
}
delete(TokenMap, id)
err = orm.Save(GenerateEvent(event.ActivityMetadata{
Category: "platform",
Group: "rbac",
Name: "user",
Type: "delete",
Labels: util.MapStr{
"id": id,
},
User: util.MapStr{
"userid": localUser.UserId,
"username": localUser.Username,
},
}, util.MapStr{
"id": id,
"name": user.Name,
"email": user.Email,
"phone": user.Phone,
"password": user.Password,
"nickname": user.NickName,
"tags": user.Tags,
"roles": user.Roles,
"created": user.Created,
"updated": user.Updated,
}, nil))
return
}
func CreateUser(localUser *User, req dto.CreateUser) (id string, password string, err error) {
q := orm.Query{Size: 1000}
q.Conds = orm.And(orm.Eq("name", req.Name))
err, result := orm.Search(rbac.User{}, &q)
if err != nil {
return
}
if result.Total > 0 {
err = fmt.Errorf("user name %s already exists", req.Name)
return
}
roles := make([]rbac.UserRole, 0)
for _, v := range req.Roles {
roles = append(roles, rbac.UserRole{
ID: v.Id,
Name: v.Name,
})
}
randStr := util.GenerateRandomString(8)
hash, err := bcrypt.GenerateFromPassword([]byte(randStr), bcrypt.DefaultCost)
if err != nil {
return
}
user := rbac.User{
Name: req.Name,
NickName: req.NickName,
Password: string(hash),
Email: req.Email,
Phone: req.Phone,
Roles: roles,
Tags: req.Tags,
}
user.ID = util.GetUUID()
user.Created = time.Now()
user.Updated = time.Now()
err = orm.Save(&user)
if err != nil {
return
}
id = user.ID
password = randStr
err = orm.Save(GenerateEvent(event.ActivityMetadata{
Category: "platform",
Group: "rbac",
Name: "user",
Type: "create",
Labels: util.MapStr{
"id": id,
"name": user.Name,
"email": user.Email,
"phone": user.Phone,
"password": user.Password,
"nick_name": user.NickName,
"tags": user.Tags,
"roles": user.Roles,
"created": user.Created,
"updated": user.Updated,
},
User: util.MapStr{
"userid": localUser.UserId,
"username": localUser.Username,
},
}, nil, nil))
return
}
func UpdateUser(localUser *User, id string, req dto.UpdateUser) (err error) {
user := rbac.User{}
user.ID = id
_, err = orm.Get(&user)
if err != nil {
return
}
roles := make([]rbac.UserRole, 0)
for _, v := range req.Roles {
roles = append(roles, rbac.UserRole{
ID: v.Id,
Name: v.Name,
})
}
changeLog, _ := util.DiffTwoObject(user, req)
user.Name = req.Name
user.Email = req.Email
user.Phone = req.Phone
user.Tags = req.Tags
user.Roles = roles
user.Updated = time.Now()
err = orm.Save(&user)
if err != nil {
return
}
delete(TokenMap, id)
err = orm.Save(GenerateEvent(event.ActivityMetadata{
Category: "platform",
Group: "rbac",
Name: "user",
Type: "update",
Labels: util.MapStr{
"id": id,
"email": user.Email,
"phone": user.Phone,
"name": user.Name,
"tags": user.Tags,
"roles": roles,
"updated": user.Updated,
},
User: util.MapStr{
"userid": localUser.UserId,
"username": localUser.Username,
},
}, nil, changeLog))
return
}
func UpdateUserRole(localUser *User, id string, req dto.UpdateUserRole) (err error) {
user := rbac.User{}
user.ID = id
_, err = orm.Get(&user)
if err != nil {
return
}
changeLog, _ := util.DiffTwoObject(user, req)
roles := make([]rbac.UserRole, 0)
for _, v := range req.Roles {
roles = append(roles, rbac.UserRole{
ID: v.Id,
Name: v.Name,
})
}
user.Roles = roles
user.Updated = time.Now()
err = orm.Save(&user)
if err != nil {
return
}
delete(TokenMap, id)
err = orm.Save(GenerateEvent(event.ActivityMetadata{
Category: "platform",
Group: "rbac",
Name: "user",
Type: "update",
Labels: util.MapStr{
"id": id,
"roles": user.Roles,
"updated": user.Updated,
},
User: util.MapStr{
"userid": localUser.UserId,
"username": localUser.Username,
},
}, nil, changeLog))
return
}
func GetUser(id string) (user rbac.User, err error) {
user.ID = id
_, err = orm.Get(&user)
if err != nil {
return
}
return
}
func SearchUser(keyword string, from, size int) (users orm.Result, err error) {
query := orm.Query{}
queryDSL := `{"query":{"bool":{"must":[%s]}}, "from": %d,"size": %d}`
mustBuilder := &strings.Builder{}
if keyword != "" {
mustBuilder.WriteString(fmt.Sprintf(`{"query_string":{"default_field":"*","query": "%s"}}`, keyword))
}
queryDSL = fmt.Sprintf(queryDSL, mustBuilder.String(), from, size)
query.RawQuery = []byte(queryDSL)
err, users = orm.Search(rbac.User{}, &query)
return
}
func UpdateUserPassword(localUser *User, id string, password string) (err error) {
user := rbac.User{}
user.ID = id
_, err = orm.Get(&user)
if err != nil {
return
}
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return
}
user.Password = string(hash)
user.Updated = time.Now()
err = orm.Save(&user)
if err != nil {
return
}
if localUser.UserId == id {
delete(TokenMap, localUser.UserId)
}
err = orm.Save(GenerateEvent(event.ActivityMetadata{
Category: "platform",
Group: "rbac",
Name: "user",
Type: "update",
Labels: util.MapStr{
"id": id,
"password": password,
"updated": user.Updated,
},
User: util.MapStr{
"userid": localUser.UserId,
"username": localUser.Username,
},
}, nil, nil))
return
}

273
internal/biz/validate.go Normal file
View File

@ -0,0 +1,273 @@
package biz
import (
"errors"
"fmt"
"infini.sh/console/internal/biz/enum"
"infini.sh/console/model/rbac"
httprouter "infini.sh/framework/core/api/router"
"infini.sh/framework/core/util"
"src/github.com/golang-jwt/jwt"
"strings"
"time"
)
type EsRequest struct {
Doc string `json:"doc"`
Privilege string `json:"privilege"`
ClusterRequest
IndexRequest
}
type ClusterRequest struct {
Cluster []string `json:"cluster"`
Privilege []string `json:"privilege"`
}
type IndexRequest struct {
Cluster []string `json:"cluster"`
Index []string `json:"index"`
Privilege []string `json:"privilege"`
}
func NewIndexRequest(ps httprouter.Params, privilege []string) IndexRequest {
index := ps.ByName("index")
clusterId := ps.ByName("id")
return IndexRequest{
Cluster: []string{clusterId},
Index: []string{index},
Privilege: privilege,
}
}
func NewClusterRequest(ps httprouter.Params, privilege []string) ClusterRequest {
clusterId := ps.ByName("id")
return ClusterRequest{
Cluster: []string{clusterId},
Privilege: privilege,
}
}
func ValidateElasticsearch(req rbac.ElasticsearchPrivilege, roleNames []string) (err error) {
return nil
}
func ValidateIndex(req IndexRequest, userRole RolePermission) (err error) {
userClusterMap := make(map[string]struct{})
for _, v := range userRole.Cluster {
userClusterMap[v] = struct{}{}
}
for _, v := range req.Cluster {
if _, ok := userClusterMap[v]; !ok {
err = errors.New("no cluster permission")
return
}
}
for _, val := range req.Privilege {
position := strings.Index(val, ".")
if position == -1 {
err = errors.New("invalid privilege parameter")
return err
}
prefix := val[:position]
for _, v := range req.Index {
privilege, ok := userRole.IndexPrivilege[v]
if !ok {
err = errors.New("no index permission")
return err
}
if util.StringInArray(privilege, prefix+".*") {
continue
}
if util.StringInArray(privilege, val) {
continue
}
return fmt.Errorf("no index api permission: %s", val)
}
}
return nil
}
func ValidateCluster(req ClusterRequest, roleNames []string) (err error) {
userClusterMap := GetRoleClusterMap(roleNames)
for _, v := range req.Cluster {
userClusterPermissions, ok := userClusterMap[v]
if !ok && userClusterMap["*"] == nil{
err = fmt.Errorf("no cluster[%s] permission", v)
return
}
if util.StringInArray(userClusterPermissions, "*") {
continue
}
// if include api.* for example: cat.* , return nil
for _, privilege := range req.Privilege {
prefix := privilege[:strings.Index(privilege, ".")]
if util.StringInArray(userClusterPermissions, prefix+".*") {
continue
}
if util.StringInArray(userClusterPermissions, privilege) {
continue
}
return fmt.Errorf("no cluster api permission: %s", privilege)
}
}
return nil
}
func CombineUserRoles(roleNames []string) RolePermission {
newRole := RolePermission{}
m := make(map[string][]string)
for _, val := range roleNames {
role := RoleMap[val]
for _, v := range role.Privilege.Elasticsearch.Cluster.Resources {
newRole.Cluster = append(newRole.Cluster, v.ID)
}
for _, v := range role.Privilege.Elasticsearch.Cluster.Permissions {
newRole.ClusterPrivilege = append(newRole.ClusterPrivilege, v)
}
for _, v := range role.Privilege.Platform {
newRole.Platform = append(newRole.Platform, v)
}
for _, v := range role.Privilege.Elasticsearch.Index {
for _, name := range v.Name {
if _, ok := m[name]; ok {
m[name] = append(m[name], v.Permissions...)
} else {
m[name] = v.Permissions
}
}
}
}
newRole.IndexPrivilege = m
return newRole
}
func GetRoleClusterMap(roles []string) map[string][]string {
userClusterMap := make(map[string][]string, 0)
for _, roleName := range roles {
role, ok := RoleMap[roleName]
if ok {
for _, ic := range role.Privilege.Elasticsearch.Cluster.Resources {
userClusterMap[ic.ID] = append(userClusterMap[ic.ID], role.Privilege.Elasticsearch.Cluster.Permissions...)
}
}
}
return userClusterMap
}
func GetRoleCluster(roles []string) []string {
userClusterMap := GetRoleClusterMap(roles)
realCluster := make([]string, 0, len(userClusterMap))
for k, _ := range userClusterMap {
realCluster = append(realCluster, k)
}
return realCluster
}
func GetRoleIndex(roles, clusterIDs []string) map[string][]string {
userClusterMap := make(map[string]struct{}, len(clusterIDs))
for _, clusterID := range clusterIDs {
userClusterMap[clusterID] = struct{}{}
}
realIndex := map[string][]string{}
for _, roleName := range roles {
role, ok := RoleMap[roleName]
if ok {
for _, ic := range role.Privilege.Elasticsearch.Cluster.Resources {
if _, ok = userClusterMap[ic.ID]; !ok {
continue
}
for _, ip := range role.Privilege.Elasticsearch.Index {
realIndex[ic.ID] = append(realIndex[ic.ID], ip.Name...)
}
}
}
}
return realIndex
}
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
}
clams, ok := token.Claims.(*UserClaims)
if clams.UserId == "" {
err = errors.New("user id is empty")
return
}
//fmt.Println("user token", clams.UserId, TokenMap[clams.UserId])
tokenVal, ok := TokenMap[clams.UserId]
if !ok {
err = errors.New("token is invalid")
return
}
if tokenVal.ExpireIn < time.Now().Unix() {
err = errors.New("token is expire in")
delete(TokenMap, clams.UserId)
return
}
if ok && token.Valid {
return clams, nil
}
return
}
func ValidatePermission(claims *UserClaims, permissions []string) (err error) {
user := claims.User
if user.UserId == "" {
err = errors.New("user id is empty")
return
}
if user.Roles == nil {
err = errors.New("api permission is empty")
return
}
// 权限校验
userPermissions := make([]string, 0)
for _, role := range user.Roles {
if _, ok := RoleMap[role]; ok {
for _, v := range RoleMap[role].Privilege.Platform {
userPermissions = append(userPermissions, v)
}
}
}
userPermissionMap := make(map[string]struct{})
for _, val := range userPermissions {
for _, v := range enum.PermissionMap[val] {
userPermissionMap[v] = struct{}{}
}
}
for _, v := range permissions {
if _, ok := userPermissionMap[v]; !ok {
err = errors.New("permission denied")
return
}
}
return nil
}

View File

@ -0,0 +1,147 @@
package biz
import (
"github.com/stretchr/testify/assert"
"infini.sh/framework/core/util"
"testing"
)
func Test_validateIndex(t *testing.T) {
type args struct {
req IndexRequest
userRole RolePermission
}
tests := []struct {
name string
args args
want string
}{
{"no index permission",
args{
req: IndexRequest{
Cluster: []string{"cluster1"},
Index: []string{"index2"},
Privilege: []string{"indices.mapping"},
},
userRole: RolePermission{
Cluster: []string{
"cluster1",
},
ClusterPrivilege: []string{
"cat.*",
},
IndexPrivilege: map[string][]string{
"index1": []string{"indices.delete"},
},
},
}, "no index permission",
},
{"no index api permission",
args{
req: IndexRequest{
Cluster: []string{"cluster1"},
Index: []string{"index1"},
Privilege: []string{"indices.mapping"},
},
userRole: RolePermission{
Cluster: []string{
"cluster1",
},
ClusterPrivilege: []string{
"cat.*",
},
IndexPrivilege: map[string][]string{
"index1": []string{"indices.delete"},
},
},
},
"no index api permission",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ValidateIndex(tt.args.req, tt.args.userRole)
assert.EqualError(t, got, tt.want)
})
}
}
func Test_validateCluster(t *testing.T) {
type args struct {
req ClusterRequest
userRole RolePermission
}
tests := []struct {
name string
args args
want string
}{
{"no cluster",
args{
req: ClusterRequest{
Cluster: []string{"cluster1"},
Privilege: []string{"indices.get_mapping"},
},
userRole: RolePermission{
Cluster: []string{
"cluster2",
},
ClusterPrivilege: []string{
"cat.*",
},
},
}, "no cluster permission",
},
{"no cluster",
args{
req: ClusterRequest{
Cluster: []string{"cluster1"},
Privilege: []string{"indices.get_mapping"},
},
userRole: RolePermission{
Cluster: []string{},
ClusterPrivilege: []string{},
},
}, "no cluster permission",
},
{"no cluster api",
args{
req: ClusterRequest{
Cluster: []string{"cluster1"},
Privilege: []string{"indices.get_mapping"},
},
userRole: RolePermission{
Cluster: []string{
"cluster1",
},
ClusterPrivilege: []string{
"cat.*",
},
},
},
"no cluster api permission",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ValidateCluster(tt.args.req, tt.args.userRole)
assert.EqualError(t, got, tt.want)
})
}
}
func TestStringInArray(t *testing.T) {
array := []string{"a", "b", "c", "d", "e"}
assert.Equal(t, true, util.StringInArray(array, "c"))
assert.Equal(t, false, util.StringInArray(array, "h"))
}

45
internal/core/response.go Normal file
View File

@ -0,0 +1,45 @@
package core
type Response struct {
Total int64 `json:"total,omitempty"`
Hit interface{} `json:"hit,omitempty"`
Id string `json:"_id,omitempty"`
Result string `json:"result,omitempty"`
}
type FoundResp struct {
Found bool `json:"found"`
Id string `json:"_id,omitempty"`
Source interface{} `json:"_source,omitempty"`
}
func CreateResponse(id string) Response {
return Response{
Id: id,
Result: "created",
}
}
func UpdateResponse(id string) Response {
return Response{
Id: id,
Result: "updated",
}
}
func DeleteResponse(id string) Response {
return Response{
Id: id,
Result: "deleted",
}
}
func NotFoundResponse(id string) FoundResp {
return FoundResp{
Id: id,
Found: false,
}
}
func FoundResponse(id string, data interface{}) FoundResp {
return FoundResp{
Id: id,
Found: true,
Source: data,
}
}

28
internal/dto/role.go Normal file
View File

@ -0,0 +1,28 @@
package dto
type CreateUser struct {
NickName string `json:"nick_name"`
Name string `json:"name"`
Email string `json:"email"`
Phone string `json:"phone"`
Roles []Role `json:"roles"`
Tags []string `json:"tags"`
}
type Role struct {
Id string `json:"id"`
Name string `json:"name"`
}
type UpdateUser struct {
Name string `json:"name"`
Email string `json:"email"`
Phone string `json:"phone"`
Tags []string `json:"tags"`
Roles []Role `json:"roles"`
}
type UpdateUserRole struct {
Roles []Role `json:"roles"`
}
type UpdateUserPassword struct {
Password string `json:"password"`
}

15
internal/dto/user.go Normal file
View File

@ -0,0 +1,15 @@
package dto
type Login struct {
Username string `json:"username"`
Password string `json:"password"`
}
type UpdatePassword struct {
OldPassword string `json:"old_password"`
NewPassword string `json:"new_password"`
}
type UpdateProfile struct {
Name string `json:"name"`
Phone string `json:"phone"`
Email string `json:"email"`
}

47
internal/middleware/es.go Normal file
View File

@ -0,0 +1,47 @@
package middleware
import (
"infini.sh/console/internal/biz"
httprouter "infini.sh/framework/core/api/router"
"net/http"
)
func IndexRequired(h httprouter.Handle, route ...string) 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, http.StatusUnauthorized, err)
return
}
newRole := biz.CombineUserRoles(claims.Roles)
indexReq := biz.NewIndexRequest(ps, route)
err = biz.ValidateIndex(indexReq, newRole)
if err != nil {
w = handleError(w, http.StatusForbidden, err)
return
}
h(w, r, ps)
}
}
func ClusterRequired(h httprouter.Handle, route ...string) 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, http.StatusUnauthorized, err)
return
}
//newRole := biz.CombineUserRoles(claims.Roles)
clusterReq := biz.NewClusterRequest(ps, route)
err = biz.ValidateCluster(clusterReq, claims.Roles)
if err != nil {
w = handleError(w, http.StatusForbidden, err)
return
}
h(w, r, ps)
}
}

View File

@ -0,0 +1,52 @@
package middleware
import (
"infini.sh/console/internal/biz"
httprouter "infini.sh/framework/core/api/router"
"infini.sh/framework/core/util"
"net/http"
)
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, http.StatusUnauthorized, 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) {
claims, err := biz.ValidateLogin(r.Header.Get("Authorization"))
if err != nil {
w = handleError(w, http.StatusUnauthorized, err)
return
}
err = biz.ValidatePermission(claims, permissions)
if err != nil {
w = handleError(w, http.StatusForbidden, err)
return
}
r = r.WithContext(biz.NewUserContext(r.Context(), claims))
h(w, r, ps)
}
}
func handleError(w http.ResponseWriter, statusCode int, err error) http.ResponseWriter {
w.Header().Set("Content-type", util.ContentTypeJson)
w.WriteHeader(statusCode)
json := util.ToJson(util.MapStr{
"error": util.MapStr{
"status": statusCode,
"reason": err.Error(),
},
}, true)
w.Write([]byte(json))
return w
}

19
main.go
View File

@ -7,7 +7,9 @@ import (
"infini.sh/console/model" "infini.sh/console/model"
"infini.sh/console/model/alerting" "infini.sh/console/model/alerting"
"infini.sh/console/model/gateway" "infini.sh/console/model/gateway"
"infini.sh/console/model/rbac"
_ "infini.sh/console/plugin" _ "infini.sh/console/plugin"
rbacApi "infini.sh/console/plugin/api/rbac"
alerting2 "infini.sh/console/service/alerting" alerting2 "infini.sh/console/service/alerting"
"infini.sh/framework" "infini.sh/framework"
"infini.sh/framework/core/elastic" "infini.sh/framework/core/elastic"
@ -51,7 +53,7 @@ func main() {
terminalFooter := "" terminalFooter := ""
app := framework.NewApp("console", "INFINI Cloud Console, The easiest way to operate your own elasticsearch platform.", app := framework.NewApp("console", "INFINI Cloud Console, The easiest way to operate your own elasticsearch platform.",
config.Version,config.BuildNumber, config.LastCommitLog, config.BuildDate, config.EOLDate, terminalHeader, terminalFooter) config.Version, config.BuildNumber, config.LastCommitLog, config.BuildDate, config.EOLDate, terminalHeader, terminalFooter)
app.Init(nil) app.Init(nil)
defer app.Shutdown() defer app.Shutdown()
@ -60,11 +62,10 @@ func main() {
if app.Setup(func() { if app.Setup(func() {
err := bootstrapRequirementCheck() err := bootstrapRequirementCheck()
if err !=nil{ if err != nil {
panic(err) panic(err)
} }
//load core modules first //load core modules first
module.RegisterSystemModule(&elastic2.ElasticModule{}) module.RegisterSystemModule(&elastic2.ElasticModule{})
module.RegisterSystemModule(&filter.FilterModule{}) module.RegisterSystemModule(&filter.FilterModule{})
@ -119,17 +120,17 @@ func main() {
module.Start() module.Start()
orm.RegisterSchemaWithIndexName(model.Dict{}, "dict") orm.RegisterSchemaWithIndexName(model.Dict{}, "dict")
orm.RegisterSchemaWithIndexName(model.Reindex{}, "reindex") orm.RegisterSchemaWithIndexName(model.Reindex{}, "reindex")
orm.RegisterSchemaWithIndexName(elastic.View{}, "view") orm.RegisterSchemaWithIndexName(elastic.View{}, "view")
orm.RegisterSchemaWithIndexName(alerting.Alert{}, "alerting-alerts") orm.RegisterSchemaWithIndexName(alerting.Alert{}, "alerting-alerts")
orm.RegisterSchemaWithIndexName(elastic.CommonCommand{}, "commands") orm.RegisterSchemaWithIndexName(elastic.CommonCommand{}, "commands")
orm.RegisterSchemaWithIndexName(elastic.TraceTemplate{}, "trace-template") orm.RegisterSchemaWithIndexName(elastic.TraceTemplate{}, "trace-template")
orm.RegisterSchemaWithIndexName(gateway.Instance{} , "gateway-instance") orm.RegisterSchemaWithIndexName(gateway.Instance{}, "gateway-instance")
orm.RegisterSchemaWithIndexName(alerting.Rule{} , "alert-rule") orm.RegisterSchemaWithIndexName(alerting.Rule{}, "alert-rule")
orm.RegisterSchemaWithIndexName(alerting.Alert{} , "alert-history") orm.RegisterSchemaWithIndexName(alerting.Alert{}, "alert-history")
orm.RegisterSchemaWithIndexName(rbac.Role{}, "rbac-role")
orm.RegisterSchemaWithIndexName(rbac.User{}, "rbac-user")
api.RegisterSchema() api.RegisterSchema()
go func() { go func() {
@ -138,6 +139,8 @@ func main() {
log.Errorf("init alerting task error: %v", err) log.Errorf("init alerting task error: %v", err)
} }
}() }()
go rbacApi.Init()
}, nil) { }, nil) {
app.Run() app.Run()
} }

40
model/rbac/role.go Normal file
View File

@ -0,0 +1,40 @@
package rbac
import (
"time"
)
type Role struct {
ID string `json:"id,omitempty" elastic_meta:"_id" elastic_mapping:"id: { type: keyword }"`
Created time.Time `json:"created,omitempty" elastic_mapping:"created: { type: date }"`
Updated time.Time `json:"updated,omitempty" elastic_mapping:"updated: { type: date }"`
Name string `json:"name" elastic_mapping:"name: { type: keyword }"`
Type string `json:"type" elastic_mapping:"type: { type: keyword }"`
Description string `json:"description" elastic_mapping:"description: { type: text }"`
Builtin bool `json:"builtin" elastic_mapping:"builtin: { type: boolean }"`
Privilege RolePrivilege `json:"privilege" elastic_mapping:"privilege: { type: object }"`
}
type RolePrivilege struct {
Platform []string `json:"platform,omitempty" elastic_mapping:"platform: { type: keyword }"`
Elasticsearch ElasticsearchPrivilege `json:"elasticsearch,omitempty" elastic_mapping:"elasticsearch: { type: object }"`
}
type ElasticsearchPrivilege struct {
Cluster ClusterPrivilege `json:"cluster,omitempty" elastic_mapping:"cluster: { type: object }"`
Index []IndexPrivilege `json:"index,omitempty" elastic_mapping:"index: { type: object }"`
}
type InnerCluster struct {
ID string `json:"id" elastic_mapping:"id: { type: keyword }"`
Name string `json:"name" elastic_mapping:"name: { type: keyword }"`
}
type ClusterPrivilege struct {
Resources []InnerCluster `json:"resources,omitempty" elastic_mapping:"resources: { type: object }"`
Permissions []string `json:"permissions,omitempty" elastic_mapping:"permissions: { type: keyword }"`
}
type IndexPrivilege struct {
Name []string `json:"name,omitempty" elastic_mapping:"name: { type: keyword }"`
Permissions []string `json:"permissions,omitempty" elastic_mapping:"permissions: { type: keyword }"`
}

23
model/rbac/user.go Normal file
View File

@ -0,0 +1,23 @@
package rbac
import (
"time"
)
type User struct {
ID string `json:"id,omitempty" elastic_meta:"_id" elastic_mapping:"id: { type: keyword }"`
Created time.Time `json:"created,omitempty" elastic_mapping:"created: { type: date }"`
Updated time.Time `json:"updated,omitempty" elastic_mapping:"updated: { type: date }"`
Name string `json:"name" elastic_mapping:"name: { type: keyword }"`
NickName string `json:"nick_name" elastic_mapping:"nick_name: { type: keyword }"`
Password string `json:"password" elastic_mapping:"password: { type: keyword }"`
Email string `json:"email" elastic_mapping:"email: { type: keyword }"`
Phone string `json:"phone" elastic_mapping:"phone: { type: keyword }"`
Tags []string `json:"tags" elastic_mapping:"mobile: { type: keyword }"`
Roles []UserRole `json:"roles" elastic_mapping:"roles: { type: object }"`
}
type UserRole struct {
ID string `json:"id" elastic_mapping:"id: { type: keyword }"`
Name string `json:"name" elastic_mapping:"name: { type: keyword }"`
}

View File

@ -1,9 +1,12 @@
package account package account
import ( import (
"infini.sh/console/internal/biz"
"infini.sh/console/internal/core"
"infini.sh/console/internal/dto"
m "infini.sh/console/internal/middleware"
"infini.sh/framework/core/api" "infini.sh/framework/core/api"
"infini.sh/framework/core/api/router" "infini.sh/framework/core/api/router"
"infini.sh/framework/core/global"
"infini.sh/framework/core/util" "infini.sh/framework/core/util"
"net/http" "net/http"
) )
@ -13,59 +16,43 @@ type Account struct {
} }
func init() { func init() {
account:=Account{} 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.GET, "/account/current_user", account.CurrentUser)
api.HandleAPIMethod(api.DELETE, "/account/logout", account.Logout)
api.HandleAPIMethod(api.GET, "/account/profile", m.LoginRequired(account.Profile))
api.HandleAPIMethod(api.PUT, "/account/password", m.LoginRequired(account.UpdatePassword))
} }
var userInSession string="user_in_session" const userInSession = "user_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"} var req dto.Login
err := h.DecodeJSON(r, &req)
json,err:=handler.GetJSON(req) if err != nil {
if err!=nil{ h.ErrorInternalServer(w, err.Error())
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)
return return
} }
u,_:=global.Env().GetConfig("bootstrap.username","admin") data, err := biz.Login(req.Username, req.Password)
p,_:=global.Env().GetConfig("bootstrap.password","admin") if err != nil {
if u==userName&&p==password{ h.ErrorInternalServer(w, err.Error())
data := util.MapStr{ return
"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["status"] = "ok"
//api.SetSession(w, r, userInSession+req.Username, req.Username)
h.WriteOKJSON(w, data)
} }
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) exists, user := api.GetSession(w, req, userInSession)
if exists{ if exists {
data:=util.MapStr{ data := util.MapStr{
"name": user, "name": user,
"avatar": "", "avatar": "",
"userid": "10001", "userid": "10001",
@ -94,13 +81,96 @@ func (handler Account)CurrentUser(w http.ResponseWriter, req *http.Request, ps h
"phone": "4001399200", "phone": "4001399200",
} }
handler.WriteJSON(w, data,200) h.WriteJSON(w, data, 200)
}else{ } else {
data := util.MapStr{ data := util.MapStr{
"status": "error", "status": "error",
"type": "account", "type": "account",
"currentAuthority": "guest", "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) {
reqUser, err := biz.FromUserContext(r.Context())
if err != nil {
h.ErrorInternalServer(w, err.Error())
return
}
delete(biz.TokenMap, reqUser.UserId)
h.WriteOKJSON(w, util.MapStr{
"status": "ok",
})
}
func (h Account) Profile(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
reqUser, err := biz.FromUserContext(r.Context())
if err != nil {
h.ErrorInternalServer(w, err.Error())
return
}
if reqUser.UserId == "admin" {
u := util.MapStr{
"user_id": "admin",
"name": "admin",
"email": "admin@infini.ltd",
"nick_name": "admin",
"phone": "13011111111",
}
h.WriteOKJSON(w, core.FoundResponse(reqUser.UserId, u))
} else {
user, err := biz.GetUser(reqUser.UserId)
if err != nil {
h.ErrorInternalServer(w, err.Error())
return
}
u := util.MapStr{
"user_id": user.ID,
"name": user.Name,
"email": user.Email,
"nick_name": user.NickName,
"phone": user.Phone,
}
h.WriteOKJSON(w, core.FoundResponse(reqUser.UserId, u))
}
return
}
func (h Account) UpdatePassword(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
reqUser, err := biz.FromUserContext(r.Context())
if err != nil {
h.ErrorInternalServer(w, err.Error())
return
}
var req dto.UpdatePassword
err = h.DecodeJSON(r, &req)
if err != nil {
h.ErrorInternalServer(w, err.Error())
return
}
err = biz.UpdatePassword(reqUser, req)
if err != nil {
h.ErrorInternalServer(w, err.Error())
return
}
h.WriteOKJSON(w, core.UpdateResponse(reqUser.UserId))
return
}
func (h Account) UpdateProfile(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
reqUser, err := biz.FromUserContext(r.Context())
if err != nil {
h.ErrorInternalServer(w, err.Error())
return
}
var req dto.UpdateProfile
err = h.DecodeJSON(r, &req)
err = biz.UpdateProfile(reqUser, req)
if err != nil {
h.ErrorInternalServer(w, err.Error())
return
}
h.WriteOKJSON(w, core.UpdateResponse(reqUser.UserId))
return
}

View File

@ -1,17 +1,19 @@
package index_management package index_management
import ( import (
"fmt"
"infini.sh/framework/core/elastic"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"infini.sh/console/config"
model2 "infini.sh/console/model"
"infini.sh/framework/core/api" "infini.sh/framework/core/api"
httprouter "infini.sh/framework/core/api/router" httprouter "infini.sh/framework/core/api/router"
"infini.sh/framework/core/orm" "infini.sh/framework/core/orm"
"infini.sh/framework/core/util" "infini.sh/framework/core/util"
"infini.sh/console/config"
model2 "infini.sh/console/model"
) )
type APIHandler struct { type APIHandler struct {
@ -116,3 +118,73 @@ func (handler APIHandler) UpdateDictItemAction(w http.ResponseWriter, req *http.
handler.WriteJSON(w, resp, http.StatusOK) handler.WriteJSON(w, resp, http.StatusOK)
} }
func (h APIHandler) ListIndex(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
clusterIds := h.GetParameterOrDefault(req, "ids", "")
keyword := h.GetParameterOrDefault(req, "keyword", "")
ids := strings.Split(clusterIds, ",")
for i := range ids {
if i < len(ids)-1 {
ids[i] = `"` + ids[i] + `",`
} else {
ids[i] = `"` + ids[i] + `"`
}
}
if len(ids) == 0 {
h.Error400(w, "id is required")
return
}
var dsl = `{
"_source": ["metadata.index_name"],
"collapse": {
"field": "metadata.index_name"
},
"size": 100,
"query": {
"bool": {
"must": [
{
"terms": {
"metadata.cluster_id": %s
}
}%s
],
"must_not": [
{
"term": {
"metadata.labels.state": {
"value": "delete"
}
}
}
]
}
}
}`
str := &strings.Builder{}
if keyword != "" {
str.WriteString(fmt.Sprintf(`,{"wildcard":{"metadata.index_name":{"value":"*%s*"}}}`, keyword))
}
dsl = fmt.Sprintf(dsl, ids, str)
esClient := elastic.GetClient(h.Config.Elasticsearch)
resp, err := esClient.SearchWithRawQueryDSL(".infini_index", []byte(dsl))
if err != nil {
return
}
list := resp.Hits.Hits
var indexNames []string
for _, v := range list {
m := v.Source["metadata"].(map[string]interface{})
indexNames = append(indexNames, m["index_name"].(string))
}
m := make(map[string]interface{})
m["indexnames"] = indexNames
h.WriteOKJSON(w, m)
return
}

View File

@ -2,6 +2,7 @@ package api
import ( import (
"infini.sh/console/config" "infini.sh/console/config"
m "infini.sh/console/internal/middleware"
"infini.sh/console/plugin/api/alerting" "infini.sh/console/plugin/api/alerting"
"infini.sh/console/plugin/api/index_management" "infini.sh/console/plugin/api/index_management"
"infini.sh/framework/core/api" "infini.sh/framework/core/api"
@ -23,28 +24,29 @@ func Init(cfg *config.AppConfig) {
//api.HandleAPIMethod(api.GET, "/api/dict/:id",handler.GetDictItemAction) //api.HandleAPIMethod(api.GET, "/api/dict/:id",handler.GetDictItemAction)
api.HandleAPIMethod(api.DELETE, path.Join(pathPrefix, "dict/:id"), handler.DeleteDictItemAction) api.HandleAPIMethod(api.DELETE, path.Join(pathPrefix, "dict/:id"), handler.DeleteDictItemAction)
api.HandleAPIMethod(api.PUT, path.Join(pathPrefix, "dict/:id"), handler.UpdateDictItemAction) api.HandleAPIMethod(api.PUT, path.Join(pathPrefix, "dict/:id"), handler.UpdateDictItemAction)
api.HandleAPIMethod(api.POST, path.Join(esPrefix, "doc/:index/_search"), handler.HandleSearchDocumentAction)
api.HandleAPIMethod(api.POST, path.Join(esPrefix, "doc/:index"), handler.HandleAddDocumentAction) api.HandleAPIMethod(api.POST, path.Join(esPrefix, "doc/:index/_search"), m.IndexRequired(handler.HandleSearchDocumentAction, "doc.search"))
api.HandleAPIMethod(api.PUT, path.Join(esPrefix, "doc/:index/:docId"), handler.HandleUpdateDocumentAction) api.HandleAPIMethod(api.POST, path.Join(esPrefix, "doc/:index"), m.IndexRequired(handler.HandleAddDocumentAction, "doc.create"))
api.HandleAPIMethod(api.DELETE, path.Join(esPrefix, "doc/:index/:docId"), handler.HandleDeleteDocumentAction) api.HandleAPIMethod(api.PUT, path.Join(esPrefix, "doc/:index/:docId"), m.IndexRequired(handler.HandleUpdateDocumentAction, "doc.update"))
api.HandleAPIMethod(api.DELETE, path.Join(esPrefix, "doc/:index/:docId"), m.IndexRequired(handler.HandleDeleteDocumentAction, "doc.delete"))
api.HandleAPIMethod(api.GET, path.Join(esPrefix, "doc/_validate"), handler.ValidateDocIDAction) api.HandleAPIMethod(api.GET, path.Join(esPrefix, "doc/_validate"), handler.ValidateDocIDAction)
api.HandleAPIMethod(api.POST, path.Join(pathPrefix, "rebuild/*id"), handler.HandleReindexAction) api.HandleAPIMethod(api.POST, path.Join(pathPrefix, "rebuild/*id"), handler.HandleReindexAction)
api.HandleAPIMethod(api.GET, path.Join(pathPrefix, "rebuild/_search"), handler.HandleGetRebuildListAction) api.HandleAPIMethod(api.GET, path.Join(pathPrefix, "rebuild/_search"), handler.HandleGetRebuildListAction)
api.HandleAPIMethod(api.DELETE, path.Join(pathPrefix, "rebuild/:id"), handler.HandleDeleteRebuildAction) api.HandleAPIMethod(api.DELETE, path.Join(pathPrefix, "rebuild/:id"), handler.HandleDeleteRebuildAction)
api.HandleAPIMethod(api.GET, path.Join(esPrefix, "_cat/indices"), handler.HandleGetIndicesAction) api.HandleAPIMethod(api.GET, path.Join(esPrefix, "_cat/indices"), m.ClusterRequired(handler.HandleGetIndicesAction, "cat.indices"))
api.HandleAPIMethod(api.GET, path.Join(esPrefix, "index/:index/_mappings"), handler.HandleGetMappingsAction) api.HandleAPIMethod(api.GET, path.Join(esPrefix, "index/:index/_mappings"), m.IndexRequired(handler.HandleGetMappingsAction, "indices.get_mapping"))
api.HandleAPIMethod(api.GET, path.Join(esPrefix, "index/:index/_settings"), handler.HandleGetSettingsAction) api.HandleAPIMethod(api.GET, path.Join(esPrefix, "index/:index/_settings"), m.IndexRequired(handler.HandleGetSettingsAction, "indices.get_settings"))
api.HandleAPIMethod(api.PUT, path.Join(esPrefix, "index/:index/_settings"), handler.HandleUpdateSettingsAction) api.HandleAPIMethod(api.PUT, path.Join(esPrefix, "index/:index/_settings"), m.IndexRequired(handler.HandleUpdateSettingsAction, "indices.put_mapping"))
api.HandleAPIMethod(api.DELETE, path.Join(esPrefix, "index/:index"), handler.HandleDeleteIndexAction) api.HandleAPIMethod(api.DELETE, path.Join(esPrefix, "index/:index"), m.IndexRequired(handler.HandleDeleteIndexAction, "indices.delete"))
api.HandleAPIMethod(api.POST, path.Join(esPrefix, "index/:index"), handler.HandleCreateIndexAction) api.HandleAPIMethod(api.POST, path.Join(esPrefix, "index/:index"), m.IndexRequired(handler.HandleCreateIndexAction, "indices.create"))
api.HandleAPIMethod(api.POST, path.Join(pathPrefix, "elasticsearch/command"), handler.HandleAddCommonCommandAction) api.HandleAPIMethod(api.POST, path.Join(pathPrefix, "elasticsearch/command"), handler.HandleAddCommonCommandAction)
api.HandleAPIMethod(api.PUT, path.Join(pathPrefix, "elasticsearch/command/:cid"), handler.HandleSaveCommonCommandAction) api.HandleAPIMethod(api.PUT, path.Join(pathPrefix, "elasticsearch/command/:cid"), handler.HandleSaveCommonCommandAction)
api.HandleAPIMethod(api.GET, path.Join(pathPrefix, "elasticsearch/command"), handler.HandleQueryCommonCommandAction) api.HandleAPIMethod(api.GET, path.Join(pathPrefix, "elasticsearch/command"), handler.HandleQueryCommonCommandAction)
api.HandleAPIMethod(api.DELETE, path.Join(pathPrefix, "elasticsearch/command/:cid"), handler.HandleDeleteCommonCommandAction) api.HandleAPIMethod(api.DELETE, path.Join(pathPrefix, "elasticsearch/command/:cid"), handler.HandleDeleteCommonCommandAction)
api.HandleAPIMethod(api.GET, path.Join(pathPrefix, "elasticsearch/indices"), handler.ListIndex)
//task.RegisterScheduleTask(task.ScheduleTask{ //task.RegisterScheduleTask(task.ScheduleTask{
// Description: "sync reindex task result", // Description: "sync reindex task result",
// Task: func() { // Task: func() {

106
plugin/api/rbac/api.go Normal file
View File

@ -0,0 +1,106 @@
package rbac
import (
"encoding/json"
"github.com/mitchellh/mapstructure"
"infini.sh/console/internal/biz"
"infini.sh/console/internal/biz/enum"
m "infini.sh/console/internal/middleware"
"infini.sh/console/model/rbac"
"infini.sh/framework/core/api"
"infini.sh/framework/core/elastic"
"infini.sh/framework/core/util"
"os"
"path"
log "github.com/cihub/seelog"
)
type Rbac struct {
api.Handler
}
func init() {
r := Rbac{}
api.HandleAPIMethod(api.GET, "/permission/:type", r.ListPermission)
api.HandleAPIMethod(api.POST, "/role/:type", m.PermissionRequired(r.CreateRole, enum.RoleAllPermission...))
api.HandleAPIMethod(api.GET, "/role/:id", m.PermissionRequired(r.GetRole, enum.RoleReadPermission...))
api.HandleAPIMethod(api.DELETE, "/role/:id", m.PermissionRequired(r.DeleteRole, enum.RoleAllPermission...))
api.HandleAPIMethod(api.PUT, "/role/:id", m.PermissionRequired(r.UpdateRole, enum.RoleAllPermission...))
api.HandleAPIMethod(api.GET, "/role/_search", m.PermissionRequired(r.SearchRole, enum.RoleReadPermission...))
api.HandleAPIMethod(api.POST, "/user", m.PermissionRequired(r.CreateUser, enum.UserAllPermission...))
api.HandleAPIMethod(api.GET, "/user/:id", m.PermissionRequired(r.GetUser, enum.UserReadPermission...))
api.HandleAPIMethod(api.DELETE, "/user/:id", m.PermissionRequired(r.DeleteUser, enum.UserAllPermission...))
api.HandleAPIMethod(api.PUT, "/user/:id", m.PermissionRequired(r.UpdateUser, enum.UserAllPermission...))
api.HandleAPIMethod(api.PUT, "/user/:id/role", m.PermissionRequired(r.UpdateUserRole, enum.UserAllPermission...))
api.HandleAPIMethod(api.GET, "/user/_search", m.PermissionRequired(r.SearchUser, enum.UserReadPermission...))
api.HandleAPIMethod(api.PUT, "/user/:id/password", m.PermissionRequired(r.UpdateUserPassword, enum.UserAllPermission...))
}
func loadJsonConfig() {
pwd, _ := os.Getwd()
bytes, err := util.FileGetContent(path.Join(pwd, "/config/permission.json"))
if err != nil {
panic("load json file err " + err.Error())
}
apis := make(map[string][]string)
err = json.Unmarshal(bytes, &apis)
if err != nil {
panic("json config unmarshal err " + err.Error())
}
biz.IndexApis = apis["indices"]
delete(apis, "indices")
biz.ClusterApis = apis
//bytes, err = util.FileGetContent(path.Join(pwd, "/config/map.json"))
//if err != nil {
// panic("load json file err " + err.Error())
//}
//esapiMap := make(map[string]string)
//err = json.Unmarshal(bytes, &esapiMap)
//if err != nil {
// panic("json config unmarshal err " + err.Error())
//}
//for k, v := range esapiMap {
// s := strings.Split(k, "-")
// biz.EsApiRoutes.AddRoute(s[0], s[1], v)
//}
}
func loadRolePermission() {
biz.RoleMap = make(map[string]rbac.Role)
biz.RoleMap["admin"] = rbac.Role{
Privilege: rbac.RolePrivilege{
Platform: enum.AdminPrivilege,
},
}
res, err := biz.SearchRole("", 0, 1000)
if err != nil {
log.Error(err)
return
}
response := elastic.SearchResponse{}
util.FromJSONBytes(res.Raw, &response)
for _, v := range response.Hits.Hits {
var role rbac.Role
delete(v.Source, "created")
delete(v.Source, "updated")
err = mapstructure.Decode(v.Source, &role)
if err != nil {
log.Error(err)
return
}
biz.RoleMap[role.Name] = role
}
}
func Init() {
loadJsonConfig()
loadRolePermission()
}

View File

@ -0,0 +1,24 @@
package rbac
import (
log "github.com/cihub/seelog"
"infini.sh/console/internal/biz"
httprouter "infini.sh/framework/core/api/router"
"net/http"
)
func (h Rbac) ListPermission(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
typ := ps.MustGetParameter("type")
err := biz.IsAllowRoleType(typ)
if err != nil {
_ = log.Error(err.Error())
h.ErrorInternalServer(w, err.Error())
return
}
var permissions interface{}
if typ == biz.Elastisearch {
permissions = biz.ListElasticsearchPermission()
}
h.WriteOKJSON(w, permissions)
return
}

150
plugin/api/rbac/role.go Normal file
View File

@ -0,0 +1,150 @@
package rbac
import (
log "github.com/cihub/seelog"
"infini.sh/console/internal/biz"
"infini.sh/console/internal/biz/enum"
"infini.sh/console/internal/core"
"infini.sh/console/model/rbac"
httprouter "infini.sh/framework/core/api/router"
"infini.sh/framework/core/elastic"
"infini.sh/framework/core/util"
"net/http"
)
func (h Rbac) CreateRole(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
roleType := ps.MustGetParameter("type")
localUser, err := biz.FromUserContext(r.Context())
if err != nil {
log.Error(err.Error())
h.ErrorInternalServer(w, err.Error())
return
}
err = biz.IsAllowRoleType(roleType)
if err != nil {
h.ErrorInternalServer(w, err.Error())
return
}
role := &rbac.Role{
Type: roleType,
}
err = h.DecodeJSON(r, role)
if err != nil {
h.Error400(w, err.Error())
return
}
var id string
id, err = biz.CreateRole(localUser, role)
if err != nil {
_ = log.Error(err.Error())
h.ErrorInternalServer(w, err.Error())
return
}
_ = h.WriteOKJSON(w, core.CreateResponse(id))
return
}
func (h Rbac) SearchRole(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
var (
keyword = h.GetParameterOrDefault(r, "keyword", "")
from = h.GetIntOrDefault(r, "from", 0)
size = h.GetIntOrDefault(r, "size", 20)
)
res, err := biz.SearchRole(keyword, from, size)
if err != nil {
log.Error(err)
h.ErrorInternalServer(w, err.Error())
return
}
response := elastic.SearchResponse{}
util.FromJSONBytes(res.Raw, &response)
hits := response.Hits.Hits
list := make([]elastic.IndexDocument, 0)
total := response.GetTotal()
var index string
for _, v := range hits {
index = v.Index
}
for k, v := range enum.BuildRoles {
list = append(list, elastic.IndexDocument{
ID: k,
Index: index,
Type: "_doc",
Source: v,
})
total++
}
list = append(list, hits...)
response.Hits.Hits = list
response.Hits.Total = total
h.WriteOKJSON(w, response)
return
}
func (h Rbac) GetRole(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
id := ps.MustGetParameter("id")
role, err := biz.GetRole(id)
if err != nil {
_ = log.Error(err.Error())
h.ErrorInternalServer(w, err.Error())
return
}
h.WriteOKJSON(w, core.Response{Hit: role})
return
}
func (h Rbac) DeleteRole(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
id := ps.MustGetParameter("id")
localUser, err := biz.FromUserContext(r.Context())
if err != nil {
log.Error(err.Error())
h.ErrorInternalServer(w, err.Error())
return
}
err = biz.DeleteRole(localUser, id)
if err != nil {
_ = log.Error(err.Error())
h.ErrorInternalServer(w, err.Error())
return
}
_ = h.WriteOKJSON(w, core.DeleteResponse(id))
return
}
func (h Rbac) UpdateRole(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
id := ps.MustGetParameter("id")
localUser, err := biz.FromUserContext(r.Context())
if err != nil {
log.Error(err.Error())
h.ErrorInternalServer(w, err.Error())
return
}
role := &rbac.Role{}
err = h.DecodeJSON(r, role)
if err != nil {
h.Error400(w, err.Error())
return
}
role.ID = id
err = biz.UpdateRole(localUser, role)
if err != nil {
_ = log.Error(err.Error())
h.ErrorInternalServer(w, err.Error())
return
}
_ = h.WriteOKJSON(w, core.UpdateResponse(id))
return
}

192
plugin/api/rbac/user.go Normal file
View File

@ -0,0 +1,192 @@
package rbac
import (
"errors"
"infini.sh/console/internal/biz"
"infini.sh/console/internal/core"
"infini.sh/console/internal/dto"
httprouter "infini.sh/framework/core/api/router"
"infini.sh/framework/core/util"
"infini.sh/framework/modules/elastic"
"net/http"
log "src/github.com/cihub/seelog"
)
type CreateUserReq struct {
Username string `json:"username" `
Password string `json:"password" `
Name string `json:"name" `
Phone string `json:"phone" `
Email string `json:"email" `
Tags []string `json:"tags"`
}
func (h Rbac) CreateUser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
var req dto.CreateUser
err := h.DecodeJSON(r, &req)
if err != nil {
h.Error400(w, err.Error())
return
}
if req.Name == "" {
h.Error400(w, "username and phone and email is require")
return
}
localUser, err := biz.FromUserContext(r.Context())
if err != nil {
log.Error(err.Error())
h.ErrorInternalServer(w, err.Error())
return
}
id, pass, err := biz.CreateUser(localUser, req)
if err != nil {
_ = log.Error(err.Error())
h.ErrorInternalServer(w, err.Error())
return
}
_ = h.WriteOKJSON(w, util.MapStr{
"_id": id,
"password": pass,
"result": "created",
})
return
}
func (h Rbac) GetUser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
id := ps.MustGetParameter("id")
user, err := biz.GetUser(id)
if errors.Is(err, elastic.ErrNotFound) {
h.WriteJSON(w, core.NotFoundResponse(id), http.StatusNotFound)
return
}
if err != nil {
_ = log.Error(err.Error())
h.ErrorInternalServer(w, err.Error())
return
}
h.WriteOKJSON(w, core.FoundResponse(id, user))
return
}
func (h Rbac) UpdateUser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
id := ps.MustGetParameter("id")
var req dto.UpdateUser
err := h.DecodeJSON(r, &req)
if err != nil {
_ = log.Error(err.Error())
h.Error400(w, err.Error())
return
}
localUser, err := biz.FromUserContext(r.Context())
if err != nil {
log.Error(err.Error())
h.ErrorInternalServer(w, err.Error())
return
}
err = biz.UpdateUser(localUser, id, req)
if err != nil {
_ = log.Error(err.Error())
h.ErrorInternalServer(w, err.Error())
return
}
_ = h.WriteOKJSON(w, core.UpdateResponse(id))
return
}
func (h Rbac) UpdateUserRole(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
id := ps.MustGetParameter("id")
var req dto.UpdateUserRole
err := h.DecodeJSON(r, &req)
if err != nil {
_ = log.Error(err.Error())
h.Error400(w, err.Error())
return
}
localUser, err := biz.FromUserContext(r.Context())
if err != nil {
log.Error(err.Error())
h.ErrorInternalServer(w, err.Error())
return
}
err = biz.UpdateUserRole(localUser, id, req)
if err != nil {
_ = log.Error(err.Error())
h.ErrorInternalServer(w, err.Error())
return
}
_ = h.WriteOKJSON(w, core.UpdateResponse(id))
return
}
func (h Rbac) DeleteUser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
id := ps.MustGetParameter("id")
localUser, err := biz.FromUserContext(r.Context())
if err != nil {
log.Error(err.Error())
h.ErrorInternalServer(w, err.Error())
return
}
err = biz.DeleteUser(localUser, id)
if errors.Is(err, elastic.ErrNotFound) {
h.WriteJSON(w, core.NotFoundResponse(id), http.StatusNotFound)
return
}
if err != nil {
_ = log.Error(err.Error())
h.ErrorInternalServer(w, err.Error())
return
}
_ = h.WriteOKJSON(w, core.DeleteResponse(id))
return
}
func (h Rbac) SearchUser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
var (
keyword = h.GetParameterOrDefault(r, "keyword", "")
from = h.GetIntOrDefault(r, "from", 0)
size = h.GetIntOrDefault(r, "size", 20)
)
res, err := biz.SearchUser(keyword, from, size)
if err != nil {
log.Error(err.Error())
h.ErrorInternalServer(w, err.Error())
return
}
h.Write(w, res.Raw)
return
}
func (h Rbac) UpdateUserPassword(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
id := ps.MustGetParameter("id")
var req dto.UpdateUserPassword
err := h.DecodeJSON(r, &req)
if err != nil {
_ = log.Error(err.Error())
h.Error400(w, err.Error())
return
}
localUser, err := biz.FromUserContext(r.Context())
if err != nil {
log.Error(err.Error())
h.ErrorInternalServer(w, err.Error())
return
}
err = biz.UpdateUserPassword(localUser, id, req.Password)
if err != nil {
_ = log.Error(err.Error())
h.ErrorInternalServer(w, err.Error())
return
}
_ = h.WriteOKJSON(w, core.UpdateResponse(id))
return
}