From db0217f6f7e8422d96a7c76b3d71cd36699f516b Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sun, 12 Feb 2023 20:49:16 +0100 Subject: [PATCH] Add GET /api/auth/sessions for listing the currently active sessions Signed-off-by: DL6ER --- src/api/api.c | 3 +- src/api/api.h | 1 + src/api/auth.c | 50 ++++++++++++++++++++++++- src/api/docs/content/specs/auth.yaml | 55 ++++++++++++++++++++++++++++ src/api/docs/content/specs/main.yaml | 3 ++ 5 files changed, 110 insertions(+), 2 deletions(-) diff --git a/src/api/api.c b/src/api/api.c index 9f176a6e..b8ec1fa3 100644 --- a/src/api/api.c +++ b/src/api/api.c @@ -35,6 +35,8 @@ static struct { // domains json fifo // Note: The order of appearance matters here, more specific URIs have to // appear *before* less specific URIs: 1. "/a/b/c", 2. "/a/b", 3. "/a" + { "/api/auth/sessions", "", api_auth_sessions, { false, true, 0 }, true, HTTP_GET }, + { "/api/auth", "", api_auth, { false, true, 0 }, false, HTTP_GET | HTTP_POST | HTTP_DELETE }, { "/api/dns/blocking", "", api_dns_blocking, { false, true, 0 }, true, HTTP_GET | HTTP_POST }, { "/api/clients", "/{client}", api_list, { false, true, 0 }, true, HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_DELETE }, { "/api/domains", "/{type}/{kind}/{domain}", api_list, { false, true, 0 }, true, HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_DELETE }, @@ -72,7 +74,6 @@ static struct { { "/api/stats/database/summary", "", api_stats_database_summary, { false, true, 0 }, true, HTTP_GET }, { "/api/stats/database/query_types", "", api_stats_database_query_types, { false, true, 0 }, true, HTTP_GET }, { "/api/stats/database/upstreams", "", api_stats_database_upstreams, { false, true, 0 }, true, HTTP_GET }, - { "/api/auth", "", api_auth, { false, true, 0 }, false, HTTP_GET | HTTP_POST | HTTP_DELETE }, { "/api/config", "", api_config, { false, true, 0 }, true, HTTP_GET | HTTP_PATCH }, { "/api/config", "/{element}", api_config, { false, true, 0 }, true, HTTP_GET | HTTP_PATCH }, { "/api/config", "/{element}/{value}", api_config, { false, true, 0 }, true, HTTP_DELETE | HTTP_PUT }, diff --git a/src/api/api.h b/src/api/api.h index 7d3a6024..67f55d91 100644 --- a/src/api/api.h +++ b/src/api/api.h @@ -78,6 +78,7 @@ int api_group(struct ftl_conn *api); int check_client_auth(struct ftl_conn *api); int api_auth(struct ftl_conn *api); void delete_all_sessions(void); +int api_auth_sessions(struct ftl_conn *api); // Documentation methods int api_docs(struct ftl_conn *api); diff --git a/src/api/auth.c b/src/api/auth.c index 6a69743b..dba10a0e 100644 --- a/src/api/auth.c +++ b/src/api/auth.c @@ -57,8 +57,9 @@ static struct { bool used; time_t valid_until; char remote_addr[48]; // Large enough for IPv4 and IPv6 addresses, hard-coded in civetweb.h as mg_request_info.remote_addr + char user_agent[128]; char sid[SID_SIZE]; -} auth_data[API_MAX_CLIENTS] = {{false, 0, {0}, {0}}}; +} auth_data[API_MAX_CLIENTS] = {{false, 0, {0}, {0}, {0}}}; #define CHALLENGE_SIZE (2*SHA256_DIGEST_SIZE) static struct { @@ -234,6 +235,28 @@ static bool check_response(const char *response, const time_t now) return false; } +static int get_all_sessions(struct ftl_conn *api, cJSON *json) +{ + const time_t now = time(NULL); + cJSON *sessions = JSON_NEW_ARRAY(); + for(unsigned int i = 0; i < API_MAX_CLIENTS; i++) + { + if(auth_data[i].used) + { + cJSON *session = JSON_NEW_OBJECT(); + JSON_ADD_NUMBER_TO_OBJECT(session, "id", i); + JSON_ADD_BOOL_TO_OBJECT(session, "valid", auth_data[i].valid_until >= now); + JSON_ADD_NUMBER_TO_OBJECT(session, "last_active", auth_data[i].valid_until - config.webserver.sessionTimeout.v.ui); + JSON_ADD_NUMBER_TO_OBJECT(session, "valid_until", auth_data[i].valid_until); + JSON_REF_STR_IN_OBJECT(session, "remote_addr", auth_data[i].remote_addr); + JSON_REF_STR_IN_OBJECT(session, "user_agent", auth_data[i].user_agent); + JSON_ADD_ITEM_TO_ARRAY(sessions, session); + } + } + JSON_ADD_ITEM_TO_OBJECT(json, "sessions", sessions); + return 0; +} + static int get_session_object(struct ftl_conn *api, cJSON *json, const int user_id, const time_t now) { // Authentication not needed @@ -277,6 +300,7 @@ static void delete_session(const int user_id) auth_data[user_id].valid_until = 0; memset(auth_data[user_id].sid, 0, sizeof(auth_data[user_id].sid)); memset(auth_data[user_id].remote_addr, 0, sizeof(auth_data[user_id].remote_addr)); + memset(auth_data[user_id].user_agent, 0, sizeof(auth_data[user_id].user_agent)); } void delete_all_sessions(void) @@ -502,10 +526,26 @@ int api_auth(struct ftl_conn *api) // Found unused authentication slot (might have been freed before) if(!auth_data[i].used) { + // Mark as used auth_data[i].used = true; + // Set validitiy to now + timeout auth_data[i].valid_until = now + config.webserver.sessionTimeout.v.ui; + // Set remote address strncpy(auth_data[i].remote_addr, api->request->remote_addr, sizeof(auth_data[i].remote_addr)); auth_data[i].remote_addr[sizeof(auth_data[i].remote_addr)-1] = '\0'; + // Store user-agent (if available) + const char *user_agent = mg_get_header(api->conn, "user-agent"); + if(user_agent != NULL) + { + strncpy(auth_data[i].user_agent, user_agent, sizeof(auth_data[i].user_agent)); + auth_data[i].user_agent[sizeof(auth_data[i].user_agent)-1] = '\0'; + } + else + { + auth_data[i].user_agent[0] = '\0'; + } + + // Generate new SID generateSID(auth_data[i].sid); user_id = i; @@ -605,3 +645,11 @@ char * __attribute__((malloc)) hash_password(const char *password) return strdup(response); } + +int api_auth_sessions(struct ftl_conn *api) +{ + // Get session object + cJSON *json = JSON_NEW_OBJECT(); + get_all_sessions(api, json); + JSON_SEND_OBJECT(json); +} diff --git a/src/api/docs/content/specs/auth.yaml b/src/api/docs/content/specs/auth.yaml index 5303e5a5..4b862a1f 100644 --- a/src/api/docs/content/specs/auth.yaml +++ b/src/api/docs/content/specs/auth.yaml @@ -105,6 +105,26 @@ components: examples: login_failed: $ref: 'auth.yaml#/components/examples/login_failed' + sessions_list: + get: + summary: List of all current sessions + tags: + - Authentication + operationId: "get_auth_sessions" + description: List of all current sessions including their validity and further information about the client such as the IP address and user agent. + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: 'auth.yaml#/components/schemas/sessions_list' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: 'common.yaml#/components/errors/unauthorized' schemas: session: @@ -143,6 +163,41 @@ components: type: string description: Response to previous challenge example: abcdef + sessions_list: + type: object + description: List of all current sessions + properties: + sessions: + type: array + items: + type: object + description: Session object + properties: + id: + type: number + description: Session ID + valid: + type: boolean + description: Valid session indicator (existing sessions may be invalid due to timeout) + last_active: + type: integer + description: Timestamp of last activity (seconds since epoch) + valid_until: + type: integer + description: Timestamp of session expiration (seconds since epoch) + remote_addr: + type: string + description: IP address of the client + user_agent: + type: string + description: User agent of the client + example: + - id: 1 + valid: true + last_active: 1580000000 + valid_until: 1580000300 + remote_addr: "192.168.0.34" + user_agent: "Mozilla/5.0 (X11; Linux x86_64; rv:107.0) Gecko/20100101 Firefox/107.0" errors: bad_request: diff --git a/src/api/docs/content/specs/main.yaml b/src/api/docs/content/specs/main.yaml index 1ef60946..ee9581d7 100644 --- a/src/api/docs/content/specs/main.yaml +++ b/src/api/docs/content/specs/main.yaml @@ -57,6 +57,9 @@ paths: /auth: $ref: 'auth.yaml#/components/paths/auth' + /auth/sessions: + $ref: 'auth.yaml#/components/paths/sessions_list' + /stats/summary: $ref: 'stats.yaml#/components/paths/summary'