diff --git a/src/Room.c b/src/Room.c index 331a17a..a3de804 100644 --- a/src/Room.c +++ b/src/Room.c @@ -26,6 +26,7 @@ #include #include /*#include "Cytoplasm/Stream.h"*/ +#include "Cytoplasm/HashMap.h" #include "Parser.h" #include "User.h" #include @@ -846,7 +847,7 @@ AuthoriseMemberV1(Room * room, PduV1 pdu) /* Step 5.2: If membership is join. */ JumpIfMembership("join", AuthorizeJoinMembershipV1); - /* Step 5.3: If membership is invite. TODO */ + /* Step 5.3: If membership is invite. */ JumpIfMembership("invite", AuthorizeInviteMembershipV1); /* Step 5.4: If membership is leave. */ @@ -862,9 +863,177 @@ AuthoriseMemberV1(Room * room, PduV1 pdu) static bool AuthorisePowerLevelsV1(Room * room, PduV1 pdu) { - /* TODO: Implement this. */ - (void) room; - (void) pdu; + /* Step 10.1: If the users property in content is not an object with + * keys that are valid user IDs with values that are integers + * (or a string that is an integer), reject. */ + JsonValue *users = JsonGet(pdu.content, 1, "users"); + HashMap *users_o, *state, *prev_plevent; + + char *user_id, *prev_pl_id, *ev_type; + JsonValue *power_level, *ev_obj; + + bool flag = true; + int64_t userpl = RoomUserPL(room, pdu.event_id, pdu.sender); + HashMap *event_obj; + if (JsonValueType(users) != JSON_OBJECT) + { + return false; + } + users_o = JsonValueAsObject(users); + while (HashMapIterate(users_o, &user_id, (void **) &power_level)) + { + CommonID as_cid; + if (!flag) + { + continue; + } + if (!ParseCommonID(user_id, &as_cid)) + { + flag = false; + } + if (as_cid.sigil != '@') + { + CommonIDFree(as_cid); + flag = false; + } + + /* Verify powerlevels. + * We'll use INT64_MAX as a sentinel value, as this isn't + * a valid powervalue for the specification. */ + if (ParsePL(power_level, INT64_MAX) == INT64_MAX) + { + flag = false; + } + } + + /* HashMapIterate does not support breaking, so we just set a + * flag to be used. */ + if (!flag) + { + return false; + } + + /* Step 10.2: If there is no previous m.room.power_levels event + * in the room, allow. */ + state = RoomStateGetID(room, pdu.event_id); + if (!(prev_pl_id = StateGet(state, "m.room.power_levels", ""))) + { + StateFree(state); + return true; + } + + /* Step 10.3: For the properties users_default, events_default, + * state_default, ban, redact, kick, invite, check if they were + * added, changed or removed. For each found alteration: */ + prev_plevent = RoomEventFetch(room, prev_pl_id); +#define CheckChange(prop) do \ + { \ + JsonValue *old = \ + JsonGet(prev_plevent, 2, "content", prop);\ + JsonValue *new = \ + JsonGet(pdu.content, 1, prop); \ + int64_t oldv, newv; \ + if ((old && !new) || (!old && new) || \ + ((oldv = JsonValueAsInteger(old)) != \ + (newv = JsonValueAsInteger(new)))) \ + { \ + if (old && (oldv > userpl)) \ + { \ + return false; \ + } \ + if (new && (newv > userpl)) \ + { \ + return false; \ + } \ + } \ + } \ + while(0) + CheckChange("users_default"); + CheckChange("events_default"); + CheckChange("state_default"); + CheckChange("ban"); + CheckChange("redact"); + CheckChange("kick"); + CheckChange("invite"); +#undef CheckChange +#define CheckPLOld(prop) \ + event_obj = \ + JsonValueAsObject(JsonGet(prev_plevent, 2, "content", prop)); \ + flag = true; \ + while (HashMapIterate(event_obj, &ev_type, (void **) &ev_obj)) \ + { \ + JsonValue *new; \ + int64_t new_pl, old_pl; \ + \ + if (!flag) \ + { \ + continue; \ + } \ + \ + new = JsonGet(pdu.content, 2, prop, ev_type); \ + old_pl = ParsePL(new, INT64_MAX); \ + if (((new_pl = ParsePL(new, INT64_MAX)) == INT64_MAX || \ + new_pl != old_pl) && old_pl > userpl) \ + { \ + flag = false; \ + } \ + } \ + if (!flag) \ + { \ + JsonFree(prev_plevent); \ + StateFree(state); \ + return false; \ + } \ + flag = true +#define CheckPLNew(prop) \ + event_obj = \ + JsonValueAsObject(JsonGet(pdu.content, 1, prop)); \ + flag = true; \ + while (HashMapIterate(event_obj, &ev_type, (void **) &ev_obj)) \ + { \ + JsonValue *old; \ + int64_t new_pl, old_pl; \ + \ + if (!flag) \ + { \ + continue; \ + } \ + \ + old = JsonGet(prev_plevent, 3, "content", prop, ev_type); \ + new_pl = ParsePL(ev_obj, INT64_MAX); \ + if (((old_pl = ParsePL(old, INT64_MAX)) == INT64_MAX || \ + new_pl != old_pl) && new_pl > userpl) \ + { \ + flag = false; \ + } \ + } \ + if (!flag) \ + { \ + JsonFree(prev_plevent); \ + StateFree(state); \ + return false; \ + } \ + flag = true + /* Step 10.4: For each entry being changed in, or removed from, the + * events property: + * - If the current value is greater than the sender's current + * power level, reject. */ + CheckPLOld("events"); + + /* Step 10.5: For each entry being added to, or changed in, the events + * property: + * - If the new value is greater than the sender's current power level, + * reject. */ + CheckPLNew("events"); + + /* Steps 10.6 and 10.7 are effectively the same. */ + CheckPLOld("users"); + CheckPLNew("users"); +#undef CheckPLOld +#undef CheckPLNew + /* Step 10.8: Otherwise, allow. */ + JsonFree(prev_plevent); + StateFree(state); return true; } bool