chore: 并入 iotlcm-openapi 历史到 openapi/(原提交 3b05d5a)
This commit is contained in:
@@ -0,0 +1,364 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gmeta"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/model/entity"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/packed/response"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/service"
|
||||
)
|
||||
|
||||
type sMiddleware struct{}
|
||||
|
||||
const apiDataScopeRoleId int64 = 17
|
||||
|
||||
func New() *sMiddleware {
|
||||
return &sMiddleware{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterMiddleware(New())
|
||||
}
|
||||
|
||||
func getContentType(r *ghttp.Request) (contentType string) {
|
||||
contentType = r.Response.Header().Get("Content-Type")
|
||||
if contentType != "" {
|
||||
return
|
||||
}
|
||||
|
||||
mime := gmeta.Get(r.GetHandlerResponse(), "mime").String()
|
||||
if mime == "" {
|
||||
contentType = consts.HTTPContentTypeJson
|
||||
} else {
|
||||
contentType = mime
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *sMiddleware) ResponseHandler(r *ghttp.Request) {
|
||||
r.Middleware.Next()
|
||||
if r.Response.BufferLength() > 0 || contexts.IsWebSocket(r.GetCtx()) {
|
||||
return
|
||||
}
|
||||
if getContentType(r) == consts.HTTPContentTypeExcel {
|
||||
r.Response.Status = http.StatusOK
|
||||
return
|
||||
}
|
||||
|
||||
err := r.GetError()
|
||||
res := r.GetHandlerResponse()
|
||||
var code gcode.Code = gcode.CodeOK
|
||||
if err != nil {
|
||||
code = gerror.Code(err)
|
||||
if code == gcode.CodeNil {
|
||||
code = gcode.CodeInternalError
|
||||
}
|
||||
response.JsonExit(r, code.Code(), err.Error())
|
||||
return
|
||||
}
|
||||
response.Json(r, code.Code(), "", res)
|
||||
}
|
||||
|
||||
func (s *sMiddleware) ApiCtx(r *ghttp.Request) {
|
||||
ctx := r.GetCtx()
|
||||
r.SetCtx(r.GetNeverDoneCtx())
|
||||
if r.URL != nil && s.IsExceptLogin(ctx, r.URL.Path) {
|
||||
r.Middleware.Next()
|
||||
return
|
||||
}
|
||||
|
||||
data, err := service.QueryToken().GetCurrentCustomer(ctx)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sMiddleware.ApiCtx current customer error: %v", err)
|
||||
r.Middleware.Next()
|
||||
return
|
||||
}
|
||||
if data != nil {
|
||||
contextModel := new(model.Context)
|
||||
contextModel.User = &model.Identity{
|
||||
BaseClaims: model.BaseClaims{
|
||||
ID: 0,
|
||||
TenantId: data.BaseClaimsApi.TenantId,
|
||||
DeptId: data.BaseClaimsApi.DeptId,
|
||||
Roles: []model.Role{
|
||||
{RoleId: apiDataScopeRoleId, DataScope: consts.SysRoleDataScopeDeptAndBelow},
|
||||
},
|
||||
LoginAt: data.BaseClaimsApi.LoginAt,
|
||||
},
|
||||
}
|
||||
contextModel.AgentUser = &data.BaseClaimsApi
|
||||
contexts.Init(r, contextModel)
|
||||
}
|
||||
r.Middleware.Next()
|
||||
}
|
||||
|
||||
func (s *sMiddleware) ApiAuth(r *ghttp.Request) {
|
||||
ctx := r.Context()
|
||||
path := r.URL.Path
|
||||
if s.IsExceptLogin(ctx, path) {
|
||||
r.Middleware.Next()
|
||||
return
|
||||
}
|
||||
|
||||
deptKey := contexts.GetDeptKey(ctx)
|
||||
if deptKey == "" {
|
||||
g.Log().Error(ctx, "sMiddleware.ApiAuth deptKey is empty", "path", path)
|
||||
response.JsonExit(r, gcode.CodeNotAuthorized.Code(), consts.CodeLoginExpired.Message())
|
||||
return
|
||||
}
|
||||
|
||||
appId := r.Get("appId").String()
|
||||
if appId == "" {
|
||||
g.Log().Error(ctx, "sMiddleware.ApiAuth appId is empty", "path", path)
|
||||
response.JsonExit(r, gcode.CodeNotAuthorized.Code(), consts.CodeLoginExpired.Message())
|
||||
return
|
||||
}
|
||||
if deptKey != appId {
|
||||
g.Log().Error(ctx, "sMiddleware.ApiAuth deptKey not match appId", "path", path)
|
||||
response.JsonExit(r, gcode.CodeNotAuthorized.Code(), consts.CodeLoginExpired.Message())
|
||||
return
|
||||
}
|
||||
if contexts.GetAgentDeptId(ctx) == 0 {
|
||||
g.Log().Error(ctx, "sMiddleware.ApiAuth deptId is 0", "path", path)
|
||||
response.JsonExit(r, gcode.CodeNotAuthorized.Code(), consts.CodeLoginExpired.Message())
|
||||
return
|
||||
}
|
||||
allowed, err := s.hasOpenApiPermission(ctx, r.Method, path)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sMiddleware.ApiAuth permission check failed, method: %s, path: %s, err: %v", r.Method, path, err)
|
||||
response.JsonExit(r, consts.CodePermissionCheckFailed.Code(), consts.CodePermissionCheckFailed.Message())
|
||||
return
|
||||
}
|
||||
if !allowed {
|
||||
response.JsonExit(r, consts.CodeNoPermission.Code(), consts.CodeNoPermission.Message())
|
||||
return
|
||||
}
|
||||
|
||||
r.Middleware.Next()
|
||||
}
|
||||
|
||||
func openApiMethodValues(method string) []string {
|
||||
method = strings.ToUpper(strings.TrimSpace(method))
|
||||
values := []string{method}
|
||||
switch method {
|
||||
case "GET":
|
||||
values = append(values, "4")
|
||||
case "POST":
|
||||
values = append(values, "1")
|
||||
case "PUT":
|
||||
values = append(values, "2")
|
||||
case "DELETE":
|
||||
values = append(values, "3")
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
// hasOpenApiPermission 判定当前 API 账号对指定路由是否已授权。
|
||||
// 返回值 error 仅代表权限数据查询本身的系统故障,与「无权限」(false, nil) 严格区分:
|
||||
// 调用方对 error 返回系统异常码,对 false 返回无权限码,避免把数据库故障误报成授权问题。
|
||||
// 未登记资源、资源停用、账号不可用、未绑定权限组均按无权限处理。
|
||||
func (s *sMiddleware) hasOpenApiPermission(ctx context.Context, method, path string) (bool, error) {
|
||||
agentUser := contexts.GetAgentUser(ctx)
|
||||
if agentUser == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
path = strings.TrimSpace(path)
|
||||
if path != "/" {
|
||||
path = strings.TrimRight(path, "/")
|
||||
}
|
||||
// 每条受保护路由(含 /common 兼容前缀的别名路由)都必须显式登记资源并完成授权,
|
||||
// 资源查询失败不得降级为放行。
|
||||
var apiResource entity.TApiResource
|
||||
err := dao.TApiResource.Ctx(ctx).
|
||||
WhereIn(dao.TApiResource.Columns().Method, openApiMethodValues(method)).
|
||||
Where(dao.TApiResource.Columns().Path, path).
|
||||
Scan(&apiResource)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return false, gerror.Wrapf(err, "query t_api_resource failed, method: %s, path: %s", method, path)
|
||||
}
|
||||
if err != nil || apiResource.Id == 0 {
|
||||
g.Log().Warningf(ctx, "sMiddleware.hasOpenApiPermission resource not configured, method: %s, path: %s", method, path)
|
||||
return false, nil
|
||||
}
|
||||
if apiResource.Status == 1 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
var tApi entity.TApi
|
||||
apiMod := dao.TApi.Ctx(ctx)
|
||||
if agentUser.ApiId > 0 {
|
||||
apiMod = apiMod.Where(dao.TApi.Columns().Id, agentUser.ApiId)
|
||||
} else if agentUser.AppKey != "" {
|
||||
apiMod = apiMod.Where(dao.TApi.Columns().AppKey, agentUser.AppKey)
|
||||
} else {
|
||||
g.Log().Warningf(ctx, "sMiddleware.hasOpenApiPermission api identity is empty, resourceApiId: %d, path: %s", apiResource.Id, path)
|
||||
return false, nil
|
||||
}
|
||||
if err := apiMod.Scan(&tApi); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
g.Log().Warningf(ctx, "sMiddleware.hasOpenApiPermission t_api not found, resourceApiId: %d, path: %s", apiResource.Id, path)
|
||||
return false, nil
|
||||
}
|
||||
return false, gerror.Wrapf(err, "query t_api failed, resourceApiId: %d, path: %s", apiResource.Id, path)
|
||||
}
|
||||
if tApi.Id == 0 || tApi.Status == 1 {
|
||||
g.Log().Warningf(ctx, "sMiddleware.hasOpenApiPermission t_api unavailable, tApiId: %d, status: %d, resourceApiId: %d, path: %s", tApi.Id, tApi.Status, apiResource.Id, path)
|
||||
return false, nil
|
||||
}
|
||||
if tApi.GroupId <= 0 {
|
||||
g.Log().Warningf(ctx, "sMiddleware.hasOpenApiPermission groupId is empty, tApiId: %d, appKey: %s, resourceApiId: %d, path: %s", tApi.Id, tApi.AppKey, apiResource.Id, path)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
count, err := dao.TApiGroupResource.Ctx(ctx).
|
||||
Where(dao.TApiGroupResource.Columns().GroupId, tApi.GroupId).
|
||||
Where(dao.TApiGroupResource.Columns().ApiId, apiResource.Id).
|
||||
Count()
|
||||
if err != nil {
|
||||
return false, gerror.Wrapf(err, "query t_api_group_resource failed, groupId: %d, resourceApiId: %d, path: %s", tApi.GroupId, apiResource.Id, path)
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
func (m *sMiddleware) OldResponseHandler(r *ghttp.Request) {
|
||||
r.Middleware.Next()
|
||||
if r.Response.BufferLength() > 0 || contexts.IsWebSocket(r.GetCtx()) {
|
||||
return
|
||||
}
|
||||
if getContentType(r) == consts.HTTPContentTypeExcel {
|
||||
r.Response.Status = http.StatusOK
|
||||
return
|
||||
}
|
||||
|
||||
err := r.GetError()
|
||||
res := r.GetHandlerResponse()
|
||||
var code gcode.Code = gcode.CodeOK
|
||||
if err != nil {
|
||||
code = gerror.Code(err)
|
||||
if code == gcode.CodeNil {
|
||||
code = gcode.CodeInternalError
|
||||
}
|
||||
response.OldJson(r, code.Code(), err.Error())
|
||||
return
|
||||
}
|
||||
response.OldJson(r, code.Code(), "", res)
|
||||
}
|
||||
|
||||
func (s *sMiddleware) OldCtx(r *ghttp.Request) {
|
||||
ctx := r.GetCtx()
|
||||
r.SetCtx(r.GetNeverDoneCtx())
|
||||
|
||||
verifyVar := r.Get("verify")
|
||||
var verify model.VerifyItem
|
||||
if err := verifyVar.Scan(&verify); err != nil {
|
||||
g.Log().Errorf(ctx, "sMiddleware.OldCtx verify scan error: %v", err)
|
||||
response.JsonExit(r, gcode.CodeNotAuthorized.Code(), "request param invalid")
|
||||
return
|
||||
}
|
||||
|
||||
var tApi entity.TApi
|
||||
err := dao.TApi.Ctx(ctx).Where(g.Map{
|
||||
dao.TApi.Columns().AppKey: verify.AppId,
|
||||
}).Scan(&tApi)
|
||||
if err != nil {
|
||||
r.Middleware.Next()
|
||||
return
|
||||
}
|
||||
if tApi.Id != 0 {
|
||||
contextModel := new(model.Context)
|
||||
contextModel.User = &model.Identity{
|
||||
BaseClaims: model.BaseClaims{
|
||||
ID: 0,
|
||||
TenantId: tApi.TenantId,
|
||||
DeptId: tApi.DeptId,
|
||||
Roles: []model.Role{
|
||||
{RoleId: apiDataScopeRoleId, DataScope: consts.SysRoleDataScopeDeptAndBelow},
|
||||
},
|
||||
LoginAt: gtime.Now().TimestampMilli(),
|
||||
},
|
||||
}
|
||||
contextModel.AgentUser = &model.BaseClaimsApi{
|
||||
ApiId: tApi.Id,
|
||||
AppKey: tApi.AppKey,
|
||||
GroupId: tApi.GroupId,
|
||||
DeptId: tApi.DeptId,
|
||||
TenantId: tApi.TenantId,
|
||||
LoginAt: gtime.Now().TimestampMilli(),
|
||||
}
|
||||
contexts.Init(r, contextModel)
|
||||
}
|
||||
r.Middleware.Next()
|
||||
}
|
||||
|
||||
func (s *sMiddleware) OldAuth(r *ghttp.Request) {
|
||||
ctx := r.Context()
|
||||
path := r.URL.Path
|
||||
if s.IsExceptLogin(ctx, path) {
|
||||
r.Middleware.Next()
|
||||
return
|
||||
}
|
||||
|
||||
verifyVar := r.Get("verify")
|
||||
var verify model.VerifyItem
|
||||
if err := verifyVar.Scan(&verify); err != nil {
|
||||
g.Log().Errorf(ctx, "sMiddleware.OldAuth verify scan error: %v", err)
|
||||
response.JsonExit(r, gcode.CodeNotAuthorized.Code(), "request param invalid")
|
||||
return
|
||||
}
|
||||
if verify.TimeStamp == "" {
|
||||
g.Log().Error(ctx, "sMiddleware.OldAuth timestamp is empty", "path", path)
|
||||
response.JsonExit(r, gcode.CodeNotAuthorized.Code(), "request param invalid")
|
||||
return
|
||||
}
|
||||
|
||||
givenTime, err := strconv.ParseInt(verify.TimeStamp, 10, 64)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sMiddleware.OldAuth timestamp parse error: %v", err)
|
||||
response.JsonExit(r, gcode.CodeNotAuthorized.Code(), "request param invalid")
|
||||
return
|
||||
}
|
||||
if givenTime > gtime.Now().TimestampMilli()+3*60*1000 {
|
||||
g.Log().Error(ctx, "sMiddleware.OldAuth timestamp expired", "path", path)
|
||||
response.JsonExit(r, gcode.CodeNotAuthorized.Code(), "request timeout")
|
||||
return
|
||||
}
|
||||
if verify.AppId == "" {
|
||||
g.Log().Error(ctx, "sMiddleware.OldAuth appId is empty", "path", path)
|
||||
response.JsonExit(r, gcode.CodeNotAuthorized.Code(), "request param invalid")
|
||||
return
|
||||
}
|
||||
|
||||
var tApi entity.TApi
|
||||
err = dao.TApi.Ctx(ctx).Where(g.Map{
|
||||
dao.TApi.Columns().AppKey: verify.AppId,
|
||||
}).Scan(&tApi)
|
||||
if err != nil || tApi.Id == 0 || verify.Password != tApi.AppSecret {
|
||||
g.Log().Errorf(ctx, "sMiddleware.OldAuth auth failed: %v", err)
|
||||
response.JsonExit(r, gcode.CodeNotAuthorized.Code(), consts.CodeUserNotFound.Message())
|
||||
return
|
||||
}
|
||||
r.Middleware.Next()
|
||||
}
|
||||
|
||||
func (s *sMiddleware) IsExceptLogin(ctx context.Context, path string) bool {
|
||||
pathList := g.Cfg().MustGet(ctx, "router.exceptLogin").Strings()
|
||||
return slices.Contains(pathList, path)
|
||||
}
|
||||
Reference in New Issue
Block a user