fix: enforce platform role menu access
This commit is contained in:
@@ -346,6 +346,7 @@ func TestListPlatformMenuReturnsParentIdentityWithoutParentID(t *testing.T) {
|
||||
AddRow(uint64(2), "child-a", nil, nil, "enabled", 1, uint64(1), "child", "Child", "", "/child", 2))
|
||||
|
||||
ctx, recorder := updateContext(http.MethodGet, "/platform/platform_menu", "", nil)
|
||||
ctx.Set("Auth", &types.JwtClaims{Role: "root"})
|
||||
ListPlatformMenu(ctx)
|
||||
|
||||
assertResponseCode(t, recorder, 0)
|
||||
@@ -355,6 +356,29 @@ func TestListPlatformMenuReturnsParentIdentityWithoutParentID(t *testing.T) {
|
||||
assertMockExpectations(t, mock)
|
||||
}
|
||||
|
||||
func TestListPlatformMenuReturnsOnlyMenusAssignedToNonRootRole(t *testing.T) {
|
||||
_, mock := setupPlatformRoleDatabase(t)
|
||||
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "platform_role" WHERE role_code = $1 AND status = $2 ORDER BY "platform_role"."id" LIMIT $3`)).
|
||||
WithArgs("finance_operator", "enabled", 1).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "identity", "created_at", "updated_at", "status", "version", "role_code", "name", "data_scope", "is_system"}).
|
||||
AddRow(uint64(7), "finance-role", nil, nil, "enabled", 1, "finance_operator", "Finance", "global", false))
|
||||
mock.ExpectQuery(regexp.QuoteMeta(`SELECT platform_menu.* FROM "platform_menu" JOIN platform_role_menu_relation ON platform_role_menu_relation.platform_menu_id = platform_menu.id WHERE platform_role_menu_relation.platform_role_id = $1 AND platform_menu.status = $2 ORDER BY sort_no asc, id asc`)).
|
||||
WithArgs(uint64(7), "enabled").
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id", "identity", "created_at", "updated_at", "status", "version", "parent_id", "menu_code", "name", "icon", "path", "sort_no"}).
|
||||
AddRow(uint64(9), "finance-menu", nil, nil, "enabled", 1, uint64(0), "finance", "Finance", "", "/finance/payment", 1))
|
||||
|
||||
ctx, recorder := updateContext(http.MethodGet, "/platform/platform_menu", "", nil)
|
||||
ctx.Set("Auth", &types.JwtClaims{Role: "finance_operator"})
|
||||
ListPlatformMenu(ctx)
|
||||
|
||||
assertResponseCode(t, recorder, 0)
|
||||
body := recorder.Body.String()
|
||||
if !strings.Contains(body, `"identity":"finance-menu"`) || strings.Contains(body, `"gas-menu"`) {
|
||||
t.Fatalf("non-root menu response was not constrained: %s", body)
|
||||
}
|
||||
assertMockExpectations(t, mock)
|
||||
}
|
||||
|
||||
func TestResourceResponseDoesNotExposeAutoIncrementRelationIDs(t *testing.T) {
|
||||
response := resourceResponse(map[string]any{"identity": "item-a", "id": uint64(1), "ec_order_id": uint64(2), "ec_product_id": uint64(3), "items": []any{map[string]any{"identity": "child-a", "delivery_task_id": uint64(4)}}})
|
||||
encoded, err := json.Marshal(response)
|
||||
@@ -398,6 +422,88 @@ func TestCreatedResourceResponseUsesSafeAllowlist(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultResourceResponseMasksPIIAndCoordinates(t *testing.T) {
|
||||
ctx, _ := updateContext(http.MethodGet, "/user/account/user-a", "user-a", nil)
|
||||
response := protectPreciseLocation(ctx, &models.UserAccount{}, map[string]any{
|
||||
"identity": "user-a",
|
||||
"name": "张三",
|
||||
"phone": "13800138000",
|
||||
"avatar": "https://private.example/avatar.png",
|
||||
"address": "敏感详细地址",
|
||||
"longitude": "120.123456",
|
||||
"latitude": "30.456789",
|
||||
})
|
||||
encoded, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
body := string(encoded)
|
||||
if !strings.Contains(body, `"identity":"user-a"`) || !strings.Contains(body, `"phone_masked":"138****8000"`) || !strings.Contains(body, `"name_masked"`) {
|
||||
t.Fatalf("default response omitted safe identity or masked PII: %s", body)
|
||||
}
|
||||
for _, forbidden := range []string{`"phone":`, `"name":`, `"avatar":`, `"address":`, `"longitude":"120.123456"`, `"latitude":"30.456789"`, "张三", "敏感详细地址", "private.example"} {
|
||||
if strings.Contains(body, forbidden) {
|
||||
t.Fatalf("default response leaked %s: %s", forbidden, body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExplicitPreciseScopeRetainsCoordinatesButStillMasksPII(t *testing.T) {
|
||||
ctx, _ := updateContext(http.MethodGet, "/user/address/address-a", "address-a", nil)
|
||||
ctx.Set("Auth", &types.JwtClaims{Extend: map[string]string{"location_scope": "precise"}})
|
||||
response := protectPreciseLocation(ctx, &models.UserAddress{}, map[string]any{
|
||||
"identity": "address-a",
|
||||
"address": "敏感详细地址",
|
||||
"longitude": "120.123456",
|
||||
"latitude": "30.456789",
|
||||
})
|
||||
encoded, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
body := string(encoded)
|
||||
if !strings.Contains(body, `"longitude":"120.123456"`) || !strings.Contains(body, `"latitude":"30.456789"`) {
|
||||
t.Fatalf("authorized response omitted precise coordinates: %s", body)
|
||||
}
|
||||
if strings.Contains(body, `"address":`) || strings.Contains(body, "敏感详细地址") {
|
||||
t.Fatalf("precise location scope leaked address PII: %s", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlatformMenuAllowsOnlyAssignedDomain(t *testing.T) {
|
||||
menus := []models.PlatformMenu{
|
||||
{MenuCode: "finance", Path: "/finance"},
|
||||
{MenuCode: "fin_payment", Path: "/finance/fin-payment"},
|
||||
}
|
||||
if !platformMenuAllowsPath(menus, "/heqi/platform/v1/finance/fin_payment") {
|
||||
t.Fatal("assigned finance domain should be allowed")
|
||||
}
|
||||
if platformMenuAllowsPath(menus, "/heqi/platform/v1/user/account") {
|
||||
t.Fatal("unassigned user domain should be denied")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreatePlatformAccountRequiresAssignableNonRootRole(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
body string
|
||||
}{
|
||||
{"missing role", `{"username":"operator","password":"secure-password"}`},
|
||||
{"root role", `{"username":"operator","password":"secure-password","platform_role_code":"root"}`},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
_, mock := setupPlatformRoleDatabase(t)
|
||||
ctx, recorder := updateContext(http.MethodPost, "/platform/platfrom_account", "", []byte(test.body))
|
||||
|
||||
CreatePlatfromAccount(ctx)
|
||||
|
||||
assertResponseCode(t, recorder, int32(status.Code(errcode.ErrInvalidArgument)))
|
||||
assertMockExpectations(t, mock)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListSafetyEventDisposalsReturnsOnlyTheRequestedEventHistory(t *testing.T) {
|
||||
_, mock := setupPlatformRoleDatabase(t)
|
||||
mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "saf_event_disposal" WHERE saf_event_identity = $1`)).
|
||||
|
||||
Reference in New Issue
Block a user