{"record":{"id":"ada9ac4635c56016","repo":"bitwarden/server","slug":"resources-must-be-unique","errorCode":null,"errorMessage":"Resources must be unique","messagePattern":"Resources must be unique","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"src/Api/SecretsManager/Utilities/AccessPolicyHelpers.cs","lineNumber":32,"sourceCode":"            {\n                UserProjectAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.OrganizationUserId, ap.GrantedProjectId),\n                UserSecretAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.OrganizationUserId, ap.GrantedSecretId),\n                UserServiceAccountAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.OrganizationUserId,\n                    ap.GrantedServiceAccountId),\n                GroupProjectAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.GroupId, ap.GrantedProjectId),\n                GroupSecretAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.GroupId, ap.GrantedSecretId),\n                GroupServiceAccountAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.GroupId, ap.GrantedServiceAccountId),\n                ServiceAccountProjectAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.ServiceAccountId,\n                    ap.GrantedProjectId),\n                ServiceAccountSecretAccessPolicy ap => new Tuple<Guid?, Guid?>(ap.ServiceAccountId,\n                    ap.GrantedSecretId),\n                _ => throw new ArgumentException(\"Unsupported access policy type provided.\", nameof(baseAccessPolicy)),\n            };\n        }).ToList();\n\n        if (accessPolicies.Count != distinctAccessPolicies.Count)\n        {\n            throw new BadRequestException(\"Resources must be unique\");\n        }\n    }\n\n    public static void CheckAccessPoliciesHaveReadPermission(IEnumerable<BaseAccessPolicy> accessPolicies)\n    {\n        var accessPoliciesPermission = accessPolicies.All(policy => policy.Read);\n        if (!accessPoliciesPermission)\n        {\n            throw new BadRequestException(\"Resources must be Read = true\");\n        }\n    }\n}\n","sourceCodeStart":14,"sourceCodeEnd":45,"githubUrl":"https://github.com/bitwarden/server/blob/e93b962371d80964556f5590c6615f5160a437a1/src/Api/SecretsManager/Utilities/AccessPolicyHelpers.cs#L14-L45","documentation":"Thrown by AccessPolicyHelpers.CheckForDistinctAccessPolicies when the number of input access policies does not equal the number of distinct policies after de-duplication by (granteeId, grantedResourceId) tuple. This means the request contains duplicate access policy entries — the same user/group is granted access to the same project/secret/service-account more than once.","triggerScenarios":"An access policy bulk-create/update request includes the same (organizationUserId, grantedProjectId) or (groupId, grantedSecretId) pair multiple times — e.g., the client sends two entries for the same user on the same project.","commonSituations":"Frontend form resubmits and appends duplicates; merging access policy lists from multiple sources without de-duplicating; copy-paste error in policy JSON.","solutions":["De-duplicate the access policy list by (granteeId, grantedResourceId) before submitting.","Inspect the request payload for repeated entries and remove duplicates.","If using a UI, ensure the add-access form prevents adding the same user/group twice for the same resource."],"exampleFix":"// before: duplicate user+project pair\nvar policies = new[] {\n  new { OrgUserId = uid, GrantedProjectId = pid, Read = true },\n  new { OrgUserId = uid, GrantedProjectId = pid, Read = true }  // duplicate\n};\n// after: distinct pairs only\nvar policies = new[] {\n  new { OrgUserId = uid, GrantedProjectId = pid, Read = true }\n};","handlingStrategy":"validation","validationCode":"// De-duplicate access policies by (grantee, granted) before submission\nvar distinct = policies\n    .GroupBy(p => GetPolicyKey(p))\n    .Select(g => g.First())\n    .ToList();\n// GetPolicyKey returns (granteeId, grantedResourceId) per policy type\nawait client.SetAccessPoliciesAsync(distinct);","typeGuard":"static bool HasDistinctAccessPolicies(IEnumerable<BaseAccessPolicy> policies)\n    => policies.Count() == policies.DistinctBy(GetPolicyKey).Count();","tryCatchPattern":"try { await client.SetAccessPoliciesAsync(policies); }\ncatch (ApiException ex) when (ex.Message.Contains(\"must be unique\"))\n{\n    var deduped = policies.GroupBy(GetPolicyKey).Select(g => g.First()).ToList();\n    await client.SetAccessPoliciesAsync(deduped);\n}","preventionTips":["Always de-duplicate access policy collections by (grantee, granted resource) before submit.","Prevent duplicate entries in UI selectors (disable already-added users/groups).","Merge policy lists from multiple sources with a union-by-key step."],"tags":["secrets-manager","access-policies","validation","bitwarden"],"backgroundTag":null,"analyzedSha":"e93b962371d80964556f5590c6615f5160a437a1","analyzedAt":"2026-08-13T14:22:19.382Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}