Skip to content

Commit

Permalink
Allow room moderators to view redacted event content
Browse files Browse the repository at this point in the history
Implements MSC2815

Signed-off-by: Tulir Asokan <tulir@maunium.net>
  • Loading branch information
tulir committed Apr 8, 2022
1 parent 579a166 commit d7edb69
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 1 deletion.
9 changes: 9 additions & 0 deletions res/css/views/rooms/_EventTile.scss
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,15 @@ $left-gutter: 64px;
opacity: 1;
}

.mx_EventTile_e2eIcon_viewingRedacted {
&::after {
mask-image: url('$(res)/img/feather-customised/trash.custom.svg');
background-color: $muted-fg-color;
}

opacity: 1;
}

/* Various markdown overrides */

.mx_EventTile_body {
Expand Down
36 changes: 35 additions & 1 deletion src/components/views/context_menus/MessageContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ interface IProps extends IPosition {

interface IState {
canRedact: boolean;
canViewRedacted: boolean;
canPin: boolean;
}

Expand All @@ -92,6 +93,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>

state = {
canRedact: false,
canViewRedacted: false,
canPin: false,
};

Expand All @@ -118,11 +120,15 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
&& this.props.mxEvent.getType() !== EventType.RoomServerAcl
&& this.props.mxEvent.getType() !== EventType.RoomEncryption;
let canPin = room.currentState.mayClientSendStateEvent(EventType.RoomPinnedEvents, cli);
// TODO check for server support first
// TODO allow if isSynapseAdmin too
const canViewRedacted = room.currentState.hasSufficientPowerLevelFor(
'redact', room.currentState.getMember(cli.credentials.userId)?.powerLevel ?? 0);

// HACK: Intentionally say we can't pin if the user doesn't want to use the functionality
if (!SettingsStore.getValue("feature_pinning")) canPin = false;

this.setState({ canRedact, canPin });
this.setState({ canRedact, canViewRedacted, canPin });
};

private isPinned(): boolean {
Expand Down Expand Up @@ -152,6 +158,22 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
this.closeMenu();
};

private onViewRedactedClick = (): void => {
MatrixClientPeg.get().unstableFetchRedactedRoomEventContent(
this.props.mxEvent.getRoomId(),
this.props.mxEvent.getId(),
).then(
unredactedEvt => {
console.log("Fetched redacted event content:", unredactedEvt);
this.closeMenu();
this.props.mxEvent.showRedactedContent(unredactedEvt);
},
err => {
console.error("Failed to fetch redacted event content:", err);
}
);
};

private onReportEventClick = (): void => {
dis.dispatch<OpenReportEventDialogPayload>({
action: Action.OpenReportEventDialog,
Expand Down Expand Up @@ -284,6 +306,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
let endPollButton: JSX.Element;
let resendReactionsButton: JSX.Element;
let redactButton: JSX.Element;
let viewRedactedButton: JSX.Element;
let forwardButton: JSX.Element;
let pinButton: JSX.Element;
let unhidePreviewButton: JSX.Element;
Expand All @@ -306,6 +329,16 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
}
}

if (mxEvent.isRedacted() && this.state.canViewRedacted) {
viewRedactedButton = (
<IconizedContextMenuOption
iconClassName=""
label={_t('View content')}
onClick={this.onViewRedactedClick}
/>
)
}

if (isSent && this.state.canRedact) {
redactButton = (
<IconizedContextMenuOption
Expand Down Expand Up @@ -489,6 +522,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
{ externalURLButton }
{ unhidePreviewButton }
{ viewSourceButton }
{ viewRedactedButton }
{ resendReactionsButton }
{ collapseReplyChain }
</IconizedContextMenuOptionList>
Expand Down
6 changes: 6 additions & 0 deletions src/components/views/rooms/EventTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,11 @@ export class UnwrappedEventTile extends React.Component<IProps, IState> {
private renderE2EPadlock() {
const ev = this.props.mxEvent;

if (ev.viewingRedactedContent) {
// Not actually an e2ee thing, but we reuse the same slot in the UI.
return <E2ePadlock title={_t("This is a deleted message")} icon={E2ePadlockIcon.Redacted} />
}

// event could not be decrypted
if (ev.getContent().msgtype === 'm.bad.encrypted') {
return <E2ePadlockUndecryptable />;
Expand Down Expand Up @@ -1513,6 +1518,7 @@ function E2ePadlockUnauthenticated(props) {
enum E2ePadlockIcon {
Normal = "normal",
Warning = "warning",
Redacted = "viewingRedacted",
}

interface IE2ePadlockProps {
Expand Down
2 changes: 2 additions & 0 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -1646,6 +1646,7 @@
"Edit message": "Edit message",
"Mod": "Mod",
"From a thread": "From a thread",
"This is a deleted message": "This is a deleted message",
"This event could not be displayed": "This event could not be displayed",
"Your key share request has been sent - please check your other sessions for key share requests.": "Your key share request has been sent - please check your other sessions for key share requests.",
"Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.": "Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.",
Expand Down Expand Up @@ -2867,6 +2868,7 @@
"Resume": "Resume",
"Hold": "Hold",
"Resend %(unsentCount)s reaction(s)": "Resend %(unsentCount)s reaction(s)",
"View content": "View content",
"Open in OpenStreetMap": "Open in OpenStreetMap",
"Forward": "Forward",
"View source": "View source",
Expand Down

0 comments on commit d7edb69

Please sign in to comment.