mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 00:12:39 +00:00
fix: show a folder shared read only on Stalwart 1.0 as read only, not custom
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { detectPreset, MAILBOX_PRESETS } from '../share-rights';
|
||||
import type { MailboxRights } from '../types';
|
||||
|
||||
// What Stalwart 1.0 and v1.0.0 return for a folder shared with the "read"
|
||||
// preset (checked on the test servers, 2026-10-28): the keyword right comes
|
||||
// back true with the seen right, and v1.0.0 adds maySetMetadata.
|
||||
const READ_AS_STORED_BY_1_0 = {
|
||||
mayReadItems: true, mayAddItems: false, mayRemoveItems: false, maySetSeen: true,
|
||||
maySetKeywords: true, mayCreateChild: false, mayRename: false, maySubmit: false,
|
||||
mayDelete: false, mayShare: false, maySetMetadata: false,
|
||||
} as MailboxRights;
|
||||
|
||||
describe('detectPreset', () => {
|
||||
it('reads a folder shared read-only on Stalwart 1.0 back as read only', () => {
|
||||
expect(detectPreset('mailbox', READ_AS_STORED_BY_1_0)).toBe('read');
|
||||
});
|
||||
|
||||
it('still tells read and write apart', () => {
|
||||
expect(detectPreset('mailbox', { ...READ_AS_STORED_BY_1_0, mayAddItems: true, mayRemoveItems: true })).toBe('readWrite');
|
||||
expect(detectPreset('mailbox', MAILBOX_PRESETS.read)).toBe('read');
|
||||
});
|
||||
|
||||
it('does not excuse a keyword right without the seen right', () => {
|
||||
expect(detectPreset('mailbox', { ...MAILBOX_PRESETS.read, maySetSeen: false, maySetKeywords: true })).toBe('custom');
|
||||
});
|
||||
|
||||
it('ignores rights a newer server adds', () => {
|
||||
expect(detectPreset('addressBook', { mayRead: true, mayWrite: true, mayDelete: false, mayShare: false, maySetMetadata: false } as never)).toBe('readWrite');
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,17 @@
|
||||
/**
|
||||
* The rights Bulwark grants when it shares a folder, calendar, address book
|
||||
* or file (`shareWith/<principalId>`), as named presets, and the preset a
|
||||
* stored grant amounts to.
|
||||
* or file (`shareWith/<principalId>`), as named presets. The share dialog of
|
||||
* a single item and the Delegates page both build their patches from here.
|
||||
*
|
||||
* What Stalwart stores differs from what it was sent in places:
|
||||
* - 1.0 keeps `maySetSeen` and `maySetKeywords` of a mailbox as one right, so
|
||||
* a "read" grant (seen yes, keywords no) reads back with both true.
|
||||
* - v1.0.0 adds `maySetMetadata` to every type. Bulwark never sends it (0.16
|
||||
* would refuse the property), and the server stores false.
|
||||
* - Stalwart before 0.16.6 names file rights mayRead / mayWrite / mayShare;
|
||||
* JMAPClient.setFileNodeShare converts on the way out.
|
||||
* So presets are compared on the rights they define, and the mailbox
|
||||
* keyword right is allowed to follow the seen right.
|
||||
*/
|
||||
|
||||
import type { AddressBookRights, CalendarRights, FileNodeRights, MailboxRights } from './types';
|
||||
@@ -79,11 +89,13 @@ function presetsFor(kind: ShareKind): Partial<Record<RolePreset, AnyRights>> {
|
||||
return ADDRESS_BOOK_PRESETS;
|
||||
}
|
||||
|
||||
function matches(preset: AnyRights, rights: AnyRights): boolean {
|
||||
function matches(kind: ShareKind, preset: AnyRights, rights: AnyRights): boolean {
|
||||
const have = rights as unknown as Record<string, boolean | undefined>;
|
||||
return Object.entries(preset).every(([key, want]) => {
|
||||
const got = have[key] ?? false;
|
||||
return got === want;
|
||||
if (got === want) return true;
|
||||
// Stalwart 1.0 stores the two as one right (see the top of this file).
|
||||
return kind === 'mailbox' && key === 'maySetKeywords' && got === true && have.maySetSeen === true;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -96,7 +108,7 @@ export function presetRights(kind: ShareKind, preset: RolePreset): AnyRights | u
|
||||
/** The preset a stored grant amounts to, or "custom". */
|
||||
export function detectPreset(kind: ShareKind, rights: AnyRights): RolePreset {
|
||||
for (const [name, preset] of Object.entries(presetsFor(kind)) as [RolePreset, AnyRights][]) {
|
||||
if (matches(preset, rights)) return name;
|
||||
if (matches(kind, preset, rights)) return name;
|
||||
}
|
||||
return 'custom';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user