From 250d28b9581dc2e02ebb5db3bc06ae0be2f65966 Mon Sep 17 00:00:00 2001 From: lda Date: Sat, 8 Jun 2024 10:31:21 +0200 Subject: [PATCH] [UNTESTED/WIP] Room joins Still have to test this. --- Cytoplasm | 2 +- src/Room.c | 138 +++++++++++++++++++++++++++++++ src/Routes.c | 63 +------------- src/Routes/RouteJoinRoom.c | 165 +++++++++++++++++++++++++++++++++++++ src/include/Room.h | 12 +++ src/include/Routes.h | 3 +- 6 files changed, 322 insertions(+), 61 deletions(-) create mode 100644 src/Routes/RouteJoinRoom.c diff --git a/Cytoplasm b/Cytoplasm index 9108fef..346b912 160000 --- a/Cytoplasm +++ b/Cytoplasm @@ -1 +1 @@ -Subproject commit 9108fef7018010e6b49a111856ca0553333804f8 +Subproject commit 346b912a0633cceac10780b8a103f6c89b5ba89f diff --git a/src/Room.c b/src/Room.c index 20d262c..a4a5b97 100644 --- a/src/Room.c +++ b/src/Room.c @@ -2054,6 +2054,144 @@ RoomContainsUser(Room *room, char *user) return ret; } +bool +RoomCanJoin(Room *room, char *user) +{ + HashMap *state; + HashMap *joinRule = NULL; + char *joinRuleV; + bool ret; + if (!room || !user) + { + return false; + } + + state = StateCurrent(room); + + /* No rooms for banned people! */ + if (RoomUserHasMembership(room, state, user, "ban")) + { + ret = false; + goto end; + } + + /* Check join_rules */ + joinRule = RoomEventFetch( + room, + StateGet(state, "m.room.join_rules", "") + ); + joinRuleV = JsonValueAsString(HashMapGet(joinRule, "join_rule")); + + if (StrEquals(joinRuleV, "public")) + { + /* Anyone can join the room without any prior action. */ + ret = true; + goto end; + } + if (StrEquals(joinRuleV, "invite")) + { + /* A user must first receive an invite from someone already in the + * room in order to join. */ + ret = RoomUserHasMembership(room, state, user, "invite"); + goto end; + } + + if (StrEquals(joinRuleV, "knock")) + { + /* TODO: Knocking and restricted rooms. */ + ret = false; + goto end; + } + if (StrEquals(joinRuleV, "restricted")) + { + /* TODO: Knocking and restricted rooms. */ + ret = false; + goto end; + } + if (StrEquals(joinRuleV, "knock_restricted")) + { + /* TODO: Knocking and restricted rooms. */ + ret = false; + goto end; + } + + /* All other rooms are considered private. */ + ret = false; +end: + StateFree(state); + JsonFree(joinRule); + return ret; +} + +static char * +GetServerName(Db * db) +{ + char *name; + + Config config; + + ConfigLock(db, &config); + if (!config.ok) + { + return NULL; + } + + name = StrDuplicate(config.serverName); + + ConfigUnlock(&config); + + return name; +} +bool +RoomJoin(Room *room, User *user) +{ + CommonID *userId = NULL; + char *userString = NULL; + char *server = NULL; + HashMap *content = NULL; + HashMap *event = NULL; + HashMap *pdu = NULL; + bool ret = false; + + if (!room || !user) + { + return false; + } + + server = GetServerName(room->db); + if (!server) + { + return false; + } + userId = UserIdParse(UserGetName(user), server); + userId->sigil = '@'; + userString = ParserRecomposeCommonID(*userId); + Free(server); + server = NULL; + + if (!RoomCanJoin(room, userString)) + { + ret = false; + goto end; + } + + content = HashMapCreate(); + JsonSet(content, JsonValueString("join"), 1, "membership"); + event = RoomEventCreate(userString, "m.room.member", userString, content); + pdu = RoomEventSend(room, event); + + ret = !!pdu; + /* TODO: Note down *somewhere* that the user joined. */ +end: + UserIdFree(userId); + JsonFree(event); + JsonFree(pdu); + if (userString) + { + Free(userString); + } + return ret; +} HashMap * RoomEventClientify(HashMap *pdu) { diff --git a/src/Routes.c b/src/Routes.c index a55c19c..c5d58dd 100644 --- a/src/Routes.c +++ b/src/Routes.c @@ -76,6 +76,10 @@ RouterBuild(void) R("/_matrix/client/v3/user/(.*)/filter", RouteFilter); R("/_matrix/client/v3/user/(.*)/filter/(.*)", RouteFilter); + R("/_matrix/client/v3/rooms/(.*)/send/(.*)/(.*)", RouteSendEvent); + R("/_matrix/client/v3/rooms/(.*)/event/(.*)", RouteFetchEvent); + R("/_matrix/client/v3/rooms/(.*)/join", RouteJoinRoom); + R("/_matrix/client/v3/createRoom", RouteCreateRoom); R("/_matrix/client/v3/directory/room/(.*)", RouteAliasDirectory); @@ -91,65 +95,6 @@ RouterBuild(void) R("/_telodendria/admin/v1/tokens/(.*)", RouteAdminTokens); R("/_telodendria/admin/v1/tokens", RouteAdminTokens); - R("/_matrix/client/r0/capabilities", RouteCapabilities); - R("/_matrix/client/r0/login", RouteLogin); - R("/_matrix/client/r0/logout", RouteLogout); - R("/_matrix/client/r0/logout/(all)", RouteLogout); - R("/_matrix/client/r0/register", RouteRegister); - R("/_matrix/client/r0/register/(available)", RouteRegister); - R("/_matrix/client/r0/refresh", RouteRefresh); - - R("/_matrix/client/r0/account/whoami", RouteWhoami); - R("/_matrix/client/r0/account/password", RouteChangePwd); - R("/_matrix/client/r0/account/deactivate", RouteDeactivate); - - R("/_matrix/client/v1/register/m.login.registration_token/validity", RouteTokenValid); - - R("/_matrix/client/r0/account/password/(email|msisdn)/requestToken", RouteRequestToken); - R("/_matrix/client/r0/register/(email|msisdn)/requestToken", RouteRequestToken); - - R("/_matrix/client/r0/profile/(.*)", RouteUserProfile); - R("/_matrix/client/r0/profile/(.*)/(avatar_url|displayname)", RouteUserProfile); - - R("/_matrix/client/r0/user/(.*)/filter", RouteFilter); - R("/_matrix/client/r0/user/(.*)/filter/(.*)", RouteFilter); - - R("/_matrix/client/r0/createRoom", RouteCreateRoom); - - R("/_matrix/client/r0/directory/room/(.*)", RouteAliasDirectory); - R("/_matrix/client/r0/rooms/(.*)/aliases", RouteRoomAliases); - - - R("/_matrix/client/v3/capabilities", RouteCapabilities); - R("/_matrix/client/v3/login", RouteLogin); - R("/_matrix/client/v3/logout", RouteLogout); - R("/_matrix/client/v3/logout/(all)", RouteLogout); - R("/_matrix/client/v3/register", RouteRegister); - R("/_matrix/client/v3/register/(available)", RouteRegister); - R("/_matrix/client/v3/refresh", RouteRefresh); - - R("/_matrix/client/v3/account/whoami", RouteWhoami); - R("/_matrix/client/v3/account/password", RouteChangePwd); - R("/_matrix/client/v3/account/deactivate", RouteDeactivate); - - R("/_matrix/client/v1/register/m.login.registration_token/validity", RouteTokenValid); - - R("/_matrix/client/v3/account/password/(email|msisdn)/requestToken", RouteRequestToken); - R("/_matrix/client/v3/register/(email|msisdn)/requestToken", RouteRequestToken); - - R("/_matrix/client/v3/profile/(.*)", RouteUserProfile); - R("/_matrix/client/v3/profile/(.*)/(avatar_url|displayname)", RouteUserProfile); - - R("/_matrix/client/v3/user/(.*)/filter", RouteFilter); - R("/_matrix/client/v3/user/(.*)/filter/(.*)", RouteFilter); - - R("/_matrix/client/v3/rooms/(.*)/send/(.*)/(.*)", RouteSendEvent); - R("/_matrix/client/v3/rooms/(.*)/event/(.*)", RouteFetchEvent); - R("/_matrix/client/v3/createRoom", RouteCreateRoom); - - R("/_matrix/client/v3/directory/room/(.*)", RouteAliasDirectory); - R("/_matrix/client/v3/rooms/(.*)/aliases", RouteRoomAliases); - #undef R return router; diff --git a/src/Routes/RouteJoinRoom.c b/src/Routes/RouteJoinRoom.c new file mode 100644 index 0000000..a0b324f --- /dev/null +++ b/src/Routes/RouteJoinRoom.c @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net> with + * other valuable contributors. See CONTRIBUTORS.txt for the full list. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#include + + +#include +#include +#include +#include + +#include +#include +#include + +#include + +static char * +GetServerName(Db * db) +{ + char *name; + + Config config; + + ConfigLock(db, &config); + if (!config.ok) + { + return NULL; + } + + name = StrDuplicate(config.serverName); + + ConfigUnlock(&config); + + return name; +} + +ROUTE_IMPL(RouteJoinRoom, path, argp) +{ + RouteArgs *args = argp; + Db *db = args->matrixArgs->db; + + HashMap *request = NULL; + HashMap *response = NULL; + + User *user = NULL; + char *token = NULL; + CommonID *id = NULL; + + char *roomId = ArrayGet(path, 0); + char *sender = NULL, *serverName = NULL; + + Room *room = NULL; + + char *err; + + if (!roomId) + { + /* Should be impossible */ + HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); + return MatrixErrorCreate(M_UNKNOWN, NULL); + } + if (HttpRequestMethodGet(args->context) != HTTP_POST) + { + err = "Unknown request method."; + HttpResponseStatus(args->context, HTTP_BAD_REQUEST); + response = MatrixErrorCreate(M_UNRECOGNIZED, err); + goto finish; + } + + request = JsonDecode(HttpServerStream(args->context)); + if (!request) + { + HttpResponseStatus(args->context, HTTP_BAD_REQUEST); + response = MatrixErrorCreate(M_NOT_JSON, NULL); + goto finish; + } + + serverName = GetServerName(db); + if (!serverName) + { + HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); + response = MatrixErrorCreate(M_UNKNOWN, NULL); + goto finish; + } + + response = MatrixGetAccessToken(args->context, &token); + if (response) + { + goto finish; + } + + user = UserAuthenticate(db, token); + if (!user) + { + HttpResponseStatus(args->context, HTTP_UNAUTHORIZED); + response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL); + goto finish; + } + id = UserIdParse(UserGetName(user), serverName); + id->sigil = '@'; + sender = ParserRecomposeCommonID(*id); + + room = RoomLock(db, roomId); + if (RoomContainsUser(room, sender)) + { + err = "User is already in the room."; + HttpResponseStatus(args->context, HTTP_UNAUTHORIZED); + response = MatrixErrorCreate(M_FORBIDDEN, err); + goto finish; + } + if (!RoomCanJoin(room, sender)) + { + err = "User cannot be in the room."; + HttpResponseStatus(args->context, HTTP_UNAUTHORIZED); + response = MatrixErrorCreate(M_FORBIDDEN, err); + goto finish; + } + /* TODO: Custom reason parameter. */ + if (!RoomJoin(room, user)) + { + err = "User could not be the room due to unknown reasons."; + HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); + response = MatrixErrorCreate(M_UNKNOWN, err); + goto finish; + } + + response = HashMapCreate(); + JsonSet(response, JsonValueString(roomId), 1, "room_id"); + +finish: + UserIdFree(id); + if (sender) + { + Free(sender); + } + if (serverName) + { + Free(serverName); + } + RoomUnlock(room); + UserUnlock(user); + return response; +} diff --git a/src/include/Room.h b/src/include/Room.h index 95e8f81..ec66fc4 100644 --- a/src/include/Room.h +++ b/src/include/Room.h @@ -217,6 +217,18 @@ extern void RoomFreeReverse(Array *); */ extern bool RoomContainsUser(Room *, char *); +/** + * Checks whenever an user can join a specific room, + * given it's permissions. + */ +extern bool RoomCanJoin(Room *, char *); + +/** + * Makes a local user join a room, and returns true if + * the room was joined. + */ +extern bool RoomJoin(Room *, User *); + /** * Adds or overwrites a room alias. */ diff --git a/src/include/Routes.h b/src/include/Routes.h index e0c328e..0c90b69 100644 --- a/src/include/Routes.h +++ b/src/include/Routes.h @@ -100,8 +100,9 @@ ROUTE(RouteConfig); ROUTE(RoutePrivileges); ROUTE(RouteCreateRoom); -ROUTE(RouteSendEvent); ROUTE(RouteFetchEvent); +ROUTE(RouteSendEvent); +ROUTE(RouteJoinRoom); ROUTE(RouteAliasDirectory); ROUTE(RouteRoomAliases);