mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-01 00:07:01 +00:00
fix: stop showing a share's principal id as its address when the server can't name the person
This commit is contained in:
@@ -22,6 +22,10 @@ import { JMAPClient } from '../jmap-client';
|
||||
import { CAPABILITIES } from '../types';
|
||||
import { applyShareChanges, getShareTargets, supportsDelegation, supportsMailboxSharing } from '../delegation';
|
||||
import { buildGrantChanges, buildRevokeChanges, summarizeDelegates } from '../../lib/delegation/delegation';
|
||||
import { principalRowDisplay, resolvePrincipal } from '../../lib/delegation/display';
|
||||
import { listPrincipals } from '../files';
|
||||
|
||||
const t = (_key: string, fallback?: string) => fallback ?? '';
|
||||
|
||||
const SERVER = process.env.BULWARK_LIVE_JMAP;
|
||||
const USER = process.env.BULWARK_LIVE_USER ?? 'usera@example.org';
|
||||
@@ -120,4 +124,32 @@ live('live Stalwart: delegates', () => {
|
||||
expect(gone.calendars.filter((id) => changes.some((c) => c.id === id))).toEqual([]);
|
||||
expect(gone.addressBooks.filter((id) => changes.some((c) => c.id === id))).toEqual([]);
|
||||
}, 120_000);
|
||||
|
||||
// Stalwart 1.0 with directory queries off (VM 106's 1.0.1) names nobody
|
||||
// the owner merely shares with: Principal/get answers notFound for the
|
||||
// delegate's id, a letter or two. The row reads "Unknown person" then, not
|
||||
// "Unknown person / e"; 0.16 names them.
|
||||
it('names who has access where the server lets it, and shows no id where it does not', async () => {
|
||||
const { targets } = await getShareTargets();
|
||||
const principalId = delegate.accountId;
|
||||
const results = await applyShareChanges(buildGrantChanges(targets, principalId, { mail: 'none', calendars: 'none', addressBooks: 'default' }, 'read'));
|
||||
expect(results.filter((r) => !r.ok)).toEqual([]);
|
||||
try {
|
||||
const listing = await listPrincipals();
|
||||
const sessionAccounts = new Map(Object.entries(owner.currentSession?.accounts ?? {})
|
||||
.filter(([id]) => id !== owner.accountId)
|
||||
.map(([id, info]) => [id, info.name]));
|
||||
const principal = resolvePrincipal(principalId, { directory: listing.principals, remembered: new Map(), sessionAccounts });
|
||||
const row = principalRowDisplay(principal, t);
|
||||
if (listing.directoryQueriesOff) {
|
||||
expect(principal).toBeNull();
|
||||
expect(row).toEqual({ name: 'Unknown person', detail: null });
|
||||
} else {
|
||||
expect(principal?.email?.toLowerCase()).toBe(USER2!.toLowerCase());
|
||||
expect(row.name).not.toBe('Unknown person');
|
||||
}
|
||||
} finally {
|
||||
expect(await revokeAll()).toBe(true);
|
||||
}
|
||||
}, 120_000);
|
||||
});
|
||||
|
||||
@@ -164,8 +164,10 @@ export function ShareCollectionSheet<R extends object>({
|
||||
<Text style={styles.principalName} numberOfLines={1}>
|
||||
{p?.description || p?.name || (unresolved ? t('sharing.unknown_user', 'Unknown user') : principalId)}
|
||||
</Text>
|
||||
{p?.email || unresolved ? (
|
||||
<Text style={styles.principalEmail} numberOfLines={1}>{p?.email || principalId}</Text>
|
||||
{/* Never the id in its place: Stalwart 1.0's are a letter or two
|
||||
("e") and read as a broken address. */}
|
||||
{p?.email ? (
|
||||
<Text style={styles.principalEmail} numberOfLines={1}>{p.email}</Text>
|
||||
) : null}
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { translate } from '../../../i18n';
|
||||
import { principalRowDisplay, resolvePrincipal } from '../display';
|
||||
|
||||
const tEn = (key: string, fallback?: string, params?: Record<string, string | number>) => translate('en', key, fallback, params);
|
||||
|
||||
describe('someone with access the server does not name', () => {
|
||||
// Stalwart 1.0 with directory queries off: Principal/get answers notFound
|
||||
// for the sharee's id ("e") and the session does not list them.
|
||||
it('reads as "Unknown person" without the id as an address', () => {
|
||||
const principal = resolvePrincipal('e', { directory: [], remembered: new Map(), sessionAccounts: new Map() });
|
||||
expect(principal).toBeNull();
|
||||
expect(principalRowDisplay(principal, tEn)).toEqual({ name: 'Unknown person', detail: null });
|
||||
});
|
||||
|
||||
it('reads as the person when the directory names them (0.16)', () => {
|
||||
const userB = { id: 'e', type: 'individual' as const, name: 'userb@example.org', email: 'userb@example.org', description: 'User B' };
|
||||
const principal = resolvePrincipal('e', { directory: [userB], remembered: new Map(), sessionAccounts: new Map() });
|
||||
expect(principalRowDisplay(principal, tEn)).toEqual({ name: 'userb@example.org', detail: 'User B' });
|
||||
});
|
||||
|
||||
it('gets no id for an address line in the calendar and address book share sheet', () => {
|
||||
const sheet = fs.readFileSync(path.resolve(__dirname, '../../../components/sharing/ShareCollectionSheet.tsx'), 'utf8');
|
||||
const label = sheet.slice(sheet.indexOf('const renderPrincipalLabel'), sheet.indexOf('return (', sheet.indexOf('const renderPrincipalLabel')) + 900);
|
||||
expect(label).not.toMatch(/p\?\.email \|\| principalId/);
|
||||
expect(label).toMatch(/\{p\?\.email \? \(/);
|
||||
});
|
||||
});
|
||||
@@ -17,6 +17,17 @@ export function principalDisplay(principal: Pick<Principal, 'id' | 'name' | 'ema
|
||||
return { name, detail };
|
||||
}
|
||||
|
||||
/**
|
||||
* The row for someone with access: the principal's name and address, or
|
||||
* "Unknown person" with nothing under it. Stalwart 1.0 with directory
|
||||
* queries off names nobody the user merely shares with; its ids are a
|
||||
* letter or two ("e"), which read as a broken address when shown.
|
||||
*/
|
||||
export function principalRowDisplay(principal: Principal | null, t: Translate): { name: string; detail: string | null } {
|
||||
if (principal) return principalDisplay(principal);
|
||||
return { name: t('delegates.unknown_person', 'Unknown person'), detail: null };
|
||||
}
|
||||
|
||||
/**
|
||||
* The principal behind an id: from the directory, the names kept on this
|
||||
* device, or, as a last way to name someone the directory won't (Stalwart
|
||||
|
||||
Reference in New Issue
Block a user