diff --git a/lib/models/room-state.js b/lib/models/room-state.js index 6f74a1555..2ca3a745d 100644 --- a/lib/models/room-state.js +++ b/lib/models/room-state.js @@ -239,6 +239,33 @@ RoomState.prototype.getUserIdsWithDisplayName = function(displayName) { return this._displayNameToUserIds[displayName] || []; }; +/** + * Short-form for maySendEvent('m.room.message', userId) + * @param {string} userId The user ID of the user to test permission for + * @return {boolean} true if the given user ID should be permitted to send + * message events into the given room. + */ +RoomState.prototype.maySendMessage = function(userId) { + return this._maySendEventOfType('m.room.message', userId, false); +}; + +/** + * Returns true if the given user ID has permission to send a normal + * event of type `eventType` into this room. + * @param {string} type The type of event to test + * @param {string} userId The user ID of the user to test permission for + * @param {boolean} state If true, tests if the user may send a state + event of this type. Otherwise tests whether + they may send a regular event. + * @return {boolean} true if the given user ID should be permitted to send + * the given type of event into this room, + * according to the room's state. + */ +RoomState.prototype.maySendEvent = function(eventType, userId) { + return this._maySendEventOfType(eventType, userId, false); +}; + + /** * Returns true if the given MatrixClient has permission to send a state * event of type `stateEventType` into this room. @@ -265,6 +292,22 @@ RoomState.prototype.mayClientSendStateEvent = function(stateEventType, cli) { * according to the room's state. */ RoomState.prototype.maySendStateEvent = function(stateEventType, userId) { + return this._maySendEventOfType(stateEventType, userId, true); +}; + +/** + * Returns true if the given user ID has permission to send a normal or state + * event of type `eventType` into this room. + * @param {string} type The type of event to test + * @param {string} userId The user ID of the user to test permission for + * @param {boolean} state If true, tests if the user may send a state + event of this type. Otherwise tests whether + they may send a regular event. + * @return {boolean} true if the given user ID should be permitted to send + * the given type of event into this room, + * according to the room's state. + */ +RoomState.prototype._maySendEventOfType = function(eventType, userId, state) { var member = this.getMember(userId); if (!member || member.membership == 'leave') { return false; } @@ -277,6 +320,7 @@ RoomState.prototype.maySendStateEvent = function(stateEventType, userId) { var user_levels = []; var state_default = 0; + var events_default = 0; if (power_levels_event) { power_levels = power_levels_event.getContent(); events_levels = power_levels.events || {}; @@ -289,13 +333,16 @@ RoomState.prototype.maySendStateEvent = function(stateEventType, userId) { } else { state_default = 50; } + if (power_levels.events_default !== undefined) { + events_default = power_levels.events_default; + } } - var state_event_level = state_default; - if (events_levels[stateEventType] !== undefined) { - state_event_level = events_levels[stateEventType]; + var required_level = state ? state_default : events_default; + if (events_levels[eventType] !== undefined) { + required_level = events_levels[eventType]; } - return member.powerLevel >= state_event_level; + return member.powerLevel >= required_level; }; /** diff --git a/spec/unit/room-state.spec.js b/spec/unit/room-state.spec.js index 6146b966d..b9dfe6583 100644 --- a/spec/unit/room-state.spec.js +++ b/spec/unit/room-state.spec.js @@ -362,4 +362,74 @@ describe("RoomState", function() { expect(state.maySendStateEvent('m.room.other_thing', userB)).toEqual(false); }); }); + + describe("maySendEvent", function() { + it("should say non-joined members may not send events", + function() { + expect(state.maySendEvent( + 'm.room.message', "@nobody:nowhere" + )).toEqual(false); + expect(state.maySendMessage("@nobody:nowhere")).toEqual(false); + }); + + it("should say any member may send events with no power level event", + function() { + expect(state.maySendEvent('m.room.message', userA)).toEqual(true); + expect(state.maySendMessage(userA)).toEqual(true); + }); + + it("should obey events_default", + function() { + var powerLevelEvent = { + type: "m.room.power_levels", room: roomId, user: userA, event: true, + content: { + users_default: 10, + state_default: 30, + events_default: 25, + users: { + } + } + }; + powerLevelEvent.content.users[userA] = 26; + powerLevelEvent.content.users[userB] = 24; + + state.setStateEvents([utils.mkEvent(powerLevelEvent)]); + + expect(state.maySendEvent('m.room.message', userA)).toEqual(true); + expect(state.maySendEvent('m.room.message', userB)).toEqual(false); + + expect(state.maySendMessage(userA)).toEqual(true); + expect(state.maySendMessage(userB)).toEqual(false); + }); + + it("should honour explicit event power levels in the power_levels event", + function() { + var powerLevelEvent = { + type: "m.room.power_levels", room: roomId, user: userA, event: true, + content: { + events: { + "m.room.other_thing": 33 + }, + users_default: 10, + state_default: 50, + events_default: 25, + users: { + } + } + }; + powerLevelEvent.content.users[userA] = 40; + powerLevelEvent.content.users[userB] = 30; + + state.setStateEvents([utils.mkEvent(powerLevelEvent)]); + + expect(state.maySendEvent('m.room.message', userA)).toEqual(true); + expect(state.maySendEvent('m.room.message', userB)).toEqual(true); + + expect(state.maySendMessage(userA)).toEqual(true); + expect(state.maySendMessage(userB)).toEqual(true); + + expect(state.maySendEvent('m.room.other_thing', userA)).toEqual(true); + expect(state.maySendEvent('m.room.other_thing', userB)).toEqual(false); + }); + }); });