change(room permissions): fix securityAndPrivacy permissions computation

This commit is contained in:
ganfra 2025-12-11 11:54:12 +01:00
parent 03dd89a77f
commit 6e2863ded6
5 changed files with 62 additions and 27 deletions

View file

@ -19,6 +19,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import dev.zacsweers.metro.Inject import dev.zacsweers.metro.Inject
import im.vector.app.features.analytics.plan.Interaction 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.knockrequests.api.knockRequestPermissions
import io.element.android.features.leaveroom.api.LeaveRoomEvent import io.element.android.features.leaveroom.api.LeaveRoomEvent
import io.element.android.features.leaveroom.api.LeaveRoomState 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.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.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.features.securityandprivacy.api.securityAndPrivacyPermissions
import io.element.android.libraries.androidutils.clipboard.ClipboardHelper import io.element.android.libraries.androidutils.clipboard.ClipboardHelper
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
@ -119,7 +121,10 @@ class RoomDetailsPresenter(
room.knockRequestsFlow.collect { value = it.size } room.knockRequestsFlow.collect { value = it.size }
} }
val canShowKnockRequests by remember { 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 { val isDeveloperModeEnabled by remember {
appPreferencesStore.isDeveloperModeEnabledFlow() appPreferencesStore.isDeveloperModeEnabledFlow()
@ -186,7 +191,7 @@ class RoomDetailsPresenter(
snackbarMessage = snackbarMessage, snackbarMessage = snackbarMessage,
canShowKnockRequests = canShowKnockRequests, canShowKnockRequests = canShowKnockRequests,
knockRequestsCount = knockRequestsCount, knockRequestsCount = knockRequestsCount,
canShowSecurityAndPrivacy = !isDm && permissions.canEditSecurityAndPrivacy, canShowSecurityAndPrivacy = canShowSecurityAndPrivacy,
hasMemberVerificationViolations = hasMemberVerificationViolations, hasMemberVerificationViolations = hasMemberVerificationViolations,
canReportRoom = canReportRoom, canReportRoom = canReportRoom,
isTombstoned = roomInfo.successorRoom != null, isTombstoned = roomInfo.successorRoom != null,
@ -221,9 +226,9 @@ class RoomDetailsPresenter(
private data class Permissions( private data class Permissions(
val canInvite: Boolean = false, val canInvite: Boolean = false,
val editDetailsPermissions: RoomDetailsEditPermissions = RoomDetailsEditPermissions.DEFAULT, val editDetailsPermissions: RoomDetailsEditPermissions = RoomDetailsEditPermissions.DEFAULT,
val canManageKnockRequests: Boolean = false, val knockRequestsPermissions: KnockRequestPermissions = KnockRequestPermissions.DEFAULT,
val securityAndPrivacyPermissions: SecurityAndPrivacyPermissions = SecurityAndPrivacyPermissions.DEFAULT,
val canEditRolesAndPermissions: Boolean = false, val canEditRolesAndPermissions: Boolean = false,
val canEditSecurityAndPrivacy: Boolean = false,
) )
@Composable @Composable
@ -232,9 +237,9 @@ class RoomDetailsPresenter(
Permissions( Permissions(
canInvite = perms.canOwnUserInvite(), canInvite = perms.canOwnUserInvite(),
editDetailsPermissions = perms.roomDetailsEditPermissions(), editDetailsPermissions = perms.roomDetailsEditPermissions(),
canManageKnockRequests = perms.knockRequestPermissions().hasAny, knockRequestsPermissions = perms.knockRequestPermissions(),
canEditRolesAndPermissions = perms.canEditRolesAndPermissions(), canEditRolesAndPermissions = perms.canEditRolesAndPermissions(),
canEditSecurityAndPrivacy = perms.securityAndPrivacyPermissions().hasAny, securityAndPrivacyPermissions = perms.securityAndPrivacyPermissions(),
) )
} }
} }

View file

@ -9,6 +9,7 @@
package io.element.android.features.securityandprivacy.api package io.element.android.features.securityandprivacy.api
import io.element.android.libraries.matrix.api.room.StateEventType 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.RoomPermissions
data class SecurityAndPrivacyPermissions( data class SecurityAndPrivacyPermissions(
@ -17,10 +18,19 @@ data class SecurityAndPrivacyPermissions(
val canChangeEncryption: Boolean, val canChangeEncryption: Boolean,
val canChangeRoomVisibility: Boolean, val canChangeRoomVisibility: Boolean,
) { ) {
val hasAny = canChangeRoomAccess || fun hasAny(isSpace: Boolean, joinRule: JoinRule?): Boolean {
canChangeHistoryVisibility || val canChangeRoomVisibility = when (joinRule) {
canChangeEncryption || is JoinRule.Public,
canChangeRoomVisibility is JoinRule.Knock,
is JoinRule.KnockRestricted -> canChangeRoomVisibility
else -> false
}
return if (isSpace) {
canChangeRoomAccess || canChangeRoomVisibility
} else {
canChangeRoomAccess || canChangeRoomVisibility || canChangeHistoryVisibility || canChangeEncryption
}
}
companion object { companion object {
val DEFAULT = SecurityAndPrivacyPermissions( val DEFAULT = SecurityAndPrivacyPermissions(

View file

@ -11,6 +11,7 @@ package io.element.android.features.space.impl.root
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@ -94,6 +95,10 @@ class SpacePresenter(
featureFlagService.isFeatureEnabledFlow(FeatureFlags.SpaceSettings) featureFlagService.isFeatureEnabledFlow(FeatureFlags.SpaceSettings)
}.collectAsState(false) }.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 currentSpace by spaceRoomList.currentSpaceFlow.collectAsState()
val (joinActions, setJoinActions) = remember { mutableStateOf(emptyMap<RoomId, AsyncAction<Unit>>()) } val (joinActions, setJoinActions) = remember { mutableStateOf(emptyMap<RoomId, AsyncAction<Unit>>()) }
@ -144,7 +149,7 @@ class SpacePresenter(
joinActions = joinActions.toImmutableMap(), joinActions = joinActions.toImmutableMap(),
acceptDeclineInviteState = acceptDeclineInviteState, acceptDeclineInviteState = acceptDeclineInviteState,
topicViewerState = topicViewerState, topicViewerState = topicViewerState,
canAccessSpaceSettings = isSpaceSettingsEnabled && permissions.hasAny, canAccessSpaceSettings = canAccessSpaceSettings,
eventSink = ::handleEvent, eventSink = ::handleEvent,
) )
} }

View file

@ -7,32 +7,41 @@
package io.element.android.features.space.impl.settings 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.roomdetailsedit.api.roomDetailsEditPermissions
import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyPermissions
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.RoomPermissions
import io.element.android.libraries.matrix.api.room.powerlevels.canEditRolesAndPermissions
data class SpaceSettingsPermissions( data class SpaceSettingsPermissions(
val canEditDetails: Boolean, val editDetailsPermissions: RoomDetailsEditPermissions,
val canManageRolesAndPermissions: Boolean, val canEditRolesAndPermissions: Boolean,
val canManageSecurityAndPrivacy: 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 { companion object {
val DEFAULT = SpaceSettingsPermissions( val DEFAULT = SpaceSettingsPermissions(
canEditDetails = false, editDetailsPermissions = RoomDetailsEditPermissions.DEFAULT,
canManageRolesAndPermissions = false, canEditRolesAndPermissions = false,
canManageSecurityAndPrivacy = false, securityAndPrivacyPermissions = SecurityAndPrivacyPermissions.DEFAULT,
) )
} }
} }
fun RoomPermissions.spaceSettingsPermissions(): SpaceSettingsPermissions { fun RoomPermissions.spaceSettingsPermissions(): SpaceSettingsPermissions {
return SpaceSettingsPermissions( return SpaceSettingsPermissions(
canEditDetails = roomDetailsEditPermissions().hasAny, editDetailsPermissions = roomDetailsEditPermissions(),
canManageRolesAndPermissions = canOwnUserSendState(StateEventType.ROOM_POWER_LEVELS), canEditRolesAndPermissions = canEditRolesAndPermissions(),
canManageSecurityAndPrivacy = securityAndPrivacyPermissions().hasAny, securityAndPrivacyPermissions = securityAndPrivacyPermissions(),
) )
} }

View file

@ -10,7 +10,9 @@ package io.element.android.features.space.impl.settings
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import dev.zacsweers.metro.Inject import dev.zacsweers.metro.Inject
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.JoinedRoom
@ -26,15 +28,19 @@ class SpaceSettingsPresenter(
val permissions by room.permissionsAsState(SpaceSettingsPermissions.DEFAULT) { perms -> val permissions by room.permissionsAsState(SpaceSettingsPermissions.DEFAULT) { perms ->
perms.spaceSettingsPermissions() perms.spaceSettingsPermissions()
} }
val showSecurityAndPrivacy by remember {
derivedStateOf { permissions.securityAndPrivacyPermissions.hasAny(isSpace = false, joinRule = roomInfo.joinRule) }
}
return SpaceSettingsState( return SpaceSettingsState(
roomId = room.roomId, roomId = room.roomId,
name = roomInfo.name.orEmpty(), name = roomInfo.name.orEmpty(),
canonicalAlias = roomInfo.canonicalAlias, canonicalAlias = roomInfo.canonicalAlias,
avatarUrl = roomInfo.avatarUrl, avatarUrl = roomInfo.avatarUrl,
memberCount = roomInfo.activeMembersCount, memberCount = roomInfo.activeMembersCount,
canEditDetails = permissions.canEditDetails, canEditDetails = permissions.editDetailsPermissions.hasAny,
showRolesAndPermissions = permissions.canManageRolesAndPermissions, showRolesAndPermissions = permissions.canEditRolesAndPermissions,
showSecurityAndPrivacy = permissions.canManageSecurityAndPrivacy, showSecurityAndPrivacy = showSecurityAndPrivacy,
eventSink = {}, eventSink = {},
) )
} }