From 3bbff5379f7d79df3cade4d1c0a8f088e565dd03 Mon Sep 17 00:00:00 2001 From: Jordan Bancino Date: Sun, 19 Feb 2023 14:58:56 +0000 Subject: [PATCH] [WIP] Replace UserInteractiveAuth with a new Uia API. Uia is a lot less characters to type. Do note that this API is far from complete and this commit breaks user interactive authentication entirely. --- TODO.txt | 6 +- src/Routes/RouteRegister.c | 23 +- src/Telodendria.c | 4 +- src/Uia.c | 311 +++++++++++++++++++ src/UserInteractiveAuth.c | 147 --------- src/include/{UserInteractiveAuth.h => Uia.h} | 21 +- 6 files changed, 350 insertions(+), 162 deletions(-) create mode 100644 src/Uia.c delete mode 100644 src/UserInteractiveAuth.c rename src/include/{UserInteractiveAuth.h => Uia.h} (79%) diff --git a/TODO.txt b/TODO.txt index 6446283..4b626e6 100644 --- a/TODO.txt +++ b/TODO.txt @@ -19,11 +19,11 @@ Milestone: v0.2.0 [ ] Logout [ ] Logout all [ ] Login fallback (static HTML page) -[ ] User Interactive +[~] User Interactive [ ] Passwords [ ] Registration tokens - [ ] Caller builds flows - [ ] Document UserInteractiveAuth (move docs from Matrix) + [~] Caller builds flows + [ ] Document Uia (move docs from Matrix) [x] Non-JSON endpoints [x] Home page (like Synapse's "it works!") diff --git a/src/Routes/RouteRegister.c b/src/Routes/RouteRegister.c index b31d48a..5570c99 100644 --- a/src/Routes/RouteRegister.c +++ b/src/Routes/RouteRegister.c @@ -31,7 +31,7 @@ #include #include -#include +#include ROUTE_IMPL(RouteRegister, args) { @@ -54,6 +54,9 @@ ROUTE_IMPL(RouteRegister, args) User *user = NULL; + Array *uiaFlows; + int uiaResult; + if (MATRIX_PATH_PARTS(args->path) == 0) { if (HttpRequestMethodGet(args->context) != HTTP_POST) @@ -102,11 +105,23 @@ ROUTE_IMPL(RouteRegister, args) } } - response = UserInteractiveAuth(args->context, - args->matrixArgs->db, request); + uiaFlows = ArrayCreate(); + ArrayAdd(uiaFlows, UiaDummyFlow()); - if (response) + /* TODO: Add registration token flow */ + + uiaResult = UiaComplete(uiaFlows, args->context, + args->matrixArgs->db, request, &response); + + if (uiaResult < 0) { + HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR); + response = MatrixErrorCreate(M_UNKNOWN); + goto finish; + } + else if (!uiaResult) + { + /* UiaComplete() sets the response and status for us. */ goto finish; } diff --git a/src/Telodendria.c b/src/Telodendria.c index f4e7ad2..339f68c 100644 --- a/src/Telodendria.c +++ b/src/Telodendria.c @@ -42,7 +42,7 @@ #include #include #include -#include +#include const char TelodendriaLogo[TELODENDRIA_LOGO_HEIGHT][TELODENDRIA_LOGO_WIDTH] = { @@ -558,7 +558,7 @@ main(int argc, char **argv) Log(lc, LOG_DEBUG, "Registering jobs..."); - CronEvery(cron, 30 * 60 * 1000, (JobFunc *) UserInteractiveAuthCleanup, &matrixArgs); + CronEvery(cron, 30 * 60 * 1000, (JobFunc *) UiaCleanup, &matrixArgs); Log(lc, LOG_NOTICE, "Starting job scheduler..."); CronStart(cron); diff --git a/src/Uia.c b/src/Uia.c new file mode 100644 index 0000000..3be4bec --- /dev/null +++ b/src/Uia.c @@ -0,0 +1,311 @@ +/* + * 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 + +struct UiaStage +{ + char *type; + HashMap *params; +}; + +static HashMap * +BuildFlows(Array *flows) +{ + HashMap *response; + Array *responseFlows; + HashMap *responseParams; + + size_t i, j; + + if (!flows) + { + return NULL; + } + + response = HashMapCreate(); + if (!response) + { + return NULL; + } + + responseFlows = ArrayCreate(); + if (!responseFlows) + { + HashMapFree(response); + return NULL; + } + + responseParams = HashMapCreate(); + if (!responseParams) + { + HashMapFree(response); + ArrayFree(responseFlows); + return NULL; + } + + HashMapSet(response, "flows", JsonValueArray(responseFlows)); + HashMapSet(response, "params", JsonValueObject(responseParams)); + + for (i = 0; i < ArraySize(flows); i++) + { + Array *stages = ArrayGet(flows, i); + HashMap *responseFlow = HashMapCreate(); + Array *responseStages = ArrayCreate(); + + HashMapSet(responseFlow, "stages", JsonValueArray(responseStages)); + ArrayAdd(responseFlows, JsonValueObject(responseFlow)); + + for (j = 0; j < ArraySize(stages); j++) + { + UiaStage *stage = ArrayGet(stages, i); + + ArrayAdd(responseStages, JsonValueString(StrDuplicate(stage->type))); + if (stage->params) + { + JsonValueFree(HashMapSet(responseParams, StrDuplicate(stage->type), JsonValueObject(stage->params))); + } + } + } + + return response; +} + +static int +BuildResponse(Array *flows, char *session, Db *db, HashMap **response) +{ + DbRef *ref; + HashMap *json; + + *response = BuildFlows(flows); + + if (!*response) + { + return -1; + } + + if (!session) + { + session = StrRandom(16); + if (!session) + { + JsonFree(*response); + return -1; + } + + ref = DbCreate(db, 2, "user_interactive", session); + if (!ref) + { + Free(session); + JsonFree(*response); + return -1; + } + + json = DbJson(ref); + HashMapSet(json, "completed", JsonValueArray(ArrayCreate())); + DbUnlock(db, ref); + + HashMapSet(*response, "completed", JsonValueArray(ArrayCreate())); + } + else + { + Array *completed = ArrayCreate(); + Array *dbCompleted; + size_t i; + + if (!completed) + { + JsonFree(*response); + return -1; + } + + ref = DbLock(db, 2, "user_interactive", session); + if (!ref) + { + JsonFree(*response); + ArrayFree(completed); + return -1; + } + + json = DbJson(ref); + dbCompleted = JsonValueAsArray(HashMapGet(json, "completed")); + + for (i = 0; i < ArraySize(dbCompleted); i++) + { + char *stage = JsonValueAsString(ArrayGet(dbCompleted, i)); + ArrayAdd(completed, JsonValueString(StrDuplicate(stage))); + } + + HashMapSet(*response, "completed", JsonValueArray(completed)); + + DbUnlock(db, ref); + } + + HashMapSet(*response, "session", JsonValueString(session)); + return 0; +} + +Array * +UiaDummyFlow(void) +{ + Array *response = ArrayCreate(); + if (!response) + { + return NULL; + } + + ArrayAdd(response, UiaBuildStage("m.login.dummy", NULL)); + + return response; +} + +UiaStage * +UiaBuildStage(char *type, HashMap *params) +{ + UiaStage *stage = Malloc(sizeof(UiaStage)); + + if (!stage) + { + return NULL; + } + + stage->type = type; + stage->params = params; + + return stage; +} + +int +UiaComplete(Array *flows, HttpServerContext * context, Db * db, + HashMap * request, HashMap ** response) +{ + JsonValue *val; + HashMap *auth; + char *session; + char *authType; + + DbRef *dbRef; + HashMap *dbJson; + + size_t i, j; + int ret = 0; + + if (!flows) + { + return -1; + } + + if (!context || !db || !request || !response) + { + ret = -1; + goto finish; + } + + val = HashMapGet(request, "auth"); + + if (!val) + { + HttpResponseStatus(context, HTTP_UNAUTHORIZED); + ret = BuildResponse(flows, NULL, db, response); + goto finish; + } + + if (JsonValueType(val) != JSON_OBJECT) + { + HttpResponseStatus(context, HTTP_BAD_REQUEST); + *response = MatrixErrorCreate(M_BAD_JSON); + ret = 0; + goto finish; + } + + auth = JsonValueAsObject(val); + val = HashMapGet(request, "session"); + + if (!val || JsonValueType(val) != JSON_STRING) + { + HttpResponseStatus(context, HTTP_BAD_REQUEST); + *response = MatrixErrorCreate(M_BAD_JSON); + ret = 0; + goto finish; + } + + session = JsonValueAsString(val); + val = HashMapGet(auth, "type"); + + if (!val || JsonValueType(val) != JSON_STRING) + { + HttpResponseStatus(context, HTTP_BAD_REQUEST); + *response = MatrixErrorCreate(M_BAD_JSON); + ret = 0; + goto finish; + } + + authType = JsonValueAsString(val); + + dbRef = DbLock(db, 2, "user_interactive", session); + if (!dbRef) + { + HttpResponseStatus(context, HTTP_UNAUTHORIZED); + ret = BuildResponse(flows, StrDuplicate(session), db, response); + goto finish; + } + + dbJson = DbJson(dbRef); + + DbUnlock(db, dbRef); + + ret = 1; + +finish: + for (i = 0; i < ArraySize(flows); i++) + { + Array *stages = ArrayGet(flows, i); + for (j = 0; j < ArraySize(stages); j++) + { + UiaStage *stage = ArrayGet(stages, j); + Free(stage); /* Members are referenced elsewhere */ + } + ArrayFree(stages); + } + ArrayFree(flows); + return ret; +} + +void +UiaCleanup(MatrixHttpHandlerArgs * args) +{ + Log(args->lc, LOG_DEBUG, "Purging old user interactive auth sessions..."); + if (!DbDelete(args->db, 1, "user_interactive")) + { + Log(args->lc, LOG_ERR, "Failed to purge user_interactive."); + } +} diff --git a/src/UserInteractiveAuth.c b/src/UserInteractiveAuth.c deleted file mode 100644 index 0a58216..0000000 --- a/src/UserInteractiveAuth.c +++ /dev/null @@ -1,147 +0,0 @@ -/* - * 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 - -static HashMap * -BuildDummyFlow(void) -{ - HashMap *response = HashMapCreate(); - HashMap *dummyFlow = HashMapCreate(); - Array *stages = ArrayCreate(); - Array *flows = ArrayCreate(); - - ArrayAdd(stages, - JsonValueString(StrDuplicate("m.login.dummy"))); - HashMapSet(dummyFlow, "stages", JsonValueArray(stages)); - ArrayAdd(flows, JsonValueObject(dummyFlow)); - - HashMapSet(response, "flows", JsonValueArray(flows)); - HashMapSet(response, "params", - JsonValueObject(HashMapCreate())); - - return response; -} - -HashMap * -UserInteractiveAuth(HttpServerContext * context, Db * db, - HashMap * request) -{ - JsonValue *auth; - JsonValue *type; - JsonValue *session; - - HashMap *authObj; - char *typeStr; - char *sessionStr; - - DbRef *ref; - - auth = HashMapGet(request, "auth"); - if (!auth) - { - HashMap *response = NULL; - HashMap *persist; - char *sessionRand = StrRandom(24); - - ref = DbLock(db, 1, "user_interactive"); - if (!ref) - { - ref = DbCreate(db, 1, "user_interactive"); - } - - persist = DbJson(ref); - HashMapSet(persist, sessionRand, JsonValueNull()); - DbUnlock(db, ref); - - HttpResponseStatus(context, HTTP_UNAUTHORIZED); - response = BuildDummyFlow(); - - HashMapSet(response, "session", - JsonValueString(StrDuplicate(sessionRand))); - - return response; - } - - if (JsonValueType(auth) != JSON_OBJECT) - { - HttpResponseStatus(context, HTTP_BAD_REQUEST); - return MatrixErrorCreate(M_BAD_JSON); - } - - authObj = JsonValueAsObject(auth); - type = HashMapGet(authObj, "type"); - session = HashMapGet(authObj, "session"); - - if (!type || JsonValueType(type) != JSON_STRING) - { - HttpResponseStatus(context, HTTP_BAD_REQUEST); - return MatrixErrorCreate(M_BAD_JSON); - } - - if (!session || JsonValueType(session) != JSON_STRING) - { - HttpResponseStatus(context, HTTP_UNAUTHORIZED); - return BuildDummyFlow(); - } - - typeStr = JsonValueAsString(type); - sessionStr = JsonValueAsString(session); - - if (strcmp(typeStr, "m.login.dummy") != 0) - { - HttpResponseStatus(context, HTTP_BAD_REQUEST); - return MatrixErrorCreate(M_INVALID_PARAM); - } - - ref = DbLock(db, 1, "user_interactive"); - - /* Check to see if session exists */ - if (!ref || !HashMapGet(DbJson(ref), sessionStr)) - { - DbUnlock(db, ref); - HttpResponseStatus(context, HTTP_BAD_REQUEST); - return MatrixErrorCreate(M_UNKNOWN); - } - - /* We only need to know that it exists. */ - DbUnlock(db, ref); - - return NULL; /* All good, auth successful */ -} - -void -UserInteractiveAuthCleanup(MatrixHttpHandlerArgs * args) -{ - Log(args->lc, LOG_DEBUG, "Purging old user interactive auth sessions..."); - if (!DbDelete(args->db, 1, "user_interactive")) - { - Log(args->lc, LOG_ERR, "Failed to purge user_interactive."); - } -} diff --git a/src/include/UserInteractiveAuth.h b/src/include/Uia.h similarity index 79% rename from src/include/UserInteractiveAuth.h rename to src/include/Uia.h index aadc748..2d69219 100644 --- a/src/include/UserInteractiveAuth.h +++ b/src/include/Uia.h @@ -21,17 +21,26 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -#ifndef TELODENDRIA_USERINTERACTIVEAUTH_H -#define TELODENDRIA_USERINTERACTIVEAUTH_H +#ifndef TELODENDRIA_UIA_H +#define TELODENDRIA_UIA_H +#include #include #include #include -extern void - UserInteractiveAuthCleanup(MatrixHttpHandlerArgs *); +typedef struct UiaStage UiaStage; -extern HashMap * - UserInteractiveAuth(HttpServerContext *, Db *, HashMap *); +extern UiaStage * +UiaBuildStage(char *, HashMap *); + +extern Array * +UiaDummyFlow(void); + +extern void + UiaCleanup(MatrixHttpHandlerArgs *); + +extern int +UiaComplete(Array *stages, HttpServerContext *, Db *, HashMap *, HashMap **); #endif