[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

This commit is contained in:
lda 2024-05-15 08:41:59 +02:00
parent 9bc36f324a
commit 50759a3298
3 changed files with 305 additions and 4 deletions

View file

@ -162,12 +162,172 @@ RoomVersionGet(Room * room)
HashMap * HashMap *
RoomStateGet(Room * room) RoomStateGet(Room * room)
{ {
HashMap *database_state;
if (!room) 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; 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 static bool
@ -254,6 +414,8 @@ ValidAuthEventV1(PduV1 *auth_pdu, PduV1 *pdu)
{ {
char *membership = char *membership =
JsonValueAsString(JsonGet(pdu->content, 1, "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)) if (IsState(auth_pdu, "m.room.member", pdu->sender))
{ {
/* TODO: Check if it's the latest in terms of [pdu] */ /* 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] */ /* TODO: Check if it's the latest in terms of [pdu] */
return true; return true;
} }
/* TODO: The rest. if (StrEquals(membership, "invite") && third_pid)
* https://spec.matrix.org/v1.7/server-server-api/#auth-events-selection */ {
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; return false;
} }
@ -286,6 +460,7 @@ ConsiderAuthEventsV1(Room * room, PduV1 pdu)
{ {
char *ignored; char *ignored;
size_t i; size_t i;
bool room_create = false;
HashMap *state_keytype; HashMap *state_keytype;
state_keytype = HashMapCreate(); state_keytype = HashMapCreate();
for (i = 0; i < ArraySize(pdu.auth_events); i++) 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, /* Step 2.4: If there is no m.room.create event among the entries,
* reject. */ * reject. */
if (!room_create && IsState((&auth_pdu), "m.room.create", ""))
{
room_create = true; /* Here, we check for the opposite. */
}
HashMapFree(event); HashMapFree(event);
PduV1Free(&auth_pdu); PduV1Free(&auth_pdu);
} }
HashMapFree(state_keytype); HashMapFree(state_keytype);
return true; return room_create; /* Step 2.4 is actually done here. */
} }
static bool static bool
VerifyServers(char *id1, char *id2) VerifyServers(char *id1, char *id2)
@ -470,12 +649,22 @@ AuthoriseMemberV1(Room * room, PduV1 pdu)
return true; return true;
#undef JumpIfMembership #undef JumpIfMembership
} }
static bool
AuthorisePowerLevelsV1(Room * room, PduV1 pdu)
{
/* TODO: Implement this. */
(void) room;
(void) pdu;
return true;
}
bool bool
AuthoriseEventV1(Room * room, PduV1 pdu) AuthoriseEventV1(Room * room, PduV1 pdu)
{ {
HashMap *state, *create_event; HashMap *state, *create_event;
char *create_event_id; char *create_event_id;
JsonValue *federate; 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 */ /* Step 1: If m.room.create */
if (StrEquals(pdu.type, "m.room.create")) if (StrEquals(pdu.type, "m.room.create"))
{ {
@ -529,6 +718,69 @@ AuthoriseEventV1(Room * room, PduV1 pdu)
{ {
return AuthoriseMemberV1(room, 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; return true;
} }
static char * static char *

View file

@ -120,3 +120,37 @@ StateFree(HashMap *state)
} }
HashMapFree(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;
}

View file

@ -59,4 +59,19 @@ extern HashMap * StateResolve(Room *, HashMap *);
*/ */
extern void StateFree(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 */ #endif /* TELODENDRIA_STATE_H */