From ac9372a30a32c4cf67514ab2520e47762bc185b9 Mon Sep 17 00:00:00 2001 From: lda Date: Sun, 18 Aug 2024 19:31:14 -0400 Subject: [PATCH 1/2] User Directory Patch #70 (#28) Reviewed-on: https://git.telodendria.io/Telodendria/Telodendria/pulls/28 Co-authored-by: lda Co-committed-by: lda --- Schema/UserDirectoryRequest.json | 13 ++ src/Routes.c | 1 + src/Routes/RouteUserDirectory.c | 198 +++++++++++++++++++++++++++++++ src/include/Routes.h | 1 + 4 files changed, 213 insertions(+) create mode 100644 Schema/UserDirectoryRequest.json create mode 100644 src/Routes/RouteUserDirectory.c diff --git a/Schema/UserDirectoryRequest.json b/Schema/UserDirectoryRequest.json new file mode 100644 index 0000000..7e27c03 --- /dev/null +++ b/Schema/UserDirectoryRequest.json @@ -0,0 +1,13 @@ +{ + "header": "Schema\/UserDirectoryRequest.h", + "types": { + "UserDirectoryRequest": { + "fields": { + "search_term": { "type": "string" }, + "limit": { "type": "integer" } + }, + "type": "struct" + } + }, + "guard": "TELODENDRIA_SCHEMA_USERDIRECTORYREQUEST_H" +} diff --git a/src/Routes.c b/src/Routes.c index 2562e78..b52eb09 100644 --- a/src/Routes.c +++ b/src/Routes.c @@ -72,6 +72,7 @@ RouterBuild(void) R("/_matrix/client/v3/profile/(.*)", RouteUserProfile); R("/_matrix/client/v3/profile/(.*)/(avatar_url|displayname)", RouteUserProfile); + R("/_matrix/client/v3/user_directory/search", RouteUserDirectory); R("/_matrix/client/v3/user/(.*)/filter", RouteFilter); R("/_matrix/client/v3/user/(.*)/filter/(.*)", RouteFilter); diff --git a/src/Routes/RouteUserDirectory.c b/src/Routes/RouteUserDirectory.c new file mode 100644 index 0000000..2c091f3 --- /dev/null +++ b/src/Routes/RouteUserDirectory.c @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * 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 dirRequest.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 + +ROUTE_IMPL(RouteUserDirectory, path, argp) +{ + RouteArgs *args = argp; + HashMap *response = NULL; + HashMap *request = NULL; + + Array *users = NULL; + Array *results = NULL; + + Db *db = args->matrixArgs->db; + + Config *config = NULL; + + User *user = NULL; + + char *token = NULL; + char *requesterName = NULL; + char *msg = NULL; + + UserDirectoryRequest dirRequest; + + size_t i, included; + + (void) path; + + dirRequest.search_term = NULL; + dirRequest.limit = Int64Create(0, 10); + + + if (HttpRequestMethodGet(args->context) != HTTP_POST) + { + msg = "Request supports only POST."; + HttpResponseStatus(args->context, HTTP_BAD_REQUEST); + response = MatrixErrorCreate(M_UNRECOGNIZED, msg); + goto finish; + } + + request = JsonDecode(HttpServerStream(args->context)); + if (!request) + { + HttpResponseStatus(args->context, HTTP_BAD_REQUEST); + response = MatrixErrorCreate(M_NOT_JSON, NULL); + goto finish; + } + if (!UserDirectoryRequestFromJson(request, &dirRequest, &msg)) + { + HttpResponseStatus(args->context, HTTP_BAD_REQUEST); + response = MatrixErrorCreate(M_BAD_JSON, msg); + goto finish; + } + if (!dirRequest.search_term) + { + msg = "Field 'search_term' not set."; + HttpResponseStatus(args->context, HTTP_BAD_REQUEST); + response = MatrixErrorCreate(M_BAD_JSON, msg); + goto finish; + + } + + response = MatrixGetAccessToken(args->context, &token); + if (response) + { + return response; + } + + /* TODO: Actually use information related to the user. */ + user = UserAuthenticate(db, token); + if (!user) + { + HttpResponseStatus(args->context, HTTP_BAD_REQUEST); + response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL); + goto finish; + } + requesterName = UserGetName(user); + + response = HashMapCreate(); + results = ArrayCreate(); + + /* TODO: Check for users matching search term and users outside our + * local server. */ + users = DbList(db, 1, "users"); + + config = ConfigLock(db); + if (!config) + { + Log(LOG_ERR, "Directory endpoint failed to lock configuration."); + HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); + response = MatrixErrorCreate(M_UNKNOWN, NULL); + + goto finish; + } + +#define IncludedLtLimit (Int64Lt(Int64Create(0, included), dirRequest.limit)) + for (i = 0, included = 0; i < ArraySize(users) && IncludedLtLimit; i++) +#undef IncludedLtLimit + { + HashMap *obj; + User *currentUser; + char *name = ArrayGet(users, i); + char *displayName; + char *lowerDisplayName; + char *avatarUrl; + + if (!StrEquals(name, requesterName)) + { + currentUser = UserLock(db, name); + } + else + { + currentUser = user; + } + + displayName = UserGetProfile(currentUser, "displayname"); + lowerDisplayName = StrLower(displayName); + avatarUrl = UserGetProfile(currentUser, "avatar_url"); + + /* Check for the user ID and display name. */ + if (strstr(name, dirRequest.search_term) || + (lowerDisplayName && + strstr(lowerDisplayName, dirRequest.search_term))) + { + included++; + + obj = HashMapCreate(); + if (displayName) + { + JsonSet(obj, JsonValueString(displayName), 1, "display_name"); + } + if (avatarUrl) + { + JsonSet(obj, JsonValueString(displayName), 1, "avatar_url"); + } + if (name) + { + char *uID = StrConcat(4, "@", name, ":", config->serverName); + JsonSet(obj, JsonValueString(uID), 1, "user_id"); + Free(uID); + } + ArrayAdd(results, JsonValueObject(obj)); + } + if (lowerDisplayName) + { + Free(lowerDisplayName); + } + if (!StrEquals(name, requesterName)) + { + UserUnlock(currentUser); + } + } + JsonSet(response, JsonValueArray(results), 1, "results"); + JsonSet(response, JsonValueBoolean( + Int64Eq(Int64Create(0, included), dirRequest.limit)), 1, + "limited"); + +finish: + UserUnlock(user); + JsonFree(request); + DbListFree(users); + ConfigUnlock(config); + UserDirectoryRequestFree(&dirRequest); + return response; +} diff --git a/src/include/Routes.h b/src/include/Routes.h index 76de377..72516a0 100644 --- a/src/include/Routes.h +++ b/src/include/Routes.h @@ -86,6 +86,7 @@ ROUTE(RouteChangePwd); ROUTE(RouteDeactivate); ROUTE(RouteTokenValid); ROUTE(RouteUserProfile); +ROUTE(RouteUserDirectory); ROUTE(RouteRequestToken); ROUTE(RouteUiaFallback); From e263eca5dca108c571b5247211c4d056697cdafe Mon Sep 17 00:00:00 2001 From: lda Date: Wed, 21 Aug 2024 14:32:42 -0400 Subject: [PATCH 2/2] Fix build issues with #28 (#55) Just here to fix old code issues with #28. (we really need CI back, don't we?) --- Please review the developer certificate of origin: 1. The contribution was created in whole or in part by me, and I have the right to submit it under the open source licenses of the Telodendria project; or 1. The contribution is based upon a previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the Telodendria project license; or 1. The contribution was provided directly to me by some other person who certified (1), (2), or (3), and I have not modified it. 1. I understand and agree that this project and the contribution are made public and that a record of the contribution—including all personal information I submit with it—is maintained indefinitely and may be redistributed consistent with this project or the open source licenses involved. - [x] I have read the Telodendria Project development certificate of origin, and I certify that I have permission to submit this patch under the conditions specified in it. Co-authored-by: LDA Reviewed-on: https://git.telodendria.io/Telodendria/Telodendria/pulls/55 Co-authored-by: lda Co-committed-by: lda --- src/Routes/RouteUserDirectory.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/Routes/RouteUserDirectory.c b/src/Routes/RouteUserDirectory.c index 2c091f3..c38441a 100644 --- a/src/Routes/RouteUserDirectory.c +++ b/src/Routes/RouteUserDirectory.c @@ -45,7 +45,7 @@ ROUTE_IMPL(RouteUserDirectory, path, argp) Db *db = args->matrixArgs->db; - Config *config = NULL; + Config config = { .ok = 0 }; User *user = NULL; @@ -60,7 +60,7 @@ ROUTE_IMPL(RouteUserDirectory, path, argp) (void) path; dirRequest.search_term = NULL; - dirRequest.limit = Int64Create(0, 10); + dirRequest.limit = 10; if (HttpRequestMethodGet(args->context) != HTTP_POST) @@ -116,17 +116,17 @@ ROUTE_IMPL(RouteUserDirectory, path, argp) * local server. */ users = DbList(db, 1, "users"); - config = ConfigLock(db); - if (!config) + ConfigLock(db, &config); + if (!config.ok) { Log(LOG_ERR, "Directory endpoint failed to lock configuration."); HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); - response = MatrixErrorCreate(M_UNKNOWN, NULL); + response = MatrixErrorCreate(M_UNKNOWN, config.err); goto finish; } -#define IncludedLtLimit (Int64Lt(Int64Create(0, included), dirRequest.limit)) +#define IncludedLtLimit ((int64_t) included < dirRequest.limit) for (i = 0, included = 0; i < ArraySize(users) && IncludedLtLimit; i++) #undef IncludedLtLimit { @@ -168,7 +168,7 @@ ROUTE_IMPL(RouteUserDirectory, path, argp) } if (name) { - char *uID = StrConcat(4, "@", name, ":", config->serverName); + char *uID = StrConcat(4, "@", name, ":", config.serverName); JsonSet(obj, JsonValueString(uID), 1, "user_id"); Free(uID); } @@ -184,15 +184,16 @@ ROUTE_IMPL(RouteUserDirectory, path, argp) } } JsonSet(response, JsonValueArray(results), 1, "results"); - JsonSet(response, JsonValueBoolean( - Int64Eq(Int64Create(0, included), dirRequest.limit)), 1, - "limited"); + JsonSet(response, + JsonValueBoolean((int64_t) included == dirRequest.limit), + 1, "limited" + ); finish: UserUnlock(user); JsonFree(request); DbListFree(users); - ConfigUnlock(config); + ConfigUnlock(&config); UserDirectoryRequestFree(&dirRequest); return response; }