chore: 同步本地 v9 整改与运营能力
This commit is contained in:
@@ -14,7 +14,7 @@ type DeviceAuthorizer interface {
|
||||
CanReadDevice(userID, deviceKey string) (bool, error)
|
||||
}
|
||||
|
||||
// DBDeviceAuthorizer 使用现有 RBAC 权限判断设备读取权;当前未做用户级资源 ACL。
|
||||
// DBDeviceAuthorizer 使用 RBAC + 组织/房间归属判断设备读取权。
|
||||
type DBDeviceAuthorizer struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
@@ -36,12 +36,28 @@ func (a *DBDeviceAuthorizer) CanReadDevice(userID, deviceKey string) (bool, erro
|
||||
return true, nil
|
||||
}
|
||||
|
||||
var count int64
|
||||
var roleCount int64
|
||||
err := a.db.Table("role_permissions").
|
||||
Joins("JOIN permissions ON permissions.id = role_permissions.permission_id").
|
||||
Where("role_permissions.role = ? AND permissions.code = ?", user.Role, "device:read").
|
||||
Count(&count).Error
|
||||
return count > 0, err
|
||||
Count(&roleCount).Error
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if roleCount == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
var device model.Device
|
||||
if err := a.db.Where("device_key = ?", deviceKey).First(&device).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
var roomCount int64
|
||||
err = a.db.Table("rooms").
|
||||
Joins("JOIN organization_members om ON om.organization_id = rooms.org_id").
|
||||
Where("rooms.id = ? AND om.user_id = ?", device.RoomID, userID).
|
||||
Count(&roomCount).Error
|
||||
return roomCount > 0, err
|
||||
}
|
||||
|
||||
func (h *Hub) authorizeSubscription(userID, deviceKey string) error {
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package ws
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func TestDBDeviceAuthorizerRequiresOrganizationMembership(t *testing.T) {
|
||||
sqlDB, mock, err := sqlmock.New()
|
||||
if err != nil {
|
||||
t.Fatalf("create sqlmock: %v", err)
|
||||
}
|
||||
defer sqlDB.Close()
|
||||
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatalf("open gorm: %v", err)
|
||||
}
|
||||
|
||||
mock.ExpectQuery(`.*FROM "users".*WHERE id = \$1.*`).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "username", "email", "password_hash", "full_name", "role", "active", "created_at", "updated_at"}).
|
||||
AddRow("user-1", "viewer", "viewer@example.com", "hash", nil, "viewer", true, time.Now(), time.Now()))
|
||||
mock.ExpectQuery(`.*role_permissions.*permissions.*`).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
|
||||
mock.ExpectQuery(`.*FROM "devices".*WHERE device_key = \$1.*`).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "device_key", "name", "kind", "model", "firmware", "online_status", "last_seen", "room_id", "topic", "created_at", "updated_at"}).
|
||||
AddRow("device-1", "device-a", "温度设备", "sensor", nil, nil, "online", nil, "room-1", nil, time.Now(), time.Now()))
|
||||
mock.ExpectQuery(`.*organization_members.*`).
|
||||
WithArgs("room-1", "user-1").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
|
||||
|
||||
authorizer := NewDBDeviceAuthorizer(db)
|
||||
ok, err := authorizer.CanReadDevice("user-1", "device-a")
|
||||
if err != nil {
|
||||
t.Fatalf("CanReadDevice error: %v", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatal("member should read device in same organization")
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatalf("unmet sql expectations: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDBDeviceAuthorizerDeniesForeignRoom(t *testing.T) {
|
||||
sqlDB, mock, err := sqlmock.New()
|
||||
if err != nil {
|
||||
t.Fatalf("create sqlmock: %v", err)
|
||||
}
|
||||
defer sqlDB.Close()
|
||||
db, err := gorm.Open(postgres.New(postgres.Config{Conn: sqlDB}), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatalf("open gorm: %v", err)
|
||||
}
|
||||
|
||||
mock.ExpectQuery(`.*FROM "users".*WHERE id = \$1.*`).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "username", "email", "password_hash", "full_name", "role", "active", "created_at", "updated_at"}).
|
||||
AddRow("user-1", "viewer", "viewer@example.com", "hash", nil, "viewer", true, time.Now(), time.Now()))
|
||||
mock.ExpectQuery(`.*role_permissions.*permissions.*`).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
|
||||
mock.ExpectQuery(`.*FROM "devices".*WHERE device_key = \$1.*`).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "device_key", "name", "kind", "model", "firmware", "online_status", "last_seen", "room_id", "topic", "created_at", "updated_at"}).
|
||||
AddRow("device-1", "device-a", "温度设备", "sensor", nil, nil, "online", nil, "room-1", nil, time.Now(), time.Now()))
|
||||
mock.ExpectQuery(`.*organization_members.*`).
|
||||
WithArgs("room-1", "user-1").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(0))
|
||||
|
||||
authorizer := NewDBDeviceAuthorizer(db)
|
||||
ok, err := authorizer.CanReadDevice("user-1", "device-a")
|
||||
if err != nil {
|
||||
t.Fatalf("CanReadDevice error: %v", err)
|
||||
}
|
||||
if ok {
|
||||
t.Fatal("user in another organization should not read device")
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatalf("unmet sql expectations: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user