forked from Telodendria/Telodendria
[UNTESTED/ADD] Finish barebones of auth rules
Some checks are pending
Compile Telodendria / Compile Telodendria (x86, alpine-v3.19) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86, debian-v12.4) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86, freebsd-v14.0) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86, netbsd-v9.3) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86_64, alpine-v3.19) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86_64, debian-v12.4) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86_64, freebsd-v14.0) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86_64, netbsd-v9.3) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86_64, openbsd-v7.4) (push) Waiting to run
Some checks are pending
Compile Telodendria / Compile Telodendria (x86, alpine-v3.19) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86, debian-v12.4) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86, freebsd-v14.0) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86, netbsd-v9.3) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86_64, alpine-v3.19) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86_64, debian-v12.4) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86_64, freebsd-v14.0) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86_64, netbsd-v9.3) (push) Waiting to run
Compile Telodendria / Compile Telodendria (x86_64, openbsd-v7.4) (push) Waiting to run
This commit is contained in:
parent
9bc36f324a
commit
50759a3298
3 changed files with 305 additions and 4 deletions
260
src/Room.c
260
src/Room.c
|
@ -162,12 +162,172 @@ RoomVersionGet(Room * room)
|
|||
HashMap *
|
||||
RoomStateGet(Room * room)
|
||||
{
|
||||
HashMap *database_state;
|
||||
if (!room)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
/* TODO: Consider caching the deserialised result, as doing that on a
|
||||
* large state would probably eat up a lot of time! */
|
||||
database_state = DbJson(room->ref);
|
||||
return StateDeserialise(database_state);
|
||||
}
|
||||
static HashMap *
|
||||
RoomStateGetID(Room * room, char *event_id)
|
||||
{
|
||||
HashMap *event, *state;
|
||||
if (!room || !event_id)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
event = RoomEventFetch(room, event_id);
|
||||
if (!event)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
state = StateResolve(room, event);
|
||||
JsonFree(event);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
#define PrepareState(room, e, type, key, n) \
|
||||
do \
|
||||
{ \
|
||||
state_point = RoomStateGetID(room, e); \
|
||||
id_##n = StateGet(state_point, type, key); \
|
||||
if (id_##n) \
|
||||
{ \
|
||||
goto finish; \
|
||||
} \
|
||||
n = RoomEventFetch(room, id_##n); \
|
||||
if (!n) \
|
||||
{ \
|
||||
goto finish; \
|
||||
} \
|
||||
} \
|
||||
while (0)
|
||||
#define FinishState(name) \
|
||||
if (state_point) \
|
||||
{ \
|
||||
StateFree(state_point); \
|
||||
} \
|
||||
if (name) \
|
||||
{ \
|
||||
JsonFree(name); \
|
||||
} \
|
||||
return ret
|
||||
/* Verifies if user has a specific membership before [e_id] in the room. */
|
||||
static bool
|
||||
RoomUserHasMembership(Room * room, char *e_id, char *user, char *mbr)
|
||||
{
|
||||
HashMap *state_point;
|
||||
HashMap *membership = NULL;
|
||||
char *id_membership = NULL;
|
||||
char *membership_value;
|
||||
bool ret = false;
|
||||
if (!room || !e_id || !user || !mbr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PrepareState(room, e_id, "m.room.member", user, membership);
|
||||
|
||||
membership_value =
|
||||
JsonValueAsString(JsonGet(membership, 2, "content", "membership"));
|
||||
ret = StrEquals(membership_value, mbr);
|
||||
|
||||
finish:
|
||||
FinishState(membership);
|
||||
}
|
||||
static int64_t
|
||||
ParsePL(JsonValue *v, int64_t def)
|
||||
{
|
||||
if (!v)
|
||||
{
|
||||
return def;
|
||||
}
|
||||
if (JsonValueType(v) == JSON_INTEGER)
|
||||
{
|
||||
return JsonValueAsInteger(v);
|
||||
}
|
||||
if (JsonValueType(v) == JSON_STRING)
|
||||
{
|
||||
char *string = JsonValueAsString(v), *end;
|
||||
int64_t value= strtoll(string, &end, 10);
|
||||
if (!((*string != '\0') && (*end == '\0')))
|
||||
{
|
||||
/* Invalid string: return the default. */
|
||||
return def;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
return def;
|
||||
}
|
||||
/* Computes the smallest PL needed to do something somewhere */
|
||||
static int64_t
|
||||
RoomMinPL(Room * room, char *e_id, char *type, char *act)
|
||||
{
|
||||
HashMap *state_point, *pl = NULL;
|
||||
JsonValue *val;
|
||||
char *id_pl;
|
||||
int64_t ret, def;
|
||||
if (!room || !e_id || !act)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
PrepareState(room, e_id, "m.room.power_levels", "", pl);
|
||||
|
||||
/* Every other act has a minimum PL of 0 */
|
||||
def = 0;
|
||||
if (StrEquals(act, "ban") ||
|
||||
StrEquals(act, "kick") ||
|
||||
StrEquals(act, "redact") ||
|
||||
StrEquals(act, "state_default") ||
|
||||
(StrEquals(type, "notifications") && StrEquals(act, "room")))
|
||||
{
|
||||
def = 50;
|
||||
}
|
||||
|
||||
if (!type)
|
||||
{
|
||||
val = JsonGet(pl, 1, act);
|
||||
}
|
||||
else
|
||||
{
|
||||
val = JsonGet(pl, 2, type, act);
|
||||
}
|
||||
|
||||
ret = ParsePL(val, def);
|
||||
finish:
|
||||
FinishState(pl);
|
||||
}
|
||||
/* Finds the power level of an user before [e_id] was sent. */
|
||||
static int64_t
|
||||
RoomUserPL(Room * room, char *e_id, char *user)
|
||||
{
|
||||
HashMap *state_point, *pl = NULL;
|
||||
char *id_pl;
|
||||
int64_t ret, def;
|
||||
|
||||
if (!room || !e_id || !user)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
PrepareState(room, e_id, "m.room.power_levels", "", pl);
|
||||
|
||||
def = RoomMinPL(room, e_id, NULL, "users_default");
|
||||
ret = ParsePL(JsonGet(pl, 2, "users", user), def);
|
||||
|
||||
finish:
|
||||
FinishState(pl);
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@ -254,6 +414,8 @@ ValidAuthEventV1(PduV1 *auth_pdu, PduV1 *pdu)
|
|||
{
|
||||
char *membership =
|
||||
JsonValueAsString(JsonGet(pdu->content, 1, "membership"));
|
||||
JsonValue *third_pid =
|
||||
JsonGet(pdu->content, 1, "third_party_invite");
|
||||
if (IsState(auth_pdu, "m.room.member", pdu->sender))
|
||||
{
|
||||
/* TODO: Check if it's the latest in terms of [pdu] */
|
||||
|
@ -266,8 +428,20 @@ ValidAuthEventV1(PduV1 *auth_pdu, PduV1 *pdu)
|
|||
/* TODO: Check if it's the latest in terms of [pdu] */
|
||||
return true;
|
||||
}
|
||||
/* TODO: The rest.
|
||||
* https://spec.matrix.org/v1.7/server-server-api/#auth-events-selection */
|
||||
if (StrEquals(membership, "invite") && third_pid)
|
||||
{
|
||||
HashMap *tpid = JsonValueAsObject(third_pid);
|
||||
JsonValue *token =
|
||||
JsonGet(tpid, 2, "signed", "token");
|
||||
char *token_str = JsonValueAsString(token);
|
||||
if (IsState(auth_pdu, "m.room.third_party_invite", token_str))
|
||||
{
|
||||
/* TODO: Check if it is the latest. */
|
||||
return true;
|
||||
}
|
||||
}
|
||||
/* V1 simply doesn't have the concept of restricted rooms,
|
||||
* so we can safely skip this one for this function. */
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -286,6 +460,7 @@ ConsiderAuthEventsV1(Room * room, PduV1 pdu)
|
|||
{
|
||||
char *ignored;
|
||||
size_t i;
|
||||
bool room_create = false;
|
||||
HashMap *state_keytype;
|
||||
state_keytype = HashMapCreate();
|
||||
for (i = 0; i < ArraySize(pdu.auth_events); i++)
|
||||
|
@ -339,12 +514,16 @@ ConsiderAuthEventsV1(Room * room, PduV1 pdu)
|
|||
|
||||
/* Step 2.4: If there is no m.room.create event among the entries,
|
||||
* reject. */
|
||||
if (!room_create && IsState((&auth_pdu), "m.room.create", ""))
|
||||
{
|
||||
room_create = true; /* Here, we check for the opposite. */
|
||||
}
|
||||
|
||||
HashMapFree(event);
|
||||
PduV1Free(&auth_pdu);
|
||||
}
|
||||
HashMapFree(state_keytype);
|
||||
return true;
|
||||
return room_create; /* Step 2.4 is actually done here. */
|
||||
}
|
||||
static bool
|
||||
VerifyServers(char *id1, char *id2)
|
||||
|
@ -470,12 +649,22 @@ AuthoriseMemberV1(Room * room, PduV1 pdu)
|
|||
return true;
|
||||
#undef JumpIfMembership
|
||||
}
|
||||
static bool
|
||||
AuthorisePowerLevelsV1(Room * room, PduV1 pdu)
|
||||
{
|
||||
/* TODO: Implement this. */
|
||||
(void) room;
|
||||
(void) pdu;
|
||||
return true;
|
||||
}
|
||||
bool
|
||||
AuthoriseEventV1(Room * room, PduV1 pdu)
|
||||
{
|
||||
HashMap *state, *create_event;
|
||||
char *create_event_id;
|
||||
JsonValue *federate;
|
||||
int64_t pdu_pl = RoomUserPL(room, pdu.event_id, pdu.sender);
|
||||
int64_t event_pl = RoomMinPL(room, pdu.event_id, "events", pdu.type);
|
||||
/* Step 1: If m.room.create */
|
||||
if (StrEquals(pdu.type, "m.room.create"))
|
||||
{
|
||||
|
@ -529,6 +718,69 @@ AuthoriseEventV1(Room * room, PduV1 pdu)
|
|||
{
|
||||
return AuthoriseMemberV1(room, pdu);
|
||||
}
|
||||
/* Step 6: If the sender's current membership state is not join, reject. */
|
||||
if (!RoomUserHasMembership(room, pdu.event_id, pdu.sender, "join"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
/* Step 7: If type is m.room.third_party_invite */
|
||||
if (StrEquals(pdu.type, "m.room.third_party_invite"))
|
||||
{
|
||||
/* Allow if and only if sender's current power level is greater than
|
||||
* or equal to the invite level */
|
||||
int64_t min_pl = RoomMinPL(room, pdu.event_id, NULL, "invite");
|
||||
|
||||
return pdu_pl >= min_pl;
|
||||
}
|
||||
|
||||
/* Step 8: If the event type's required power level is greater than the
|
||||
* sender's power level, reject. */
|
||||
if (event_pl > pdu_pl)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Step 9: If the event has a state_key that starts with an @ and does
|
||||
* not match the sender, reject. */
|
||||
if (pdu.state_key && *pdu.state_key == '@')
|
||||
{
|
||||
if (!StrEquals(pdu.state_key, pdu.sender))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Step 10: If type is m.room.power_levels */
|
||||
if (StrEquals(pdu.type, "m.room.power_levels"))
|
||||
{
|
||||
return AuthorisePowerLevelsV1(room, pdu);
|
||||
}
|
||||
|
||||
/* Step 11: If type is m.room.redaction */
|
||||
if (StrEquals(pdu.type, "m.room.redaction"))
|
||||
{
|
||||
int64_t min_pl = RoomMinPL(room, pdu.event_id, NULL, "redact");
|
||||
|
||||
/* Step 11.1: If the sender's power level is greater than or equal
|
||||
* to the redact level, allow. */
|
||||
if (pdu_pl >= min_pl)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Step 11.2: If the domain of the event_id of the event being
|
||||
* redacted is the same as the domain of the event_id of the
|
||||
* m.room.redaction, allow. */
|
||||
if (pdu.redacts && VerifyServers(pdu.redacts, pdu.event_id))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Step 11.3: Otherwise, reject. */
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Step 12: Otherwise, allow. */
|
||||
return true;
|
||||
}
|
||||
static char *
|
||||
|
|
34
src/State.c
34
src/State.c
|
@ -120,3 +120,37 @@ StateFree(HashMap *state)
|
|||
}
|
||||
HashMapFree(state);
|
||||
}
|
||||
HashMap *
|
||||
StateDeserialise(HashMap *json_state)
|
||||
{
|
||||
HashMap *raw_state;
|
||||
|
||||
char *state_type;
|
||||
JsonValue *state_keys;
|
||||
|
||||
if (!json_state)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
raw_state = HashMapCreate();
|
||||
|
||||
while (HashMapIterate(json_state, &state_type, (void **) &state_keys))
|
||||
{
|
||||
HashMap *state_keys_obj = JsonValueAsObject(state_keys);
|
||||
char *state_key;
|
||||
JsonValue *event_id;
|
||||
|
||||
while (HashMapIterate(state_keys_obj, &state_key, (void **) &event_id))
|
||||
{
|
||||
char *eid_string = JsonValueAsString(event_id);
|
||||
char *key_name = StrConcat(3, state_type, ",", state_key);
|
||||
|
||||
HashMapSet(raw_state, key_name, StrDuplicate(eid_string));
|
||||
|
||||
Free(key_name);
|
||||
}
|
||||
}
|
||||
|
||||
return raw_state;
|
||||
}
|
||||
|
|
|
@ -59,4 +59,19 @@ extern HashMap * StateResolve(Room *, HashMap *);
|
|||
*/
|
||||
extern void StateFree(HashMap *);
|
||||
|
||||
/**
|
||||
* Deserialises a state map from JSON to the internal format
|
||||
* used by this API.
|
||||
*
|
||||
* The returned value is independent from the JSON format,
|
||||
* and should be freed with
|
||||
* .Fn StateFree .
|
||||
*/
|
||||
extern HashMap * StateDeserialise(HashMap *);
|
||||
/**
|
||||
* Serialises a state map from the internal format to JSON
|
||||
* used for the database, for example
|
||||
*/
|
||||
extern HashMap * StateSerialise(HashMap *);
|
||||
|
||||
#endif /* TELODENDRIA_STATE_H */
|
||||
|
|
Loading…
Reference in a new issue