updatePNI: Cleanup only for obsolete PNI

This commit is contained in:
Scott Nonnenberg 2022-12-12 14:06:16 -08:00 committed by GitHub
parent a72a431e0f
commit f366454893
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 132 additions and 45 deletions

View file

@ -1062,7 +1062,9 @@ export class ConversationController {
log.warn(`${logId}: Delete all sessions tied to old conversationId`); log.warn(`${logId}: Delete all sessions tied to old conversationId`);
// Note: we use the conversationId here in case we've already lost our uuid. // Note: we use the conversationId here in case we've already lost our uuid.
await window.textsecure.storage.protocol.removeAllSessions(obsoleteId); await window.textsecure.storage.protocol.removeSessionsByConversation(
obsoleteId
);
log.warn( log.warn(
`${logId}: Delete all identity information tied to old conversationId` `${logId}: Delete all identity information tied to old conversationId`

View file

@ -1226,35 +1226,68 @@ export class SignalProtocolStore extends EventEmitter {
}); });
} }
async removeAllSessions(identifier: string): Promise<void> { async removeSessionsByConversation(identifier: string): Promise<void> {
return this.withZone(GLOBAL_ZONE, 'removeAllSessions', async () => { return this.withZone(
GLOBAL_ZONE,
'removeSessionsByConversation',
async () => {
if (!this.sessions) {
throw new Error(
'removeSessionsByConversation: this.sessions not yet cached!'
);
}
if (identifier == null) {
throw new Error(
'removeSessionsByConversation: identifier was undefined/null'
);
}
log.info(
'removeSessionsByConversation: deleting sessions for',
identifier
);
const id = window.ConversationController.getConversationId(identifier);
strictAssert(
id,
`removeSessionsByConversation: Conversation not found: ${identifier}`
);
const entries = Array.from(this.sessions.values());
for (let i = 0, max = entries.length; i < max; i += 1) {
const entry = entries[i];
if (entry.fromDB.conversationId === id) {
this.sessions.delete(entry.fromDB.id);
this.pendingSessions.delete(entry.fromDB.id);
}
}
await window.Signal.Data.removeSessionsByConversation(id);
}
);
}
async removeSessionsByUUID(uuid: UUIDStringType): Promise<void> {
return this.withZone(GLOBAL_ZONE, 'removeSessionsByUUID', async () => {
if (!this.sessions) { if (!this.sessions) {
throw new Error('removeAllSessions: this.sessions not yet cached!'); throw new Error('removeSessionsByUUID: this.sessions not yet cached!');
} }
if (identifier == null) { log.info('removeSessionsByUUID: deleting sessions for', uuid);
throw new Error('removeAllSessions: identifier was undefined/null');
}
log.info('removeAllSessions: deleting sessions for', identifier);
const id = window.ConversationController.getConversationId(identifier);
strictAssert(
id,
`removeAllSessions: Conversation not found: ${identifier}`
);
const entries = Array.from(this.sessions.values()); const entries = Array.from(this.sessions.values());
for (let i = 0, max = entries.length; i < max; i += 1) { for (let i = 0, max = entries.length; i < max; i += 1) {
const entry = entries[i]; const entry = entries[i];
if (entry.fromDB.conversationId === id) { if (entry.fromDB.uuid === uuid) {
this.sessions.delete(entry.fromDB.id); this.sessions.delete(entry.fromDB.id);
this.pendingSessions.delete(entry.fromDB.id); this.pendingSessions.delete(entry.fromDB.id);
} }
} }
await window.Signal.Data.removeSessionsByConversation(id); await window.Signal.Data.removeSessionsByUUID(uuid);
}); });
} }
@ -1961,10 +1994,7 @@ export class SignalProtocolStore extends EventEmitter {
return false; return false;
} }
async removeIdentityKey( async removeIdentityKey(uuid: UUID): Promise<void> {
uuid: UUID,
options?: { disableSessionDeletion: boolean }
): Promise<void> {
if (!this.identityKeys) { if (!this.identityKeys) {
throw new Error('removeIdentityKey: this.identityKeys not yet cached!'); throw new Error('removeIdentityKey: this.identityKeys not yet cached!');
} }
@ -1972,9 +2002,7 @@ export class SignalProtocolStore extends EventEmitter {
const id = uuid.toString(); const id = uuid.toString();
this.identityKeys.delete(id); this.identityKeys.delete(id);
await window.Signal.Data.removeIdentityKeyById(id); await window.Signal.Data.removeIdentityKeyById(id);
if (!options?.disableSessionDeletion) { await this.removeSessionsByUUID(id);
await this.removeAllSessions(id);
}
} }
// Not yet processed messages - for resiliency // Not yet processed messages - for resiliency

View file

@ -1966,14 +1966,7 @@ export class ConversationModel extends window.Backbone
// for the case where we need to do old and new PNI comparisons. We'll wait // for the case where we need to do old and new PNI comparisons. We'll wait
// for the PNI update to do that. // for the PNI update to do that.
if (oldValue && oldValue !== this.get('pni')) { if (oldValue && oldValue !== this.get('pni')) {
// We've already changed our UUID, so we need account for lookups on that old UUID window.textsecure.storage.protocol.removeIdentityKey(UUID.cast(oldValue));
// to returng nothing: pass conversationId into removeAllSessions, and disable
// auto-deletion in removeIdentityKey.
window.textsecure.storage.protocol.removeAllSessions(this.id);
window.textsecure.storage.protocol.removeIdentityKey(
UUID.cast(oldValue),
{ disableSessionDeletion: true }
);
} }
this.captureChange('updateUuid'); this.captureChange('updateUuid');
@ -2059,14 +2052,7 @@ export class ConversationModel extends window.Backbone
// If this PNI is going away or going to someone else, we'll delete all its sessions // If this PNI is going away or going to someone else, we'll delete all its sessions
if (oldValue) { if (oldValue) {
// We've already changed our UUID, so we need account for lookups on that old UUID window.textsecure.storage.protocol.removeIdentityKey(UUID.cast(oldValue));
// to returng nothing: pass conversationId into removeAllSessions, and disable
// auto-deletion in removeIdentityKey.
window.textsecure.storage.protocol.removeAllSessions(this.id);
window.textsecure.storage.protocol.removeIdentityKey(
UUID.cast(oldValue),
{ disableSessionDeletion: true }
);
} }
if (pni && !this.get('uuid')) { if (pni && !this.get('uuid')) {

View file

@ -425,6 +425,7 @@ export type DataInterface = {
bulkAddSessions: (array: Array<SessionType>) => Promise<void>; bulkAddSessions: (array: Array<SessionType>) => Promise<void>;
removeSessionById: (id: SessionIdType) => Promise<void>; removeSessionById: (id: SessionIdType) => Promise<void>;
removeSessionsByConversation: (conversationId: string) => Promise<void>; removeSessionsByConversation: (conversationId: string) => Promise<void>;
removeSessionsByUUID: (uuid: UUIDStringType) => Promise<void>;
removeAllSessions: () => Promise<void>; removeAllSessions: () => Promise<void>;
getAllSessions: () => Promise<Array<SessionType>>; getAllSessions: () => Promise<Array<SessionType>>;

View file

@ -200,6 +200,7 @@ const dataInterface: ServerInterface = {
bulkAddSessions, bulkAddSessions,
removeSessionById, removeSessionById,
removeSessionsByConversation, removeSessionsByConversation,
removeSessionsByUUID,
removeAllSessions, removeAllSessions,
getAllSessions, getAllSessions,
@ -1308,6 +1309,17 @@ async function removeSessionsByConversation(
conversationId, conversationId,
}); });
} }
async function removeSessionsByUUID(uuid: UUIDStringType): Promise<void> {
const db = getInstance();
db.prepare<Query>(
`
DELETE FROM sessions
WHERE uuid = $uuid;
`
).run({
uuid,
});
}
async function removeAllSessions(): Promise<void> { async function removeAllSessions(): Promise<void> {
return removeAllFromTable(getInstance(), SESSIONS_TABLE); return removeAllFromTable(getInstance(), SESSIONS_TABLE);
} }

