Merge branch 'krille/fix-powerlevels' into 'main'

fix: PowerLevel calculation regarding to spec

See merge request famedly/company/frontend/famedlysdk!1172
This commit is contained in:
td 2022-11-21 09:39:50 +00:00
commit d517581fa5
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. /// 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) { int getPowerLevelByUserId(String userId) {
var powerLevel = 0;
final powerLevelMap = getState(EventTypes.RoomPowerLevels)?.content; final powerLevelMap = getState(EventTypes.RoomPowerLevels)?.content;
if (powerLevelMap == null) return powerLevel; if (powerLevelMap == null) {
powerLevel = getDefaultPowerLevel(powerLevelMap); return getState(EventTypes.RoomCreate)?.senderId == userId ? 100 : 0;
if (powerLevelMap
.tryGet<Map<String, dynamic>>('users')
?.tryGet<int>(userId) !=
null) {
powerLevel = powerLevelMap['users'][userId];
} }
return powerLevel; return powerLevelMap
.tryGetMap<String, dynamic>('users')
?.tryGet<int>(userId) ??
powerLevelMap.tryGet<int>('users_default') ??
0;
} }
/// Returns the user's own power level. /// Returns the user's own power level.
int get ownPowerLevel => getPowerLevelByUserId(client.userID!); int get ownPowerLevel => getPowerLevelByUserId(client.userID!);
/// Returns the power levels from all users for this room or null if not given. /// 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 { Map<String, int>? get powerLevels {
final powerLevelState = final powerLevelState =
getState(EventTypes.RoomPowerLevels)?.content['users']; 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. /// 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` /// returns if user can change a particular state event by comparing `ownPowerLevel`
/// with possible overrides in `events`, if not present compares `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 /// 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) { int powerForChangingStateEvent(String action) {
final powerLevelMap = getState(EventTypes.RoomPowerLevels)?.content; final powerLevelMap = getState(EventTypes.RoomPowerLevels)?.content;
return powerLevelMap! if (powerLevelMap == null) return 0;
return powerLevelMap
.tryGetMap<String, dynamic>('events') .tryGetMap<String, dynamic>('events')
?.tryGet<int>(action) ?? ?.tryGet<int>(action) ??
powerLevelMap.tryGet<int>('state_default') ?? 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. /// The default level required to send message events. Can be overridden by the events key.
bool get canSendDefaultMessages => bool get canSendDefaultMessages =>
_hasPermissionFor('events_default') && (getState(EventTypes.RoomPowerLevels)
?.content
.tryGet<int>('events_default') ??
0) <=
ownPowerLevel &&
(!encrypted || client.encryptionEnabled); (!encrypted || client.encryptionEnabled);
/// The level required to invite a user. /// 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. /// 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. /// 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. /// 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;
bool get canChangePowerLevel => canSendEvent(EventTypes.RoomPowerLevels); if (powerLevelsMap == null) return 0 <= ownPowerLevel;
return (getState(EventTypes.RoomPowerLevels)
bool canSendEvent(String eventType) {
final pl = getState(EventTypes.RoomPowerLevels)
?.content ?.content
.tryGetMap<String, dynamic>('events') .tryGet<int>('state_default') ??
?.tryGet<int>(eventType); 50) <=
if (pl == null) { ownPowerLevel;
return eventType == EventTypes.Message
? canSendDefaultMessages
: canSendDefaultStates;
} }
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 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; return ownPowerLevel >= pl;
} }
/// The power level requirements for specific notification types.
bool canSendNotification(String userid, {String notificationType = 'room'}) { bool canSendNotification(String userid, {String notificationType = 'room'}) {
final userLevel = getPowerLevelByUserId(userid); final userLevel = getPowerLevelByUserId(userid);
final notificationLevel = getState(EventTypes.RoomPowerLevels) final notificationLevel = getState(EventTypes.RoomPowerLevels)
@ -1922,7 +1947,7 @@ class Room {
} }
/// Whether the user has the permission to change the join rules. /// 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 /// 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". /// 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. /// 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. /// This event controls whether a user can see the events that happened in a room from before they joined.
HistoryVisibility? get historyVisibility { HistoryVisibility? get historyVisibility {
@ -1974,7 +1999,7 @@ class Room {
/// Whether the user has the permission to change the history visibility. /// Whether the user has the permission to change the history visibility.
bool get canChangeHistoryVisibility => bool get canChangeHistoryVisibility =>
canSendEvent(EventTypes.HistoryVisibility); canChangeStateEvent(EventTypes.HistoryVisibility);
/// Returns the encryption algorithm. Currently only `m.megolm.v1.aes-sha2` is supported. /// Returns the encryption algorithm. Currently only `m.megolm.v1.aes-sha2` is supported.
/// Returns null if there is no encryption algorithm. /// Returns null if there is no encryption algorithm.

View File

@ -331,6 +331,15 @@ void main() {
expect(event.stateKeyUser?.id, '@alice:example.com'); expect(event.stateKeyUser?.id, '@alice:example.com');
}); });
test('canRedact', () async { 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); expect(event.canRedact, true);
}); });
test('getLocalizedBody, isEventKnown', () async { test('getLocalizedBody, isEventKnown', () async {

View File

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