Bind Serial Transports To Web Sessions
This commit is contained in:
+104
-17
@@ -49,6 +49,7 @@ typedef struct {
|
||||
uint8_t digest[WEB_SERIAL_SHA256_BYTES];
|
||||
int64_t expires_at_us;
|
||||
user_principal_t principal;
|
||||
web_session_id_t web_session_id;
|
||||
bool active;
|
||||
} web_serial_ticket_t;
|
||||
|
||||
@@ -68,6 +69,7 @@ typedef struct web_serial_slot {
|
||||
uint32_t generation;
|
||||
session_broker_client_id_t broker_client_id;
|
||||
user_principal_t principal;
|
||||
web_session_id_t web_session_id;
|
||||
int64_t next_currentness_check_us;
|
||||
bool writer;
|
||||
bool hello_pending;
|
||||
@@ -95,6 +97,16 @@ static web_serial_slot_t s_slots[WEB_SERIAL_TRANSPORT_MAX_SESSIONS];
|
||||
static web_serial_transport_counters_t s_counters;
|
||||
static uint32_t s_httpd_close_operations;
|
||||
static uint32_t s_inflight_handlers;
|
||||
/* Cancels ticket publication across revocation and server detach/re-attach.
|
||||
* Never wraps: exhaustion disables minting for the remainder of the boot. */
|
||||
static uint64_t s_ticket_epoch;
|
||||
|
||||
static esp_err_t identity_is_current(const user_principal_t *principal,
|
||||
web_session_id_t id, bool *current)
|
||||
{
|
||||
return id == 0U ? user_database_principal_is_current(principal, current)
|
||||
: web_session_store_check_principal(id, principal, current);
|
||||
}
|
||||
|
||||
static TickType_t milliseconds_to_ticks(uint32_t milliseconds)
|
||||
{
|
||||
@@ -146,6 +158,7 @@ static void clear_ticket_locked(web_serial_ticket_t *ticket)
|
||||
secure_wipe(ticket->digest, sizeof(ticket->digest));
|
||||
secure_wipe(&ticket->principal, sizeof(ticket->principal));
|
||||
ticket->expires_at_us = 0;
|
||||
ticket->web_session_id = 0U;
|
||||
ticket->active = false;
|
||||
}
|
||||
|
||||
@@ -281,6 +294,7 @@ static esp_err_t validate_origin(httpd_req_t *request)
|
||||
}
|
||||
|
||||
static esp_err_t consume_ticket(const char *ticket,
|
||||
web_session_id_t web_session_id,
|
||||
user_principal_t *principal, bool *consumed)
|
||||
{
|
||||
uint8_t digest[WEB_SERIAL_SHA256_BYTES] = {0};
|
||||
@@ -315,7 +329,8 @@ static esp_err_t consume_ticket(const char *ticket,
|
||||
if (matching_count == 1U &&
|
||||
matching_index < WEB_SERIAL_TRANSPORT_MAX_TICKETS) {
|
||||
web_serial_ticket_t *entry = &s_tickets[matching_index];
|
||||
if (entry->active && entry->expires_at_us > now_us) {
|
||||
if (entry->active && entry->expires_at_us > now_us &&
|
||||
entry->web_session_id == web_session_id) {
|
||||
candidate = entry->principal;
|
||||
clear_ticket_locked(entry);
|
||||
ticket_found = true;
|
||||
@@ -336,7 +351,7 @@ static esp_err_t consume_ticket(const char *ticket,
|
||||
|
||||
if (ticket_found) {
|
||||
bool current = false;
|
||||
result = user_database_principal_is_current(&candidate, ¤t);
|
||||
result = identity_is_current(&candidate, web_session_id, ¤t);
|
||||
if (result == ESP_OK && current) {
|
||||
*principal = candidate;
|
||||
*consumed = true;
|
||||
@@ -371,6 +386,7 @@ static web_serial_slot_t *reserve_slot(httpd_handle_t server, int socket_fd,
|
||||
slot->socket_fd = socket_fd;
|
||||
slot->broker_client_id = SESSION_BROKER_NO_CLIENT;
|
||||
secure_wipe(&slot->principal, sizeof(slot->principal));
|
||||
slot->web_session_id = 0U;
|
||||
slot->next_currentness_check_us = 0;
|
||||
slot->writer = false;
|
||||
slot->hello_pending = false;
|
||||
@@ -396,6 +412,7 @@ static void make_slot_free_locked(web_serial_slot_t *slot)
|
||||
slot->socket_fd = -1;
|
||||
slot->broker_client_id = SESSION_BROKER_NO_CLIENT;
|
||||
secure_wipe(&slot->principal, sizeof(slot->principal));
|
||||
slot->web_session_id = 0U;
|
||||
slot->next_currentness_check_us = 0;
|
||||
slot->writer = false;
|
||||
slot->hello_pending = false;
|
||||
@@ -512,7 +529,8 @@ static esp_err_t send_plain_bad_request(httpd_req_t *request)
|
||||
return result;
|
||||
}
|
||||
|
||||
static esp_err_t connect_websocket(httpd_req_t *request, int socket_fd)
|
||||
static esp_err_t connect_websocket(httpd_req_t *request, int socket_fd,
|
||||
web_session_id_t web_session_id)
|
||||
{
|
||||
char ticket[WEB_SERIAL_TRANSPORT_TICKET_CAPACITY] = {0};
|
||||
uint32_t slot_generation = 0U;
|
||||
@@ -535,7 +553,7 @@ static esp_err_t connect_websocket(httpd_req_t *request, int socket_fd)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
result = consume_ticket(ticket, &principal, &consumed);
|
||||
result = consume_ticket(ticket, web_session_id, &principal, &consumed);
|
||||
if (result != ESP_OK || !consumed) {
|
||||
release_reserved_slot(slot, slot_generation);
|
||||
result = ESP_FAIL;
|
||||
@@ -547,6 +565,7 @@ static esp_err_t connect_websocket(httpd_req_t *request, int socket_fd)
|
||||
!slot->close_requested;
|
||||
if (principal_staged) {
|
||||
slot->principal = principal;
|
||||
slot->web_session_id = web_session_id;
|
||||
}
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
if (!principal_staged) {
|
||||
@@ -571,7 +590,7 @@ static esp_err_t connect_websocket(httpd_req_t *request, int socket_fd)
|
||||
}
|
||||
|
||||
bool principal_current = false;
|
||||
result = user_database_principal_is_current(&principal, &principal_current);
|
||||
result = identity_is_current(&principal, web_session_id, &principal_current);
|
||||
if (result != ESP_OK || !principal_current) {
|
||||
release_reserved_slot(slot, slot_generation);
|
||||
result = ESP_FAIL;
|
||||
@@ -602,7 +621,7 @@ static esp_err_t connect_websocket(httpd_req_t *request, int socket_fd)
|
||||
}
|
||||
|
||||
principal_current = false;
|
||||
result = user_database_principal_is_current(&principal, &principal_current);
|
||||
result = identity_is_current(&principal, web_session_id, &principal_current);
|
||||
if (result != ESP_OK || !principal_current) {
|
||||
close_unpublished_broker_session(slot, slot_generation, client_id);
|
||||
result = ESP_FAIL;
|
||||
@@ -625,7 +644,7 @@ static esp_err_t connect_websocket(httpd_req_t *request, int socket_fd)
|
||||
}
|
||||
|
||||
principal_current = false;
|
||||
result = user_database_principal_is_current(&principal, &principal_current);
|
||||
result = identity_is_current(&principal, web_session_id, &principal_current);
|
||||
if (result != ESP_OK || !principal_current) {
|
||||
close_unpublished_broker_session(slot, slot_generation, client_id);
|
||||
result = ESP_FAIL;
|
||||
@@ -672,7 +691,8 @@ cleanup:
|
||||
static bool capture_active_session(httpd_req_t *request, web_serial_slot_t **slot_out,
|
||||
uint32_t *generation,
|
||||
session_broker_client_id_t *client_id,
|
||||
user_principal_t *principal)
|
||||
user_principal_t *principal,
|
||||
web_session_id_t *web_session_id)
|
||||
{
|
||||
web_serial_slot_t *slot = request->sess_ctx;
|
||||
int socket_fd = httpd_req_to_sockfd(request);
|
||||
@@ -692,6 +712,7 @@ static bool capture_active_session(httpd_req_t *request, web_serial_slot_t **slo
|
||||
*generation = slot->generation;
|
||||
*client_id = slot->broker_client_id;
|
||||
*principal = slot->principal;
|
||||
*web_session_id = slot->web_session_id;
|
||||
}
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
return valid;
|
||||
@@ -803,8 +824,9 @@ static esp_err_t process_websocket_frame(httpd_req_t *request)
|
||||
uint32_t generation = 0U;
|
||||
session_broker_client_id_t client_id = SESSION_BROKER_NO_CLIENT;
|
||||
user_principal_t principal = {0};
|
||||
web_session_id_t web_session_id = 0U;
|
||||
if (!capture_active_session(request, &slot, &generation, &client_id,
|
||||
&principal)) {
|
||||
&principal, &web_session_id)) {
|
||||
add_counter(&s_counters.protocol_errors, 1U);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
@@ -839,7 +861,7 @@ static esp_err_t process_websocket_frame(httpd_req_t *request)
|
||||
|
||||
bool current = false;
|
||||
esp_err_t currentness_result =
|
||||
user_database_principal_is_current(&principal, ¤t);
|
||||
identity_is_current(&principal, web_session_id, ¤t);
|
||||
secure_wipe(&principal, sizeof(principal));
|
||||
if (currentness_result != ESP_OK || !current) {
|
||||
request_handler_close(slot, generation);
|
||||
@@ -1253,6 +1275,7 @@ static void process_broker_disconnect(web_serial_slot_t *slot)
|
||||
static void process_principal_currentness(web_serial_slot_t *slot)
|
||||
{
|
||||
user_principal_t principal = {0};
|
||||
web_session_id_t web_session_id = 0U;
|
||||
uint32_t generation = 0U;
|
||||
bool check = false;
|
||||
int64_t now_us = monotonic_time_us();
|
||||
@@ -1262,6 +1285,7 @@ static void process_principal_currentness(web_serial_slot_t *slot)
|
||||
slot->next_currentness_check_us <= now_us) {
|
||||
generation = slot->generation;
|
||||
principal = slot->principal;
|
||||
web_session_id = slot->web_session_id;
|
||||
slot->next_currentness_check_us =
|
||||
now_us + WEB_SERIAL_CURRENTNESS_INTERVAL_US;
|
||||
check = true;
|
||||
@@ -1272,7 +1296,7 @@ static void process_principal_currentness(web_serial_slot_t *slot)
|
||||
}
|
||||
|
||||
bool current = false;
|
||||
esp_err_t result = user_database_principal_is_current(&principal, ¤t);
|
||||
esp_err_t result = identity_is_current(&principal, web_session_id, ¤t);
|
||||
secure_wipe(&principal, sizeof(principal));
|
||||
if (result == ESP_OK && current) {
|
||||
return;
|
||||
@@ -1417,6 +1441,9 @@ esp_err_t web_serial_transport_attach_server(httpd_handle_t server)
|
||||
} else {
|
||||
clear_all_tickets_locked();
|
||||
s_server = server;
|
||||
if (s_ticket_epoch != UINT64_MAX) {
|
||||
++s_ticket_epoch;
|
||||
}
|
||||
}
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
if (result == ESP_OK) {
|
||||
@@ -1443,6 +1470,9 @@ esp_err_t web_serial_transport_detach_server(httpd_handle_t server)
|
||||
* observes the cleared server and disconnects unpublished broker state.
|
||||
*/
|
||||
s_server = NULL;
|
||||
if (s_ticket_epoch != UINT64_MAX) {
|
||||
++s_ticket_epoch;
|
||||
}
|
||||
clear_all_tickets_locked();
|
||||
for (size_t index = 0U; index < WEB_SERIAL_TRANSPORT_MAX_SESSIONS;
|
||||
++index) {
|
||||
@@ -1524,6 +1554,7 @@ esp_err_t web_serial_transport_detach_server(httpd_handle_t server)
|
||||
}
|
||||
|
||||
esp_err_t web_serial_transport_mint_ticket(const user_principal_t *principal,
|
||||
web_session_id_t web_session_id,
|
||||
char *ticket, size_t capacity)
|
||||
{
|
||||
if (principal == NULL || ticket == NULL ||
|
||||
@@ -1532,8 +1563,12 @@ esp_err_t web_serial_transport_mint_ticket(const user_principal_t *principal,
|
||||
}
|
||||
ticket[0] = '\0';
|
||||
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
uint64_t epoch = s_ticket_epoch;
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
|
||||
bool current = false;
|
||||
esp_err_t result = user_database_principal_is_current(principal, ¤t);
|
||||
esp_err_t result = identity_is_current(principal, web_session_id, ¤t);
|
||||
if (result != ESP_OK) {
|
||||
return result;
|
||||
}
|
||||
@@ -1553,10 +1588,18 @@ esp_err_t web_serial_transport_mint_ticket(const user_principal_t *principal,
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
current = false;
|
||||
result = identity_is_current(principal, web_session_id, ¤t);
|
||||
if (result != ESP_OK || !current) {
|
||||
result = ESP_ERR_INVALID_STATE;
|
||||
ticket[0] = '\0';
|
||||
goto cleanup;
|
||||
}
|
||||
int64_t now_us = monotonic_time_us();
|
||||
bool stored = false;
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
if (s_initialized && s_server != NULL) {
|
||||
if (s_initialized && s_server != NULL && epoch == s_ticket_epoch &&
|
||||
epoch != UINT64_MAX) {
|
||||
purge_tickets_locked(now_us);
|
||||
size_t selected = WEB_SERIAL_TRANSPORT_MAX_TICKETS;
|
||||
int64_t oldest_expiry = INT64_MAX;
|
||||
@@ -1580,6 +1623,7 @@ esp_err_t web_serial_transport_mint_ticket(const user_principal_t *principal,
|
||||
now_us + (int64_t)WEB_SERIAL_TRANSPORT_TICKET_LIFETIME_SECONDS *
|
||||
1000000LL;
|
||||
entry->principal = *principal;
|
||||
entry->web_session_id = web_session_id;
|
||||
entry->active = true;
|
||||
++s_counters.tickets_issued;
|
||||
stored = true;
|
||||
@@ -1599,7 +1643,8 @@ cleanup:
|
||||
}
|
||||
|
||||
esp_err_t web_serial_transport_handle_authenticated_ticket_request(
|
||||
httpd_req_t *request, const user_principal_t *principal)
|
||||
httpd_req_t *request, const user_principal_t *principal,
|
||||
web_session_id_t web_session_id)
|
||||
{
|
||||
if (request == NULL || principal == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
@@ -1620,7 +1665,7 @@ esp_err_t web_serial_transport_handle_authenticated_ticket_request(
|
||||
char ticket[WEB_SERIAL_TRANSPORT_TICKET_CAPACITY] = {0};
|
||||
char response[WEB_SERIAL_TICKET_RESPONSE_CAPACITY];
|
||||
esp_err_t result = web_serial_transport_mint_ticket(
|
||||
principal, ticket, sizeof(ticket));
|
||||
principal, web_session_id, ticket, sizeof(ticket));
|
||||
if (result != ESP_OK) {
|
||||
secure_wipe(ticket, sizeof(ticket));
|
||||
return result;
|
||||
@@ -1652,6 +1697,12 @@ esp_err_t web_serial_transport_handle_authenticated_ticket_request(
|
||||
}
|
||||
|
||||
esp_err_t web_serial_transport_ws_handler(httpd_req_t *request)
|
||||
{
|
||||
return web_serial_transport_session_ws_handler(request, 0U);
|
||||
}
|
||||
|
||||
esp_err_t web_serial_transport_session_ws_handler(httpd_req_t *request,
|
||||
web_session_id_t web_session_id)
|
||||
{
|
||||
if (request == NULL || request->handle == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
@@ -1686,7 +1737,7 @@ esp_err_t web_serial_transport_ws_handler(httpd_req_t *request)
|
||||
esp_err_t result;
|
||||
if (request->sess_ctx == NULL) {
|
||||
/* IDF has already sent 101; authentication failures must only close. */
|
||||
result = connect_websocket(request, socket_fd);
|
||||
result = connect_websocket(request, socket_fd, web_session_id);
|
||||
} else {
|
||||
result = process_websocket_frame(request);
|
||||
}
|
||||
@@ -1769,8 +1820,12 @@ esp_err_t web_serial_transport_revoke_user(const uint8_t *username,
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
web_session_store_invalidate_username(username, username_length);
|
||||
bool notify = false;
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
if (s_ticket_epoch != UINT64_MAX) {
|
||||
++s_ticket_epoch;
|
||||
}
|
||||
if (!s_initialized) {
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
@@ -1801,7 +1856,11 @@ esp_err_t web_serial_transport_revoke_user(const uint8_t *username,
|
||||
|
||||
esp_err_t web_serial_transport_revoke_sessions(void)
|
||||
{
|
||||
web_session_store_invalidate_username(NULL, 0U);
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
if (s_ticket_epoch != UINT64_MAX) {
|
||||
++s_ticket_epoch;
|
||||
}
|
||||
if (!s_initialized) {
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
@@ -1810,7 +1869,35 @@ esp_err_t web_serial_transport_revoke_sessions(void)
|
||||
clear_all_tickets_locked();
|
||||
for (size_t index = 0U; index < WEB_SERIAL_TRANSPORT_MAX_SESSIONS; ++index) {
|
||||
web_serial_slot_t *slot = &s_slots[index];
|
||||
if (slot->state == WEB_SERIAL_SLOT_ACTIVE) {
|
||||
if (slot->state == WEB_SERIAL_SLOT_ACTIVE ||
|
||||
slot->state == WEB_SERIAL_SLOT_RESERVED) {
|
||||
slot->close_requested = true;
|
||||
}
|
||||
}
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
notify_transport_task();
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t web_serial_transport_revoke_web_session(web_session_id_t id)
|
||||
{
|
||||
if (id == 0U) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
web_session_store_invalidate(id);
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
if (s_ticket_epoch != UINT64_MAX) {
|
||||
++s_ticket_epoch;
|
||||
}
|
||||
for (size_t i = 0U; i < WEB_SERIAL_TRANSPORT_MAX_TICKETS; ++i) {
|
||||
if (s_tickets[i].active && s_tickets[i].web_session_id == id) {
|
||||
clear_ticket_locked(&s_tickets[i]);
|
||||
}
|
||||
}
|
||||
for (size_t i = 0U; i < WEB_SERIAL_TRANSPORT_MAX_SESSIONS; ++i) {
|
||||
web_serial_slot_t *slot = &s_slots[i];
|
||||
if ((slot->state == WEB_SERIAL_SLOT_RESERVED ||
|
||||
slot->state == WEB_SERIAL_SLOT_ACTIVE) && slot->web_session_id == id) {
|
||||
slot->close_requested = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "esp_http_server.h"
|
||||
#include "session_broker.h"
|
||||
#include "user_database.h"
|
||||
#include "web_session_store.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -98,20 +99,24 @@ esp_err_t web_serial_transport_attach_server(httpd_handle_t server);
|
||||
esp_err_t web_serial_transport_detach_server(httpd_handle_t server);
|
||||
|
||||
/*
|
||||
* Mint a one-time bearer ticket bound to a current authenticated principal. The
|
||||
* Mint a one-time bearer ticket bound to a current authenticated principal and
|
||||
* originating web-session ID (zero only for the shipped Basic path). The
|
||||
* principal is copied; the output is exactly 32 Base64URL characters plus a
|
||||
* terminator and expires after 30 monotonic seconds. Never log or persist it.
|
||||
*/
|
||||
esp_err_t web_serial_transport_mint_ticket(const user_principal_t *principal,
|
||||
web_session_id_t web_session_id,
|
||||
char *ticket, size_t capacity);
|
||||
|
||||
/*
|
||||
* Convenience POST response helper for /api/ws-ticket. Authentication is
|
||||
* intentionally outside this module: pass the principal returned by successful
|
||||
* Basic authentication. Register it as HTTP_POST, not as a public handler.
|
||||
* authentication, and its session ID (zero for Basic). Cookie callers must also
|
||||
* enforce CSRF/Origin policy. Register as HTTP_POST, not as a public handler.
|
||||
*/
|
||||
esp_err_t web_serial_transport_handle_authenticated_ticket_request(
|
||||
httpd_req_t *request, const user_principal_t *principal);
|
||||
httpd_req_t *request, const user_principal_t *principal,
|
||||
web_session_id_t web_session_id);
|
||||
|
||||
/*
|
||||
* Handler for /ws/serial. Register as HTTP_GET with is_websocket=true and
|
||||
@@ -119,6 +124,13 @@ esp_err_t web_serial_transport_handle_authenticated_ticket_request(
|
||||
* ticket; later invocations process one complete data frame.
|
||||
*/
|
||||
esp_err_t web_serial_transport_ws_handler(httpd_req_t *request);
|
||||
/* Trusted future cookie-authorized upgrade caller; must validate cookie/Origin
|
||||
* first. Zero identifies only the shipped Basic path, never a cookie fallback. */
|
||||
esp_err_t web_serial_transport_session_ws_handler(httpd_req_t *request,
|
||||
web_session_id_t web_session_id);
|
||||
/* Invalidates the store first, then marks only matching tickets/slots for owner
|
||||
* cleanup. Safe to repeat after either store or transport slot reuse. */
|
||||
esp_err_t web_serial_transport_revoke_web_session(web_session_id_t id);
|
||||
|
||||
esp_err_t web_serial_transport_get_snapshot(
|
||||
web_serial_transport_snapshot_t *snapshot);
|
||||
@@ -126,7 +138,9 @@ esp_err_t web_serial_transport_get_snapshot(
|
||||
/* Clearing counters does not alter tickets, sessions, ownership, or queued data. */
|
||||
esp_err_t web_serial_transport_clear_counters(void);
|
||||
|
||||
/* Invalidate tickets/sessions for one account, or all authenticated sessions. */
|
||||
/* Invalidate cookie records and tickets/sockets for one username (also after
|
||||
* deletion), or all accounts. Store invalidation occurs even if serial init
|
||||
* failed; these do not touch the HTTPD-owned Basic cache, which rechecks DB. */
|
||||
esp_err_t web_serial_transport_revoke_user(const uint8_t *username,
|
||||
size_t username_length);
|
||||
esp_err_t web_serial_transport_revoke_sessions(void);
|
||||
|
||||
+1
-1
@@ -346,7 +346,7 @@ static esp_err_t ticket_handler(httpd_req_t *request)
|
||||
}
|
||||
|
||||
error = web_serial_transport_handle_authenticated_ticket_request(
|
||||
request, &principal);
|
||||
request, &principal, 0U);
|
||||
secure_wipe(&principal, sizeof(principal));
|
||||
if (error == ESP_OK) {
|
||||
return ESP_OK;
|
||||
|
||||
+50
-4
@@ -164,7 +164,8 @@ void web_session_store_stop(void)
|
||||
|
||||
/* Never enter the database while holding our lock. On return, re-find the
|
||||
* non-reused ID and deadline: logout/stop/slot reuse may have raced the call. */
|
||||
static esp_err_t resolve(web_session_id_t id, web_session_view_t *view)
|
||||
static esp_err_t resolve(web_session_id_t id, web_session_view_t *view,
|
||||
const user_principal_t *expected)
|
||||
{
|
||||
session_entry_t candidate = {0};
|
||||
int64_t now = esp_timer_get_time();
|
||||
@@ -178,6 +179,16 @@ static esp_err_t resolve(web_session_id_t id, web_session_view_t *view)
|
||||
}
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
|
||||
if (error == ESP_OK && expected != NULL &&
|
||||
(candidate.principal.user_id != expected->user_id ||
|
||||
candidate.principal.auth_generation != expected->auth_generation ||
|
||||
candidate.principal.role != expected->role ||
|
||||
candidate.principal.method != expected->method ||
|
||||
candidate.principal.username_length != expected->username_length ||
|
||||
memcmp(candidate.principal.username, expected->username,
|
||||
candidate.principal.username_length) != 0)) {
|
||||
error = ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
if (error == ESP_OK) {
|
||||
bool current = false;
|
||||
error = user_database_principal_is_current(&candidate.principal, ¤t);
|
||||
@@ -214,7 +225,7 @@ void web_session_store_prune(void)
|
||||
web_session_id_t id = s_state.entries[i].id;
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
if (id != 0U) {
|
||||
(void)resolve(id, NULL);
|
||||
(void)resolve(id, NULL, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -347,7 +358,7 @@ esp_err_t web_session_store_lookup(
|
||||
}
|
||||
}
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
error = resolve(id, view);
|
||||
error = resolve(id, view, NULL);
|
||||
}
|
||||
secure_wipe(token_hash, sizeof(token_hash));
|
||||
secure_wipe(origin_hash, sizeof(origin_hash));
|
||||
@@ -360,13 +371,48 @@ esp_err_t web_session_store_is_current(web_session_id_t id, bool *current)
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
*current = false;
|
||||
esp_err_t error = resolve(id, NULL);
|
||||
esp_err_t error = resolve(id, NULL, NULL);
|
||||
if (error == ESP_OK) {
|
||||
*current = true;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
esp_err_t web_session_store_check_principal(web_session_id_t id,
|
||||
const user_principal_t *principal,
|
||||
bool *current)
|
||||
{
|
||||
if (current == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
*current = false;
|
||||
if (principal == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
esp_err_t error = resolve(id, NULL, principal);
|
||||
*current = error == ESP_OK;
|
||||
return error;
|
||||
}
|
||||
|
||||
void web_session_store_invalidate_username(const uint8_t *username, size_t length)
|
||||
{
|
||||
if ((username == NULL && length != 0U) ||
|
||||
(username != NULL && !user_database_username_valid(username, length))) {
|
||||
return;
|
||||
}
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
advance_epoch_locked();
|
||||
for (size_t i = 0U; i < WEB_SESSION_STORE_CAPACITY; ++i) {
|
||||
session_entry_t *entry = &s_state.entries[i];
|
||||
if (username == NULL ||
|
||||
(entry->principal.username_length == length &&
|
||||
memcmp(entry->principal.username, username, length) == 0)) {
|
||||
retire_locked(entry, false);
|
||||
}
|
||||
}
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
}
|
||||
|
||||
void web_session_store_invalidate(web_session_id_t id)
|
||||
{
|
||||
if (id == 0U) {
|
||||
|
||||
@@ -60,8 +60,15 @@ esp_err_t web_session_store_lookup(
|
||||
* authorization. Every successful lookup/check revalidates the principal.
|
||||
* No API result is a lease: recheck at later sensitive boundaries. */
|
||||
esp_err_t web_session_store_is_current(web_session_id_t id, bool *current);
|
||||
/* Also verifies that the transport's copied principal belongs to this ID. */
|
||||
esp_err_t web_session_store_check_principal(web_session_id_t id,
|
||||
const user_principal_t *principal,
|
||||
bool *current);
|
||||
void web_session_store_invalidate(web_session_id_t id);
|
||||
void web_session_store_invalidate_user(uint32_t user_id);
|
||||
/* Command notifications use names, including after account deletion. NULL/0
|
||||
* invalidates all records without disabling the store. */
|
||||
void web_session_store_invalidate_username(const uint8_t *username, size_t length);
|
||||
void web_session_store_prune(void);
|
||||
|
||||
/* Counts only: never token/digest/CSRF/principal material. Expired records are
|
||||
|
||||
Reference in New Issue
Block a user