From ac9372a30a32c4cf67514ab2520e47762bc185b9 Mon Sep 17 00:00:00 2001 From: lda Date: Sun, 18 Aug 2024 19:31:14 -0400 Subject: [PATCH] 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);