misc(power level) : introduce RoomPermissions

This commit is contained in:
ganfra
2025-12-05 12:53:58 +01:00
parent 9e997a2fa6
commit 9b056f8aec
6 changed files with 316 additions and 0 deletions

View File

@@ -14,6 +14,7 @@ import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.ThreadId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.draft.ComposerDraft
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues
import io.element.android.libraries.matrix.api.room.tombstone.PredecessorRoom
import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility
@@ -99,6 +100,11 @@ interface BaseRoom : Closeable {
*/
suspend fun userRole(userId: UserId): Result<RoomMember.Role>
/**
* Gets the permissions of the room.
*/
suspend fun roomPermissions(): Result<RoomPermissions>
/**
* Gets the display name of the user with the provided [userId] in the room.
*/

View File

@@ -0,0 +1,142 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.api.room.powerlevels
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.MessageEventType
import io.element.android.libraries.matrix.api.room.StateEventType
/**
* Provides information about the permissions of users in a room.
*/
interface RoomPermissions : AutoCloseable {
fun canOwnUserBan(): Boolean
/**
* Returns true if the current user is able to invite in the room.
*/
fun canOwnUserInvite(): Boolean
/**
* Returns true if the current user is able to kick in the room.
*/
fun canOwnUserKick(): Boolean
/**
* Returns true if the current user is able to pin or unpin events in the
* room.
*/
fun canOwnUserPinUnpin(): Boolean
/**
* Returns true if the current user user is able to redact messages of
* other users in the room.
*/
fun canOwnUserRedactOther(): Boolean
/**
* Returns true if the current user is able to redact their own messages in
* the room.
*/
fun canOwnUserRedactOwn(): Boolean
/**
* Returns true if the current user is able to send a specific message type
* in the room.
*/
fun canOwnUserSendMessage(message: MessageEventType): Boolean
/**
* Returns true if the current user is able to send a specific state event
* type in the room.
*/
fun canOwnUserSendState(stateEvent: StateEventType): Boolean
/**
* Returns true if the current user is able to trigger a notification in
* the room.
*/
fun canOwnUserTriggerRoomNotification(): Boolean
/**
* Returns true if the user with the given userId is able to ban in the
* room.
*/
fun canUserBan(userId: UserId): Boolean
/**
* Returns true if the user with the given userId is able to invite in the
* room.
*/
fun canUserInvite(userId: UserId): Boolean
/**
* Returns true if the user with the given userId is able to kick in the
* room.
*/
fun canUserKick(userId: UserId): Boolean
/**
* Returns true if the user with the given userId is able to pin or unpin
* events in the room.
*/
fun canUserPinUnpin(userId: UserId): Boolean
/**
* Returns true if the user with the given userId is able to redact
* messages of other users in the room.
*/
fun canUserRedactOther(userId: UserId): Boolean
/**
* Returns true if the user with the given userId is able to redact
* their own messages in the room.
*/
fun canUserRedactOwn(userId: UserId): Boolean
/**
* Returns true if the user with the given userId is able to send a
* specific message type in the room.
*/
fun canUserSendMessage(userId: UserId, message: MessageEventType): Boolean
/**
* Returns true if the user with the given userId is able to send a
* specific state event type in the room.
*/
fun canUserSendState(userId: UserId, stateEvent: StateEventType): Boolean
/**
* Returns true if the user with the given userId is able to trigger a
* notification in the room.
*
* The call may fail if there is an error in getting the power levels.
*/
fun canUserTriggerRoomNotification(userId: UserId): Boolean
}
fun RoomPermissions.canEditRoomDetails(): Boolean {
return canOwnUserSendState(StateEventType.ROOM_NAME) ||
canOwnUserSendState(StateEventType.ROOM_TOPIC) ||
canOwnUserSendState(StateEventType.ROOM_AVATAR)
}
fun RoomPermissions.canManageKnockRequests(): Boolean {
return canOwnUserInvite() || canOwnUserBan() || canOwnUserKick()
}
fun RoomPermissions.canEditSecurityAndPrivacy(): Boolean {
return canOwnUserSendState(StateEventType.ROOM_JOIN_RULES) ||
canOwnUserSendState(StateEventType.ROOM_HISTORY_VISIBILITY) ||
canOwnUserSendState(StateEventType.ROOM_CANONICAL_ALIAS) ||
canOwnUserSendState(StateEventType.ROOM_ENCRYPTION)
}
fun RoomPermissions.canEditRolesAndPermissions(): Boolean {
return canOwnUserSendState(StateEventType.ROOM_POWER_LEVELS)
}

View File

@@ -25,6 +25,7 @@ import io.element.android.libraries.matrix.api.room.RoomMembersState
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.api.room.StateEventType
import io.element.android.libraries.matrix.api.room.draft.ComposerDraft
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues
import io.element.android.libraries.matrix.api.room.tombstone.PredecessorRoom
import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility
@@ -33,6 +34,7 @@ import io.element.android.libraries.matrix.impl.room.draft.into
import io.element.android.libraries.matrix.impl.room.member.RoomMemberListFetcher
import io.element.android.libraries.matrix.impl.room.member.RoomMemberMapper
import io.element.android.libraries.matrix.impl.room.powerlevels.RoomPowerLevelsValuesMapper
import io.element.android.libraries.matrix.impl.room.powerlevels.RustRoomPermissions
import io.element.android.libraries.matrix.impl.room.tombstone.map
import io.element.android.libraries.matrix.impl.roomdirectory.map
import io.element.android.libraries.matrix.impl.timeline.toRustReceiptType
@@ -178,6 +180,12 @@ class RustBaseRoom(
}
}
override suspend fun roomPermissions(): Result<RoomPermissions> = withContext(roomDispatcher) {
runCatchingExceptions {
RustRoomPermissions(innerRoom.getPowerLevels())
}
}
override suspend fun canUserInvite(userId: UserId): Result<Boolean> = withContext(roomDispatcher) {
runCatchingExceptions {
innerRoom.getPowerLevels().use { it.canUserInvite(userId.value) }

View File

@@ -0,0 +1,95 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.room.powerlevels
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.MessageEventType
import io.element.android.libraries.matrix.api.room.StateEventType
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions
import io.element.android.libraries.matrix.impl.room.map
import org.matrix.rustcomponents.sdk.RoomPowerLevels
class RustRoomPermissions(
private val inner: RoomPowerLevels,
) : RoomPermissions {
override fun canOwnUserBan(): Boolean {
return inner.canOwnUserBan()
}
override fun canOwnUserInvite(): Boolean {
return inner.canOwnUserInvite()
}
override fun canOwnUserKick(): Boolean {
return inner.canOwnUserKick()
}
override fun canOwnUserPinUnpin(): Boolean {
return inner.canOwnUserPinUnpin()
}
override fun canOwnUserRedactOther(): Boolean {
return inner.canOwnUserRedactOther()
}
override fun canOwnUserRedactOwn(): Boolean {
return inner.canOwnUserRedactOwn()
}
override fun canOwnUserSendMessage(message: MessageEventType): Boolean {
return inner.canOwnUserSendMessage(message.map())
}
override fun canOwnUserSendState(stateEvent: StateEventType): Boolean {
return inner.canOwnUserSendState(stateEvent.map())
}
override fun canOwnUserTriggerRoomNotification(): Boolean {
return inner.canOwnUserTriggerRoomNotification()
}
override fun canUserBan(userId: UserId): Boolean {
return inner.canUserBan(userId.value)
}
override fun canUserInvite(userId: UserId): Boolean {
return inner.canUserInvite(userId.value)
}
override fun canUserKick(userId: UserId): Boolean {
return inner.canUserKick(userId.value)
}
override fun canUserPinUnpin(userId: UserId): Boolean {
return inner.canUserPinUnpin(userId.value)
}
override fun canUserRedactOther(userId: UserId): Boolean {
return inner.canUserRedactOther(userId.value)
}
override fun canUserRedactOwn(userId: UserId): Boolean {
return inner.canUserRedactOwn(userId.value)
}
override fun canUserSendMessage(userId: UserId, message: MessageEventType): Boolean {
return inner.canUserSendMessage(userId.value, message.map())
}
override fun canUserSendState(userId: UserId, stateEvent: StateEventType): Boolean {
return inner.canUserSendState(userId.value, stateEvent.map())
}
override fun canUserTriggerRoomNotification(userId: UserId): Boolean {
return inner.canUserTriggerRoomNotification(userId.value)
}
override fun close() {
inner.close()
}
}

View File

@@ -21,12 +21,14 @@ import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomMembersState
import io.element.android.libraries.matrix.api.room.StateEventType
import io.element.android.libraries.matrix.api.room.draft.ComposerDraft
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues
import io.element.android.libraries.matrix.api.room.tombstone.PredecessorRoom
import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility
import io.element.android.libraries.matrix.api.timeline.ReceiptType
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.room.powerlevels.FakeRoomPermissions
import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.simulateLongTask
import kotlinx.coroutines.CoroutineScope
@@ -48,6 +50,7 @@ class FakeBaseRoom(
private val userRoleResult: () -> Result<RoomMember.Role> = { lambdaError() },
private val getUpdatedMemberResult: (UserId) -> Result<RoomMember> = { lambdaError() },
private val joinRoomResult: () -> Result<Unit> = { lambdaError() },
private val roomPermissionsResult: () -> Result<RoomPermissions> = { Result.success(FakeRoomPermissions()) },
private val canInviteResult: (UserId) -> Result<Boolean> = { lambdaError() },
private val canKickResult: (UserId) -> Result<Boolean> = { lambdaError() },
private val canBanResult: (UserId) -> Result<Boolean> = { lambdaError() },
@@ -129,6 +132,10 @@ class FakeBaseRoom(
return userRoleResult()
}
override suspend fun roomPermissions(): Result<RoomPermissions> {
return roomPermissionsResult()
}
override suspend fun getPermalink(): Result<String> {
return roomPermalinkResult()
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.room.powerlevels
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.MessageEventType
import io.element.android.libraries.matrix.api.room.StateEventType
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions
data class FakeRoomPermissions(
val ownerCanBan: Boolean = false,
val ownerCanInvite: Boolean = false,
val ownerCanKick: Boolean = false,
val ownerCanPinUnpin: Boolean = false,
val ownerCanRedactOther: Boolean = false,
val ownerCanRedactOwn: Boolean = false,
val ownerCanTriggerRoomNotification: Boolean = false,
val ownerCanSendMessage: (MessageEventType) -> Boolean = { false },
val ownerCanSendState: (StateEventType) -> Boolean = { false },
val userCanBan: (UserId) -> Boolean = { false },
val userCanInvite: (UserId) -> Boolean = { false },
val userCanKick: (UserId) -> Boolean = { false },
val userCanPinUnpin: (UserId) -> Boolean = { false },
val userCanRedactOther: (UserId) -> Boolean = { false },
val userCanRedactOwn: (UserId) -> Boolean = { false },
val userCanTriggerRoomNotification: (UserId) -> Boolean = { false },
val userCanSendMessage: (UserId, MessageEventType) -> Boolean = { _, _ -> false },
val userCanSendState: (UserId, StateEventType) -> Boolean = { _, _ -> false },
) : RoomPermissions {
override fun canOwnUserBan(): Boolean = ownerCanBan
override fun canOwnUserInvite(): Boolean = ownerCanInvite
override fun canOwnUserKick(): Boolean = ownerCanKick
override fun canOwnUserPinUnpin(): Boolean = ownerCanPinUnpin
override fun canOwnUserRedactOther(): Boolean = ownerCanRedactOther
override fun canOwnUserRedactOwn(): Boolean = ownerCanRedactOwn
override fun canOwnUserSendMessage(message: MessageEventType): Boolean = ownerCanSendMessage(message)
override fun canOwnUserSendState(stateEvent: StateEventType): Boolean = ownerCanSendState(stateEvent)
override fun canOwnUserTriggerRoomNotification(): Boolean = ownerCanTriggerRoomNotification
override fun canUserBan(userId: UserId): Boolean = userCanBan(userId)
override fun canUserInvite(userId: UserId): Boolean = userCanInvite(userId)
override fun canUserKick(userId: UserId): Boolean = userCanKick(userId)
override fun canUserPinUnpin(userId: UserId): Boolean = userCanPinUnpin(userId)
override fun canUserRedactOther(userId: UserId): Boolean = userCanRedactOther(userId)
override fun canUserRedactOwn(userId: UserId): Boolean = userCanRedactOwn(userId)
override fun canUserSendMessage(userId: UserId, message: MessageEventType): Boolean = userCanSendMessage(userId, message)
override fun canUserSendState(userId: UserId, stateEvent: StateEventType): Boolean = userCanSendState(userId, stateEvent)
override fun canUserTriggerRoomNotification(userId: UserId): Boolean = userCanTriggerRoomNotification(userId)
override fun close() {
// no-op for the fake
}
}