View file

@ -995,7 +995,7 @@ describe('SignalProtocolStore', () => {
assert.equal(record, testRecord); assert.equal(record, testRecord);
}); });
}); });
describe('removeAllSessions', () => { describe('removeSessionsByUUID', () => {
it('removes all sessions for a uuid', async () => { it('removes all sessions for a uuid', async () => {
const devices = [1, 2, 3].map( const devices = [1, 2, 3].map(
deviceId => deviceId =>
@ -1008,14 +1008,72 @@ describe('SignalProtocolStore', () => {
}) })
); );
await store.removeAllSessions(theirUuid.toString()); const records0 = await Promise.all(
devices.map(device => store.loadSession(device))
);
for (let i = 0, max = records0.length; i < max; i += 1) {
assert.exists(records0[i], 'before delete');
}
await store.removeSessionsByUUID(theirUuid.toString());
const records = await Promise.all( const records = await Promise.all(
devices.map(device => store.loadSession(device)) devices.map(device => store.loadSession(device))
); );
for (let i = 0, max = records.length; i < max; i += 1) { for (let i = 0, max = records.length; i < max; i += 1) {
assert.isUndefined(records[i]); assert.isUndefined(records[i], 'in-memory');
}
await store.hydrateCaches();
const records2 = await Promise.all(
devices.map(device => store.loadSession(device))
);
for (let i = 0, max = records2.length; i < max; i += 1) {
assert.isUndefined(records2[i], 'from database');
}
});
});
describe('removeSessionsByConversation', () => {
it('removes all sessions for a uuid', async () => {
const devices = [1, 2, 3].map(
deviceId =>
new QualifiedAddress(ourUuid, new Address(theirUuid, deviceId))
);
const conversationId = window.ConversationController.getOrCreate(
theirUuid.toString(),
'private'
).id;
await Promise.all(
devices.map(async encodedAddress => {
await store.storeSession(encodedAddress, getSessionRecord());
})
);
const records0 = await Promise.all(
devices.map(device => store.loadSession(device))
);
for (let i = 0, max = records0.length; i < max; i += 1) {
assert.exists(records0[i], 'before delete');
}
await store.removeSessionsByConversation(conversationId);
const records = await Promise.all(
devices.map(device => store.loadSession(device))
);
for (let i = 0, max = records.length; i < max; i += 1) {
assert.isUndefined(records[i], 'in-memory');
}
await store.hydrateCaches();
const records2 = await Promise.all(
devices.map(device => store.loadSession(device))
);
for (let i = 0, max = records2.length; i < max; i += 1) {
assert.isUndefined(records[i], 'from database');
} }
}); });
}); });
@ -1145,7 +1203,7 @@ describe('SignalProtocolStore', () => {
beforeEach(async () => { beforeEach(async () => {
await store.removeAllUnprocessed(); await store.removeAllUnprocessed();
await store.removeAllSessions(theirUuid.toString()); await store.removeSessionsByUUID(theirUuid.toString());
await store.removeAllSenderKeys(); await store.removeAllSenderKeys();
}); });