package middleware import ( "context" "errors" "net/http" "slices" "strings" "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts" "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/bcache" "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts" "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/mcache" "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/rolepolicy" "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model" "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/packed/response" "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service" "git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility" "git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/queue" "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/util/gmeta" ) type sMiddleware struct{} 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 { return } if contexts.IsWebSocket(r.GetCtx()) { return } if disposition := r.Response.Header().Get("Content-Disposition"); strings.Contains(strings.ToLower(disposition), "attachment") { r.Response.Status = http.StatusOK return } contentType := getContentType(r) if contentType == 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 } var channelTestErr *model.TSimChannelsTestError if errors.As(err, &channelTestErr) { response.JsonExit(r, code.Code(), err.Error(), &model.TSimChannelsTestOutput{ GatewayResponseRaw: channelTestErr.GatewayResponseRaw, }) } response.JsonExit(r, code.Code(), err.Error()) } else { response.Json(r, code.Code(), "", res) } } func (s *sMiddleware) Ctx(r *ghttp.Request) { ctx := r.GetCtx() r.SetCtx(r.GetNeverDoneCtx()) data, err := service.SysAuth().GetCurrentUser(ctx) if err != nil { r.Middleware.Next() return } if data != nil { contextModel := new(model.Context) contextModel.User = &model.Identity{ BaseClaims: data.BaseClaims, } contexts.Init(r, contextModel) } r.Middleware.Next() } func (s *sMiddleware) CORS(r *ghttp.Request) { r.SetCtx(r.GetNeverDoneCtx()) if contexts.IsWebSocket(r.GetCtx()) { r.Middleware.Next() return } corsOptions := r.Response.DefaultCORSOptions() corsConfig := g.Cfg().MustGet(context.Background(), "server.allowedDomains").Strings() if len(corsConfig) == 0 { r.Response.CORSDefault() } else { corsOptions.AllowDomain = corsConfig r.Response.CORS(corsOptions) } r.Middleware.Next() } func (s *sMiddleware) Auth(r *ghttp.Request) { var ( ctx = r.Context() method = r.Method path = "" ) if r.URL != nil { path = r.URL.Path } if s.IsExceptLogin(ctx, path) { r.Middleware.Next() return } userId := contexts.GetUserId(ctx) tenantId := contexts.GetTenantId(ctx) g.Log().Debugf(ctx, "sMiddleware.Auth userId: %d tenantId: %s method: %s path: %s, ================>>", userId, tenantId, method, path) if userId == 0 { g.Log().Error(ctx, "sMiddleware.Auth userId is 0", "path", path, "clientIp", r.GetClientIp(), "User-Agent", utility.GetUserAgent(ctx), "Authorization", r.Header.Get("Authorization")) response.JsonExit(r, gcode.CodeNotAuthorized.Code(), consts.CodeLoginExpired.Message()) return } if contexts.IsWebSocket(ctx) { r.Middleware.Next() return } forceLogout, err := mcache.GetSystemConfig(ctx, consts.ConfigOnlineForceLogout, consts.ConfigOnlineForceLogoutFalse) if err != nil { g.Log().Errorf(ctx, "sMiddleware.Auth GetSystemConfig error: %v", err) response.JsonExit(r, gcode.CodeNotAuthorized.Code(), consts.CodeLoginExpired.Message()) return } if forceLogout == consts.ConfigOnlineForceLogoutTrue { uuid := contexts.GetUserUuid(ctx) token, err := bcache.GetSysAuthTokenReject(ctx, userId, uuid) if err != nil { g.Log().Errorf(ctx, "sMiddleware.Auth GetSysAuthTokenReject error: %v", err) response.JsonExit(r, gcode.CodeNotAuthorized.Code(), consts.CodeLoginExpired.Message()) return } if token != "" { g.Log().Infof(ctx, "sMiddleware.Auth token is reject, userId: %d, uuid: %s, token: %s", userId, uuid, token) response.JsonExit(r, gcode.CodeNotAuthorized.Code(), consts.CodeLoginExpired.Message()) return } } if contexts.IsSuperAdmin(r.GetCtx()) && tenantId == consts.DefaultSystemTenantCode { isAdmin, checkErr := rolepolicy.CurrentSuperAdmin(ctx, userId) if checkErr != nil { g.Log().Errorf(ctx, "verify super admin failed: %v", checkErr) response.JsonExit(r, gcode.CodeNotAuthorized.Code(), consts.CodeLoginExpired.Message()) return } if isAdmin { r.Middleware.Next() return } } if contexts.IsSuperAdmin(ctx) { user := contexts.GetUser(ctx) user.Roles = slices.DeleteFunc(user.Roles, func(role model.Role) bool { return role.RoleId == consts.SuperAdminRoleId }) } serveHandler := r.GetServeHandler() userAccessCodeList, menuRoleDataAccessCodeList, err := service.SysAuth().GetUserAccessCodeList(ctx, userId) if err != nil { g.Log().Errorf(ctx, "sMiddleware.Auth GetUserAccessCodeList error: %v", err) response.JsonExit(r, gcode.CodeNotAuthorized.Code(), consts.CodeLoginExpired.Message()) return } // 回填到请求上下文:Identity 由 Ctx 中间件只带 BaseClaims 构造,权限码仅在此处查出。 // 若不回填,contexts.GetAccessCodeList 在后续业务里恒为空(下载中心按导出权限过滤任务、 // 下载/删除二次校验都依赖它),表现为非超管看不到自己的导出任务、下载被拒。 if user := contexts.GetUser(ctx); user != nil { user.AccessCodeList = userAccessCodeList } if serveHandler != nil { g.Log().Infof(ctx, "sMiddleware.Auth serveHandler GetMetaTag(x-check-permission): %+v", serveHandler.GetMetaTag("x-check-permission")) accessCode := serveHandler.GetMetaTag("x-check-permission") if accessCode != "" { accessCodeList := strings.Split(accessCode, ",") hasPermission := false for _, code := range accessCodeList { if strings.HasPrefix(code, consts.SysCheckPermissionPrefix) { accessCodeItemList := strings.Split(code, "&") hasItem := true for _, item := range accessCodeItemList { if !hasAccessCode(userAccessCodeList, item) { g.Log().Errorf(ctx, "sMiddleware.Auth userAccessCodeList not contains item: %v, userAccessCodeList: %v", item, userAccessCodeList) hasItem = false break } for _, roleDataAccessCode := range menuRoleDataAccessCodeList { roleDataAccessCodeItemList := strings.Split(roleDataAccessCode, "|") if len(roleDataAccessCodeItemList) > 0 && matchAccessCode(roleDataAccessCodeItemList[0], item) { g.Log().Debugf(ctx, "sMiddleware.Auth roleDataAccessCodeItemList: %v", roleDataAccessCodeItemList) if len(roleDataAccessCodeItemList) == 3 { roleId := roleDataAccessCodeItemList[1] roleDataScope := roleDataAccessCodeItemList[2] contexts.SetDataValue(ctx, "currentRoleId", roleId) contexts.SetDataValue(ctx, "currentRoleDataScope", roleDataScope) g.Log().Debugf(ctx, "sMiddleware.Auth currentRoleId: %s, currentRoleDataScope: %s", roleId, roleDataScope) } break } } } if hasItem { hasPermission = true break } } } if !hasPermission { response.JsonExit(r, consts.CodeNoPermission.Code(), consts.CodeNoPermission.Message()) return } } } r.Middleware.Next() } func matchAccessCode(pattern, code string) bool { if pattern == code { return true } if strings.HasSuffix(pattern, "*") { prefix := pattern[:len(pattern)-1] return strings.HasPrefix(code, prefix) } return false } func hasAccessCode(userAccessCodeList []string, requiredCode string) bool { for _, userCode := range userAccessCodeList { if matchAccessCode(userCode, requiredCode) { return true } } return false } func (s *sMiddleware) IsExceptLogin(ctx context.Context, path string) bool { pathList := g.Cfg().MustGet(ctx, "router.exceptLogin").Strings() return slices.Contains(pathList, path) } func (s *sMiddleware) OperationLog(r *ghttp.Request) { r.Middleware.Next() ctx := r.Context() enabledDBOperationLog := g.Cfg().MustGet(ctx, "server.enabledDBOperationLog").Bool() if !enabledDBOperationLog { return } // Replay accepts only a Run ID. Rejecting extra request fields at the // controller protects the Worker; redact the generic operation log as an // additional guard against clients submitting card/token data anyway. if strings.HasPrefix(r.URL.Path, "/api/v1/iot/fullSyncRuns/replay") { return } data, err := service.SysOperLog().AnalysisLog(ctx) if err != nil { g.Log().Errorf(context.TODO(), "sMiddleware.OperationLog error:%v", err) return } queue.Push(r.GetCtx(), consts.QueueSysOptLog, data) } func (s *sMiddleware) ResponseHandlerRaw(r *ghttp.Request) { r.Middleware.Next() if r.Response.BufferLength() > 0 { g.Log().Infof(r.Context(), "ResponseHandler r.Response.BufferLength() %d buf:%s", r.Response.BufferLength(), r.Response.BufferString()) return } res := r.GetHandlerResponse() if r.GetError() != nil { r.Response.WriteJson(res) r.ExitAll() } else { r.Response.WriteJson(res) } }