fix: PowerLevel calculation regarding to spec

This commit is contained in:
Christian Pauly 2022-11-21 08:54:21 +01:00
parent 53204fe338
commit f3287dbb99
3 changed files with 76 additions and 46 deletions

View File

@ -1625,24 +1625,28 @@ class Room {
}
/// Returns the power level of the given user ID.
/// If a user_id is in the users list, then that user_id has the associated
/// power level. Otherwise they have the default level users_default.
/// If users_default is not supplied, it is assumed to be 0. If the room
/// contains no m.room.power_levels event, the rooms creator has a power
/// level of 100, and all other users have a power level of 0.
int getPowerLevelByUserId(String userId) {
var powerLevel = 0;
final powerLevelMap = getState(EventTypes.RoomPowerLevels)?.content;
if (powerLevelMap == null) return powerLevel;
powerLevel = getDefaultPowerLevel(powerLevelMap);
if (powerLevelMap
.tryGet<Map<String, dynamic>>('users')
?.tryGet<int>(userId) !=
null) {
powerLevel = powerLevelMap['users'][userId];
if (powerLevelMap == null) {
return getState(EventTypes.RoomCreate)?.senderId == userId ? 100 : 0;
}
return powerLevel;
return powerLevelMap
.tryGetMap<String, dynamic>('users')
?.tryGet<int>(userId) ??
powerLevelMap.tryGet<int>('users_default') ??
0;
}
/// Returns the user's own power level.
int get ownPowerLevel => getPowerLevelByUserId(client.userID!);
/// Returns the power levels from all users for this room or null if not given.
@Deprecated('Use `getPowerLevelByUserId(String userId)` instead')
Map<String, int>? get powerLevels {
final powerLevelState =
getState(EventTypes.RoomPowerLevels)?.content['users'];
@ -1665,17 +1669,11 @@ class Room {
);
}
bool _hasPermissionFor(String action) {
final pl =
getState(EventTypes.RoomPowerLevels)?.content.tryGet<int>(action);
if (pl == null) {
return true;
}
return ownPowerLevel >= pl;
}
/// The level required to ban a user.
bool get canBan => _hasPermissionFor('ban');
bool get canBan =>
(getState(EventTypes.RoomPowerLevels)?.content.tryGet<int>('ban') ??
50) <=
ownPowerLevel;
/// returns if user can change a particular state event by comparing `ownPowerLevel`
/// with possible overrides in `events`, if not present compares `ownPowerLevel`
@ -1685,10 +1683,14 @@ class Room {
}
/// returns the powerlevel required for chaning the `action` defaults to
/// state_default if `action` isn't specified in events override
/// state_default if `action` isn't specified in events override.
/// If there is no state_default in the m.room.power_levels event, the
/// state_default is 50. If the room contains no m.room.power_levels event,
/// the state_default is 0.
int powerForChangingStateEvent(String action) {
final powerLevelMap = getState(EventTypes.RoomPowerLevels)?.content;
return powerLevelMap!
if (powerLevelMap == null) return 0;
return powerLevelMap
.tryGetMap<String, dynamic>('events')
?.tryGet<int>(action) ??
powerLevelMap.tryGet<int>('state_default') ??
@ -1743,36 +1745,59 @@ class Room {
/// The default level required to send message events. Can be overridden by the events key.
bool get canSendDefaultMessages =>
_hasPermissionFor('events_default') &&
(getState(EventTypes.RoomPowerLevels)
?.content
.tryGet<int>('events_default') ??
0) <=
ownPowerLevel &&
(!encrypted || client.encryptionEnabled);
/// The level required to invite a user.
bool get canInvite => _hasPermissionFor('invite');
bool get canInvite =>
(getState(EventTypes.RoomPowerLevels)?.content.tryGet<int>('invite') ??
0) <=
ownPowerLevel;
/// The level required to kick a user.
bool get canKick => _hasPermissionFor('kick');
bool get canKick =>
(getState(EventTypes.RoomPowerLevels)?.content.tryGet<int>('kick') ??
50) <=
ownPowerLevel;
/// The level required to redact an event.
bool get canRedact => _hasPermissionFor('redact');
bool get canRedact =>
(getState(EventTypes.RoomPowerLevels)?.content.tryGet<int>('redact') ??
50) <=
ownPowerLevel;
/// The default level required to send state events. Can be overridden by the events key.
bool get canSendDefaultStates => _hasPermissionFor('state_default');
bool get canSendDefaultStates {
final powerLevelsMap = getState(EventTypes.RoomPowerLevels)?.content;
if (powerLevelsMap == null) return 0 <= ownPowerLevel;
return (getState(EventTypes.RoomPowerLevels)
?.content
.tryGet<int>('state_default') ??
50) <=
ownPowerLevel;
}
bool get canChangePowerLevel => canSendEvent(EventTypes.RoomPowerLevels);
bool get canChangePowerLevel =>
canChangeStateEvent(EventTypes.RoomPowerLevels);
/// The level required to send a certain event. Defaults to 0 if there is no
/// events_default set or there is no power level state in the room.
bool canSendEvent(String eventType) {
final pl = getState(EventTypes.RoomPowerLevels)
?.content
.tryGetMap<String, dynamic>('events')
?.tryGet<int>(eventType);
if (pl == null) {
return eventType == EventTypes.Message
? canSendDefaultMessages
: canSendDefaultStates;
}
final powerLevelsMap = getState(EventTypes.RoomPowerLevels)?.content;
if (powerLevelsMap == null) return 0 <= ownPowerLevel;
final pl = powerLevelsMap
.tryGetMap<String, dynamic>('events')
?.tryGet<int>(eventType) ??
powerLevelsMap.tryGet<int>('events_default') ??
0;
return ownPowerLevel >= pl;
}
/// The power level requirements for specific notification types.
bool canSendNotification(String userid, {String notificationType = 'room'}) {
final userLevel = getPowerLevelByUserId(userid);
final notificationLevel = getState(EventTypes.RoomPowerLevels)
@ -1922,7 +1947,7 @@ class Room {
}
/// Whether the user has the permission to change the join rules.
bool get canChangeJoinRules => canSendEvent(EventTypes.RoomJoinRules);
bool get canChangeJoinRules => canChangeStateEvent(EventTypes.RoomJoinRules);
/// This event controls whether guest users are allowed to join rooms. If this event
/// is absent, servers should act as if it is present and has the guest_access value "forbidden".
@ -1948,7 +1973,7 @@ class Room {
}
/// Whether the user has the permission to change the guest access.
bool get canChangeGuestAccess => canSendEvent(EventTypes.GuestAccess);
bool get canChangeGuestAccess => canChangeStateEvent(EventTypes.GuestAccess);
/// This event controls whether a user can see the events that happened in a room from before they joined.
HistoryVisibility? get historyVisibility {
@ -1974,7 +1999,7 @@ class Room {
/// Whether the user has the permission to change the history visibility.
bool get canChangeHistoryVisibility =>
canSendEvent(EventTypes.HistoryVisibility);
canChangeStateEvent(EventTypes.HistoryVisibility);
/// Returns the encryption algorithm. Currently only `m.megolm.v1.aes-sha2` is supported.
/// Returns null if there is no encryption algorithm.

View File

@ -331,6 +331,15 @@ void main() {
expect(event.stateKeyUser?.id, '@alice:example.com');
});
test('canRedact', () async {
final client = await getClient();
jsonObj['sender'] = client.userID!;
final event = Event.fromJson(
jsonObj,
Room(
id: '!localpart:server.abc',
client: client,
),
);
expect(event.canRedact, true);
});
test('getLocalizedBody, isEventKnown', () async {

View File

@ -414,13 +414,10 @@ void main() {
expect(room.canKick, true);
expect(room.canRedact, true);
expect(room.canSendDefaultMessages, true);
expect(room.canSendDefaultStates, true);
expect(room.canChangePowerLevel, true);
expect(room.canSendEvent('m.room.name'), true);
expect(room.canSendEvent('m.room.power_levels'), true);
expect(room.canSendEvent('m.room.member'), true);
expect(room.powerLevels,
room.getState('m.room.power_levels')?.content['users']);
room.setState(
Event(
senderId: '@test:example.com',
@ -477,11 +474,10 @@ void main() {
expect(room.canKick, false);
expect(room.canRedact, false);
expect(room.canSendDefaultMessages, true);
expect(room.canSendDefaultStates, false);
expect(room.canChangePowerLevel, false);
expect(room.canSendEvent('m.room.name'), true);
expect(room.canSendEvent('m.room.power_levels'), false);
expect(room.canSendEvent('m.room.member'), false);
expect(room.canChangeStateEvent('m.room.name'), true);
expect(room.canChangeStateEvent('m.room.power_levels'), false);
expect(room.canChangeStateEvent('m.room.member'), false);
expect(room.canSendEvent('m.room.message'), true);
final resp = await room.setPower('@test:fakeServer.notExisting', 90);
expect(resp, '42');