Skip to content

Commit

Permalink
Merge branch 'develop' into florianduros/rip-out-legacy-crypto/migrat…
Browse files Browse the repository at this point in the history
…e-EventTile-isRoomEncrypted

# Conflicts:
#	test/test-utils/test-utils.ts
  • Loading branch information
florianduros committed Nov 27, 2024
2 parents ce1f668 + ec46032 commit 2e30390
Show file tree
Hide file tree
Showing 59 changed files with 2,116 additions and 473 deletions.
33 changes: 33 additions & 0 deletions .github/actions/download-verify-element-tarball/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Upload release assets
description: Uploads assets to an existing release and optionally signs them
inputs:
tag:
description: GitHub release tag to fetch assets from.
required: true
out-file-path:
description: Path to where the webapp should be extracted to.
required: true
runs:
using: composite
steps:
- name: Download current version for its old bundles
id: current_download
uses: robinraju/release-downloader@a96f54c1b5f5e09e47d9504526e96febd949d4c2 # v1
with:
tag: steps.current_version.outputs.version
fileName: element-*.tar.gz*
out-file-path: ${{ runner.temp }}/download-verify-element-tarball

- name: Verify tarball
run: gpg --verify element-*.tar.gz.asc element-*.tar.gz
working-directory: ${{ runner.temp }}/download-verify-element-tarball

- name: Extract tarball
run: tar xvzf element-*.tar.gz -C webapp --strip-components=1
working-directory: ${{ runner.temp }}/download-verify-element-tarball

- name: Move webapp to out-file-path
run: mv ${{ runner.temp }}/download-verify-element-tarball/webapp ${{ inputs.out-file-path }}

- name: Clean up temp directory
run: rm -R ${{ runner.temp }}/download-verify-element-tarball
1 change: 1 addition & 0 deletions .github/workflows/build_develop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ jobs:
permissions:
checks: read
pages: write
deployments: write
env:
R2_BUCKET: "element-web-develop"
R2_URL: ${{ vars.CF_R2_S3_API }}
Expand Down
88 changes: 88 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Manual deploy workflow for deploying to app.element.io & staging.element.io
# Runs automatically for staging.element.io when an RC or Release is published
# Note: Does *NOT* run automatically for app.element.io so that it gets tested on staging.element.io beforehand
name: Build and Deploy ${{ inputs.site || 'staging.element.io' }}
on:
release:
types: [published]
workflow_dispatch:
inputs:
site:
description: Which site to deploy to
required: true
default: staging.element.io
type: choice
options:
- staging.element.io
- app.element.io
concurrency: ${{ inputs.site || 'staging.element.io' }}
permissions: {}
jobs:
deploy:
name: "Deploy to Cloudflare Pages"
runs-on: ubuntu-24.04
environment: ${{ inputs.site || 'staging.element.io' }}
permissions:
checks: read
deployments: write
env:
SITE: ${{ inputs.site || 'staging.element.io' }}
steps:
- name: Load GPG key
run: |
curl https://packages.element.io/element-release-key.gpg | gpg --import
gpg -k "$GPG_FINGERPRINT"
env:
GPG_FINGERPRINT: ${{ secrets.GPG_FINGERPRINT }}

- name: Check current version on deployment
id: current_version
run: |
echo "version=$(curl -s https://$SITE/version)" >> $GITHUB_OUTPUT
# The current version bundle melding dance is skipped if the version we're deploying is the same
# as then we're just doing a re-deploy of the same version with potentially different configs.
- name: Download current version for its old bundles
id: current_download
if: steps.current_version.outputs.version != github.ref_name
uses: element-hq/element-web/.github/actions/download-verify-element-tarball@${{ github.ref_name }}
with:
tag: steps.current_version.outputs.version
out-file-path: current_version

- name: Download target version
uses: element-hq/element-web/.github/actions/download-verify-element-tarball@${{ github.ref_name }}
with:
tag: ${{ github.ref_name }}
out-file-path: _deploy

