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/SelectParentDialog.qml
|
||||||
qml/Security.qml
|
qml/Security.qml
|
||||||
qml/QrCodeMaximizeComponent.qml
|
qml/QrCodeMaximizeComponent.qml
|
||||||
|
qml/SelectSpacesDialog.qml
|
||||||
RESOURCES
|
RESOURCES
|
||||||
qml/confetti.png
|
qml/confetti.png
|
||||||
qml/glowdot.png
|
qml/glowdot.png
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
|
|||||||
Q_EMIT canEncryptRoomChanged();
|
Q_EMIT canEncryptRoomChanged();
|
||||||
Q_EMIT parentIdsChanged();
|
Q_EMIT parentIdsChanged();
|
||||||
Q_EMIT canonicalParentChanged();
|
Q_EMIT canonicalParentChanged();
|
||||||
|
Q_EMIT joinRuleChanged();
|
||||||
});
|
});
|
||||||
connect(connection, &Connection::capabilitiesLoaded, this, &NeoChatRoom::maxRoomVersionChanged);
|
connect(connection, &Connection::capabilitiesLoaded, this, &NeoChatRoom::maxRoomVersionChanged);
|
||||||
connect(this, &Room::changed, this, [this]() {
|
connect(this, &Room::changed, this, [this]() {
|
||||||
@@ -712,16 +713,51 @@ QString NeoChatRoom::joinRule() const
|
|||||||
return joinRulesEvent->joinRule();
|
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)) {
|
if (!canSendState("m.room.join_rules"_ls)) {
|
||||||
qWarning() << "Power level too low to set join rules";
|
qWarning() << "Power level too low to set join rules";
|
||||||
return;
|
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.
|
// 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
|
QString NeoChatRoom::historyVisibility() const
|
||||||
{
|
{
|
||||||
return currentState().get("m.room.history_visibility"_ls)->contentJson()["history_visibility"_ls].toString();
|
return currentState().get("m.room.history_visibility"_ls)->contentJson()["history_visibility"_ls].toString();
|
||||||
@@ -1141,6 +1177,21 @@ QList<QString> NeoChatRoom::parentIds() const
|
|||||||
return parentIds;
|
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
|
QString NeoChatRoom::canonicalParent() const
|
||||||
{
|
{
|
||||||
auto parentEvents = currentState().eventsOfType("m.space.parent"_ls);
|
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)
|
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.
|
* @brief Get the maximum room version that the server supports.
|
||||||
*
|
*
|
||||||
@@ -505,6 +512,17 @@ public:
|
|||||||
|
|
||||||
QList<QString> parentIds() const;
|
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;
|
QString canonicalParent() const;
|
||||||
void setCanonicalParent(const QString &parentId);
|
void setCanonicalParent(const QString &parentId);
|
||||||
|
|
||||||
@@ -559,7 +577,23 @@ public:
|
|||||||
Q_INVOKABLE void clearInvitationNotification();
|
Q_INVOKABLE void clearInvitationNotification();
|
||||||
|
|
||||||
[[nodiscard]] QString joinRule() const;
|
[[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;
|
int maxRoomVersion() const;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,10 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
import QtQuick.Controls as QQC2
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
import org.kde.kirigamiaddons.formcard as FormCard
|
import org.kde.kirigamiaddons.formcard as FormCard
|
||||||
|
|
||||||
import org.kde.neochat
|
import org.kde.neochat
|
||||||
@@ -42,18 +45,37 @@ FormCard.FormCardPage {
|
|||||||
description: i18n("Only invited people can join.")
|
description: i18n("Only invited people can join.")
|
||||||
checked: room.joinRule === "invite"
|
checked: room.joinRule === "invite"
|
||||||
enabled: room.canSendState("m.room.join_rules")
|
enabled: room.canSendState("m.room.join_rules")
|
||||||
onCheckedChanged: if (checked) {
|
onCheckedChanged: if (checked && room.joinRule != "invite") {
|
||||||
room.joinRule = "invite";
|
root.room.joinRule = "invite";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FormCard.FormRadioDelegate {
|
FormCard.FormRadioDelegate {
|
||||||
text: i18nc("@option:check", "Space members")
|
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}` : "")
|
(!["8", "9", "10"].includes(room.version) ? `\n${needUpgradeRoom}` : "")
|
||||||
checked: room.joinRule === "restricted"
|
checked: room.joinRule === "restricted"
|
||||||
enabled: room.canSendState("m.room.join_rules") && ["8", "9", "10"].includes(room.version) && false
|
enabled: room.canSendState("m.room.join_rules") && ["8", "9", "10"].includes(room.version)
|
||||||
onCheckedChanged: if (checked) {
|
onCheckedChanged: if (checked && room.joinRule != "restricted") {
|
||||||
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 {
|
FormCard.FormRadioDelegate {
|
||||||
@@ -63,8 +85,8 @@ FormCard.FormCardPage {
|
|||||||
checked: room.joinRule === "knock"
|
checked: room.joinRule === "knock"
|
||||||
// https://spec.matrix.org/v1.4/rooms/#feature-matrix
|
// https://spec.matrix.org/v1.4/rooms/#feature-matrix
|
||||||
enabled: room.canSendState("m.room.join_rules") && ["7", "8", "9", "10"].includes(room.version)
|
enabled: room.canSendState("m.room.join_rules") && ["7", "8", "9", "10"].includes(room.version)
|
||||||
onCheckedChanged: if (checked) {
|
onCheckedChanged: if (checked && room.joinRule != "knock") {
|
||||||
room.joinRule = "knock";
|
root.room.joinRule = "knock";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FormCard.FormRadioDelegate {
|
FormCard.FormRadioDelegate {
|
||||||
@@ -72,8 +94,8 @@ FormCard.FormCardPage {
|
|||||||
description: i18nc("@option:check", "Anyone can find and join.")
|
description: i18nc("@option:check", "Anyone can find and join.")
|
||||||
checked: room.joinRule === "public"
|
checked: room.joinRule === "public"
|
||||||
enabled: room.canSendState("m.room.join_rules")
|
enabled: room.canSendState("m.room.join_rules")
|
||||||
onCheckedChanged: if (checked) {
|
onCheckedChanged: if (checked && root.room.joinRule != "public") {
|
||||||
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