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 *
|
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 *
|
||||||
|
|
34
src/State.c
34
src/State.c
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
Loading…
Reference in a new issue