- name: Merge current bundles into target
if: steps.current_download.outcome == 'success'
run: cp -vnpr current_version/bundles/* _deploy/bundles/

- name: Copy config
run: cp element.io/app/config.json _deploy/config.json

- name: Populate 404.html
run: echo "404 Not Found" > _deploy/404.html

- name: Populate _headers
run: cp .github/cfp_headers _deploy/_headers

- name: Wait for other steps to succeed
uses: t3chguy/wait-on-check-action@18541021811b56544d90e0f073401c2b99e249d6 # fork
with:
ref: ${{ github.sha }}
running-workflow-name: "Build and Deploy ${{ env.SITE }}"
repo-token: ${{ secrets.GITHUB_TOKEN }}
wait-interval: 10
check-regexp: ^((?!SonarCloud|SonarQube|issue|board|label|Release|prepare|GitHub Pages).)*$

- name: Deploy to Cloudflare Pages
uses: cloudflare/pages-action@f0a1cd58cd66095dee69bfa18fa5efd1dde93bca # v1
with:
apiToken: ${{ secrets.CF_PAGES_TOKEN }}
accountId: ${{ secrets.CF_PAGES_ACCOUNT_ID }}
projectName: ${{ env.SITE == 'staging.element.io' && 'element-web-staging' || 'element-web' }}
directory: _deploy
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
4 changes: 2 additions & 2 deletions .github/workflows/dockerhub.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:

- name: Docker meta
id: meta
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5
uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5
with:
images: |
vectorim/element-web
Expand All @@ -51,7 +51,7 @@ jobs:
- name: Build and push
id: build-and-push
uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6
with:
context: .
push: true
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pull_request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ jobs:
action:
uses: matrix-org/matrix-js-sdk/.github/workflows/pull_request.yaml@develop
permissions:
pull-requests: read
pull-requests: write
secrets:
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ jobs:
permissions:
contents: write
issues: write
pull-requests: read
secrets:
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ jobs:

- name: Skip SonarCloud in merge queue
if: github.event_name == 'merge_group' || inputs.disable_coverage == 'true'
uses: guibranco/github-status-action-v2@1f26a0237cd1a57626fbb5a0eb2494c9b8797d07
uses: guibranco/github-status-action-v2@66088c44e212a906c32a047529a213d81809ec1c
with:
authToken: ${{ secrets.GITHUB_TOKEN }}
state: success
Expand Down
6 changes: 0 additions & 6 deletions __mocks__/FontManager.js

This file was deleted.

1 change: 0 additions & 1 deletion jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ const config: Config = {
"decoderWorker\\.min\\.wasm": "<rootDir>/__mocks__/empty.js",
"waveWorker\\.min\\.js": "<rootDir>/__mocks__/empty.js",
"context-filter-polyfill": "<rootDir>/__mocks__/empty.js",
"FontManager.ts": "<rootDir>/__mocks__/FontManager.js",
"workers/(.+)Factory": "<rootDir>/__mocks__/workerFactoryMock.js",
"^!!raw-loader!.*": "jest-raw-loader",
"recorderWorkletFactory": "<rootDir>/__mocks__/empty.js",
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,10 @@
"jsrsasign": "^11.0.0",
"jszip": "^3.7.0",
"katex": "^0.16.0",
"linkify-element": "4.1.3",
"linkify-react": "4.1.3",
"linkify-string": "4.1.3",
"linkifyjs": "4.1.3",
"linkify-element": "4.1.4",
"linkify-react": "4.1.4",
"linkify-string": "4.1.4",
"linkifyjs": "4.1.4",
"lodash": "^4.17.21",
"maplibre-gl": "^4.0.0",
"matrix-encrypt-attachment": "^1.0.3",
Expand Down
2 changes: 1 addition & 1 deletion playwright/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM mcr.microsoft.com/playwright:v1.48.2-jammy
FROM mcr.microsoft.com/playwright:v1.49.0-jammy

WORKDIR /work

Expand Down
3 changes: 3 additions & 0 deletions playwright/e2e/crypto/decryption-failure-messages.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ test.describe("Cryptography", function () {
await page.locator(".mx_AuthPage").getByRole("button", { name: "I'll verify later" }).click();
await app.viewRoomByName("Test room");

// In this case, the call to cryptoApi.isEncryptionEnabledInRoom is taking a long time to resolve
await page.waitForTimeout(1000);

// There should be two historical events in the timeline
const tiles = await page.locator(".mx_EventTile").all();
expect(tiles.length).toBeGreaterThanOrEqual(2);
Expand Down
26 changes: 26 additions & 0 deletions playwright/e2e/crypto/event-shields.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
logOutOfElement,
verify,
} from "./utils";
import { bootstrapCrossSigningForClient } from "../../pages/client.ts";

test.describe("Cryptography", function () {
test.use({
Expand Down Expand Up @@ -307,5 +308,30 @@ test.describe("Cryptography", function () {
const penultimate = page.locator(".mx_EventTile").filter({ hasText: "test encrypted from verified" });
await expect(penultimate.locator(".mx_EventTile_e2eIcon")).not.toBeVisible();
});

test("should show correct shields on events sent by users with changed identity", async ({
page,
app,
bot: bob,
homeserver,
}) => {
// Verify Bob
await verify(app, bob);

// Bob logs in a new device and resets cross-signing
const bobSecondDevice = await createSecondBotDevice(page, homeserver, bob);
await bootstrapCrossSigningForClient(await bobSecondDevice.prepareClient(), bob.credentials, true);

/* should show an error for a message from a previously verified device */
await bobSecondDevice.sendMessage(testRoomId, "test encrypted from user that was previously verified");
const last = page.locator(".mx_EventTile_last");
await expect(last).toContainText("test encrypted from user that was previously verified");
const lastE2eIcon = last.locator(".mx_EventTile_e2eIcon");
await expect(lastE2eIcon).toHaveClass(/mx_EventTile_e2eIcon_warning/);
await lastE2eIcon.focus();
await expect(await app.getTooltipForElement(lastE2eIcon)).toContainText(
"Sender's verified identity has changed",
);
});
});
});
2 changes: 1 addition & 1 deletion playwright/plugins/homeserver/synapse/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { randB64Bytes } from "../../utils/rand";
// Docker tag to use for synapse docker image.
// We target a specific digest as every now and then a Synapse update will break our CI.
// This digest is updated by the playwright-image-updates.yaml workflow periodically.
const DOCKER_TAG = "develop@sha256:127c68d4468019ce363c8b2fd7a42a3ef50710eb3aaf288a2295dd4623ce9f54";
const DOCKER_TAG = "develop@sha256:e163b15bf4905e4067dece856cca00e6ac8d1d655f4f1307978eee256b3ea775";

