Skip to content

Commit

Permalink
Merge pull request #3663 from vector-im/feature/bca/room_caps_restriced
Browse files Browse the repository at this point in the history
Feature/bca/room caps restricted
  • Loading branch information
BillCarsonFr authored Jul 30, 2021
2 parents c7c5893 + aaa9c7e commit 60caac4
Show file tree
Hide file tree
Showing 45 changed files with 2,104 additions and 151 deletions.
1 change: 1 addition & 0 deletions changelog.d/3509.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Spaces - Support Restricted Room via room capabilities API
1 change: 1 addition & 0 deletions changelog.d/3665.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Spaces | Support restricted room access in room settings
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,63 @@ data class HomeServerCapabilities(
*/
val roomVersions: RoomVersionCapabilities? = null
) {

enum class RoomCapabilitySupport {
SUPPORTED,
SUPPORTED_UNSTABLE,
UNSUPPORTED,
UNKNOWN
}

/**
* Check if a feature is supported by the homeserver.
* @return
* UNKNOWN if the server does not implement room caps
* UNSUPPORTED if this feature is not supported
* SUPPORTED if this feature is supported by a stable version
* SUPPORTED_UNSTABLE if this feature is supported by an unstable version
* (unstable version should only be used for dev/experimental purpose)
*/
fun isFeatureSupported(feature: String): RoomCapabilitySupport {
if (roomVersions?.capabilities == null) return RoomCapabilitySupport.UNKNOWN
val info = roomVersions.capabilities[feature] ?: return RoomCapabilitySupport.UNSUPPORTED

val preferred = info.preferred ?: info.support.lastOrNull()
val versionCap = roomVersions.supportedVersion.firstOrNull { it.version == preferred }

return when {
versionCap == null -> {
RoomCapabilitySupport.UNKNOWN
}
versionCap.status == RoomVersionStatus.STABLE -> {
RoomCapabilitySupport.SUPPORTED
}
else -> {
RoomCapabilitySupport.SUPPORTED_UNSTABLE
}
}
}
fun isFeatureSupported(feature: String, byRoomVersion: String): Boolean {
if (roomVersions?.capabilities == null) return false
val info = roomVersions.capabilities[feature] ?: return false

return info.preferred == byRoomVersion || info.support.contains(byRoomVersion)
}

/**
* Use this method to know if you should force a version when creating
* a room that requires this feature.
* You can also use #isFeatureSupported prior to this call to check if the
* feature is supported and report some feedback to user.
*/
fun versionOverrideForFeature(feature: String) : String? {
val cap = roomVersions?.capabilities?.get(feature)
return cap?.preferred ?: cap?.support?.lastOrNull()
}

companion object {
const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L
const val ROOM_CAP_KNOCK = "knock"
const val ROOM_CAP_RESTRICTED = "restricted"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,21 @@ package org.matrix.android.sdk.api.session.homeserver

data class RoomVersionCapabilities(
val defaultRoomVersion: String,
val supportedVersion: List<RoomVersionInfo>
val supportedVersion: List<RoomVersionInfo>,
// Keys are capabilities defined per spec, as for now knock or restricted
val capabilities: Map<String, RoomCapabilitySupport>?
)

data class RoomVersionInfo(
val version: String,
val status: RoomVersionStatus
)

data class RoomCapabilitySupport(
val preferred: String?,
val support: List<String>
)

enum class RoomVersionStatus {
STABLE,
UNSTABLE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import org.matrix.android.sdk.api.session.room.model.GuestAccess
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM

open class CreateRoomParams {
Expand Down Expand Up @@ -162,7 +161,7 @@ open class CreateRoomParams {

var roomVersion: String? = null

var joinRuleRestricted: List<RoomJoinRulesAllowEntry>? = null
var featurePreset: RoomFeaturePreset? = null

companion object {
private const val CREATION_CONTENT_KEY_M_FEDERATE = "m.federate"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.matrix.android.sdk.api.session.room.model.create

import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.api.session.room.model.GuestAccess
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent

interface RoomFeaturePreset {

fun updateRoomParams(params: CreateRoomParams)

fun setupInitialStates(): List<Event>?
}

class RestrictedRoomPreset(val homeServerCapabilities: HomeServerCapabilities, val restrictedList: List<RoomJoinRulesAllowEntry>) : RoomFeaturePreset {

override fun updateRoomParams(params: CreateRoomParams) {
params.historyVisibility = params.historyVisibility ?: RoomHistoryVisibility.SHARED
params.guestAccess = params.guestAccess ?: GuestAccess.Forbidden
params.roomVersion = homeServerCapabilities.versionOverrideForFeature(HomeServerCapabilities.ROOM_CAP_RESTRICTED)
}

override fun setupInitialStates(): List<Event>? {
return listOf(
Event(
type = EventType.STATE_ROOM_JOIN_RULES,
stateKey = "",
content = RoomJoinRulesContent(
_joinRules = RoomJoinRules.RESTRICTED.value,
allowList = restrictedList
).toContent()
)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.model.GuestAccess
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.api.util.Optional

Expand Down Expand Up @@ -53,7 +54,7 @@ interface StateService {
/**
* Update the join rule and/or the guest access
*/
suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?)
suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?, allowList: List<RoomJoinRulesAllowEntry>? = null)

/**
* Update the avatar of the room
Expand Down Expand Up @@ -91,4 +92,8 @@ interface StateService {
* @param eventTypes Set of eventType to observe. If empty, all state events will be observed
*/
fun getStateEventsLive(eventTypes: Set<String>, stateKey: QueryStringValue = QueryStringValue.NoCondition): LiveData<List<Event>>

suspend fun setJoinRulePublic()
suspend fun setJoinRuleInviteOnly()
suspend fun setJoinRuleRestricted(allowList: List<String>)
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.database.mapper

import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.api.session.homeserver.RoomCapabilitySupport
import org.matrix.android.sdk.api.session.homeserver.RoomVersionCapabilities
import org.matrix.android.sdk.api.session.homeserver.RoomVersionInfo
import org.matrix.android.sdk.api.session.homeserver.RoomVersionStatus
Expand Down Expand Up @@ -45,19 +46,28 @@ internal object HomeServerCapabilitiesMapper {
roomVersionsJson ?: return null

return tryOrNull {
MoshiProvider.providesMoshi().adapter(RoomVersions::class.java).fromJson(roomVersionsJson)?.let {
MoshiProvider.providesMoshi().adapter(RoomVersions::class.java).fromJson(roomVersionsJson)?.let { roomVersions ->
RoomVersionCapabilities(
defaultRoomVersion = it.default ?: DefaultRoomVersionService.DEFAULT_ROOM_VERSION,
supportedVersion = it.available.entries.map { entry ->
RoomVersionInfo(
version = entry.key,
status = if (entry.value == "stable") {
RoomVersionStatus.STABLE
} else {
RoomVersionStatus.UNSTABLE
}
)
}
defaultRoomVersion = roomVersions.default ?: DefaultRoomVersionService.DEFAULT_ROOM_VERSION,
supportedVersion = roomVersions.available?.entries?.map { entry ->
RoomVersionInfo(entry.key, RoomVersionStatus.STABLE
.takeIf { entry.value == "stable" }
?: RoomVersionStatus.UNSTABLE)
}.orEmpty(),
capabilities = roomVersions.roomCapabilities?.entries?.mapNotNull { entry ->
(entry.value as? Map<*, *>)?.let {
val preferred = it["preferred"] as? String ?: return@mapNotNull null
val support = (it["support"] as? List<*>)?.filterIsInstance<String>()
entry.key to RoomCapabilitySupport(preferred, support.orEmpty())
}
}?.toMap()
// Just for debug purpose
// ?: mapOf(
// HomeServerCapabilities.ROOM_CAP_RESTRICTED to RoomCapabilitySupport(
// preferred = null,
// support = listOf("org.matrix.msc3083")
// )
// )
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,22 @@ internal data class RoomVersions(
* Required. A detailed description of the room versions the server supports.
*/
@Json(name = "available")
val available: JsonDict
val available: JsonDict? = null,

/**
* "room_capabilities": {
* "knock" : {
* "preferred": "7",
* "support" : ["7"]
* },
* "restricted" : {
* "preferred": "9",
* "support" : ["8", "9"]
* }
* }
*/
@Json(name = "room_capabilities")
val roomCapabilities: JsonDict? = null
)

// The spec says: If not present, the client should assume that password changes are possible via the API
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,24 @@
package org.matrix.android.sdk.internal.session.permalinks

import org.matrix.android.sdk.api.MatrixPatterns.getDomain
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.room.RoomGetter
import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
import java.net.URLEncoder
import javax.inject.Inject
import javax.inject.Provider

internal class ViaParameterFinder @Inject constructor(
@UserId private val userId: String,
private val roomGetterProvider: Provider<RoomGetter>
private val roomGetterProvider: Provider<RoomGetter>,
private val stateEventDataSource: StateEventDataSource
) {

fun computeViaParams(roomId: String, max: Int): List<String> {
Expand Down Expand Up @@ -70,4 +77,28 @@ internal class ViaParameterFinder @Inject constructor(
.orEmpty()
.toSet()
}

fun computeViaParamsForRestricted(roomId: String, max: Int): List<String> {
val userThatCanInvite = roomGetterProvider.get().getRoom(roomId)
?.getRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.JOIN) })
?.map { it.userId }
?.filter { userCanInvite(userId, roomId) }
.orEmpty()
.toSet()

return userThatCanInvite.map { it.getDomain() }
.groupBy { it }
.mapValues { it.value.size }
.toMutableMap()
.let { map -> map.keys.sortedByDescending { map[it] } }
.take(max)
}

fun userCanInvite(userId: String, roomId: String): Boolean {
val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition)
?.content?.toModel<PowerLevelsContent>()
?.let { PowerLevelsHelper(it) }

return powerLevelsHelper?.isUserAbleToInvite(userId) ?: false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,10 @@
package org.matrix.android.sdk.internal.session.room.create

import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.identity.IdentityServiceError
import org.matrix.android.sdk.api.session.identity.toMedium
import org.matrix.android.sdk.api.session.room.model.GuestAccess
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
import org.matrix.android.sdk.api.util.MimeTypes
import org.matrix.android.sdk.internal.crypto.DeviceListManager
Expand All @@ -45,7 +39,6 @@ import javax.inject.Inject

internal class CreateRoomBodyBuilder @Inject constructor(
private val ensureIdentityTokenTask: EnsureIdentityTokenTask,
private val crossSigningService: CrossSigningService,
private val deviceListManager: DeviceListManager,
private val identityStore: IdentityStore,
private val fileUploader: FileUploader,
Expand Down Expand Up @@ -76,19 +69,18 @@ internal class CreateRoomBodyBuilder @Inject constructor(
}
}

if (params.joinRuleRestricted != null) {
params.roomVersion = "org.matrix.msc3083"
params.historyVisibility = params.historyVisibility ?: RoomHistoryVisibility.SHARED
params.guestAccess = params.guestAccess ?: GuestAccess.Forbidden
}
val initialStates = (listOfNotNull(
buildEncryptionWithAlgorithmEvent(params),
buildHistoryVisibilityEvent(params),
buildAvatarEvent(params),
buildGuestAccess(params),
buildJoinRulesRestricted(params)
)
+ buildCustomInitialStates(params))
params.featurePreset?.updateRoomParams(params)

val initialStates = (
listOfNotNull(
buildEncryptionWithAlgorithmEvent(params),
buildHistoryVisibilityEvent(params),
buildAvatarEvent(params),
buildGuestAccess(params)
)
+ params.featurePreset?.setupInitialStates().orEmpty()
+ buildCustomInitialStates(params)
)
.takeIf { it.isNotEmpty() }

return CreateRoomBody(
Expand Down Expand Up @@ -158,20 +150,6 @@ internal class CreateRoomBodyBuilder @Inject constructor(
}
}

private fun buildJoinRulesRestricted(params: CreateRoomParams): Event? {
return params.joinRuleRestricted
?.let { allowList ->
Event(
type = EventType.STATE_ROOM_JOIN_RULES,
stateKey = "",
content = RoomJoinRulesContent(
_joinRules = RoomJoinRules.RESTRICTED.value,
allowList = allowList
).toContent()
)
}
}

/**
* Add the crypto algorithm to the room creation parameters.
*/
Expand Down
Loading

0 comments on commit 60caac4

Please sign in to comment.