Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 3818c1d

Browse files
authored
Cypress tests for event shields (#11525)
* Factor downloadKey out to `utils.ts` * Add a new `describe` block for event shields * create a beforeEach block * Cypress tests for event shields
1 parent fca9f0e commit 3818c1d

File tree

2 files changed

+285
-76
lines changed

2 files changed

+285
-76
lines changed

cypress/e2e/crypto/crypto.spec.ts

Lines changed: 222 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,14 @@ import type { VerificationRequest } from "matrix-js-sdk/src/crypto-api";
1919
import type { CypressBot } from "../../support/bot";
2020
import { HomeserverInstance } from "../../plugins/utils/homeserver";
2121
import { UserCredentials } from "../../support/login";
22-
import { doTwoWaySasVerification, waitForVerificationRequest } from "./utils";
22+
import {
23+
doTwoWaySasVerification,
24+
downloadKey,
25+
enableKeyBackup,
26+
logIntoElement,
27+
logOutOfElement,
28+
waitForVerificationRequest,
29+
} from "./utils";
2330
import { skipIfRustCrypto } from "../../support/util";
2431

2532
interface CryptoTestContext extends Mocha.Context {
@@ -129,19 +136,26 @@ const verify = function (this: CryptoTestContext) {
129136

130137
describe("Cryptography", function () {
131138
let aliceCredentials: UserCredentials;
139+
let homeserver: HomeserverInstance;
140+
let bob: CypressBot;
132141

133142
beforeEach(function () {
134143
cy.startHomeserver("default")
135144
.as("homeserver")
136-
.then((homeserver: HomeserverInstance) => {
145+
.then((data) => {
146+
homeserver = data;
137147
cy.initTestUser(homeserver, "Alice", undefined, "alice_").then((credentials) => {
138148
aliceCredentials = credentials;
139149
});
140-
cy.getBot(homeserver, {
150+
return cy.getBot(homeserver, {
141151
displayName: "Bob",
142152
autoAcceptInvites: false,
143153
userIdPrefix: "bob_",
144-
}).as("bob");
154+
});
155+
})
156+
.as("bob")
157+
.then((data) => {
158+
bob = data;
145159
});
146160
});
147161

@@ -169,15 +183,6 @@ describe("Cryptography", function () {
169183
});
170184
}
171185

172-
/**
173-
* Click on download button and continue
174-
*/
175-
function downloadKey() {
176-
// Clicking download instead of Copy because of https://github.com/cypress-io/cypress/issues/2851
177-
cy.findByRole("button", { name: "Download" }).click();
178-
cy.contains(".mx_Dialog_primary:not([disabled])", "Continue").click();
179-
}
180-
181186
it("by recovery code", () => {
182187
skipIfRustCrypto();
183188

@@ -294,89 +299,232 @@ describe("Cryptography", function () {
294299
verify.call(this);
295300
});
296301

297-
it("should show the correct shield on edited e2e events", function (this: CryptoTestContext) {
298-
skipIfRustCrypto();
299-
cy.bootstrapCrossSigning(aliceCredentials);
302+
describe("event shields", () => {
303+
let testRoomId: string;
300304

301-
// bob has a second, not cross-signed, device
302-
cy.loginBot(this.homeserver, this.bob.getUserId(), this.bob.__cypress_password, {}).as("bobSecondDevice");
305+
beforeEach(() => {
306+
cy.bootstrapCrossSigning(aliceCredentials);
307+
autoJoin(bob);
303308

304-
autoJoin(this.bob);
309+
// create an encrypted room
310+
cy.createRoom({ name: "TestRoom", invite: [bob.getUserId()] })
311+
.as("testRoomId")
312+
.then((roomId) => {
313+
testRoomId = roomId;
314+
cy.log(`Created test room ${roomId}`);
315+
cy.visit(`/#/room/${roomId}`);
305316

306-
// first create the room, so that we can open the verification panel
307-
cy.createRoom({ name: "TestRoom", invite: [this.bob.getUserId()] })
308-
.as("testRoomId")
309-
.then((roomId) => {
310-
cy.log(`Created test room ${roomId}`);
311-
cy.visit(`/#/room/${roomId}`);
317+
// enable encryption
318+
cy.getClient().then((cli) => {
319+
cli.sendStateEvent(roomId, "m.room.encryption", { algorithm: "m.megolm.v1.aes-sha2" });
320+
});
312321

313-
// enable encryption
314-
cy.getClient().then((cli) => {
315-
cli.sendStateEvent(roomId, "m.room.encryption", { algorithm: "m.megolm.v1.aes-sha2" });
322+
// wait for Bob to join the room, otherwise our attempt to open his user details may race
323+
// with his join.
324+
cy.findByText("Bob joined the room").should("exist");
316325
});
326+
});
327+
328+
it("should show the correct shield on e2e events", function (this: CryptoTestContext) {
329+
skipIfRustCrypto();
317330

318-
// wait for Bob to join the room, otherwise our attempt to open his user details may race
319-
// with his join.
320-
cy.findByText("Bob joined the room").should("exist");
331+
// Bob has a second, not cross-signed, device
332+
let bobSecondDevice: MatrixClient;
333+
cy.loginBot(homeserver, bob.getUserId(), bob.__cypress_password, {}).then(async (data) => {
334+
bobSecondDevice = data;
321335
});
322336

323-
verify.call(this);
337+
/* Should show an error for a decryption failure */
338+
cy.wrap(0).then(() =>
339+
bob.sendEvent(testRoomId, "m.room.encrypted", {
340+
algorithm: "m.megolm.v1.aes-sha2",
341+
ciphertext: "the bird is in the hand",
342+
}),
343+
);
344+
345+
cy.get(".mx_EventTile_last")
346+
.should("contain", "Unable to decrypt message")
347+
.find(".mx_EventTile_e2eIcon")
348+
.should("have.class", "mx_EventTile_e2eIcon_decryption_failure")
349+
.should("have.attr", "aria-label", "This message could not be decrypted");
350+
351+
/* Should show a red padlock for an unencrypted message in an e2e room */
352+
cy.wrap(0)
353+
.then(() =>
354+
bob.http.authedRequest<ISendEventResponse>(
355+
// @ts-ignore-next this wants a Method instance, but that is hard to get to here
356+
"PUT",
357+
`/rooms/${encodeURIComponent(testRoomId)}/send/m.room.message/test_txn_1`,
358+
undefined,
359+
{
360+
msgtype: "m.text",
361+
body: "test unencrypted",
362+
},
363+
),
364+
)
365+
.then((resp) => cy.log(`Bob sent unencrypted event with event id ${resp.event_id}`));
324366

325-
cy.get<string>("@testRoomId").then((roomId) => {
367+
cy.get(".mx_EventTile_last")
368+
.should("contain", "test unencrypted")
369+
.find(".mx_EventTile_e2eIcon")
370+
.should("have.class", "mx_EventTile_e2eIcon_warning")
371+
.should("have.attr", "aria-label", "Unencrypted");
372+
373+
/* Should show no padlock for an unverified user */
326374
// bob sends a valid event
327-
cy.wrap(this.bob.sendTextMessage(roomId, "Hoo!")).as("testEvent");
328-
329-
// the message should appear, decrypted, with no warning
330-
cy.get(".mx_EventTile_last .mx_EventTile_body")
331-
.within(() => {
332-
cy.findByText("Hoo!");
333-
})
334-
.closest(".mx_EventTile")
335-
.should("not.have.descendants", ".mx_EventTile_e2eIcon_warning");
336-
337-
// bob sends an edit to the first message with his unverified device
338-
cy.get<MatrixClient>("@bobSecondDevice").then((bobSecondDevice) => {
375+
cy.wrap(0)
376+
.then(() => bob.sendTextMessage(testRoomId, "test encrypted 1"))
377+
.then((resp) => cy.log(`Bob sent message from primary device with event id ${resp.event_id}`));
378+
379+
// the message should appear, decrypted, with no warning, but also no "verified"
380+
cy.get(".mx_EventTile_last")
381+
.should("contain", "test encrypted 1")
382+
// no e2e icon
383+
.should("not.have.descendants", ".mx_EventTile_e2eIcon");
384+
385+
/* Now verify Bob */
386+
verify.call(this);
387+
388+
/* Existing message should be updated when user is verified. */
389+
cy.get(".mx_EventTile_last")
390+
.should("contain", "test encrypted 1")
391+
// still no e2e icon
392+
.should("not.have.descendants", ".mx_EventTile_e2eIcon");
393+
394+
/* should show no padlock, and be verified, for a message from a verified device */
395+
cy.wrap(0)
396+
.then(() => bob.sendTextMessage(testRoomId, "test encrypted 2"))
397+
.then((resp) => cy.log(`Bob sent second message from primary device with event id ${resp.event_id}`));
398+
399+
cy.get(".mx_EventTile_last")
400+
.should("contain", "test encrypted 2")
401+
// no e2e icon
402+
.should("not.have.descendants", ".mx_EventTile_e2eIcon");
403+
404+
/* should show red padlock for a message from an unverified device */
405+
cy.wrap(0)
406+
.then(() => bobSecondDevice.sendTextMessage(testRoomId, "test encrypted from unverified"))
407+
.then((resp) => cy.log(`Bob sent message from unverified device with event id ${resp.event_id}`));
408+
409+
cy.get(".mx_EventTile_last")
410+
.should("contain", "test encrypted from unverified")
411+
.find(".mx_EventTile_e2eIcon", { timeout: 100000 })
412+
.should("have.class", "mx_EventTile_e2eIcon_warning")
413+
.should("have.attr", "aria-label", "Encrypted by an unverified session");
414+
415+
/* Should show a grey padlock for a message from an unknown device */
416+
417+
// bob deletes his second device, making the encrypted event from the unverified device "unknown".
418+
cy.wrap(0)
419+
.then(() => bobSecondDevice.logout(true))
420+
.then(() => cy.log(`Bob logged out second device`));
421+
422+
cy.get(".mx_EventTile_last")
423+
.should("contain", "test encrypted from unverified")
424+
.find(".mx_EventTile_e2eIcon")
425+
.should("have.class", "mx_EventTile_e2eIcon_normal")
426+
.should("have.attr", "aria-label", "Encrypted by a deleted session");
427+
});
428+
429+
it("Should show a grey padlock for a key restored from backup", () => {
430+
skipIfRustCrypto();
431+
432+
enableKeyBackup();
433+
434+
// bob sends a valid event
435+
cy.wrap(0)
436+
.then(() => bob.sendTextMessage(testRoomId, "test encrypted 1"))
437+
.then((resp) => cy.log(`Bob sent message from primary device with event id ${resp.event_id}`));
438+
439+
cy.get(".mx_EventTile_last")
440+
.should("contain", "test encrypted 1")
441+
// no e2e icon
442+
.should("not.have.descendants", ".mx_EventTile_e2eIcon");
443+
444+
/* log out, and back i */
445+
logOutOfElement();
446+
cy.get<string>("@securityKey").then((securityKey) => {
447+
logIntoElement(homeserver.baseUrl, aliceCredentials.username, aliceCredentials.password, securityKey);
448+
});
449+
450+
/* go back to the test room and find Bob's message again */
451+
cy.viewRoomById(testRoomId);
452+
cy.get(".mx_EventTile_last")
453+
.should("contain", "test encrypted 1")
454+
.find(".mx_EventTile_e2eIcon")
455+
.should("have.class", "mx_EventTile_e2eIcon_normal")
456+
.should(
457+
"have.attr",
458+
"aria-label",
459+
"The authenticity of this encrypted message can't be guaranteed on this device.",
460+
);
461+
});
462+
463+
it("should show the correct shield on edited e2e events", function (this: CryptoTestContext) {
464+
skipIfRustCrypto();
465+
466+
// bob has a second, not cross-signed, device
467+
cy.loginBot(this.homeserver, this.bob.getUserId(), this.bob.__cypress_password, {}).as("bobSecondDevice");
468+
469+
// verify Bob
470+
verify.call(this);
471+
472+
cy.get<string>("@testRoomId").then((roomId) => {
473+
// bob sends a valid event
474+
cy.wrap(this.bob.sendTextMessage(roomId, "Hoo!")).as("testEvent");
475+
476+
// the message should appear, decrypted, with no warning
477+
cy.get(".mx_EventTile_last .mx_EventTile_body")
478+
.within(() => {
479+
cy.findByText("Hoo!");
480+
})
481+
.closest(".mx_EventTile")
482+
.should("not.have.descendants", ".mx_EventTile_e2eIcon_warning");
483+
484+
// bob sends an edit to the first message with his unverified device
485+
cy.get<MatrixClient>("@bobSecondDevice").then((bobSecondDevice) => {
486+
cy.get<ISendEventResponse>("@testEvent").then((testEvent) => {
487+
bobSecondDevice.sendMessage(roomId, {
488+
"m.new_content": {
489+
msgtype: "m.text",
490+
body: "Haa!",
491+
},
492+
"m.relates_to": {
493+
rel_type: "m.replace",
494+
event_id: testEvent.event_id,
495+
},
496+
});
497+
});
498+
});
499+
500+
// the edit should have a warning
501+
cy.contains(".mx_EventTile_body", "Haa!")
502+
.closest(".mx_EventTile")
503+
.within(() => {
504+
cy.get(".mx_EventTile_e2eIcon_warning").should("exist");
505+
});
506+
507+
// a second edit from the verified device should be ok
339508
cy.get<ISendEventResponse>("@testEvent").then((testEvent) => {
340-
bobSecondDevice.sendMessage(roomId, {
509+
this.bob.sendMessage(roomId, {
341510
"m.new_content": {
342511
msgtype: "m.text",
343-
body: "Haa!",
512+
body: "Hee!",
344513
},
345514
"m.relates_to": {
346515
rel_type: "m.replace",
347516
event_id: testEvent.event_id,
348517
},
349518
});
350519
});
351-
});
352-
353-
// the edit should have a warning
354-
cy.contains(".mx_EventTile_body", "Haa!")
355-
.closest(".mx_EventTile")
356-
.within(() => {
357-
cy.get(".mx_EventTile_e2eIcon_warning").should("exist");
358-
});
359520

360-
// a second edit from the verified device should be ok
361-
cy.get<ISendEventResponse>("@testEvent").then((testEvent) => {
362-
this.bob.sendMessage(roomId, {
363-
"m.new_content": {
364-
msgtype: "m.text",
365-
body: "Hee!",
366-
},
367-
"m.relates_to": {
368-
rel_type: "m.replace",
369-
event_id: testEvent.event_id,
370-
},
371-
});
521+
cy.get(".mx_EventTile_last .mx_EventTile_body")
522+
.within(() => {
523+
cy.findByText("Hee!");
524+
})
525+
.closest(".mx_EventTile")
526+
.should("not.have.descendants", ".mx_EventTile_e2eIcon_warning");
372527
});
373-
374-
cy.get(".mx_EventTile_last .mx_EventTile_body")
375-
.within(() => {
376-
cy.findByText("Hee!");
377-
})
378-
.closest(".mx_EventTile")
379-
.should("not.have.descendants", ".mx_EventTile_e2eIcon_warning");
380528
});
381529
});
382530
});

0 commit comments

Comments
 (0)