From d3a5aba703c171b9b78892dfbe47c02a92aad670 Mon Sep 17 00:00:00 2001 From: Cory McWilliams Date: Tue, 17 Sep 2024 12:47:28 -0400 Subject: [PATCH] A brave new world where admin users can use the server identity. --- src/ssb.db.c | 21 +++++++++++++++++++++ src/ssb.db.h | 9 +++++++++ src/ssb.js.c | 24 ++++++++++++++++++++++++ tools/autotest.py | 31 ++++++++++++++++++++++++------- 4 files changed, 78 insertions(+), 7 deletions(-) diff --git a/src/ssb.db.c b/src/ssb.db.c index 0d7a1c0f..cacb4a88 100644 --- a/src/ssb.db.c +++ b/src/ssb.db.c @@ -1946,3 +1946,24 @@ bool tf_ssb_db_verify(tf_ssb_t* ssb, const char* id) } return verified; } + +bool tf_ssb_db_user_has_permission(tf_ssb_t* ssb, const char* id, const char* permission) +{ + bool has_permission = false; + sqlite3* db = tf_ssb_acquire_db_reader(ssb); + sqlite3_stmt* statement = NULL; + if (sqlite3_prepare(db, + "SELECT COUNT(*) FROM properties, json_each(properties.value -> 'permissions' -> ?) AS permission WHERE properties.id = 'core' AND properties.key = 'settings' AND " + "permission.value = ?", + -1, &statement, NULL) == SQLITE_OK) + { + if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK && sqlite3_bind_text(statement, 2, permission, -1, NULL) == SQLITE_OK && + sqlite3_step(statement) == SQLITE_ROW) + { + has_permission = sqlite3_column_int64(statement, 0) > 0; + } + sqlite3_finalize(statement); + } + tf_ssb_release_db_reader(ssb, db); + return has_permission; +} diff --git a/src/ssb.db.h b/src/ssb.db.h index 2b8d59b8..36b8b2f6 100644 --- a/src/ssb.db.h +++ b/src/ssb.db.h @@ -416,6 +416,15 @@ void tf_ssb_db_resolve_index_async(tf_ssb_t* ssb, const char* host, void (*callb */ bool tf_ssb_db_verify(tf_ssb_t* ssb, const char* id); +/** +** Check if a user has a specific permission. +** @param ssb The SSB instance. +** @param id The user ID. +** @param permission The name of the permission. +** @return true If the user has the requested permission. +*/ +bool tf_ssb_db_user_has_permission(tf_ssb_t* ssb, const char* id, const char* permission); + /** ** An SQLite authorizer callback. See https://www.sqlite.org/c3ref/set_authorizer.html for use. ** @param user_data User data registered with the authorizer. diff --git a/src/ssb.js.c b/src/ssb.js.c index fd2d49d8..ec59fd87 100644 --- a/src/ssb.js.c +++ b/src/ssb.js.c @@ -381,6 +381,14 @@ static void _tf_ssb_getIdentities_visit(const char* identity, void* user_data) static void _tf_ssb_get_identities_work(tf_ssb_t* ssb, void* user_data) { identities_visit_t* work = user_data; + if (tf_ssb_db_user_has_permission(ssb, work->user, "administration")) + { + char id[k_id_base64_len] = ""; + if (tf_ssb_whoami(ssb, id, sizeof(id))) + { + _tf_ssb_getIdentities_visit(*id == '@' ? id + 1 : id, work); + } + } tf_ssb_db_identity_visit(ssb, work->user, _tf_ssb_getIdentities_visit, user_data); } @@ -445,6 +453,10 @@ static void _tf_ssb_get_private_key_work(tf_ssb_t* ssb, void* user_data) { get_private_key_t* work = user_data; work->got_private_key = tf_ssb_db_identity_get_private_key(ssb, work->user, work->id, work->private_key, sizeof(work->private_key)); + if (!work->got_private_key && tf_ssb_db_user_has_permission(ssb, work->user, "administration")) + { + work->got_private_key = tf_ssb_db_identity_get_private_key(ssb, ":admin", work->id, work->private_key, sizeof(work->private_key)); + } } static void _tf_ssb_get_private_key_after_work(tf_ssb_t* ssb, int status, void* user_data) @@ -625,6 +637,14 @@ static void _tf_ssb_getIdentityInfo_visit(const char* identity, void* data) static void _tf_ssb_getIdentityInfo_work(tf_ssb_t* ssb, void* user_data) { identity_info_work_t* request = user_data; + if (tf_ssb_db_user_has_permission(ssb, request->name, "administration")) + { + char id[k_id_base64_len] = ""; + if (tf_ssb_whoami(ssb, id, sizeof(id))) + { + _tf_ssb_getIdentityInfo_visit(*id == '@' ? id + 1 : id, request); + } + } tf_ssb_db_identity_visit(ssb, request->name, _tf_ssb_getIdentityInfo_visit, request); sqlite3* db = tf_ssb_acquire_db_reader(ssb); @@ -777,6 +797,10 @@ static void _tf_ssb_append_message_with_identity_get_key_work(tf_ssb_t* ssb, voi { append_message_t* work = user_data; work->got_private_key = tf_ssb_db_identity_get_private_key(ssb, work->user, work->id, work->private_key, sizeof(work->private_key)); + if (!work->got_private_key && tf_ssb_db_user_has_permission(ssb, work->user, "administration")) + { + work->got_private_key = tf_ssb_db_identity_get_private_key(ssb, ":admin", work->id, work->private_key, sizeof(work->private_key)); + } tf_ssb_db_get_latest_message_by_author(ssb, work->id, &work->previous_sequence, work->previous_id, sizeof(work->previous_id)); } diff --git a/tools/autotest.py b/tools/autotest.py index a5f43fe3..534764bf 100755 --- a/tools/autotest.py +++ b/tools/autotest.py @@ -25,6 +25,30 @@ try: #options.add_argument('--headless') driver = webdriver.Firefox(options = options, service = service) wait = WebDriverWait(driver, 10) + + driver.get('http://localhost:8888') + driver.find_element(By.TAG_NAME, 'tf-navigation').shadow_root.find_element(By.LINK_TEXT, 'login').click() + driver.find_element(By.TAG_NAME, 'tf-auth').shadow_root.find_element(By.ID, 'register_label').click() + driver.find_element(By.TAG_NAME, 'tf-auth').shadow_root.find_element(By.ID, 'name').send_keys('adminuser') + driver.find_element(By.TAG_NAME, 'tf-auth').shadow_root.find_element(By.ID, 'password').send_keys('admin_password') + driver.find_element(By.TAG_NAME, 'tf-auth').shadow_root.find_element(By.ID, 'confirm').send_keys('admin_password') + driver.find_element(By.TAG_NAME, 'tf-auth').shadow_root.find_element(By.ID, 'loginButton').click() + wait.until(expected_conditions.presence_of_element_located((By.ID, 'document'))) + driver.switch_to.frame(driver.find_element(By.ID, 'document')) + wait.until(expected_conditions.presence_of_element_located((By.LINK_TEXT, 'identity'))) + driver.switch_to.default_content() + + driver.get('http://localhost:8888/~core/admin/') + wait.until(expected_conditions.presence_of_element_located((By.ID, 'document'))) + driver.switch_to.frame(driver.find_element(By.ID, 'document')) + wait.until(expected_conditions.presence_of_element_located((By.ID, 'gs_room_name'))).send_keys('test room') + wait.until(expected_conditions.presence_of_element_located((By.XPATH, '//*[@id="gs_room_name"]/following-sibling::button'))).click() + driver.switch_to.alert.accept() + driver.switch_to.default_content() + + driver.find_element(By.TAG_NAME, 'tf-navigation').shadow_root.find_element(By.ID, 'identity').click() + driver.find_element(By.TAG_NAME, 'tf-navigation').shadow_root.find_element(By.ID, 'logout').click() + driver.get('http://localhost:8888') driver.find_element(By.TAG_NAME, 'tf-navigation').shadow_root.find_element(By.LINK_TEXT, 'login').click() driver.find_element(By.TAG_NAME, 'tf-auth').shadow_root.find_element(By.ID, 'register_label').click() @@ -82,13 +106,6 @@ try: driver.switch_to.frame(wait.until(expected_conditions.presence_of_element_located((By.ID, 'document')))) id1 = wait.until(expected_conditions.presence_of_element_located((By.TAG_NAME, 'li'))).text.split(' ')[-1] - driver.get('http://localhost:8888/~core/admin/') - wait.until(expected_conditions.presence_of_element_located((By.ID, 'document'))) - driver.switch_to.frame(driver.find_element(By.ID, 'document')) - wait.until(expected_conditions.presence_of_element_located((By.ID, 'gs_room_name'))).send_keys('test room') - wait.until(expected_conditions.presence_of_element_located((By.XPATH, '//*[@id="gs_room_name"]/following-sibling::button'))).click() - driver.switch_to.alert.accept() - driver.get('http://localhost:8888') wait.until(expected_conditions.presence_of_element_located((By.ID, 'document'))) driver.switch_to.frame(driver.find_element(By.ID, 'document'))