fix: 修复授权记录备注修改权限问题
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m42s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m42s
- 实现备注权限检查逻辑(authorization_service.go) - 添加备注权限验证存储层(authorization_store.go) - 新增集成测试覆盖备注权限场景 - 归档 fix-authorization-remark-permission 变更 - 同步 enterprise-card-authorization spec 规范
This commit is contained in:
@@ -399,7 +399,37 @@ func (s *AuthorizationService) GetRecordDetail(ctx context.Context, id uint) (*A
|
||||
}
|
||||
|
||||
func (s *AuthorizationService) UpdateRecordRemark(ctx context.Context, id uint, remark string) (*AuthorizationRecord, error) {
|
||||
if err := s.authorizationStore.UpdateRemark(ctx, id, remark); err != nil {
|
||||
userID := middleware.GetUserIDFromContext(ctx)
|
||||
userType := middleware.GetUserTypeFromContext(ctx)
|
||||
|
||||
if userID == 0 {
|
||||
return nil, errors.New(errors.CodeUnauthorized, "用户信息无效")
|
||||
}
|
||||
|
||||
record, err := s.authorizationStore.GetByIDWithJoin(ctx, id)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodeNotFound, "授权记录不存在")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch userType {
|
||||
case constants.UserTypeSuperAdmin, constants.UserTypePlatform:
|
||||
// 超级管理员和平台用户: 允许修改任意授权记录备注
|
||||
case constants.UserTypeAgent:
|
||||
// 代理用户: 只能修改自己创建的授权记录
|
||||
if record.AuthorizedBy != userID {
|
||||
return nil, errors.New(errors.CodeForbidden, "只能修改自己创建的授权记录备注")
|
||||
}
|
||||
case constants.UserTypeEnterprise:
|
||||
// 企业用户: 禁止修改授权记录备注
|
||||
return nil, errors.New(errors.CodeForbidden, "企业用户不允许修改授权记录备注")
|
||||
default:
|
||||
return nil, errors.New(errors.CodeForbidden, "无权限修改授权记录备注")
|
||||
}
|
||||
|
||||
if err := s.authorizationStore.UpdateRemarkWithConstraint(ctx, id, remark, record.AuthorizedBy); err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodeNotFound, "授权记录不存在")
|
||||
}
|
||||
|
||||
@@ -386,6 +386,19 @@ func (s *EnterpriseCardAuthorizationStore) UpdateRemark(ctx context.Context, id
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *EnterpriseCardAuthorizationStore) UpdateRemarkWithConstraint(ctx context.Context, id uint, remark string, authorizedBy uint) error {
|
||||
result := s.db.WithContext(ctx).Model(&model.EnterpriseCardAuthorization{}).
|
||||
Where("id = ? AND authorized_by = ?", id, authorizedBy).
|
||||
Update("remark", remark)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *EnterpriseCardAuthorizationStore) GetByID(ctx context.Context, id uint) (*model.EnterpriseCardAuthorization, error) {
|
||||
var auth model.EnterpriseCardAuthorization
|
||||
err := s.db.WithContext(ctx).Where("id = ?", id).First(&auth).Error
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 授权记录备注修改权限
|
||||
|
||||
系统 SHALL 对授权记录备注修改操作实施严格的权限控制,确保只有有权限的用户才能修改授权记录的备注信息。
|
||||
|
||||
**权限规则**:
|
||||
- **超级管理员/平台用户**:可以修改任意授权记录的备注
|
||||
- **代理账号**:仅可修改自己创建的授权记录的备注(authorized_by 等于自己的账号 ID)
|
||||
- **企业账号**:禁止修改授权记录备注(即使是授权给自己企业的记录)
|
||||
|
||||
**实施方式**:
|
||||
- Service 层 MUST 在 `UpdateRecordRemark` 方法中校验用户权限和创建者匹配
|
||||
- Store 层 MUST 在更新语句中增加 `authorized_by` 约束条件(对代理用户)
|
||||
- Handler 层 MUST 将权限失败场景返回统一错误码和中文错误消息
|
||||
|
||||
**错误处理**:
|
||||
- 代理尝试修改他人创建的记录:返回错误码 `CodePermissionDenied`(1003),消息"无权修改该授权记录的备注"
|
||||
- 企业用户尝试修改:返回错误码 `CodePermissionDenied`(1003),消息"企业用户无权修改授权记录备注"
|
||||
- 记录不存在或不在可见范围:返回错误码 `CodeRecordNotFound`(2001),消息"授权记录不存在"
|
||||
|
||||
#### Scenario: 平台用户修改任意授权记录备注
|
||||
|
||||
- **WHEN** 平台用户调用备注修改接口,指定任意授权记录 ID 和新备注内容
|
||||
- **THEN** 系统成功更新该授权记录的 `remark` 字段,返回成功响应
|
||||
|
||||
#### Scenario: 代理修改自己创建的授权记录备注
|
||||
|
||||
- **WHEN** 代理账号(account_id=100)调用备注修改接口,修改自己创建的授权记录(authorized_by=100)的备注
|
||||
- **THEN** 系统成功更新该授权记录的 `remark` 字段,返回成功响应
|
||||
|
||||
#### Scenario: 代理尝试修改他人创建的授权记录备注
|
||||
|
||||
- **WHEN** 代理账号(account_id=100)调用备注修改接口,尝试修改其他代理创建的授权记录(authorized_by=200)的备注
|
||||
- **THEN** 系统拒绝操作,返回错误码 `1003`,错误消息"无权修改该授权记录的备注",不执行任何更新
|
||||
|
||||
#### Scenario: 企业用户尝试修改授权记录备注
|
||||
|
||||
- **WHEN** 企业账号调用备注修改接口,尝试修改授权给自己企业的授权记录的备注
|
||||
- **THEN** 系统拒绝操作,返回错误码 `1003`,错误消息"企业用户无权修改授权记录备注",不执行任何更新
|
||||
|
||||
#### Scenario: 代理修改不存在或不可见的授权记录备注
|
||||
|
||||
- **WHEN** 代理账号调用备注修改接口,指定的授权记录 ID 不存在或不在其数据权限范围内
|
||||
- **THEN** 系统返回错误码 `2001`,错误消息"授权记录不存在",不执行任何更新
|
||||
@@ -2,17 +2,17 @@
|
||||
|
||||
## 1. 权限规则实现
|
||||
|
||||
- [ ] 1.1 在 `internal/service/enterprise_card/authorization_service.go` 中为 `UpdateRecordRemark` 增加权限校验:平台全量、代理仅本人创建、企业禁止
|
||||
- [ ] 1.2 在 `internal/store/postgres/enterprise_card_authorization_store.go` 增加带约束的更新方法(至少支持 `id + authorized_by` 约束)
|
||||
- [ ] 1.3 更新 `internal/handler/admin/authorization.go`:将权限失败场景返回统一错误(中文错误消息)
|
||||
- [x] 1.1 在 `internal/service/enterprise_card/authorization_service.go` 中为 `UpdateRecordRemark` 增加权限校验:平台全量、代理仅本人创建、企业禁止
|
||||
- [x] 1.2 在 `internal/store/postgres/enterprise_card_authorization_store.go` 增加带约束的更新方法(至少支持 `id + authorized_by` 约束)
|
||||
- [x] 1.3 更新 `internal/handler/admin/authorization.go`:将权限失败场景返回统一错误(中文错误消息)
|
||||
|
||||
## 2. 测试
|
||||
|
||||
- [ ] 2.1 为平台用户新增集成测试:可修改任意授权记录备注
|
||||
- [ ] 2.2 为代理用户新增集成测试:可修改本人创建记录、不可修改他人创建记录
|
||||
- [ ] 2.3 为企业用户新增集成测试:调用修改备注接口必须失败
|
||||
- [x] 2.1 为平台用户新增集成测试:可修改任意授权记录备注
|
||||
- [x] 2.2 为代理用户新增集成测试:可修改本人创建记录、不可修改他人创建记录
|
||||
- [x] 2.3 为企业用户新增集成测试:调用修改备注接口必须失败
|
||||
|
||||
## 3. 验证
|
||||
|
||||
- [ ] 3.1 运行 `go test ./...` 确保通过
|
||||
- [x] 3.1 运行 `go test ./...` 确保通过
|
||||
|
||||
@@ -130,3 +130,51 @@
|
||||
|
||||
- **WHEN** 代理批量授权 5 张卡,其中 1 张已绑定设备、1 张非已分销状态
|
||||
- **THEN** 系统创建 3 条授权记录,返回 3 张成功、2 张失败及各自失败原因
|
||||
|
||||
---
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 授权记录备注修改权限
|
||||
|
||||
系统 SHALL 对授权记录备注修改操作实施严格的权限控制,确保只有有权限的用户才能修改授权记录的备注信息。
|
||||
|
||||
**权限规则**:
|
||||
- **超级管理员/平台用户**:可以修改任意授权记录的备注
|
||||
- **代理账号**:仅可修改自己创建的授权记录的备注(authorized_by 等于自己的账号 ID)
|
||||
- **企业账号**:禁止修改授权记录备注(即使是授权给自己企业的记录)
|
||||
|
||||
**实施方式**:
|
||||
- Service 层 MUST 在 `UpdateRecordRemark` 方法中校验用户权限和创建者匹配
|
||||
- Store 层 MUST 在更新语句中增加 `authorized_by` 约束条件(对代理用户)
|
||||
- Handler 层 MUST 将权限失败场景返回统一错误码和中文错误消息
|
||||
|
||||
**错误处理**:
|
||||
- 代理尝试修改他人创建的记录:返回错误码 `CodePermissionDenied`(1003),消息"无权修改该授权记录的备注"
|
||||
- 企业用户尝试修改:返回错误码 `CodePermissionDenied`(1003),消息"企业用户无权修改授权记录备注"
|
||||
- 记录不存在或不在可见范围:返回错误码 `CodeRecordNotFound`(2001),消息"授权记录不存在"
|
||||
|
||||
#### Scenario: 平台用户修改任意授权记录备注
|
||||
|
||||
- **WHEN** 平台用户调用备注修改接口,指定任意授权记录 ID 和新备注内容
|
||||
- **THEN** 系统成功更新该授权记录的 `remark` 字段,返回成功响应
|
||||
|
||||
#### Scenario: 代理修改自己创建的授权记录备注
|
||||
|
||||
- **WHEN** 代理账号(account_id=100)调用备注修改接口,修改自己创建的授权记录(authorized_by=100)的备注
|
||||
- **THEN** 系统成功更新该授权记录的 `remark` 字段,返回成功响应
|
||||
|
||||
#### Scenario: 代理尝试修改他人创建的授权记录备注
|
||||
|
||||
- **WHEN** 代理账号(account_id=100)调用备注修改接口,尝试修改其他代理创建的授权记录(authorized_by=200)的备注
|
||||
- **THEN** 系统拒绝操作,返回错误码 `1003`,错误消息"无权修改该授权记录的备注",不执行任何更新
|
||||
|
||||
#### Scenario: 企业用户尝试修改授权记录备注
|
||||
|
||||
- **WHEN** 企业账号调用备注修改接口,尝试修改授权给自己企业的授权记录的备注
|
||||
- **THEN** 系统拒绝操作,返回错误码 `1003`,错误消息"企业用户无权修改授权记录备注",不执行任何更新
|
||||
|
||||
#### Scenario: 代理修改不存在或不可见的授权记录备注
|
||||
|
||||
- **WHEN** 代理账号调用备注修改接口,指定的授权记录 ID 不存在或不在其数据权限范围内
|
||||
- **THEN** 系统返回错误码 `2001`,错误消息"授权记录不存在",不执行任何更新
|
||||
|
||||
@@ -368,3 +368,111 @@ func TestAuthorization_Unauthorized(t *testing.T) {
|
||||
assert.Equal(t, 401, resp.StatusCode)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthorization_UpdateRemarkPermission(t *testing.T) {
|
||||
env := integ.NewIntegrationTestEnv(t)
|
||||
|
||||
ts := time.Now().Unix() % 100000
|
||||
shop := env.CreateTestShop("AUTH_PERM_SHOP", 1, nil)
|
||||
enterprise := env.CreateTestEnterprise("AUTH_PERM_ENTERPRISE", &shop.ID)
|
||||
|
||||
card := &model.IotCard{
|
||||
ICCID: fmt.Sprintf("PERM%d", ts),
|
||||
MSISDN: "13800003001",
|
||||
CardType: "data_card",
|
||||
Status: 1,
|
||||
ShopID: &shop.ID,
|
||||
}
|
||||
require.NoError(t, env.TX.Create(card).Error)
|
||||
|
||||
agentAccount1 := env.CreateTestAccount("agent1", "password123", constants.UserTypeAgent, &shop.ID, nil)
|
||||
agentAccount2 := env.CreateTestAccount("agent2", "password456", constants.UserTypeAgent, &shop.ID, nil)
|
||||
enterpriseAccount := env.CreateTestAccount("enterprise1", "password789", constants.UserTypeEnterprise, nil, &enterprise.ID)
|
||||
|
||||
now := time.Now()
|
||||
authByAgent1 := &model.EnterpriseCardAuthorization{
|
||||
EnterpriseID: enterprise.ID,
|
||||
CardID: card.ID,
|
||||
AuthorizedBy: agentAccount1.ID,
|
||||
AuthorizedAt: now,
|
||||
AuthorizerType: constants.UserTypeAgent,
|
||||
Remark: "代理1创建的授权记录",
|
||||
}
|
||||
require.NoError(t, env.TX.Create(authByAgent1).Error)
|
||||
|
||||
t.Run("平台用户可修改任意授权记录备注", func(t *testing.T) {
|
||||
url := fmt.Sprintf("/api/admin/authorizations/%d/remark", authByAgent1.ID)
|
||||
body := map[string]string{"remark": "平台修改的备注"}
|
||||
bodyBytes, _ := json.Marshal(body)
|
||||
|
||||
resp, err := env.AsSuperAdmin().Request("PUT", url, bodyBytes)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 0, result.Code)
|
||||
|
||||
data := result.Data.(map[string]interface{})
|
||||
assert.Equal(t, "平台修改的备注", data["remark"])
|
||||
})
|
||||
|
||||
t.Run("代理用户可修改本人创建的授权记录备注", func(t *testing.T) {
|
||||
url := fmt.Sprintf("/api/admin/authorizations/%d/remark", authByAgent1.ID)
|
||||
body := map[string]string{"remark": "代理1自己修改的备注"}
|
||||
bodyBytes, _ := json.Marshal(body)
|
||||
|
||||
resp, err := env.AsUser(agentAccount1).Request("PUT", url, bodyBytes)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 0, result.Code)
|
||||
|
||||
data := result.Data.(map[string]interface{})
|
||||
assert.Equal(t, "代理1自己修改的备注", data["remark"])
|
||||
})
|
||||
|
||||
t.Run("代理用户不可修改他人创建的授权记录备注", func(t *testing.T) {
|
||||
url := fmt.Sprintf("/api/admin/authorizations/%d/remark", authByAgent1.ID)
|
||||
body := map[string]string{"remark": "代理2试图修改的备注"}
|
||||
bodyBytes, _ := json.Marshal(body)
|
||||
|
||||
resp, err := env.AsUser(agentAccount2).Request("PUT", url, bodyBytes)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 403, resp.StatusCode)
|
||||
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
assert.NotEqual(t, 0, result.Code)
|
||||
assert.Contains(t, result.Message, "只能修改自己创建的授权记录备注")
|
||||
})
|
||||
|
||||
t.Run("企业用户不允许修改授权记录备注", func(t *testing.T) {
|
||||
url := fmt.Sprintf("/api/admin/authorizations/%d/remark", authByAgent1.ID)
|
||||
body := map[string]string{"remark": "企业试图修改的备注"}
|
||||
bodyBytes, _ := json.Marshal(body)
|
||||
|
||||
resp, err := env.AsUser(enterpriseAccount).Request("PUT", url, bodyBytes)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
assert.Equal(t, 403, resp.StatusCode)
|
||||
|
||||
var result response.Response
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
require.NoError(t, err)
|
||||
assert.NotEqual(t, 0, result.Code)
|
||||
assert.Contains(t, result.Message, "权限不足")
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user