From 6e2863ded68f0c1e7fb4376745dade6a33a78267 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 11 Dec 2025 11:54:12 +0100 Subject: [PATCH] change(room permissions): fix securityAndPrivacy permissions computation --- .../roomdetails/impl/RoomDetailsPresenter.kt | 17 +++++---- .../api/SecurityAndPrivacyPermissions.kt | 18 +++++++--- .../space/impl/root/SpacePresenter.kt | 7 +++- .../impl/settings/SpaceSettingsPermissions.kt | 35 ++++++++++++------- .../impl/settings/SpaceSettingsPresenter.kt | 12 +++++-- 5 files changed, 62 insertions(+), 27 deletions(-) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt index 83840ad604..07b76d41b9 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt @@ -19,6 +19,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import dev.zacsweers.metro.Inject import im.vector.app.features.analytics.plan.Interaction +import io.element.android.features.knockrequests.api.KnockRequestPermissions import io.element.android.features.knockrequests.api.knockRequestPermissions import io.element.android.features.leaveroom.api.LeaveRoomEvent import io.element.android.features.leaveroom.api.LeaveRoomState @@ -26,6 +27,7 @@ import io.element.android.features.roomcall.api.RoomCallState import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter import io.element.android.features.roomdetailsedit.api.RoomDetailsEditPermissions import io.element.android.features.roomdetailsedit.api.roomDetailsEditPermissions +import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyPermissions import io.element.android.features.securityandprivacy.api.securityAndPrivacyPermissions import io.element.android.libraries.androidutils.clipboard.ClipboardHelper import io.element.android.libraries.architecture.Presenter @@ -119,7 +121,10 @@ class RoomDetailsPresenter( room.knockRequestsFlow.collect { value = it.size } } val canShowKnockRequests by remember { - derivedStateOf { isKnockRequestsEnabled && permissions.canManageKnockRequests && joinRule == JoinRule.Knock } + derivedStateOf { isKnockRequestsEnabled && permissions.knockRequestsPermissions.hasAny && joinRule == JoinRule.Knock } + } + val canShowSecurityAndPrivacy by remember { + derivedStateOf { !isDm && permissions.securityAndPrivacyPermissions.hasAny(isSpace = false, joinRule = joinRule) } } val isDeveloperModeEnabled by remember { appPreferencesStore.isDeveloperModeEnabledFlow() @@ -186,7 +191,7 @@ class RoomDetailsPresenter( snackbarMessage = snackbarMessage, canShowKnockRequests = canShowKnockRequests, knockRequestsCount = knockRequestsCount, - canShowSecurityAndPrivacy = !isDm && permissions.canEditSecurityAndPrivacy, + canShowSecurityAndPrivacy = canShowSecurityAndPrivacy, hasMemberVerificationViolations = hasMemberVerificationViolations, canReportRoom = canReportRoom, isTombstoned = roomInfo.successorRoom != null, @@ -221,9 +226,9 @@ class RoomDetailsPresenter( private data class Permissions( val canInvite: Boolean = false, val editDetailsPermissions: RoomDetailsEditPermissions = RoomDetailsEditPermissions.DEFAULT, - val canManageKnockRequests: Boolean = false, + val knockRequestsPermissions: KnockRequestPermissions = KnockRequestPermissions.DEFAULT, + val securityAndPrivacyPermissions: SecurityAndPrivacyPermissions = SecurityAndPrivacyPermissions.DEFAULT, val canEditRolesAndPermissions: Boolean = false, - val canEditSecurityAndPrivacy: Boolean = false, ) @Composable @@ -232,9 +237,9 @@ class RoomDetailsPresenter( Permissions( canInvite = perms.canOwnUserInvite(), editDetailsPermissions = perms.roomDetailsEditPermissions(), - canManageKnockRequests = perms.knockRequestPermissions().hasAny, + knockRequestsPermissions = perms.knockRequestPermissions(), canEditRolesAndPermissions = perms.canEditRolesAndPermissions(), - canEditSecurityAndPrivacy = perms.securityAndPrivacyPermissions().hasAny, + securityAndPrivacyPermissions = perms.securityAndPrivacyPermissions(), ) } } diff --git a/features/securityandprivacy/api/src/main/kotlin/io/element/android/features/securityandprivacy/api/SecurityAndPrivacyPermissions.kt b/features/securityandprivacy/api/src/main/kotlin/io/element/android/features/securityandprivacy/api/SecurityAndPrivacyPermissions.kt index bacd863ca6..3601f2c4e6 100644 --- a/features/securityandprivacy/api/src/main/kotlin/io/element/android/features/securityandprivacy/api/SecurityAndPrivacyPermissions.kt +++ b/features/securityandprivacy/api/src/main/kotlin/io/element/android/features/securityandprivacy/api/SecurityAndPrivacyPermissions.kt @@ -9,6 +9,7 @@ package io.element.android.features.securityandprivacy.api import io.element.android.libraries.matrix.api.room.StateEventType +import io.element.android.libraries.matrix.api.room.join.JoinRule import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions data class SecurityAndPrivacyPermissions( @@ -17,10 +18,19 @@ data class SecurityAndPrivacyPermissions( val canChangeEncryption: Boolean, val canChangeRoomVisibility: Boolean, ) { - val hasAny = canChangeRoomAccess || - canChangeHistoryVisibility || - canChangeEncryption || - canChangeRoomVisibility + fun hasAny(isSpace: Boolean, joinRule: JoinRule?): Boolean { + val canChangeRoomVisibility = when (joinRule) { + is JoinRule.Public, + is JoinRule.Knock, + is JoinRule.KnockRestricted -> canChangeRoomVisibility + else -> false + } + return if (isSpace) { + canChangeRoomAccess || canChangeRoomVisibility + } else { + canChangeRoomAccess || canChangeRoomVisibility || canChangeHistoryVisibility || canChangeEncryption + } + } companion object { val DEFAULT = SecurityAndPrivacyPermissions( diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpacePresenter.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpacePresenter.kt index 0794d02920..40a58ddbd9 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpacePresenter.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/root/SpacePresenter.kt @@ -11,6 +11,7 @@ package io.element.android.features.space.impl.root import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -94,6 +95,10 @@ class SpacePresenter( featureFlagService.isFeatureEnabledFlow(FeatureFlags.SpaceSettings) }.collectAsState(false) + val roomInfo by room.roomInfoFlow.collectAsState() + val canAccessSpaceSettings by remember { + derivedStateOf { isSpaceSettingsEnabled && permissions.hasAny(roomInfo.joinRule) } + } val currentSpace by spaceRoomList.currentSpaceFlow.collectAsState() val (joinActions, setJoinActions) = remember { mutableStateOf(emptyMap>()) } @@ -144,7 +149,7 @@ class SpacePresenter( joinActions = joinActions.toImmutableMap(), acceptDeclineInviteState = acceptDeclineInviteState, topicViewerState = topicViewerState, - canAccessSpaceSettings = isSpaceSettingsEnabled && permissions.hasAny, + canAccessSpaceSettings = canAccessSpaceSettings, eventSink = ::handleEvent, ) } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPermissions.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPermissions.kt index d7588b59e1..9a90435f77 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPermissions.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPermissions.kt @@ -7,32 +7,41 @@ package io.element.android.features.space.impl.settings +import io.element.android.features.roomdetailsedit.api.RoomDetailsEditPermissions import io.element.android.features.roomdetailsedit.api.roomDetailsEditPermissions +import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyPermissions import io.element.android.features.securityandprivacy.api.securityAndPrivacyPermissions -import io.element.android.libraries.matrix.api.room.StateEventType +import io.element.android.libraries.matrix.api.room.join.JoinRule import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions +import io.element.android.libraries.matrix.api.room.powerlevels.canEditRolesAndPermissions data class SpaceSettingsPermissions( - val canEditDetails: Boolean, - val canManageRolesAndPermissions: Boolean, - val canManageSecurityAndPrivacy: Boolean, -){ + val editDetailsPermissions: RoomDetailsEditPermissions, + val canEditRolesAndPermissions: Boolean, + val securityAndPrivacyPermissions: SecurityAndPrivacyPermissions, +) { + + fun hasAny(joinRule: JoinRule?): Boolean { + return editDetailsPermissions.hasAny || + canEditRolesAndPermissions || + securityAndPrivacyPermissions.hasAny(isSpace = true, joinRule = joinRule) + } + - val hasAny = canEditDetails || canManageRolesAndPermissions || canManageSecurityAndPrivacy companion object { val DEFAULT = SpaceSettingsPermissions( - canEditDetails = false, - canManageRolesAndPermissions = false, - canManageSecurityAndPrivacy = false, + editDetailsPermissions = RoomDetailsEditPermissions.DEFAULT, + canEditRolesAndPermissions = false, + securityAndPrivacyPermissions = SecurityAndPrivacyPermissions.DEFAULT, ) } } -fun RoomPermissions.spaceSettingsPermissions(): SpaceSettingsPermissions { +fun RoomPermissions.spaceSettingsPermissions(): SpaceSettingsPermissions { return SpaceSettingsPermissions( - canEditDetails = roomDetailsEditPermissions().hasAny, - canManageRolesAndPermissions = canOwnUserSendState(StateEventType.ROOM_POWER_LEVELS), - canManageSecurityAndPrivacy = securityAndPrivacyPermissions().hasAny, + editDetailsPermissions = roomDetailsEditPermissions(), + canEditRolesAndPermissions = canEditRolesAndPermissions(), + securityAndPrivacyPermissions = securityAndPrivacyPermissions(), ) } diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPresenter.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPresenter.kt index 96c3fcbab6..565008a778 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPresenter.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/settings/SpaceSettingsPresenter.kt @@ -10,7 +10,9 @@ package io.element.android.features.space.impl.settings import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import dev.zacsweers.metro.Inject import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.matrix.api.room.JoinedRoom @@ -26,15 +28,19 @@ class SpaceSettingsPresenter( val permissions by room.permissionsAsState(SpaceSettingsPermissions.DEFAULT) { perms -> perms.spaceSettingsPermissions() } + val showSecurityAndPrivacy by remember { + derivedStateOf { permissions.securityAndPrivacyPermissions.hasAny(isSpace = false, joinRule = roomInfo.joinRule) } + } + return SpaceSettingsState( roomId = room.roomId, name = roomInfo.name.orEmpty(), canonicalAlias = roomInfo.canonicalAlias, avatarUrl = roomInfo.avatarUrl, memberCount = roomInfo.activeMembersCount, - canEditDetails = permissions.canEditDetails, - showRolesAndPermissions = permissions.canManageRolesAndPermissions, - showSecurityAndPrivacy = permissions.canManageSecurityAndPrivacy, + canEditDetails = permissions.editDetailsPermissions.hasAny, + showRolesAndPermissions = permissions.canEditRolesAndPermissions, + showSecurityAndPrivacy = showSecurityAndPrivacy, eventSink = {}, ) }