Files
EdgeAdmin/internal/web/helpers/user_must_auth.go

363 lines
9.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package helpers
import (
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
"github.com/TeaOSLab/EdgeAdmin/internal/events"
"github.com/TeaOSLab/EdgeAdmin/internal/goman"
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
"github.com/TeaOSLab/EdgeAdmin/internal/setup"
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/index/loginutils"
"github.com/TeaOSLab/EdgeCommon/pkg/langs"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/actions"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
"net/http"
"net/url"
"reflect"
"strings"
)
var nodeLogsCountChanges = make(chan bool, 1)
var ipItemsCountChanges = make(chan bool, 1)
func NotifyNodeLogsCountChange() {
select {
case nodeLogsCountChanges <- true:
default:
}
}
func NotifyIPItemsCountChanges() {
select {
case ipItemsCountChanges <- true:
default:
}
}
// 运行日志
var countUnreadNodeLogs int64 = 0
var nodeLogsType = ""
// IP名单
var countUnreadIPItems int64 = 0
func init() {
events.On(events.EventStart, func() {
// 节点日志数量
goman.New(func() {
for range nodeLogsCountChanges {
rpcClient, err := rpc.SharedRPC()
if err != nil {
continue
}
countNodeLogsResp, err := rpcClient.NodeLogRPC().CountNodeLogs(rpcClient.Context(0), &pb.CountNodeLogsRequest{
Role: nodeconfigs.NodeRoleNode,
IsUnread: true,
})
if err != nil {
logs.Error(err)
} else {
countUnreadNodeLogs = countNodeLogsResp.Count
if countUnreadNodeLogs > 0 {
if countUnreadNodeLogs >= 100 {
countUnreadNodeLogs = 99
}
nodeLogsType = "unread"
}
}
}
})
// 服务数量
goman.New(func() {
for range ipItemsCountChanges {
rpcClient, err := rpc.SharedRPC()
if err != nil {
continue
}
countUnreadIPItemsResp, err := rpcClient.IPItemRPC().CountAllEnabledIPItems(rpcClient.Context(0), &pb.CountAllEnabledIPItemsRequest{Unread: true})
if err != nil {
logs.Error(err)
} else {
countUnreadIPItems = countUnreadIPItemsResp.Count
}
}
})
})
}
// 认证拦截
type userMustAuth struct {
AdminId int64
module string
}
func NewUserMustAuth(module string) *userMustAuth {
return &userMustAuth{module: module}
}
func (this *userMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramName string) (goNext bool) {
var action = actionPtr.Object()
// 检查请求是否合法
if isEvilRequest(action.Request) {
action.ResponseWriter.WriteHeader(http.StatusForbidden)
return false
}
// 检测注入
if !safeFilterRequest(action.Request) {
action.ResponseWriter.WriteHeader(http.StatusForbidden)
_, _ = action.ResponseWriter.Write([]byte("Denied By WAF"))
return false
}
// 恢复模式
if teaconst.IsRecoverMode {
action.RedirectURL("/recover")
return false
}
// DEMO模式
if teaconst.IsDemoMode {
if action.Request.Method == http.MethodPost {
var actionName = action.Spec.ClassName[strings.LastIndex(action.Spec.ClassName, ".")+1:]
var denyPrefixes = []string{"Update", "Create", "Delete", "Truncate", "Clean", "Clear", "Reset", "Add", "Remove", "Sync", "Run", "Exec"}
for _, prefix := range denyPrefixes {
if strings.HasPrefix(actionName, prefix) {
action.Fail(teaconst.ErrorDemoOperation)
return false
}
}
if strings.Index(action.Spec.PkgPath, "settings") > 0 || strings.Index(action.Spec.PkgPath, "delete") > 0 || strings.Index(action.Spec.PkgPath, "update") > 0 {
action.Fail(teaconst.ErrorDemoOperation)
return false
}
}
}
// 安全相关
securityConfig, _ := configloaders.LoadSecurityConfig()
if securityConfig == nil {
action.AddHeader("X-Frame-Options", "SAMEORIGIN")
} else if len(securityConfig.Frame) > 0 {
action.AddHeader("X-Frame-Options", securityConfig.Frame)
}
action.AddHeader("Content-Security-Policy", "default-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'")
// 检查IP
if !checkIP(securityConfig, loginutils.RemoteIP(action)) {
action.ResponseWriter.WriteHeader(http.StatusForbidden)
return false
}
// 检查请求
if !checkRequestSecurity(securityConfig, action.Request) {
action.ResponseWriter.WriteHeader(http.StatusForbidden)
return false
}
// 检查系统是否已经配置过
if !setup.IsConfigured() {
action.RedirectURL("/setup")
return
}
var session = action.Session()
var adminId = session.GetInt64(teaconst.SessionAdminId)
if adminId <= 0 {
var errString = session.GetString("@error")
if len(errString) > 0 {
action.WriteString("read session failed: " + errString)
return false
}
this.login(action)
return false
}
// 检查指纹
if securityConfig != nil && securityConfig.CheckClientFingerprint {
var clientFingerprint = session.GetString("@fingerprint")
if len(clientFingerprint) > 0 && clientFingerprint != loginutils.CalculateClientFingerprint(action) {
loginutils.UnsetCookie(action)
session.Delete()
this.login(action)
return false
}
}
// 检查区域
var oldClientIP = session.GetString("@ip")
var currentClientIP = loginutils.RemoteIP(action)
if len(oldClientIP) > 0 && len(currentClientIP) > 0 && oldClientIP != currentClientIP {
var oldRegion = loginutils.LookupIPRegion(oldClientIP)
var newRegion = loginutils.LookupIPRegion(currentClientIP)
if newRegion != oldRegion {
if securityConfig != nil && securityConfig.CheckClientRegion {
loginutils.UnsetCookie(action)
session.Delete()
this.login(action)
return false
} else {
// TODO 考虑IP变化时也需要验证主要是考虑被反向代理的情形
action.RedirectURL("/login/validate?from=" + url.QueryEscape(action.Request.URL.String()))
return false
}
}
}
// 检查用户是否存在
if !configloaders.CheckAdmin(adminId) {
loginutils.UnsetCookie(action)
session.Delete()
this.login(action)
return false
}
// 检查用户权限
if len(this.module) > 0 && !configloaders.AllowModule(adminId, this.module) {
action.ResponseWriter.WriteHeader(http.StatusForbidden)
action.WriteString("Permission Denied.")
return false
}
this.AdminId = adminId
action.Context.Set("adminId", this.AdminId)
if action.Request.Method != http.MethodGet {
return true
}
uiConfig, err := configloaders.LoadAdminUIConfig()
if err != nil {
action.WriteString(err.Error())
return false
}
// 初始化内置方法
action.ViewFunc("teaTitle", func() string {
return action.Data["teaTitle"].(string)
})
action.Data["teaShowVersion"] = uiConfig.ShowVersion
action.Data["teaTitle"] = uiConfig.AdminSystemName
action.Data["teaName"] = uiConfig.ProductName
action.Data["teaFaviconFileId"] = uiConfig.FaviconFileId
action.Data["teaLogoFileId"] = uiConfig.LogoFileId
action.Data["teaUsername"] = configloaders.FindAdminFullname(adminId)
action.Data["teaTheme"] = configloaders.FindAdminTheme(adminId)
action.Data["teaUserAvatar"] = ""
if !action.Data.Has("teaMenu") {
action.Data["teaMenu"] = ""
}
// 语言
// Language
var lang = configloaders.FindAdminLang(adminId)
if len(lang) == 0 {
lang = langs.ParseLangFromAction(action)
}
action.Data["teaLang"] = lang
action.Data["teaModules"] = this.modules(lang, actionPtr, adminId, uiConfig)
action.Data["teaSubMenus"] = []map[string]interface{}{}
action.Data["teaTabbar"] = []map[string]interface{}{}
if len(uiConfig.Version) == 0 {
action.Data["teaVersion"] = teaconst.Version
} else {
action.Data["teaVersion"] = uiConfig.Version
}
action.Data["teaShowOpenSourceInfo"] = uiConfig.ShowOpenSourceInfo
action.Data["teaIsSuper"] = false
action.Data["teaIsPlus"] = teaconst.IsPlus
action.Data["teaDemoEnabled"] = teaconst.IsDemoMode
action.Data["teaShowFinance"] = configloaders.ShowFinance()
if !action.Data.Has("teaSubMenu") {
action.Data["teaSubMenu"] = ""
}
action.Data["teaCheckNodeTasks"] = configloaders.AllowModule(adminId, configloaders.AdminModuleCodeNode)
action.Data["teaCheckDNSTasks"] = configloaders.AllowModule(adminId, configloaders.AdminModuleCodeDNS)
// 菜单
action.Data["firstMenuItem"] = ""
// 未读消息数
action.Data["teaBadge"] = 0
// 调用Init
initMethod := reflect.ValueOf(actionPtr).MethodByName("Init")
if initMethod.IsValid() {
initMethod.Call([]reflect.Value{})
}
return true
}
// 菜单配置
func (this *userMustAuth) modules(langCode string, actionPtr actions.ActionWrapper, adminId int64, adminUIConfig *systemconfigs.AdminUIConfig) []maps.Map {
// 父级动作
var action = actionPtr.Object()
// 未读日志数
var mainMenu = action.Data.GetString("teaMenu")
if mainMenu == "clusters" {
select {
case nodeLogsCountChanges <- true:
default:
}
} else if mainMenu == "servers" {
select {
case ipItemsCountChanges <- true:
default:
}
}
var result = []maps.Map{}
for _, m := range FindAllMenuMaps(langCode, nodeLogsType, countUnreadNodeLogs, countUnreadIPItems) {
if m.GetString("code") == "finance" && !configloaders.ShowFinance() {
continue
}
var module = m.GetString("module")
if configloaders.AllowModule(adminId, module) {
if module == "ns" && !adminUIConfig.ContainsModule(userconfigs.UserModuleNS) {
continue
}
if lists.ContainsString([]string{
configloaders.AdminModuleCodeNode,
configloaders.AdminModuleCodeDNS,
configloaders.AdminModuleCodePlan,
configloaders.AdminModuleCodeServer,
configloaders.AdminModuleCodeDashboard,
}, module) && !adminUIConfig.ContainsModule(userconfigs.UserModuleCDN) {
continue
}
result = append(result, m)
}
}
return result
}
// 跳转到登录页
func (this *userMustAuth) login(action *actions.ActionObject) {
action.RedirectURL("/?from=" + url.QueryEscape(action.Request.RequestURI))
}