async function cfgDirFromTemplate(opts: StartHomeserverOpts): Promise<Omit<HomeserverConfig, "dockerUrl">> {
const templateDir = path.join(__dirname, "templates", opts.template);
Expand Down
1 change: 1 addition & 0 deletions res/css/_components.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@
@import "./views/rooms/_ThirdPartyMemberInfo.pcss";
@import "./views/rooms/_ThreadSummary.pcss";
@import "./views/rooms/_TopUnreadMessagesBar.pcss";
@import "./views/rooms/_UserIdentityWarning.pcss";
@import "./views/rooms/_VoiceRecordComposerTile.pcss";
@import "./views/rooms/_WhoIsTypingTile.pcss";
@import "./views/rooms/wysiwyg_composer/_EditWysiwygComposer.pcss";
Expand Down
28 changes: 28 additions & 0 deletions res/css/views/rooms/_UserIdentityWarning.pcss
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
Copyright 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/

.mx_UserIdentityWarning {
/* 42px is the padding-left of .mx_MessageComposer_wrapper in res/css/views/rooms/_MessageComposer.pcss */
margin-left: calc(-42px + var(--RoomView_MessageList-padding));

.mx_UserIdentityWarning_row {
display: flex;
align-items: center;

.mx_BaseAvatar {
margin-left: var(--cpd-space-2x);
}
.mx_UserIdentityWarning_main {
margin-left: var(--cpd-space-6x);
flex-grow: 1;
}
}
}

.mx_MessageComposer.mx_MessageComposer--compact > .mx_UserIdentityWarning {
margin-left: calc(-25px + var(--RoomView_MessageList-padding));
}
Binary file removed res/fonts/Twemoji_Mozilla/TwemojiMozilla-sbix.woff2
Binary file not shown.
18 changes: 18 additions & 0 deletions res/themes/light/css/_fonts.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,21 @@ $inter-unicode-range: U+0000-20e2, U+20e4-23ce, U+23d0-24c1, U+24c3-259f, U+25c2
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC,
U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Twemoji COLR */
@font-face {
font-family: "Twemoji";
font-weight: 400;
src: url("$(res)/fonts/Twemoji_Mozilla/TwemojiMozilla-colr.woff2") format("woff2");
}
/* For at least Chrome on Windows 10, we have to explictly add extra weights for the emoji to appear in bold messages, etc. */
@font-face {
font-family: "Twemoji";
font-weight: 600;
src: url("$(res)/fonts/Twemoji_Mozilla/TwemojiMozilla-colr.woff2") format("woff2");
}
@font-face {
font-family: "Twemoji";
font-weight: 700;
src: url("$(res)/fonts/Twemoji_Mozilla/TwemojiMozilla-colr.woff2") format("woff2");
}
7 changes: 7 additions & 0 deletions src/@types/matrix-js-sdk.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ declare module "matrix-js-sdk/src/types" {
[BLURHASH_FIELD]?: string;
}

export interface ImageInfo {
/**
* @see https://github.com/matrix-org/matrix-spec-proposals/pull/4230
*/
"org.matrix.msc4230.is_animated"?: boolean;
}

export interface StateEvents {
// Jitsi-backed video room state events
[JitsiCallMemberEventType]: JitsiCallMemberContent;
Expand Down
8 changes: 7 additions & 1 deletion src/ContentMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import { createThumbnail } from "./utils/image-media";
import { attachMentions, attachRelation } from "./components/views/rooms/SendMessageComposer";
import { doMaybeLocalRoomAction } from "./utils/local-room";
import { SdkContextClass } from "./contexts/SDKContext";
import { blobIsAnimated } from "./utils/Image.ts";

// scraped out of a macOS hidpi (5660ppm) screenshot png
// 5669 px (x-axis) , 5669 px (y-axis) , per metre
Expand Down Expand Up @@ -150,15 +151,20 @@ async function infoForImageFile(matrixClient: MatrixClient, roomId: string, imag
thumbnailType = "image/jpeg";
}

// We don't await this immediately so it can happen in the background
const isAnimatedPromise = blobIsAnimated(imageFile.type, imageFile);

const imageElement = await loadImageElement(imageFile);

const result = await createThumbnail(imageElement.img, imageElement.width, imageElement.height, thumbnailType);
const imageInfo = result.info;

imageInfo["org.matrix.msc4230.is_animated"] = await isAnimatedPromise;

// For lesser supported image types, always include the thumbnail even if it is larger
if (!ALWAYS_INCLUDE_THUMBNAIL.includes(imageFile.type)) {
// we do all sizing checks here because we still rely on thumbnail generation for making a blurhash from.
const sizeDifference = imageFile.size - imageInfo.thumbnail_info!.size;
const sizeDifference = imageFile.size - imageInfo.thumbnail_info!.size!;
if (
// image is small enough already
imageFile.size <= IMAGE_SIZE_THRESHOLD_THUMBNAIL ||
Expand Down
5 changes: 4 additions & 1 deletion src/DeviceListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,12 +230,15 @@ export default class DeviceListener {
private async getKeyBackupInfo(): Promise<KeyBackupInfo | null> {
if (!this.client) return null;
const now = new Date().getTime();
const crypto = this.client.getCrypto();
if (!crypto) return null;

if (
!this.keyBackupInfo ||
!this.keyBackupFetchedAt ||
this.keyBackupFetchedAt < now - KEY_BACKUP_POLL_INTERVAL
) {
this.keyBackupInfo = await this.client.getKeyBackupVersion();
this.keyBackupInfo = await crypto.getKeyBackupInfo();
this.keyBackupFetchedAt = now;
}
return this.keyBackupInfo;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
if (!forceReset) {
try {
this.setState({ phase: Phase.Loading });
backupInfo = await cli.getKeyBackupVersion();
backupInfo = await crypto.getKeyBackupInfo();
} catch (e) {
logger.error("Error fetching backup data from server", e);
this.setState({ phase: Phase.LoadError });
Expand Down
Loading

0 comments on commit 2e30390

Please sign in to comment.