From ffeb45375e416342ebd1c0f3cedc59d985b56df3 Mon Sep 17 00:00:00 2001 From: Jordan Bancino Date: Wed, 19 Apr 2023 18:52:05 +0000 Subject: [PATCH] Implement /_telodendria/admin/privileges Note that it's not exactly as the proposal defines it; theres a bit more nuance that will be documented soon. --- TODO.txt | 5 +- src/Matrix.c | 4 + src/Routes.c | 2 + src/Routes/RoutePrivileges.c | 142 ++++++++++++++++++++++++++++++++++ src/Routes/RouteProcControl.c | 57 +++++++++----- src/User.c | 4 +- src/include/Routes.h | 1 + 7 files changed, 191 insertions(+), 24 deletions(-) create mode 100644 src/Routes/RoutePrivileges.c diff --git a/TODO.txt b/TODO.txt index 1c5c96e..3fcbed9 100644 --- a/TODO.txt +++ b/TODO.txt @@ -77,7 +77,10 @@ Milestone: v0.3.0 [ ] HashMap [ ] HttpRouter [ ] Str - [ ] Admin API + [ ] telodendria-admin + [ ] telodendria-setup + [ ] Refactor dev pages so function description and + return value are in the same location. [~] Client-Server API [x] 4: Token-based user registration diff --git a/src/Matrix.c b/src/Matrix.c index 673ac0e..85fe826 100644 --- a/src/Matrix.c +++ b/src/Matrix.c @@ -228,6 +228,10 @@ MatrixErrorCreate(MatrixError errorArg) errcode = "M_MISSING_PARAM"; error = "A required parameter was missing from the request."; break; + case M_INVALID_PARAM: + errcode = "M_INVALID_PARAM"; + error = "A required parameter was invalid in some way."; + break; case M_TOO_LARGE: errcode = "M_TOO_LARGE"; error = "The request or entity was too large."; diff --git a/src/Routes.c b/src/Routes.c index ae4c6a6..3cf53e2 100644 --- a/src/Routes.c +++ b/src/Routes.c @@ -69,6 +69,8 @@ RouterBuild(void) R("/_telodendria/admin/(restart|shutdown|stats)", RouteProcControl); R("/_telodendria/admin/config", RouteConfig); + R("/_telodendria/admin/privileges", RoutePrivileges); + R("/_telodendria/admin/privileges/(.*)", RoutePrivileges); #undef R diff --git a/src/Routes/RoutePrivileges.c b/src/Routes/RoutePrivileges.c new file mode 100644 index 0000000..fad9be5 --- /dev/null +++ b/src/Routes/RoutePrivileges.c @@ -0,0 +1,142 @@ +/* + * 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 + +ROUTE_IMPL(RoutePrivileges, path, argp) +{ + RouteArgs *args = argp; + HashMap *response; + char *token; + User *user = NULL; + + HashMap *request = NULL; + JsonValue *val; + int privileges; + + response = MatrixGetAccessToken(args->context, &token); + if (response) + { + goto finish; + } + + user = UserAuthenticate(args->matrixArgs->db, token); + if (!user) + { + HttpResponseStatus(args->context, HTTP_BAD_REQUEST); + response = MatrixErrorCreate(M_UNKNOWN_TOKEN); + goto finish; + } + + if (!(UserGetPrivileges(user) & USER_GRANT_PRIVILEGES)) + { + HttpResponseStatus(args->context, HTTP_FORBIDDEN); + response = MatrixErrorCreate(M_FORBIDDEN); + goto finish; + } + + /* If a user was specified in the URL, switch to that user after + * verifying that the current user has privileges to do so + */ + if (ArraySize(path) == 1) + { + UserUnlock(user); + user = UserLock(args->matrixArgs->db, ArrayGet(path, 0)); + if (!user) + { + HttpResponseStatus(args->context, HTTP_BAD_REQUEST); + response = MatrixErrorCreate(M_INVALID_PARAM); + goto finish; + } + } + + switch (HttpRequestMethodGet(args->context)) + { + case HTTP_POST: + case HTTP_PUT: + case HTTP_DELETE: + request = JsonDecode(HttpServerStream(args->context)); + if (!request) + { + HttpResponseStatus(args->context, HTTP_BAD_REQUEST); + response = MatrixErrorCreate(M_NOT_JSON); + break; + } + + val = HashMapGet(request, "privileges"); + if (!val || JsonValueType(val) != JSON_ARRAY) + { + HttpResponseStatus(args->context, HTTP_BAD_REQUEST); + response = MatrixErrorCreate(M_BAD_JSON); + break; + } + + switch (HttpRequestMethodGet(args->context)) + { + case HTTP_POST: + privileges = UserDecodePrivileges(val); + break; + case HTTP_PUT: + privileges = UserGetPrivileges(user); + privileges |= UserDecodePrivileges(val); + break; + case HTTP_DELETE: + privileges = UserGetPrivileges(user); + privileges &= ~UserDecodePrivileges(val); + break; + default: + /* Impossible */ + privileges = USER_NONE; + break; + } + + if (!UserSetPrivileges(user, privileges)) + { + HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); + response = MatrixErrorCreate(M_UNKNOWN); + break; + } + + /* Fall through */ + case HTTP_GET: + response = HashMapCreate(); + HashMapSet(response, "privileges", UserEncodePrivileges(UserGetPrivileges(user))); + break; + default: + HttpResponseStatus(args->context, HTTP_BAD_REQUEST); + response = MatrixErrorCreate(M_UNRECOGNIZED); + goto finish; + break; + } + +finish: + UserUnlock(user); + JsonFree(request); + return response; +} diff --git a/src/Routes/RouteProcControl.c b/src/Routes/RouteProcControl.c index b9a85b1..76be5ad 100644 --- a/src/Routes/RouteProcControl.c +++ b/src/Routes/RouteProcControl.c @@ -58,30 +58,45 @@ ROUTE_IMPL(RouteProcControl, path, argp) goto finish; } - if (strcmp(op, "restart") == 0) + switch (HttpRequestMethodGet(args->context)) { - Restart(); - } - else if (strcmp(op, "shutdown") == 0) - { - Shutdown(); - } - else if (strcmp(op, "stats") == 0) - { - response = HashMapCreate(); + case HTTP_POST: + if (strcmp(op, "restart") == 0) + { + Restart(); + } + else if (strcmp(op, "shutdown") == 0) + { + Shutdown(); + } + else + { + HttpResponseStatus(args->context, HTTP_BAD_REQUEST); + response = MatrixErrorCreate(M_UNRECOGNIZED); + goto finish; + } + case HTTP_GET: + if (strcmp(op, "stats") == 0) + { + response = HashMapCreate(); - HashMapSet(response, "version", JsonValueString(TELODENDRIA_VERSION)); - HashMapSet(response, "memory_allocated", JsonValueInteger(MemoryAllocated())); - HashMapSet(response, "uptime", JsonValueInteger(Uptime())); + HashMapSet(response, "version", JsonValueString(TELODENDRIA_VERSION)); + HashMapSet(response, "memory_allocated", JsonValueInteger(MemoryAllocated())); + HashMapSet(response, "uptime", JsonValueInteger(Uptime())); - goto finish; - } - else - { - /* Should be impossible */ - HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); - response = MatrixErrorCreate(M_UNKNOWN); - goto finish; + goto finish; + } + else + { + HttpResponseStatus(args->context, HTTP_BAD_REQUEST); + response = MatrixErrorCreate(M_UNRECOGNIZED); + goto finish; + } + default: + HttpResponseStatus(args->context, HTTP_BAD_REQUEST); + response = MatrixErrorCreate(M_UNRECOGNIZED); + goto finish; + break; } response = HashMapCreate(); diff --git a/src/User.c b/src/User.c index 153a684..b5357ec 100644 --- a/src/User.c +++ b/src/User.c @@ -804,14 +804,14 @@ UserEncodePrivileges(int privileges) return NULL; } - if (privileges & USER_ALL) + if ((privileges & USER_ALL) == USER_ALL) { ArrayAdd(arr, JsonValueString("ALL")); goto finish; } #define A(priv, as) \ - if (privileges & priv) \ + if ((privileges & priv) == priv) \ { \ ArrayAdd(arr, JsonValueString(as)); \ } diff --git a/src/include/Routes.h b/src/include/Routes.h index 95bbe88..385026c 100644 --- a/src/include/Routes.h +++ b/src/include/Routes.h @@ -71,6 +71,7 @@ ROUTE(RouteStaticLogin); ROUTE(RouteProcControl); ROUTE(RouteConfig); +ROUTE(RoutePrivileges); #undef ROUTE