Restricted Room Security
Create the required ux to allow the restricted room security setting to be re-enabled BUG: 471307
This commit is contained in:
@@ -289,6 +289,7 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
||||
qml/SelectParentDialog.qml
|
||||
qml/Security.qml
|
||||
qml/QrCodeMaximizeComponent.qml
|
||||
qml/SelectSpacesDialog.qml
|
||||
RESOURCES
|
||||
qml/confetti.png
|
||||
qml/glowdot.png
|
||||
|
||||
@@ -123,6 +123,7 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
|
||||
Q_EMIT canEncryptRoomChanged();
|
||||
Q_EMIT parentIdsChanged();
|
||||
Q_EMIT canonicalParentChanged();
|
||||
Q_EMIT joinRuleChanged();
|
||||
});
|
||||
connect(connection, &Connection::capabilitiesLoaded, this, &NeoChatRoom::maxRoomVersionChanged);
|
||||
connect(this, &Room::changed, this, [this]() {
|
||||
@@ -712,16 +713,51 @@ QString NeoChatRoom::joinRule() const
|
||||
return joinRulesEvent->joinRule();
|
||||
}
|
||||
|
||||
void NeoChatRoom::setJoinRule(const QString &joinRule)
|
||||
void NeoChatRoom::setJoinRule(const QString &joinRule, const QList<QString> &allowedSpaces)
|
||||
{
|
||||
if (!canSendState("m.room.join_rules"_ls)) {
|
||||
qWarning() << "Power level too low to set join rules";
|
||||
return;
|
||||
}
|
||||
setState("m.room.join_rules"_ls, {}, QJsonObject{{"join_rule"_ls, joinRule}});
|
||||
auto actualRule = joinRule;
|
||||
if (joinRule == "restricted"_ls && allowedSpaces.isEmpty()) {
|
||||
actualRule = "private"_ls;
|
||||
}
|
||||
|
||||
QJsonArray allowConditions;
|
||||
if (actualRule == "restricted"_ls) {
|
||||
for (auto allowedSpace : allowedSpaces) {
|
||||
allowConditions += QJsonObject{{"type"_ls, "m.room_membership"_ls}, {"room_id"_ls, allowedSpace}};
|
||||
}
|
||||
}
|
||||
|
||||
QJsonObject content;
|
||||
content.insert("join_rule"_ls, joinRule);
|
||||
if (!allowConditions.isEmpty()) {
|
||||
content.insert("allow"_ls, allowConditions);
|
||||
}
|
||||
qWarning() << content;
|
||||
setState("m.room.join_rules"_ls, {}, content);
|
||||
// Not emitting joinRuleChanged() here, since that would override the change in the UI with the *current* value, which is not the *new* value.
|
||||
}
|
||||
|
||||
QList<QString> NeoChatRoom::restrictedIds() const
|
||||
{
|
||||
auto joinRulesEvent = currentState().get<JoinRulesEvent>();
|
||||
if (!joinRulesEvent) {
|
||||
return {};
|
||||
}
|
||||
if (joinRulesEvent->joinRule() != "restricted"_ls) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QList<QString> roomIds;
|
||||
for (auto allow : joinRulesEvent->allow()) {
|
||||
roomIds += allow.toObject().value("room_id"_ls).toString();
|
||||
}
|
||||
return roomIds;
|
||||
}
|
||||
|
||||
QString NeoChatRoom::historyVisibility() const
|
||||
{
|
||||
return currentState().get("m.room.history_visibility"_ls)->contentJson()["history_visibility"_ls].toString();
|
||||
@@ -1141,6 +1177,21 @@ QList<QString> NeoChatRoom::parentIds() const
|
||||
return parentIds;
|
||||
}
|
||||
|
||||
QList<NeoChatRoom *> NeoChatRoom::parentObjects(bool multiLevel) const
|
||||
{
|
||||
QList<NeoChatRoom *> parentObjects;
|
||||
QList<QString> parentIds = this->parentIds();
|
||||
for (const auto &parentId : parentIds) {
|
||||
if (auto parentObject = static_cast<NeoChatRoom *>(connection()->room(parentId))) {
|
||||
parentObjects += parentObject;
|
||||
if (multiLevel) {
|
||||
parentObjects += parentObject->parentObjects(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
return parentObjects;
|
||||
}
|
||||
|
||||
QString NeoChatRoom::canonicalParent() const
|
||||
{
|
||||
auto parentEvents = currentState().eventsOfType("m.space.parent"_ls);
|
||||
|
||||
@@ -160,6 +160,13 @@ class NeoChatRoom : public Quotient::Room
|
||||
*/
|
||||
Q_PROPERTY(QString joinRule READ joinRule WRITE setJoinRule NOTIFY joinRuleChanged)
|
||||
|
||||
/**
|
||||
* @brief The space IDs that members of can join this room.
|
||||
*
|
||||
* Empty if the join rule is not restricted.
|
||||
*/
|
||||
Q_PROPERTY(QList<QString> restrictedIds READ restrictedIds NOTIFY joinRuleChanged)
|
||||
|
||||
/**
|
||||
* @brief Get the maximum room version that the server supports.
|
||||
*
|
||||
@@ -505,6 +512,17 @@ public:
|
||||
|
||||
QList<QString> parentIds() const;
|
||||
|
||||
/**
|
||||
* @brief Get a list of parent space objects for this room.
|
||||
*
|
||||
* Will only return retrun spaces that are know, i.e. the user has joined and
|
||||
* a valid NeoChatRoom is available.
|
||||
*
|
||||
* @param multiLevel whether the function should recursively gather all levels
|
||||
* of parents
|
||||
*/
|
||||
Q_INVOKABLE QList<NeoChatRoom *> parentObjects(bool multiLevel = false) const;
|
||||
|
||||
QString canonicalParent() const;
|
||||
void setCanonicalParent(const QString &parentId);
|
||||
|
||||
@@ -559,7 +577,23 @@ public:
|
||||
Q_INVOKABLE void clearInvitationNotification();
|
||||
|
||||
[[nodiscard]] QString joinRule() const;
|
||||
void setJoinRule(const QString &joinRule);
|
||||
|
||||
/**
|
||||
* @brief Set the join rule for the room.
|
||||
*
|
||||
* Will fail if the user doesn't have the required privileges.
|
||||
*
|
||||
* @param joinRule the join rule [public, knock, invite, private, restricted].
|
||||
* @param allowedSpaces only used when the join rule is restricted. This is a
|
||||
* list of space Matrix IDs that members of can join without an invite.
|
||||
* If the rule is restricted and this list is empty it is treated as a join
|
||||
* rule of private instead.
|
||||
*
|
||||
* @sa https://spec.matrix.org/latest/client-server-api/#mroomjoin_rules
|
||||
*/
|
||||
Q_INVOKABLE void setJoinRule(const QString &joinRule, const QList<QString> &allowedSpaces = {});
|
||||
|
||||
QList<QString> restrictedIds() const;
|
||||
|
||||
int maxRoomVersion() const;
|
||||
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard as FormCard
|
||||
|
||||
import org.kde.neochat
|
||||
@@ -42,18 +45,37 @@ FormCard.FormCardPage {
|
||||
description: i18n("Only invited people can join.")
|
||||
checked: room.joinRule === "invite"
|
||||
enabled: room.canSendState("m.room.join_rules")
|
||||
onCheckedChanged: if (checked) {
|
||||
room.joinRule = "invite";
|
||||
onCheckedChanged: if (checked && room.joinRule != "invite") {
|
||||
root.room.joinRule = "invite";
|
||||
}
|
||||
}
|
||||
FormCard.FormRadioDelegate {
|
||||
text: i18nc("@option:check", "Space members")
|
||||
description: i18n("Anyone in a space can find and join.") +
|
||||
description: i18n("Anyone in the selected spaces can find and join.") +
|
||||
(!["8", "9", "10"].includes(room.version) ? `\n${needUpgradeRoom}` : "")
|
||||
checked: room.joinRule === "restricted"
|
||||
enabled: room.canSendState("m.room.join_rules") && ["8", "9", "10"].includes(room.version) && false
|
||||
onCheckedChanged: if (checked) {
|
||||
room.joinRule = "restricted";
|
||||
enabled: room.canSendState("m.room.join_rules") && ["8", "9", "10"].includes(room.version)
|
||||
onCheckedChanged: if (checked && room.joinRule != "restricted") {
|
||||
selectSpacesDialog.createObject(applicationWindow().overlay).open();
|
||||
}
|
||||
|
||||
contentItem.children: QQC2.Button {
|
||||
visible: root.room.joinRule === "restricted"
|
||||
text: i18n("Select spaces")
|
||||
icon.name: "list-add"
|
||||
|
||||
onClicked: selectSpacesDialog.createObject(applicationWindow().overlay).open();
|
||||
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
QQC2.ToolTip.visible: hovered
|
||||
}
|
||||
|
||||
Component {
|
||||
id: selectSpacesDialog
|
||||
SelectSpacesDialog {
|
||||
room: root.room
|
||||
}
|
||||
}
|
||||
}
|
||||
FormCard.FormRadioDelegate {
|
||||
@@ -63,8 +85,8 @@ FormCard.FormCardPage {
|
||||
checked: room.joinRule === "knock"
|
||||
// https://spec.matrix.org/v1.4/rooms/#feature-matrix
|
||||
enabled: room.canSendState("m.room.join_rules") && ["7", "8", "9", "10"].includes(room.version)
|
||||
onCheckedChanged: if (checked) {
|
||||
room.joinRule = "knock";
|
||||
onCheckedChanged: if (checked && room.joinRule != "knock") {
|
||||
root.room.joinRule = "knock";
|
||||
}
|
||||
}
|
||||
FormCard.FormRadioDelegate {
|
||||
@@ -72,8 +94,8 @@ FormCard.FormCardPage {
|
||||
description: i18nc("@option:check", "Anyone can find and join.")
|
||||
checked: room.joinRule === "public"
|
||||
enabled: room.canSendState("m.room.join_rules")
|
||||
onCheckedChanged: if (checked) {
|
||||
room.joinRule = "public";
|
||||
onCheckedChanged: if (checked && root.room.joinRule != "public") {
|
||||
root.room.joinRule = "public";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
77
src/qml/SelectSpacesDialog.qml
Normal file
77
src/qml/SelectSpacesDialog.qml
Normal file
@@ -0,0 +1,77 @@
|
||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard as FormCard
|
||||
import org.kde.kirigamiaddons.labs.components as Components
|
||||
|
||||
import org.kde.neochat
|
||||
|
||||
Kirigami.Dialog {
|
||||
id: root
|
||||
|
||||
/**
|
||||
* @brief The current room this dialog is opened for.
|
||||
*/
|
||||
required property NeoChatRoom room
|
||||
|
||||
/**
|
||||
* @brief The current list of space IDs that members of can join this room.
|
||||
*/
|
||||
property list<string> restrictedIds: room.restrictedIds
|
||||
|
||||
parent: applicationWindow().overlay
|
||||
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
|
||||
width: Math.min(applicationWindow().width, Kirigami.Units.gridUnit * 24)
|
||||
title: i18nc("@title", "Select Spaces")
|
||||
|
||||
standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
|
||||
onAccepted: {
|
||||
let ids = [];
|
||||
for (var i in spaceGroup.buttons) {
|
||||
if (spaceGroup.buttons[i].checked) {
|
||||
ids.push(spaceGroup.buttons[i].modelData.id);
|
||||
}
|
||||
}
|
||||
root.room.setJoinRule("restricted", ids)
|
||||
console.warn(ids)
|
||||
}
|
||||
|
||||
QQC2.ButtonGroup {
|
||||
id: spaceGroup
|
||||
exclusive: false
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
Repeater {
|
||||
model: root.room.parentObjects(true)
|
||||
|
||||
delegate: FormCard.FormCheckDelegate {
|
||||
required property var modelData
|
||||
|
||||
text: modelData.displayName
|
||||
description: modelData.canonicalAlias
|
||||
checked: root.restrictedIds.includes(modelData.id)
|
||||
QQC2.ButtonGroup.group: spaceGroup
|
||||
|
||||
leading: Components.Avatar {
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
|
||||
|
||||
source: modelData.avatarUrl.toString().length > 0 ? connection.makeMediaUrl(modelData.avatarUrl) : ""
|
||||
name: modelData.displayName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user