Files
silk/server-go/internal/handler/access.go
T
2026-08-17 21:43:26 +08:00

384 lines
10 KiB
Go

package handler
import (
"net/http"
"silk-server-go/internal/model"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
const defaultOrgCode = "default"
// currentUserRole 从 JWT context 中取当前角色。
func currentUserRole(c *gin.Context) string {
user, ok := c.Get("user")
if !ok {
return ""
}
m, ok := user.(map[string]interface{})
if !ok {
return ""
}
role, _ := m["role"].(string)
return role
}
// hasGlobalAccess admin 拥有跨组织全部对象访问权。
func hasGlobalAccess(c *gin.Context) bool {
return currentUserRole(c) == model.RoleAdmin
}
// applyOrgScope 对组织归属列(如 rooms.org_id)追加当前用户可见组织过滤。
func applyOrgScope(q *gorm.DB, c *gin.Context, column string) *gorm.DB {
if hasGlobalAccess(c) {
return q
}
userID := currentUserID(c)
if userID == nil {
return q.Where("1 = 0")
}
return q.Where("CAST("+column+" AS text) IN (SELECT organization_id::text FROM organization_members WHERE user_id = ?)", *userID)
}
// applyRoomScope 对业务记录的房间列追加当前用户可见房间过滤。
func applyRoomScope(q *gorm.DB, c *gin.Context, column string) *gorm.DB {
if hasGlobalAccess(c) {
return q
}
userID := currentUserID(c)
if userID == nil {
return q.Where("1 = 0")
}
return q.Where("CAST("+column+" AS text) IN ("+
"SELECT r.id::text FROM rooms r "+
"JOIN organization_members om ON om.organization_id = r.org_id "+
"WHERE om.user_id = ?)", *userID)
}
// canAccessOrganization 判断当前用户是否属于指定组织。
func canAccessOrganization(db *gorm.DB, c *gin.Context, orgID string) bool {
if hasGlobalAccess(c) {
return true
}
userID := currentUserID(c)
if userID == nil {
return false
}
var count int64
db.Model(&model.OrganizationMember{}).
Where("organization_id = ? AND user_id = ?", orgID, *userID).
Count(&count)
return count > 0
}
// canAccessRoom 判断当前用户是否能访问指定房间。
func canAccessRoom(db *gorm.DB, c *gin.Context, roomID *string) bool {
if roomID == nil || *roomID == "" {
return false
}
if hasGlobalAccess(c) {
return true
}
userID := currentUserID(c)
if userID == nil {
return false
}
var count int64
db.Model(&model.Room{}).
Joins("JOIN organization_members om ON om.organization_id = rooms.org_id").
Where("rooms.id = ? AND om.user_id = ?", *roomID, *userID).
Count(&count)
return count > 0
}
// canAccessDeviceKey 判断当前用户是否能访问指定 deviceKey 对应的设备。
func canAccessDeviceKey(db *gorm.DB, c *gin.Context, deviceKey string) bool {
if hasGlobalAccess(c) {
return true
}
if deviceKey == "" {
return false
}
if currentUserID(c) == nil {
return false
}
var device model.Device
if db.Where("device_key = ?", deviceKey).First(&device).Error != nil {
return false
}
return canAccessRoom(db, c, &device.RoomID)
}
// requireDeviceKeyAccess 统一校验 deviceKey 对象授权;无权返回 403。
func requireDeviceKeyAccess(c *gin.Context, db *gorm.DB, deviceKey string) bool {
if canAccessDeviceKey(db, c, deviceKey) {
return true
}
if deviceKey == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "deviceKey 不能为空"})
return false
}
var count int64
db.Model(&model.Device{}).Where("device_key = ?", deviceKey).Count(&count)
if count == 0 {
c.JSON(http.StatusNotFound, gin.H{"error": "device not found"})
return false
}
c.JSON(http.StatusForbidden, gin.H{"error": "无权访问该设备"})
return false
}
// requireObjectAccess 统一对象授权检查;无权返回 403。
func requireObjectAccess(c *gin.Context, db *gorm.DB, entityType, id string) bool {
if hasGlobalAccess(c) {
return true
}
roomID, ok := objectRoomID(db, entityType, id)
if !ok {
c.JSON(http.StatusNotFound, gin.H{"error": entityType + " not found"})
return false
}
if !canAccessRoom(db, c, roomID) {
c.JSON(http.StatusForbidden, gin.H{"error": "无权访问该对象"})
return false
}
return true
}
// objectRoomID 返回实体归属房间;实体不存在时返回 false。
func objectRoomID(db *gorm.DB, entityType, id string) (*string, bool) {
switch entityType {
case "room":
var room model.Room
if db.Where("id = ?", id).First(&room).Error != nil {
return nil, false
}
return &room.ID, true
case "device":
var device model.Device
if db.Where("id = ?", id).First(&device).Error != nil {
return nil, false
}
return &device.RoomID, true
case "sensor":
var sensor model.Sensor
if db.Where("id = ?", id).First(&sensor).Error != nil {
return nil, false
}
var device model.Device
if db.Where("id = ?", sensor.DeviceID).First(&device).Error != nil {
return nil, false
}
return &device.RoomID, true
case "threshold":
var threshold model.Threshold
if db.Where("id = ?", id).First(&threshold).Error != nil {
return nil, false
}
var sensor model.Sensor
if db.Where("id = ?", threshold.SensorID).First(&sensor).Error != nil {
return nil, false
}
var device model.Device
if db.Where("id = ?", sensor.DeviceID).First(&device).Error != nil {
return nil, false
}
return &device.RoomID, true
case "alarm":
var alarm model.Alarm
if db.Where("id = ?", id).First(&alarm).Error != nil {
return nil, false
}
if alarm.DeviceKey == nil || *alarm.DeviceKey == "" {
return nil, false
}
var device model.Device
if db.Where("device_key = ?", *alarm.DeviceKey).First(&device).Error != nil {
return nil, false
}
return &device.RoomID, true
case "device_maintenance_record":
var rec model.DeviceMaintenanceRecord
if db.Where("id = ?", id).First(&rec).Error != nil {
return nil, false
}
var device model.Device
if db.Where("id = ?", rec.DeviceID).First(&device).Error != nil {
return nil, false
}
return &device.RoomID, true
case "camera":
var camera model.Camera
if db.Where("id = ?", id).First(&camera).Error != nil {
return nil, false
}
return camera.RoomID, true
case "video_clip":
var clip model.VideoClip
if db.Where("id = ?", id).First(&clip).Error != nil {
return nil, false
}
return clip.RoomID, true
case "tray":
var tray model.Tray
if db.Where("id = ?", id).First(&tray).Error != nil {
return nil, false
}
return &tray.RoomID, true
case "batch":
var batch model.Batch
if db.Where("id = ?", id).First(&batch).Error != nil {
return nil, false
}
return &batch.RoomID, true
case "rearing_record":
var rec model.RearingRecord
if db.Where("id = ?", id).First(&rec).Error != nil {
return nil, false
}
return batchRoomID(db, &rec.BatchID)
case "inspection":
var rec model.InspectionRecord
if db.Where("id = ?", id).First(&rec).Error != nil {
return nil, false
}
return rec.RoomID, true
case "lamp_test":
var rec model.LampTest
if db.Where("id = ?", id).First(&rec).Error != nil {
return nil, false
}
return rec.RoomID, true
case "detection_task":
var rec model.DetectionTask
if db.Where("id = ?", id).First(&rec).Error != nil {
return nil, false
}
return rec.RoomID, true
case "consultation":
var rec model.Consultation
if db.Where("id = ?", id).First(&rec).Error != nil {
return nil, false
}
return rec.RoomID, true
case "trace_record":
var rec model.TraceRecord
if db.Where("id = ?", id).First(&rec).Error != nil {
return nil, false
}
return rec.RoomID, true
case "disease_event":
var rec model.DiseaseEvent
if db.Where("id = ?", id).First(&rec).Error != nil {
return nil, false
}
return rec.RoomID, true
case "sample":
var rec model.Sample
if db.Where("id = ?", id).First(&rec).Error != nil {
return nil, false
}
return rec.RoomID, true
case "seed_source":
var rec model.SeedSource
if db.Where("id = ?", id).First(&rec).Error != nil {
return nil, false
}
return batchRoomID(db, rec.BatchID)
case "disinfection_record":
var rec model.DisinfectionRecord
if db.Where("id = ?", id).First(&rec).Error != nil {
return nil, false
}
if rec.RoomID != nil {
return rec.RoomID, true
}
return batchRoomID(db, rec.BatchID)
case "production_loss_record":
var rec model.ProductionLossRecord
if db.Where("id = ?", id).First(&rec).Error != nil {
return nil, false
}
if rec.RoomID != nil {
return rec.RoomID, true
}
return batchRoomID(db, rec.BatchID)
case "laboratory_result":
var rec model.LaboratoryResult
if db.Where("id = ?", id).First(&rec).Error != nil {
return nil, false
}
if rec.RoomID != nil {
return rec.RoomID, true
}
if rec.BatchID != nil {
return batchRoomID(db, rec.BatchID)
}
if rec.TraceRecordID != nil {
var trace model.TraceRecord
if db.Where("id = ?", *rec.TraceRecordID).First(&trace).Error == nil {
return trace.RoomID, true
}
}
return nil, true
default:
return nil, false
}
}
func batchRoomID(db *gorm.DB, batchID *string) (*string, bool) {
if batchID == nil {
return nil, false
}
var batch model.Batch
if db.Where("id = ?", *batchID).First(&batch).Error != nil {
return nil, false
}
return &batch.RoomID, true
}
// defaultOrgID 返回当前用户第一个组织;管理员未指定时返回默认组织。
func defaultOrgID(db *gorm.DB, c *gin.Context) *string {
if userID := currentUserID(c); userID != nil {
var member model.OrganizationMember
if db.Where("user_id = ?", *userID).Order("created_at ASC").First(&member).Error == nil {
return &member.OrganizationID
}
}
var org model.Organization
if db.Where("code = ?", defaultOrgCode).First(&org).Error == nil {
return &org.ID
}
return nil
}
// firstAccessibleRoomID 返回当前用户可见的第一个房间;用于无显式 roomId 的历史创建流程。
func firstAccessibleRoomID(db *gorm.DB, c *gin.Context) *string {
var room model.Room
q := db.Model(&model.Room{})
if !hasGlobalAccess(c) {
q = applyOrgScope(q, c, "org_id")
}
if q.Order("created_at ASC").First(&room).Error == nil {
return &room.ID
}
return nil
}
// assignDefaultOrganization 注册新用户时补齐默认组织成员关系。
func assignDefaultOrganization(db *gorm.DB, userID string) {
var org model.Organization
if db.Where("code = ?", defaultOrgCode).First(&org).Error != nil {
return
}
var count int64
db.Model(&model.OrganizationMember{}).
Where("organization_id = ? AND user_id = ?", org.ID, userID).
Count(&count)
if count == 0 {
_ = db.Create(&model.OrganizationMember{OrganizationID: org.ID, UserID: userID, Role: "member"}).Error
}
}