You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
281 lines
8.8 KiB
281 lines
8.8 KiB
package api
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"yunyan/comm"
|
|
"yunyan/lego/core"
|
|
"yunyan/lego/core/cbase"
|
|
"yunyan/lego/sys/log"
|
|
"yunyan/pb"
|
|
)
|
|
|
|
// 接口授权语义
|
|
const (
|
|
scopeNone = "*none" // 无需登录
|
|
scopeAdmin = "*admin" // 仅站长
|
|
scopeAny = "*any" // 已登录任意身份均可(工具类接口,无对应页面)
|
|
scopeAgent = "*agent" // 仅代理身份(identity=3)
|
|
)
|
|
|
|
// apiPageMap 接口 -> 后台页面 id(与 console/admin.html 中 scopedPages 对齐)
|
|
// 取值含义:
|
|
// - 页面 id(dashboard/factory/...):Admin/Manager 直通;Agent/Operator 需在 access 中勾选
|
|
// - scopeNone:完全开放
|
|
// - scopeAdmin:仅 Admin
|
|
// - scopeAny:登录后任意身份可访问(无页面归属的工具类接口)
|
|
var apiPageMap = map[string]string{
|
|
// === 无需登录 ===
|
|
"api_login": scopeNone,
|
|
"api_getsiteinfo": scopeNone,
|
|
|
|
// === 仅站长(账号管理) ===
|
|
"api_addadminuser": scopeAdmin,
|
|
"api_deladminuser": scopeAdmin,
|
|
"api_updateadminuser": scopeAdmin,
|
|
"api_getadminusers": scopeAdmin,
|
|
"api_grantadminresource": scopeAdmin, // 超管→后台账号 资源点赠送
|
|
|
|
// === dashboard 仪表盘 ===
|
|
"api_getglobalstats": "dashboard",
|
|
"api_rebuildstats": scopeAdmin, // 全量重建统计(清空+全库重算,仅超管)
|
|
|
|
// === factory 品牌商管理 ===
|
|
"api_getbrand": "factory",
|
|
"api_getbrands": "factory",
|
|
"api_addbrand": "factory",
|
|
"api_updatebrand": "factory",
|
|
"api_delbrand": "factory",
|
|
"api_getfactorypubliccodes": "factory",
|
|
"api_createfactorypubliccode": "factory",
|
|
"api_updatefactorypubliccode": "factory",
|
|
"api_delfactorypubliccode": "factory",
|
|
|
|
// === product 产品管理 ===
|
|
"api_getproduct": "product",
|
|
"api_getproducts": "product",
|
|
"api_getproductversions": "product",
|
|
"api_addproduct": "product",
|
|
"api_updateproduct": "product",
|
|
"api_delproduct": "product",
|
|
"api_addproductversion": "product",
|
|
"api_delproductversion": "product",
|
|
|
|
// === device 生产管理 ===
|
|
"api_getauthcode": "device",
|
|
"api_getauthcodes": "device",
|
|
"api_createauthcodes": "device",
|
|
"api_disableauthcodes": "device",
|
|
"api_restfactorydevics": "device",
|
|
"api_resetlicensestatus": "device",
|
|
"api_getproductionbatches": "device",
|
|
"api_revokeproductionbatch": scopeAdmin, // 撤销生产批次,仅超管
|
|
|
|
// === goods 商品管理 ===
|
|
"api_getgoods": "goods",
|
|
"api_getgoodss": "goods",
|
|
"api_addgoods": "goods",
|
|
"api_updategoods": "goods",
|
|
"api_delgoods": "goods",
|
|
|
|
// === order 订单查询 ===
|
|
"api_getpayorders": "order",
|
|
"api_getpaystats": "order",
|
|
|
|
// === user 用户查询 ===
|
|
"api_getuser": "user",
|
|
"api_updateuser": "user",
|
|
"api_getuseruselog": "user",
|
|
"api_getuserechomeetrecords": "userechomeet",
|
|
"api_dispatchresource": "user",
|
|
|
|
// === transaction 流水 ===
|
|
// 代理在此接口内被强制只看自己派发的流水(handler 内做隔离),其它身份可自由按类型/UID过滤
|
|
"api_getuseruselogspaged": scopeAny,
|
|
"api_getadminresourcelogs": scopeAny, // 超管查全部,其他账号只能查自己;handler 内做隔离
|
|
|
|
// === config 服务配置 ===
|
|
"api_getconfig": "config",
|
|
"api_addconfig": "config",
|
|
"api_updateconfig": "config",
|
|
"api_delconfig": "config",
|
|
|
|
// === mcp MCP配置 ===
|
|
"api_getmcpserver": "mcp",
|
|
"api_getmcpservers": "mcp",
|
|
"api_addmcpservers": "mcp",
|
|
"api_updatemcpserver": "mcp",
|
|
"api_delmcpservers": "mcp",
|
|
|
|
// === agent Agent管理 ===
|
|
"api_getagent": "agent",
|
|
"api_getagents": "agent",
|
|
"api_addagent": "agent",
|
|
"api_updateagent": "agent",
|
|
"api_delagent": "agent",
|
|
|
|
// === template 模板管理 ===
|
|
"api_gettemplates": "template",
|
|
"api_gettemplate": "template",
|
|
"api_addtemplates": "template",
|
|
"api_updatetemplates": "template",
|
|
"api_deltemplate": "template",
|
|
|
|
// === channelapp 渠道应用 ===
|
|
"api_getchannelapp": "channelapp",
|
|
"api_getchannelapps": "channelapp",
|
|
"api_addchannelapp": "channelapp",
|
|
"api_uploadchannelapp": "channelapp",
|
|
"api_delchannelapp": "channelapp",
|
|
|
|
// === wakeup 唤醒词管理 ===
|
|
"api_getwakeupvoice": "wakeup",
|
|
"api_getwakeupvoices": "wakeup",
|
|
"api_addwakeupvoice": "wakeup",
|
|
"api_updatewakeupvoice": "wakeup",
|
|
"api_delwakeupvoice": "wakeup",
|
|
|
|
// === sysconfig 系统配置 ===
|
|
"api_getglobalconfig": "sysconfig",
|
|
"api_addglobalconfigs": "sysconfig",
|
|
"api_updateglobalconfig": "sysconfig",
|
|
"api_delglobalconfigs": "sysconfig",
|
|
|
|
// === audit 操作日志 ===
|
|
"api_getauditlogs": "audit",
|
|
"api_getconsolelogs": "audit",
|
|
"api_getconsolelogfilters": "audit",
|
|
|
|
// === agentdashboard 代理仪表盘 ===
|
|
"api_getagentdashboard": scopeAgent,
|
|
|
|
// === 工具类接口(无页面归属,登录即可) ===
|
|
"api_sendsmtpemail": scopeAny,
|
|
"api_translate": scopeAny,
|
|
"api_getcostoken": scopeAny,
|
|
"api_getuserrank": scopeAny, // 用户使用量排行 dashboard / agentdashboard 共用
|
|
"api_getmyadminpool": scopeAny, // 当前账号资源点池查询(派发界面余额展示)
|
|
}
|
|
|
|
// permissionInterceptorComp 权限拦截器组件
|
|
type permissionInterceptorComp struct {
|
|
cbase.ModuleCompBase
|
|
service core.IService
|
|
module *API
|
|
}
|
|
|
|
func (this *permissionInterceptorComp) Init(service core.IService, module core.IModule, comp core.IModuleComp, opt core.IModuleOptions) (err error) {
|
|
this.ModuleCompBase.Init(service, module, comp, opt)
|
|
this.service = service
|
|
this.module = module.(*API)
|
|
return
|
|
}
|
|
|
|
func (this *permissionInterceptorComp) Start() (err error) {
|
|
if err = this.ModuleCompBase.Start(); err != nil {
|
|
return
|
|
}
|
|
var scomp core.IServiceComp
|
|
if scomp, err = this.service.GetComp(comm.SC_ServiceHttpRouteComp); err != nil {
|
|
return
|
|
}
|
|
scomp.(comm.ISC_HttpRouteComp).AddInterceptor(this.checkPermission)
|
|
return
|
|
}
|
|
|
|
// checkPermission 权限校验拦截器
|
|
func (this *permissionInterceptorComp) checkPermission(apiName string, session comm.IUserSession, msg interface{}) *pb.ErrorData {
|
|
// console 运维接口(重载/重置业务配置)不走后台管理员登录——调用方是 console 而非人,没有 identity。
|
|
// 它们的身份由**网关**用 ${FIELD_ENCRYPT_KEY} 的 HMAC 签名验过(见 comm.IsConsoleOnlyRoute),
|
|
// 且网关已禁止它们从不验签的 /web 入口进入,故此处放行是安全的。
|
|
//
|
|
// 用 comm.IsConsoleOnlyRoute 而不是往 apiPageMap 里加 scopeNone:与网关共用同一份清单,
|
|
// 免得两边各维护一份、哪天漂移成"网关验签、拦截器却放行任意来源"。
|
|
if comm.IsConsoleOnlyRoute(apiName) {
|
|
return nil
|
|
}
|
|
|
|
scope, exists := apiPageMap[apiName]
|
|
if !exists {
|
|
// 未配置的接口默认仅 Admin 可访问,避免新接口意外暴露
|
|
scope = scopeAdmin
|
|
}
|
|
|
|
if scope == scopeNone {
|
|
return nil
|
|
}
|
|
|
|
identityStr := session.GetMateToString("identity")
|
|
if identityStr == "" {
|
|
return &pb.ErrorData{
|
|
Code: pb.ErrorCode_NoLogin,
|
|
Message: "未登录,请先登录",
|
|
}
|
|
}
|
|
identity := pb.Identity(session.GetMateToInt32("identity"))
|
|
if identity <= 0 {
|
|
return &pb.ErrorData{
|
|
Code: pb.ErrorCode_NoLogin,
|
|
Message: "登录信息异常",
|
|
}
|
|
}
|
|
|
|
switch scope {
|
|
case scopeAdmin:
|
|
if identity != pb.Identity_Admin {
|
|
return permDenied()
|
|
}
|
|
return nil
|
|
case scopeAny:
|
|
return nil
|
|
case scopeAgent:
|
|
if identity != pb.Identity_Agent {
|
|
return permDenied()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// 按页面授权:Admin/Manager 直通;Agent/Operator 需在 access 中勾选
|
|
if identity == pb.Identity_Admin || identity == pb.Identity_Manager {
|
|
return nil
|
|
}
|
|
if identity != pb.Identity_Agent && identity != pb.Identity_Operator {
|
|
return permDenied()
|
|
}
|
|
|
|
account := session.GetMateToString(comm.SessionMeta_UserId)
|
|
if account == "" {
|
|
log.Errorf("[perm] api=%s identity=%d account=empty", apiName, identity)
|
|
return permDenied()
|
|
}
|
|
user, err := this.module.model.findforaccount(account)
|
|
if err != nil || user == nil {
|
|
log.Errorf("[perm] api=%s identity=%d account=%s findforaccount err=%v user=%v", apiName, identity, account, err, user)
|
|
return permDenied()
|
|
}
|
|
if !accessHasPage(user.Access, scope) {
|
|
log.Errorf("[perm] api=%s identity=%d account=%s page=%s access=%q -> denied", apiName, identity, account, scope, user.Access)
|
|
return permDenied()
|
|
}
|
|
log.Debugf("[perm] api=%s identity=%d account=%s page=%s access=%q -> allow", apiName, identity, account, scope, user.Access)
|
|
return nil
|
|
}
|
|
|
|
func accessHasPage(access, page string) bool {
|
|
if access == "" || page == "" {
|
|
return false
|
|
}
|
|
for _, p := range strings.Split(access, ",") {
|
|
if strings.TrimSpace(p) == page {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func permDenied() *pb.ErrorData {
|
|
return &pb.ErrorData{
|
|
Code: pb.ErrorCode_InsufficientPermissions,
|
|
Message: "权限不足,需要更高级别权限",
|
|
}
|
|
}
|
|
|