diff --git a/TODO.txt b/TODO.txt index 889aec8..92f1f0b 100644 --- a/TODO.txt +++ b/TODO.txt @@ -87,7 +87,7 @@ Milestone: v0.3.0 [x] Make sure UserLogin() fails if user is deactivated. [x] Change password [x] Whoami - [ ] 9: User Data + [~] 9: User Data [ ] 5: Capabilities negotiation [ ] 10: Security (Rate Limiting) diff --git a/src/Routes/RouteMatrix.c b/src/Routes/RouteMatrix.c index 08c107c..2ef00f9 100644 --- a/src/Routes/RouteMatrix.c +++ b/src/Routes/RouteMatrix.c @@ -104,6 +104,10 @@ ROUTE_IMPL(RouteMatrix, args) response = MatrixErrorCreate(M_NOT_FOUND); } } + else if (MATRIX_PATH_EQUALS(pathPart, "profile")) + { + response = RouteUserProfile(args); + } else { HttpResponseStatus(args->context, HTTP_NOT_FOUND); diff --git a/src/Routes/RouteUserProfile.c b/src/Routes/RouteUserProfile.c new file mode 100644 index 0000000..d512d37 --- /dev/null +++ b/src/Routes/RouteUserProfile.c @@ -0,0 +1,179 @@ +/* + * 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 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 + +ROUTE_IMPL(RouteUserProfile, args) +{ + Db *db = args->matrixArgs->db; + + HashMap *request = NULL; + HashMap *response = NULL; + + UserId *userId = NULL; + User *user = NULL; + + char *serverName = args->matrixArgs->config->serverName; + char *username = NULL; + char *entry = NULL; + char *token = NULL; + char *value = NULL; + + if (MATRIX_PATH_PARTS(args->path) < 1) + { + HttpResponseStatus(args->context, HTTP_BAD_REQUEST); + response = MatrixErrorCreate(M_MISSING_PARAM); + goto finish; + } + username = MATRIX_PATH_POP(args->path); + userId = UserIdParse(username, serverName); + if (!userId) + { + HttpResponseStatus(args->context, HTTP_BAD_REQUEST); + response = MatrixErrorCreate(M_INVALID_PARAM); + goto finish; + } + if (strcmp(userId->server, serverName)) + { + /* TODO: Implement lookup over federation. */ + HttpResponseStatus(args->context, HTTP_FORBIDDEN); + response = MatrixErrorCreate(M_FORBIDDEN); + goto finish; + } + + /* TODO: Actually implement user data */ + switch (HttpRequestMethodGet(args->context)) + { + case HTTP_GET: + user = UserLock(db, userId->localpart); + if (!user) + { + HttpResponseStatus(args->context, HTTP_NOT_FOUND); + response = MatrixErrorCreate(M_NOT_FOUND); + goto finish; + } + if (MATRIX_PATH_PARTS(args->path) > 1) + { + HttpResponseStatus(args->context, HTTP_BAD_REQUEST); + response = MatrixErrorCreate(M_INVALID_PARAM); + goto finish; + } + else if (MATRIX_PATH_PARTS(args->path) == 1) + { + entry = MATRIX_PATH_POP(args->path); + response = HashMapCreate(); + + value = UserGetProfile(user, entry); + if (value) + { + HashMapSet(response, entry, JsonValueString(value)); + } + goto finish; + } + response = HashMapCreate(); + value = UserGetProfile(user, "avatar_url"); + if (value) + { + HashMapSet(response, "avatar_url", JsonValueString(value)); + } + value = UserGetProfile(user, "displayname"); + if (value) + { + HashMapSet(response, "displayname", JsonValueString(value)); + } + goto finish; + case HTTP_PUT: + if (MATRIX_PATH_PARTS(args->path) == 1) + { + request = JsonDecode(HttpServerStream(args->context)); + if (!request) + { + HttpResponseStatus(args->context, HTTP_BAD_REQUEST); + response = MatrixErrorCreate(M_NOT_JSON); + goto finish; + } + response = MatrixGetAccessToken(args->context, &token); + if (response) + { + goto finish; + } + user = UserAuthenticate(db, token); + if (!user) + { + HttpResponseStatus(args->context, HTTP_BAD_REQUEST); + response = MatrixErrorCreate(M_UNKNOWN_TOKEN); + goto finish; + } + entry = MATRIX_PATH_POP(args->path); + if (strcmp(entry, "displayname") == 0 || + strcmp(entry, "avatar_url") == 0) + { + /* Check if user has privilege to do that action. */ + if (!strcmp(userId->localpart, UserGetName(user))) + { + value = JsonValueAsString(HashMapGet(request, entry)); + /* TODO: Make UserSetProfile notify other + * parties of the change */ + UserSetProfile(user, entry, value); + response = HashMapCreate(); + goto finish; + } + /* User is not allowed to carry-on the action */ + HttpResponseStatus(args->context, HTTP_FORBIDDEN); + response = MatrixErrorCreate(M_FORBIDDEN); + goto finish; + } + else + { + HttpResponseStatus(args->context, HTTP_BAD_REQUEST); + response = MatrixErrorCreate(M_UNRECOGNIZED); + goto finish; + } + } + else + { + HttpResponseStatus(args->context, HTTP_BAD_REQUEST); + response = MatrixErrorCreate(M_MISSING_PARAM); + goto finish; + } + default: + HttpResponseStatus(args->context, HTTP_BAD_REQUEST); + response = MatrixErrorCreate(M_UNKNOWN); + break; + } +finish: + Free(username); + Free(entry); + UserIdFree(userId); + UserUnlock(user); + JsonFree(request); + return response; +} diff --git a/src/User.c b/src/User.c index 02a89f3..080c91a 100644 --- a/src/User.c +++ b/src/User.c @@ -614,6 +614,35 @@ UserDeleteToken(User * user, char *token) return 1; } +char * +UserGetProfile(User *user, char *name) +{ + HashMap *json = NULL; + + if (!user || !name) + { + return NULL; + } + + json = DbJson(user->ref); + + return JsonValueAsString(JsonGet(json, 2, "profile", name)); +} + +void +UserSetProfile(User *user, char *name, char *val) +{ + HashMap *json = NULL; + + if (!user || !name || !val) + { + return; + } + + json = DbJson(user->ref); + JsonValueFree(JsonSet(json, JsonValueString(val), 2, "profile", name)); +} + int UserDeleteTokens(User * user, char *exempt) { diff --git a/src/include/Routes.h b/src/include/Routes.h index 2f6df73..7dbf209 100644 --- a/src/include/Routes.h +++ b/src/include/Routes.h @@ -73,6 +73,11 @@ ROUTE(RouteChangePwd); /* /_matrix/client/(r0|v3)/account/pa ROUTE(RouteTokenValid); /* /_matrix/client/v1/register/m.logi * n.registration_token/validity */ +ROUTE(RouteUserProfile); /* This route handles: + /_matrix/client/(r0|v3)/profile/(.*), + /_matrix/client/(r0|v3)/profile/(.*)/avatar_url and + /_matrix/client/(r0|v3)/profile/(.*)/displayname*/ + #undef ROUTE #endif diff --git a/src/include/User.h b/src/include/User.h index 84b89b1..dd74c7f 100644 --- a/src/include/User.h +++ b/src/include/User.h @@ -26,6 +26,8 @@ #include +#include + typedef struct User User; typedef struct UserAccessToken @@ -102,6 +104,12 @@ extern void extern int UserDeleteToken(User *, char *); +extern char * + UserGetProfile(User *, char *); + +extern void + UserSetProfile(User *, char *, char *); + extern int UserDeleteTokens(User *, char *);