Environment
- admin-api reporting API 1.21.0, backend 1.21.2, DB schema 133 (includes d8557b3 and bb64403,
verified by observed write mask 0x700)
- gromox backend on the same appliance
- Observed both through grommunio-admin-web and through direct REST calls
Summary
Since d8557b3 / bb64403 ("Fix permission bits for store owner"), the storeAccess endpoints
operate on STOREACCESS = FOLDEROWNER | FOLDERCONTACT | FOLDERVISIBLE (0x700). This works for
grants created through the API itself, but breaks down for store owners granted by the mailbox
owner through a client (grommunio-web / Outlook, "Owner" permission on the top of the store):
DELETE /domains/{d}/users/{u}/storeAccess/{username} returns 200 "Success" but the revoked
user keeps full read/write/delete access to the store root (rights 0x4FB remain).
- Because
foldervisible is part of the remaining rights, GET storeAccess keeps listing the
user. In admin-web this becomes an endless loop: remove the entry from "Additional store
owners", save, reload, the entry is back. The grant cannot be removed from the admin side at
all.
GET storeAccess also over-reports in general: any of the three bits matches the filter, so a
user holding a plain read-only grant (foldervisible + readany) on IPM_SUBTREE is listed as
an "additional store owner" even though gromox derives store ownership from FOLDEROWNER only.
Steps to reproduce
Mailbox target@example.com (user ID {u}, domain ID {d}), grantee grantee@example.com.
- As
target@example.com in grommunio-web: right-click the top-level store entry → share folder →
add grantee@example.com with permission profile "Owner".
- Read the raw ACL row:
GET /api/v1/system/exmdb/target@example.com/folders/9
→ member grantee@example.com with rights: 2043 (0x7FB). So far so good; the grant is also
visible in GET storeAccess (this part is an improvement over the pre-bb64403 behaviour).
- Revoke via API:
DELETE /api/v1/domains/{d}/users/{u}/storeAccess/grantee@example.com → 200 "Success"
- Read the ACL row again:
→ member still present with rights: 1275 (0x4FB = readany, create, editowned, deleteowned,
editany, deleteany, createsubfolder, foldervisible)
GET /api/v1/domains/{d}/users/{u}/storeAccess → grantee is still listed.
- Repeat the DELETE (or
PUT with {"usernames": []}): both return success, the row stays at
0x4FB. Removing the entry in admin-web ("Mailbox permissions" → remove chip → save → reload)
shows the same loop.
Counter-check: a grant created via POST storeAccess (row = exactly 0x700) is revoked cleanly,
the member row is removed entirely. The problem only manifests when the row carries additional
working bits, which is exactly what every client-side "Owner" grant produces.
Analysis (source reading)
The DELETE handler does remove all of 0x700 (0x7FB → 0xFB). However, gromox normalizes every
written ACL row in permission_adjust (exch/exmdb/folder.cpp): a row containing frightsReadAny
gets frightsVisible added back ("0x1 => 0x401"). The result is 0x4FB. Since
getUserStoreAccess filters on member.rights & Permissions.STOREACCESS, the re-added
foldervisible keeps the user listed, and every further revoke is a net no-op:
remove 0x700 → normalization adds 0x400 back → row unchanged.
So each component behaves as designed, but the composition of admin-api delete mask, gromox
normalization, and the GET filter makes client-granted store owners irrevocable through the
storeAccess API.
Expected behaviour / suggestions
DELETE storeAccess should actually end store access, either by removing the member row on
IPM_SUBTREE entirely, or by clearing all rights bits (like the
DELETE /system/exmdb/{user}/folders/{fid}/permissions endpoint does), or at minimum by
documenting that residual client-granted rights survive.
GET storeAccess should list actual store owners. Given that gromox maps FOLDEROWNER on
IPM_SUBTREE to store ownership, filtering on member.rights & FOLDEROWNER would avoid both the
loop display and listing read-only grantees as store owners.
Impact
An administrator who revokes an "additional store owner" gets a success response while the revoked
user retains full read/write access to the mailbox top level. The admin UI keeps showing the user
as store owner and offers no working way to remove them. This is security-relevant: the only
complete removal paths are the mailbox owner acting in a client, or the raw
/system/exmdb/.../folders/9/permissions endpoint.
Environment
verified by observed write mask 0x700)
Summary
Since
d8557b3/bb64403("Fix permission bits for store owner"), the storeAccess endpointsoperate on
STOREACCESS = FOLDEROWNER | FOLDERCONTACT | FOLDERVISIBLE(0x700). This works forgrants created through the API itself, but breaks down for store owners granted by the mailbox
owner through a client (grommunio-web / Outlook, "Owner" permission on the top of the store):
DELETE /domains/{d}/users/{u}/storeAccess/{username}returns 200 "Success" but the revokeduser keeps full read/write/delete access to the store root (rights 0x4FB remain).
foldervisibleis part of the remaining rights,GET storeAccesskeeps listing theuser. In admin-web this becomes an endless loop: remove the entry from "Additional store
owners", save, reload, the entry is back. The grant cannot be removed from the admin side at
all.
GET storeAccessalso over-reports in general: any of the three bits matches the filter, so auser holding a plain read-only grant (
foldervisible+readany) on IPM_SUBTREE is listed asan "additional store owner" even though gromox derives store ownership from
FOLDEROWNERonly.Steps to reproduce
Mailbox
target@example.com(user ID{u}, domain ID{d}), granteegrantee@example.com.target@example.comin grommunio-web: right-click the top-level store entry → share folder →add
grantee@example.comwith permission profile "Owner".GET /api/v1/system/exmdb/target@example.com/folders/9→ member
grantee@example.comwithrights: 2043(0x7FB). So far so good; the grant is alsovisible in
GET storeAccess(this part is an improvement over the pre-bb64403 behaviour).DELETE /api/v1/domains/{d}/users/{u}/storeAccess/grantee@example.com→ 200 "Success"→ member still present with
rights: 1275(0x4FB = readany, create, editowned, deleteowned,editany, deleteany, createsubfolder, foldervisible)
GET /api/v1/domains/{d}/users/{u}/storeAccess→ grantee is still listed.PUTwith{"usernames": []}): both return success, the row stays at0x4FB. Removing the entry in admin-web ("Mailbox permissions" → remove chip → save → reload)
shows the same loop.
Counter-check: a grant created via
POST storeAccess(row = exactly 0x700) is revoked cleanly,the member row is removed entirely. The problem only manifests when the row carries additional
working bits, which is exactly what every client-side "Owner" grant produces.
Analysis (source reading)
The DELETE handler does remove all of 0x700 (0x7FB → 0xFB). However, gromox normalizes every
written ACL row in
permission_adjust(exch/exmdb/folder.cpp): a row containingfrightsReadAnygets
frightsVisibleadded back ("0x1 => 0x401"). The result is 0x4FB. SincegetUserStoreAccessfilters onmember.rights & Permissions.STOREACCESS, the re-addedfoldervisiblekeeps the user listed, and every further revoke is a net no-op:remove 0x700 → normalization adds 0x400 back → row unchanged.
So each component behaves as designed, but the composition of admin-api delete mask, gromox
normalization, and the GET filter makes client-granted store owners irrevocable through the
storeAccess API.
Expected behaviour / suggestions
DELETE storeAccessshould actually end store access, either by removing the member row onIPM_SUBTREE entirely, or by clearing all rights bits (like the
DELETE /system/exmdb/{user}/folders/{fid}/permissionsendpoint does), or at minimum bydocumenting that residual client-granted rights survive.
GET storeAccessshould list actual store owners. Given that gromox mapsFOLDEROWNERonIPM_SUBTREE to store ownership, filtering on
member.rights & FOLDEROWNERwould avoid both theloop display and listing read-only grantees as store owners.
Impact
An administrator who revokes an "additional store owner" gets a success response while the revoked
user retains full read/write access to the mailbox top level. The admin UI keeps showing the user
as store owner and offers no working way to remove them. This is security-relevant: the only
complete removal paths are the mailbox owner acting in a client, or the raw
/system/exmdb/.../folders/9/permissionsendpoint.