Hide user settings if permission is missing

This commit is contained in:
Simon Stürz 2025-11-04 16:26:42 +01:00
parent 0c574dff84
commit 3ba0c0405e
5 changed files with 123 additions and 46 deletions

View File

@ -372,7 +372,7 @@ void registerQmlTypes() {
qmlRegisterType<ScriptAutoSaver>(uri, 1, 0, "ScriptAutoSaver");
qmlRegisterType<UserManager>(uri, 1, 0, "UserManager");
qmlRegisterUncreatableType<UserInfo>(uri, 1, 0, "UserInfo", "Get it from UserManager");
qmlRegisterType<UserInfo>(uri, 1, 0, "UserInfo");
qmlRegisterUncreatableType<TokenInfo>(uri, 1, 0, "TokenInfo", "Get it from TokenInfos");
qmlRegisterUncreatableType<TokenInfos>(uri, 1, 0, "TokenInfos", "Get it from UserManager");
qmlRegisterUncreatableType<Users>(uri, 1, 0, "Users", "Get it from UserManager");

View File

@ -31,18 +31,18 @@
class UserInfo : public QObject
{
Q_OBJECT
Q_PROPERTY(QString username READ username NOTIFY usernameChanged)
Q_PROPERTY(QString email READ email NOTIFY emailChanged)
Q_PROPERTY(QString displayName READ displayName NOTIFY displayNameChanged)
Q_PROPERTY(PermissionScopes scopes READ scopes NOTIFY scopesChanged)
Q_PROPERTY(QList<QUuid> allowedThingIds READ allowedThingIds NOTIFY allowedThingIdsChanged)
Q_PROPERTY(QString username READ username WRITE setUsername NOTIFY usernameChanged)
Q_PROPERTY(QString email READ email WRITE setEmail NOTIFY emailChanged)
Q_PROPERTY(QString displayName READ displayName WRITE setDisplayName NOTIFY displayNameChanged)
Q_PROPERTY(PermissionScopes scopes READ scopes WRITE setScopes NOTIFY scopesChanged)
Q_PROPERTY(QList<QUuid> allowedThingIds READ allowedThingIds WRITE setAllowedThingIds NOTIFY allowedThingIdsChanged)
public:
enum PermissionScope {
PermissionScopeNone = 0x0000,
PermissionScopeControlThings = 0x0001,
PermissionScopeConfigureThings = 0x0003,
PermissionScopeAccessAllThings = 0x0004,
PermissionScopeAccessAllThings = 0x0004, // Since 8.4
PermissionScopeExecuteRules = 0x0010,
PermissionScopeConfigureRules = 0x0030,
PermissionScopeAdmin = 0xFFFF,

View File

@ -103,7 +103,14 @@ int UserManager::createUser(const QString &username, const QString &password, co
if (m_engine->jsonRpcClient()->ensureServerVersion("6.0")) {
params.insert("displayName", displayName);
params.insert("email", email);
params.insert("scopes", UserInfo::scopesToList((UserInfo::PermissionScopes)permissionScopes));
// Backports compatibility for pre 8.4
UserInfo::PermissionScopes scopes = static_cast<UserInfo::PermissionScopes>(permissionScopes);
if (!m_engine->jsonRpcClient()->ensureServerVersion("8.4"))
scopes.setFlag(UserInfo::PermissionScopeAccessAllThings, false);
params.insert("scopes", UserInfo::scopesToList(scopes));
}
if (m_engine->jsonRpcClient()->ensureServerVersion("8.4") && !allowedThingIds.isEmpty()) {
@ -145,7 +152,14 @@ int UserManager::setUserScopes(const QString &username, int scopes, const QList<
{
QVariantMap params;
params.insert("username", username);
params.insert("scopes", UserInfo::scopesToList((UserInfo::PermissionScopes)scopes));
// Backports compatibility for pre 8.4
UserInfo::PermissionScopes finalScopes = static_cast<UserInfo::PermissionScopes>(scopes);
if (!m_engine->jsonRpcClient()->ensureServerVersion("8.4"))
finalScopes.setFlag(UserInfo::PermissionScopeAccessAllThings, false);
params.insert("scopes", UserInfo::scopesToList(finalScopes));
if (m_engine->jsonRpcClient()->ensureServerVersion("8.4") && !allowedThingIds.isEmpty()) {
QVariantList thingIds;
foreach (const QUuid &thingId, allowedThingIds)

View File

@ -98,7 +98,7 @@ SettingsPageBase {
Layout.fillWidth: true
text: qsTr("Change password")
iconName: "qrc:/icons/key.svg"
visible: !engine.jsonRpcClient.pushButtonAuthAvailable
visible: NymeaUtils.hasPermissionScope(engine.jsonRpcClient.permissions, UserInfo.PermissionScopeAdmin) && !engine.jsonRpcClient.pushButtonAuthAvailable
onClicked: {
var page = pageStack.push(changePasswordComponent)
page.confirmed.connect(function(newPassword) {
@ -112,16 +112,15 @@ SettingsPageBase {
text: qsTr("Edit user information")
iconName: "qrc:/icons/edit.svg"
onClicked: pageStack.push(editUserInfoComponent)
visible: !engine.jsonRpcClient.pushButtonAuthAvailable
visible: NymeaUtils.hasPermissionScope(engine.jsonRpcClient.permissions, UserInfo.PermissionScopeAdmin) && !engine.jsonRpcClient.pushButtonAuthAvailable
}
NymeaItemDelegate {
Layout.fillWidth: true
text: qsTr("Manage authorized devices")
iconName: "qrc:/icons/smartphone.svg"
onClicked: {
pageStack.push(manageTokensComponent)
}
visible: NymeaUtils.hasPermissionScope(engine.jsonRpcClient.permissions, UserInfo.PermissionScopeAdmin)
onClicked: pageStack.push(manageTokensComponent)
}
SettingsPageSectionHeader {
@ -134,12 +133,9 @@ SettingsPageBase {
text: qsTr("Manage users")
visible: NymeaUtils.hasPermissionScope(engine.jsonRpcClient.permissions, UserInfo.PermissionScopeAdmin) && !engine.jsonRpcClient.pushButtonAuthAvailable
iconName: "qrc:/icons/contact-group.svg"
onClicked: {
pageStack.push(manageUsersComponent)
}
onClicked: pageStack.push(manageUsersComponent)
}
Component {
id: editUserInfoComponent
SettingsPageBase {
@ -199,14 +195,16 @@ SettingsPageBase {
id: configureAllowedThingsPage
property UserInfo userInfo: null
property bool existingUser: true
title: qsTr("Accessable things for") + " \"" + userInfo.username + "\""
header: NymeaHeader {
text: root.title
text: configureAllowedThingsPage.title
backButtonVisible: true
onBackPressed: pageStack.pop()
}
title: qsTr("Allowed things for") + " \"" + userInfo.username + "\""
ColumnLayout {
anchors.fill: parent
@ -236,7 +234,10 @@ SettingsPageBase {
checked: configureAllowedThingsPage.userInfo.thingAllowed(thingDelegate.thing.id)
onCheckedChanged: {
configureAllowedThingsPage.userInfo.allowThingId(thingDelegate.thing.id, checked)
userManager.setUserScopes(configureAllowedThingsPage.userInfo.username, configureAllowedThingsPage.userInfo.scopes, configureAllowedThingsPage.userInfo.allowedThingIds)
if (configureAllowedThingsPage.existingUser) {
// Only update if this user already exists
userManager.setUserScopes(configureAllowedThingsPage.userInfo.username, configureAllowedThingsPage.userInfo.scopes, configureAllowedThingsPage.userInfo.allowedThingIds)
}
}
}
}
@ -438,7 +439,7 @@ SettingsPageBase {
Repeater {
id: permissionRepeater
model: NymeaUtils.scopesModel
model: engine.jsonRpcClient.ensureServerVersion("8.4") ? NymeaUtils.scopesModel : NymeaUtils.scopesModelPre8dot4
delegate: NymeaSwipeDelegate {
Layout.fillWidth: true
@ -478,24 +479,22 @@ SettingsPageBase {
}
}
SettingsPageSectionHeader {
text: qsTr("Acessable things")
visible: (userDetailsPage.userInfo.scopes & UserInfo.PermissionScopeAccessAllThings) !== UserInfo.PermissionScopeAccessAllThings
visible: engine.jsonRpcClient.ensureServerVersion("8.4") &&
(userDetailsPage.userInfo.scopes & UserInfo.PermissionScopeAccessAllThings) !== UserInfo.PermissionScopeAccessAllThings
Layout.fillWidth: true
}
NymeaSwipeDelegate {
id: allowedThingsEntry
Layout.fillWidth: true
text: qsTr("Allowed things for this user")
subText: userDetailsPage.userInfo.allowedThingIds.length + " " + qsTr("things accessable")
visible: (userDetailsPage.userInfo.scopes & UserInfo.PermissionScopeAccessAllThings) !== UserInfo.PermissionScopeAccessAllThings
visible: engine.jsonRpcClient.ensureServerVersion("8.4") &&
(userDetailsPage.userInfo.scopes & UserInfo.PermissionScopeAccessAllThings) !== UserInfo.PermissionScopeAccessAllThings
progressive: true
onClicked: {
pageStack.push(configureAllowedThingsComponent, {userInfo: userDetailsPage.userInfo})
}
onClicked: pageStack.push(configureAllowedThingsComponent, {userInfo: userDetailsPage.userInfo})
}
SettingsPageSectionHeader {
@ -537,7 +536,13 @@ SettingsPageBase {
id: createUserPage
title: qsTr("Add a user")
property var permissionScopes: UserInfo.PermissionScopeNone
UserInfo {
id: newUserInfo
username: usernameTextField.text
email: emailTextField.text
displayName: displayNameTextField.text
}
SettingsPageSectionHeader {
text: qsTr("User information")
@ -588,30 +593,59 @@ SettingsPageBase {
text: qsTr("Permissions")
}
Repeater {
id: scopesRepeater
model: NymeaUtils.scopesModel
delegate: CheckDelegate {
model: engine.jsonRpcClient.ensureServerVersion("8.4") ? NymeaUtils.scopesModel : NymeaUtils.scopesModelPre8dot4
delegate: NymeaSwipeDelegate {
Layout.fillWidth: true
text: model.text
checked: (createUserPage.permissionScopes & model.scope) === model.scope
onClicked: {
var scopes = createUserPage.permissionScopes
if (checked) {
scopes |= model.scope
} else {
scopes &= ~model.scope
}
// make sure the new permissions are consistant before sending them to the core
scopes = NymeaUtils.getPermissionScopeAdjustments(model.scope, checked, scopes)
createUserPage.permissionScopes = scopes
text: model.text
subText: model.description
progressive: false
CheckBox {
anchors.right: parent.right
anchors.rightMargin: app.margins
anchors.verticalCenter: parent.verticalCenter
enabled: model.scope === UserInfo.PermissionScopeAdmin || ((newUserInfo.scopes & UserInfo.PermissionScopeAdmin) !== UserInfo.PermissionScopeAdmin)
checked: (newUserInfo.scopes & model.scope) === model.scope
onClicked: {
var scopes = newUserInfo.scopes
if (checked) {
scopes |= model.scope
} else {
scopes &= ~model.scope
}
// make sure the new permissions are consistant before sending them to the core
scopes = NymeaUtils.getPermissionScopeAdjustments(model.scope, checked, scopes)
newUserInfo.scopes = scopes
}
}
}
}
SettingsPageSectionHeader {
text: qsTr("Acessable things")
visible: engine.jsonRpcClient.ensureServerVersion("8.4") &&
(newUserInfo.scopes & UserInfo.PermissionScopeAccessAllThings) !== UserInfo.PermissionScopeAccessAllThings
Layout.fillWidth: true
}
NymeaSwipeDelegate {
id: allowedThingsEntry
Layout.fillWidth: true
text: qsTr("Allowed things for this user")
subText: newUserInfo.allowedThingIds.length + " " + qsTr("things accessable")
visible: engine.jsonRpcClient.ensureServerVersion("8.4") &&
(newUserInfo.scopes & UserInfo.PermissionScopeAccessAllThings) !== UserInfo.PermissionScopeAccessAllThings
progressive: true
onClicked: pageStack.push(configureAllowedThingsComponent, {userInfo: newUserInfo, existingUser: false})
}
Button {
text: qsTr("Create new user")
Layout.fillWidth: true
@ -620,7 +654,7 @@ SettingsPageBase {
enabled: usernameTextField.displayText.length >= 3 && passwordTextField.isValid
onClicked: {
createUserPage.busy = true
userManager.createUser(usernameTextField.displayText, passwordTextField.password, displayNameTextField.text, emailTextField.text, createUserPage.permissionScopes)
userManager.createUser(usernameTextField.displayText, passwordTextField.password, displayNameTextField.text, emailTextField.text, newUserInfo.scopes, newUserInfo.allowedThingIds)
}
}
Connections {

View File

@ -166,6 +166,35 @@ Item {
return namedIcons[name]
}
property ListModel scopesModelPre8dot4: ListModel {
ListElement {
text: qsTr("Admin")
description: qsTr("Full access to the system.")
scope: UserInfo.PermissionScopeAdmin
}
ListElement {
text: qsTr("Control things")
description: qsTr("Execute actions and use things and services.")
scope: UserInfo.PermissionScopeControlThings
}
ListElement {
text: qsTr("Configure things")
description: qsTr("Add new things and change settings.")
scope: UserInfo.PermissionScopeConfigureThings
}
ListElement {
text: qsTr("Execute magic")
description: qsTr("Execute rules, scenes and scripts.")
scope: UserInfo.PermissionScopeExecuteRules
}
ListElement {
text: qsTr("Configure magic")
description: qsTr("Create new rules and scripts in the system.")
scope: UserInfo.PermissionScopeConfigureRules
}
}
property ListModel scopesModel: ListModel {
ListElement {
text: qsTr("Admin")