fix: answer for a group mailbox as the group when the group address is one of your identities #634

This commit is contained in:
Linus Rath
2026-11-01 06:00:43 +01:00
parent e796631389
commit 36160f9fbd
2 changed files with 16 additions and 5 deletions
+7
View File
@@ -45,6 +45,13 @@ describe('resolveAnswerTargets', () => {
expect(view.targets[0].sentBy).toBeUndefined();
});
it('answers as the group also when the member has the group address as an identity', () => {
const view = resolveAnswerTargets(base(invitation(['team@example.org'], { accountId: 'team' }), {
owner: TEAM, userEmails: ['bob@example.org', 'team@example.org'], canRsvpSelf: true,
}));
expect(view.targets.map((t) => [t.kind, t.participantId, t.sentBy])).toEqual([['owner', 'team', undefined]]);
});
it('offers no answer for the owner without edit access, and says why', () => {
for (const rights of [CALENDAR_PRESETS.read, { ...CALENDAR_PRESETS.read, mayRSVP: true }]) {
const view = resolveAnswerTargets(base(invitation(['alice@example.org']), { calendar: { isShared: true, myRights: rights } }));
+9 -5
View File
@@ -99,27 +99,31 @@ export function resolveAnswerTargets(input: ResolveAnswerInput): AnswerView {
if (calendar?.isShared && owner) {
const ownerId = getUserParticipantId(event, owner.emails);
if (ownerId && ownerId !== selfId && !isOrganizer(event, owner.emails) && !eventHasNoOwner(event)) {
const participant = event.participants![ownerId];
// Members of a group often have the group's address as an identity of
// their own: the group's participant is still answered as the group.
const ownersOwn = ownerId && (ownerId !== selfId || owner.isGroup);
if (ownersOwn && !isOrganizer(event, owner.emails) && !eventHasNoOwner(event)) {
const participant = event.participants![ownerId!];
const answeredBy = addressOf(participant.sentBy);
if (!calendar.myRights?.mayWriteAll) {
return { targets: selfTarget(), note: { kind: 'no_write', owner: owner.name }, answeredBy };
}
if (input.server016 && selfId) {
const alsoInvited = !!selfId && selfId !== ownerId;
if (input.server016 && alsoInvited) {
return { targets: ownCopyTarget(), note: { kind: 'also_invited', owner: owner.name }, answeredBy };
}
const delegate = userEmails[0]?.trim().toLowerCase();
const ownerTarget: AnswerTarget = {
kind: 'owner',
eventId: event.id,
participantId: ownerId,
participantId: ownerId!,
status: participant.participationStatus ?? null,
ownerName: owner.name,
// Always the mailto: form: Stalwart passes sentBy through unchecked,
// and RFC 5545's SENT-BY needs a cal-address.
...(!owner.isGroup && delegate ? { sentBy: `mailto:${delegate}` } : {}),
};
return { targets: [ownerTarget, ...(selfId ? ownCopyTarget() : [])], note: null, answeredBy };
return { targets: [ownerTarget, ...(alsoInvited ? ownCopyTarget() : [])], note: null, answeredBy };
}
}