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.
This commit is contained in:
Jordan Bancino 2023-04-19 18:52:05 +00:00
parent ae38791df2
commit ffeb45375e
7 changed files with 191 additions and 24 deletions

View file

@ -77,7 +77,10 @@ Milestone: v0.3.0
[ ] HashMap [ ] HashMap
[ ] HttpRouter [ ] HttpRouter
[ ] Str [ ] Str
[ ] Admin API [ ] telodendria-admin
[ ] telodendria-setup
[ ] Refactor dev pages so function description and
return value are in the same location.
[~] Client-Server API [~] Client-Server API
[x] 4: Token-based user registration [x] 4: Token-based user registration

View file

@ -228,6 +228,10 @@ MatrixErrorCreate(MatrixError errorArg)
errcode = "M_MISSING_PARAM"; errcode = "M_MISSING_PARAM";
error = "A required parameter was missing from the request."; error = "A required parameter was missing from the request.";
break; break;
case M_INVALID_PARAM:
errcode = "M_INVALID_PARAM";
error = "A required parameter was invalid in some way.";
break;
case M_TOO_LARGE: case M_TOO_LARGE:
errcode = "M_TOO_LARGE"; errcode = "M_TOO_LARGE";
error = "The request or entity was too large."; error = "The request or entity was too large.";

View file

@ -69,6 +69,8 @@ RouterBuild(void)
R("/_telodendria/admin/(restart|shutdown|stats)", RouteProcControl); R("/_telodendria/admin/(restart|shutdown|stats)", RouteProcControl);
R("/_telodendria/admin/config", RouteConfig); R("/_telodendria/admin/config", RouteConfig);
R("/_telodendria/admin/privileges", RoutePrivileges);
R("/_telodendria/admin/privileges/(.*)", RoutePrivileges);
#undef R #undef R

View file

@ -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 <Routes.h>
#include <User.h>
#include <Main.h>
#include <Memory.h>
#include <string.h>
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;
}

View file

@ -58,30 +58,45 @@ ROUTE_IMPL(RouteProcControl, path, argp)
goto finish; goto finish;
} }
if (strcmp(op, "restart") == 0) switch (HttpRequestMethodGet(args->context))
{ {
Restart(); case HTTP_POST:
} if (strcmp(op, "restart") == 0)
else if (strcmp(op, "shutdown") == 0) {
{ Restart();
Shutdown(); }
} else if (strcmp(op, "shutdown") == 0)
else if (strcmp(op, "stats") == 0) {
{ Shutdown();
response = HashMapCreate(); }
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, "version", JsonValueString(TELODENDRIA_VERSION));
HashMapSet(response, "memory_allocated", JsonValueInteger(MemoryAllocated())); HashMapSet(response, "memory_allocated", JsonValueInteger(MemoryAllocated()));
HashMapSet(response, "uptime", JsonValueInteger(Uptime())); HashMapSet(response, "uptime", JsonValueInteger(Uptime()));
goto finish; goto finish;
} }
else else
{ {
/* Should be impossible */ HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); response = MatrixErrorCreate(M_UNRECOGNIZED);
response = MatrixErrorCreate(M_UNKNOWN); goto finish;
goto finish; }
default:
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_UNRECOGNIZED);
goto finish;
break;
} }
response = HashMapCreate(); response = HashMapCreate();

View file

@ -804,14 +804,14 @@ UserEncodePrivileges(int privileges)
return NULL; return NULL;
} }
if (privileges & USER_ALL) if ((privileges & USER_ALL) == USER_ALL)
{ {
ArrayAdd(arr, JsonValueString("ALL")); ArrayAdd(arr, JsonValueString("ALL"));
goto finish; goto finish;
} }
#define A(priv, as) \ #define A(priv, as) \
if (privileges & priv) \ if ((privileges & priv) == priv) \
{ \ { \
ArrayAdd(arr, JsonValueString(as)); \ ArrayAdd(arr, JsonValueString(as)); \
} }

View file

@ -71,6 +71,7 @@ ROUTE(RouteStaticLogin);
ROUTE(RouteProcControl); ROUTE(RouteProcControl);
ROUTE(RouteConfig); ROUTE(RouteConfig);
ROUTE(RoutePrivileges);
#undef ROUTE #undef ROUTE