implement UI for credentials settings

Mon, 28 Oct 2024 15:20:58 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Mon, 28 Oct 2024 15:20:58 +0100
changeset 65
48f43130b4a2
parent 64
98d0e2516f4e
child 66
eee1f3092844

implement UI for credentials settings

application/config.c file | annotate | diff | comparison | revisions
application/config.h file | annotate | diff | comparison | revisions
application/settings.c file | annotate | diff | comparison | revisions
application/settings.h file | annotate | diff | comparison | revisions
libidav/pwdstore.c file | annotate | diff | comparison | revisions
libidav/pwdstore.h file | annotate | diff | comparison | revisions
ui/common/types.c file | annotate | diff | comparison | revisions
ui/gtk/list.c file | annotate | diff | comparison | revisions
ui/gtk/window.c file | annotate | diff | comparison | revisions
ui/ui/window.h file | annotate | diff | comparison | revisions
--- a/application/config.c	Mon Oct 28 07:37:45 2024 +0100
+++ b/application/config.c	Mon Oct 28 15:20:58 2024 +0100
@@ -265,6 +265,13 @@
     return pstore;
 }
 
+void set_pwdstore(PwdStore *newstore) {
+    if(pstore) {
+        // TODO: free
+    }
+    pstore = newstore;
+}
+
 int pwdstore_save(PwdStore *pwdstore) {
     if(check_config_dir()) {
         return 1;
--- a/application/config.h	Mon Oct 28 07:37:45 2024 +0100
+++ b/application/config.h	Mon Oct 28 15:20:58 2024 +0100
@@ -62,6 +62,7 @@
 cxmutstr load_key_file(const char *filename);
 
 PwdStore* get_pwdstore(void);
+void set_pwdstore(PwdStore *newstore);
 int pwdstore_save(PwdStore *pwdstore);
 
 
--- a/application/settings.c	Mon Oct 28 07:37:45 2024 +0100
+++ b/application/settings.c	Mon Oct 28 15:20:58 2024 +0100
@@ -29,12 +29,16 @@
 #include "settings.h"
 
 #include <cx/list.h>
+#include <cx/array_list.h>
+#include <cx/printf.h>
 #include <libidav/utils.h>
 
 
+
 #define SETTINGS_STATE_REPOLIST_SELECTED 1
-
 #define SETTINGS_STATE_REPO_ENCRYPTION 20
+#define SETTINGS_STATE_CREDENTIALS_SELECTED 30
+#define SETTINGS_STATE_CREDENTIALS_LOCATION_SELECTED 31
 
 
 static void repolist_activate(UiEvent *event, void *userdata) {
@@ -89,17 +93,204 @@
     ui_set(settings->repo_tabview, 0);
 }
 
+static void credentials_add(UiEvent *event, void *userdata) {
+    SettingsWindow *settings = event->window;
+    if(settings_credentials_save(settings)) {
+        return;
+    }
+    if(settings->credentials_list_needs_update) {
+        settings->credentials_ignore_selectionevent = TRUE;
+        ui_list_update(settings->credentials_users);
+        settings_reload_repo_credentials(settings);
+        settings->credentials_ignore_selectionevent = FALSE;
+        settings->credentials_list_needs_update = FALSE;
+    }
+    settings_credentials_clear(settings);
+    settings->credentials_new = TRUE;
+    PwdStore *pwd = settings->pwdstore;
+    if(!pwd->isdecrypted) {
+        settings_credentials_decrypt(settings);
+        return;
+    }
+    settings_credentials_select(settings, NULL);
+}
+
+static void credentials_remove(UiEvent *event, void *userdata) {
+    SettingsWindow *settings = event->window;
+    if(settings->credentials_selected_id) {
+        pwdstore_remove_entry(settings->pwdstore, settings->credentials_selected_id);
+        ui_list_remove(settings->credentials_users, settings->credentials_selected_index);
+        ui_list_update(settings->credentials_users);
+        settings_reload_repo_credentials(settings);
+    }
+}
+
+static void credentials_onselect(UiEvent *event, void *userdata) {
+    SettingsWindow *settings = event->window;
+    if(settings->credentials_ignore_selectionevent) {
+        return;
+    }
+    UiListSelection *sel = event->eventdata;
+    if(settings_credentials_save(settings)) {
+        return;
+    }
+    if(sel->count > 0) {
+        const char *id = ui_list_get(settings->credentials_users, sel->rows[0]);
+        settings->credentials_selected_index = sel->rows[0];
+        settings_credentials_select(settings, id);
+        if(settings->credentials_list_needs_update) {
+            settings->credentials_ignore_selectionevent = TRUE;
+            ui_list_update(settings->credentials_users);
+            settings_reload_repo_credentials(settings);
+            ui_list_setselection(settings->credentials_users, sel->rows[0]);
+            settings->credentials_ignore_selectionevent = FALSE;
+            settings->credentials_list_needs_update = FALSE;
+        }
+    } else {
+        settings_credentials_clear(settings);
+    }
+}
+
+static void c_add_location(UiEvent *event, void *userdata) {
+    SettingsWindow *settings = event->window;
+    if(event->intval == 1) {
+        ui_list_append(settings->credentials_locations, ui_strdup(event->obj->ctx, event->eventdata));
+        ui_list_update(settings->credentials_locations);
+    }
+}
+
+static void credentials_location_add(UiEvent *event, void *userdata) {
+    SettingsWindow *settings = event->window;
+    ui_dialog(event->obj,
+            .title = "Add Location",
+            .content = "New Location URL",
+            .input = TRUE,
+            .result = c_add_location,
+            .button1_label = "Add Location",
+            .closebutton_label = "Cancel");
+}
+
+static void c_edit_location(UiEvent *event, void *userdata) {
+    SettingsWindow *settings = event->window;
+    if(event->intval == 1) {
+        CxList *list = settings->credentials_locations->data;
+        ssize_t i = cxListFind(list, userdata);
+        if(i >= 0) {
+            cxListRemove(list, i);
+            cxListInsert(list, i, ui_strdup(event->obj->ctx, event->eventdata));
+            ui_list_update(settings->credentials_locations);
+        }
+    }
+}
+
+static void credentials_location_edit(UiEvent *event, void *userdata) {
+    SettingsWindow *settings = event->window;
+    char *location = ui_list_get(settings->credentials_locations, settings->credentials_location_selected_index);
+    ui_dialog(event->obj,
+            .title = "Edit Location",
+            .content = "Location URL",
+            .input_value = location,
+            .input = TRUE,
+            .result = c_edit_location,
+            .resultdata = location,
+            .button1_label = "Edit Location",
+            .closebutton_label = "Cancel");
+}
+
+static void credentials_location_remove(UiEvent *event, void *userdata) {
+    SettingsWindow *settings = event->window;
+    if(settings->credentials_location_selected_index >= 0) {
+        CxList *list = settings->credentials_locations->data;
+        cxListRemove(list, settings->credentials_location_selected_index);
+        ui_list_update(settings->credentials_locations);
+    }
+}
+
+static void credentials_location_up(UiEvent *event, void *userdata) {
+    SettingsWindow *settings = event->window;
+    int index = settings->credentials_location_selected_index;
+    if(index >= 1) {
+        CxList *list = settings->credentials_locations->data;
+        cxListSwap(list, index, index-1);
+        ui_list_update(settings->credentials_locations);
+        ui_list_setselection(settings->credentials_locations, index-1);
+    }
+}
+
+static void credentials_location_down(UiEvent *event, void *userdata) {
+    SettingsWindow *settings = event->window;
+    int index = settings->credentials_location_selected_index;
+    if(index >= 0 && index + 1 < ui_list_count(settings->credentials_locations)) {
+        CxList *list = settings->credentials_locations->data;
+        cxListSwap(list, index, index+1);
+        ui_list_update(settings->credentials_locations);
+        ui_list_setselection(settings->credentials_locations, index+1);
+    }
+}
+
+static void credentials_location_onselect(UiEvent *event, void *userdata) {
+    SettingsWindow *settings = event->window;
+    UiListSelection *sel = event->eventdata;
+    if(sel->count > 0) {
+        settings->credentials_location_selected_index = sel->rows[0];
+        ui_set_group(event->obj->ctx, SETTINGS_STATE_CREDENTIALS_LOCATION_SELECTED);
+    } else {
+        settings->credentials_location_selected_index = -1;
+        ui_unset_group(event->obj->ctx, SETTINGS_STATE_CREDENTIALS_LOCATION_SELECTED);
+    }
+}
+
+static void credentials_setmasterpw(UiEvent *event, void *userdata) {
+    if(event->intval == 1) {
+        SettingsWindow *settings = event->window;
+        char *pw = event->eventdata;
+        size_t pwlen = strlen(pw);
+        if(pwlen > 0) {
+            pwdstore_setpassword(settings->pwdstore, event->eventdata);
+            memset(pw, 0, pwlen);
+            if(!pwdstore_decrypt(settings->pwdstore)) {
+                settings_credentials_select(settings, NULL);
+            } else {
+                ui_dialog(event->obj, .title = "Error", .content = "Cannot decrypt Secret Store", .closebutton_label = "OK");
+            }
+        }
+    }
+}
+
+void settings_credentials_decrypt(SettingsWindow *settings) {
+    ui_dialog(settings->obj,
+            .title = "Secret Store",
+            .content = "Master password",
+            .password = TRUE,
+            .result = credentials_setmasterpw,
+            .button1_label = "Decrypt Secret Store",
+            .closebutton_label = "Cancel");
+}
+
+static void list_str_destructor(void *data, void *ptr) {
+    UiContext *ctx = data;
+    char *s = ptr;
+    ui_free(ctx, ptr);
+}
+
 
 void settings_ok(UiEvent *event, void *userdata) {
     SettingsWindow *settings = event->window;
     settings->config_saved = TRUE;
     // save any changed settings
     settings_store_repository(settings);
+    settings_credentials_save(settings);
     
     set_config(settings->config); // TODO: free old config
     if(store_config()) {
         ui_dialog(event->obj, .title = "Error", .content = "Cannot store settings", .closebutton_label = "OK");
     }
+    settings->config = NULL;
+    if(settings->credentials_modified) {
+        set_pwdstore(settings->pwdstore);
+        pwdstore_save(settings->pwdstore);
+        settings->pwdstore = NULL;
+    }
     application_update_repolist(get_application());
     ui_close(event->obj);
 }
@@ -112,7 +303,12 @@
         // don't free anything
         return;
     }
-    dav_config_free(settings->config);
+    if(settings->config) {
+        dav_config_free(settings->config);
+    }
+    if(settings->pwdstore) {
+        pwdstore_free(settings->pwdstore);
+    }
 }
 
 void settings_cancel(UiEvent *event, void *userdata) {
@@ -125,12 +321,17 @@
     if(!config) {
         return;
     }
+    PwdStore *pwdstore = get_pwdstore();
+    pwdstore = !pwdstore ? pwdstore_new() : pwdstore_clone(pwdstore);
     
     UiObject *obj = ui_simple_window("Settings", NULL);
     ui_context_closefunc(obj->ctx, settings_close, NULL);
     SettingsWindow *wdata = ui_malloc(obj->ctx, sizeof(SettingsWindow));
+    memset(wdata, 0, sizeof(SettingsWindow));
     wdata->config = config;
+    wdata->pwdstore = pwdstore;
     obj->window = wdata;
+    wdata->obj = obj;
     settings_init(obj, wdata);
     
     ui_tabview(obj) {
@@ -247,8 +448,47 @@
         }
         
         ui_tab(obj, "Credentials") {
-            ui_grid(obj, .margin = 10) {
-                ui_label(obj, .label = "TODO");
+            ui_hbox(obj, .margin = 10, .spacing = 10) {
+                ui_vbox(obj, .fill = UI_OFF) {
+                    ui_listview(obj, .list = wdata->credentials_users, .fill = UI_ON, .onselection = credentials_onselect);
+                    ui_hbox(obj, .fill = UI_OFF, .spacing = 4) {
+                        ui_button(obj, .label = "Add", .onclick = credentials_add);
+                        ui_button(obj, .label = "Remove", .onclick = credentials_remove, .groups = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED));
+                    }
+                }
+                
+                ui_grid(obj, .columnspacing = 30, .rowspacing = 10) {
+                    ui_llabel(obj, .label = "Identifier");
+                    ui_textfield(obj, .value = wdata->credentials_id, .hexpand = TRUE, .groups = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED));
+                    ui_newline(obj);
+                    
+                    ui_llabel(obj, .label = "User");
+                    ui_textfield(obj, .value = wdata->credentials_user, .hexpand = TRUE, .groups = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED));
+                    ui_newline(obj);
+                    
+                    ui_llabel(obj, .label = "Password");
+                    ui_passwordfield(obj, .value = wdata->credentials_password, .hexpand = TRUE, .groups = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED));
+                    ui_newline(obj);
+                    
+                    ui_label(obj, .label = " ");
+                    ui_newline(obj);
+                    
+                    ui_llabel(obj, .style = UI_LABEL_STYLE_TITLE, .label = "Locations", .colspan = 2);
+                    ui_newline(obj);
+                    ui_llabel(obj, .style = UI_LABEL_STYLE_DIM, .label = "List of URLs for which these credentials should be used (optional)", .colspan = 2);
+                    ui_newline(obj);
+                    
+                    ui_hbox(obj, .colspan = 2, .vexpand = TRUE, .hexpand = TRUE, .spacing = 10) {
+                        ui_listview(obj, .list = wdata->credentials_locations, .onactivate = credentials_location_edit, .onselection = credentials_location_onselect, .colspan = 2, .fill = UI_ON, .groups = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED));
+                        ui_vbox(obj, .fill = UI_OFF, .spacing = 4) {
+                            ui_button(obj, .label = "Add", .onclick = credentials_location_add, .groups = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED));
+                            ui_button(obj, .label = "Edit", .onclick = credentials_location_edit, .groups = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED, SETTINGS_STATE_CREDENTIALS_LOCATION_SELECTED));
+                            ui_button(obj, .label = "Remove", .onclick = credentials_location_remove, .groups = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED, SETTINGS_STATE_CREDENTIALS_LOCATION_SELECTED));
+                            ui_button(obj, .label = "Move Up", .onclick = credentials_location_up, .groups = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED, SETTINGS_STATE_CREDENTIALS_LOCATION_SELECTED));
+                            ui_button(obj, .label = "Move Down", .onclick = credentials_location_down, .groups = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED, SETTINGS_STATE_CREDENTIALS_LOCATION_SELECTED));
+                        }
+                    }
+                }
             }
         }
         
@@ -264,7 +504,7 @@
             }
         }
     }
-    
+        
     ui_hbox(obj, .fill = UI_OFF, .margin = 10) {
         ui_button(obj, .label = "Cancel", .onclick = settings_cancel);
         ui_label(obj, .fill = UI_ON);
@@ -289,23 +529,41 @@
     settings->repo_tls_versions = ui_list_new(obj->ctx, NULL);
     settings->repo_encryption = ui_int_new(obj->ctx, NULL);
     settings->repo_disable_verification = ui_int_new(obj->ctx, NULL);
-    
+     
     ui_list_append(settings->repo_tls_versions, "Default");
     ui_list_append(settings->repo_tls_versions, "TLSv1.3");
     ui_list_append(settings->repo_tls_versions, "TLSv1.2");
     ui_list_append(settings->repo_tls_versions, "TLSv1.1");
     ui_list_append(settings->repo_tls_versions, "TLSv1.0");
     
+    settings->credentials_selected_index = -1;
+    settings->credentials_users = ui_list_new(obj->ctx, NULL);
+    settings->credentials_locations = ui_list_new(obj->ctx, NULL);
+    settings->credentials_id = ui_string_new(obj->ctx, NULL);
+    settings->credentials_user = ui_string_new(obj->ctx, NULL);
+    settings->credentials_password = ui_string_new(obj->ctx, NULL);
+    CxList *credentials_users = settings->credentials_users->data;
+    CxList *credentials_locations = settings->credentials_locations->data;
+    credentials_users->collection.advanced_destructor = list_str_destructor;
+    credentials_users->collection.destructor_data = settings->obj->ctx;
+    credentials_users->collection.cmpfunc = (cx_compare_func)strcmp;
+    credentials_locations->collection.advanced_destructor = list_str_destructor;
+    credentials_locations->collection.destructor_data = settings->obj->ctx;
+    credentials_locations->collection.cmpfunc = (cx_compare_func)strcmp;
+    
     // load some list values, that can be reused
     settings_update_repolist(settings);
     settings_reload_keys(settings);
     settings_reload_credentials(settings);
+    void settings_reload_repo_credentials(SettingsWindow *settings);
     
     settings->selected_repo = -1;
 }
 
 #define SETTINGS_SET_STRING(str, setting) if(setting.value.ptr) ui_set(str, setting.value.ptr);
 
+/* ----------------------------- Repository ----------------------------- */
+
 void settings_edit_repository(SettingsWindow *settings, int repo_index) {
     DavCfgRepository *repo = ui_list_get(settings->repos, repo_index);
     if(!repo) {
@@ -566,27 +824,179 @@
     return NULL;
 }
 
+
+/* ----------------------------- Credentials ----------------------------- */
+
 void settings_reload_credentials(SettingsWindow *settings) {
-    PwdStore *pwd = get_pwdstore();
-    if(!pwd) {
-        return;
+    PwdStore *pwd = settings->pwdstore;
+    
+    ui_list_clear(settings->credentials_users);
+    
+    CxIterator i = cxListIterator(pwd->noloc);
+    cx_foreach(PwdIndexEntry*, entry, i) {
+        char *id = ui_strdup(settings->obj->ctx, entry->id);
+        ui_list_append(settings->credentials_users, id);
+    }
+    i = cxListIterator(pwd->locations);
+    cx_foreach(PwdIndexEntry*, entry, i) {
+        char *id = ui_strdup(settings->obj->ctx, entry->id);
+        ui_list_append(settings->credentials_users, id);
     }
     
+    ui_list_update(settings->credentials_users);
+}
+
+void settings_reload_repo_credentials(SettingsWindow *settings) {
+    PwdStore *pwd = settings->pwdstore;
+    
     ui_list_clear(settings->repo_credentials);
+    
+    // repo_credentials needs an entry for selecting no credentials
     ui_list_append(settings->repo_credentials, "-");
     
     CxIterator i = cxListIterator(pwd->noloc);
     cx_foreach(PwdIndexEntry*, entry, i) {
-        ui_list_append(settings->repo_credentials, entry->id);
+        char *id = ui_strdup(settings->obj->ctx, entry->id);
+        ui_list_append(settings->repo_credentials, id);
     }
     i = cxListIterator(pwd->locations);
     cx_foreach(PwdIndexEntry*, entry, i) {
-        ui_list_append(settings->repo_credentials, entry->id);
+        char *id = ui_strdup(settings->obj->ctx, entry->id);
+        ui_list_append(settings->repo_credentials, id);
     }
     
-    if(settings->repo_credentials->update) {
-        ui_list_update(settings->repo_credentials);
-    }
+    ui_list_update(settings->repo_credentials);
 }
 
 
+void settings_credentials_select(SettingsWindow *settings, const char *id) {
+    if(!id && !settings->credentials_selected_id && !settings->credentials_new) {
+        fprintf(stderr, "Error: no credentials id selected\n");
+        return;
+    }
+    
+    PwdStore *pwd = settings->pwdstore;
+    
+    if(id) {
+        ui_free(settings->obj->ctx, settings->credentials_selected_id);
+        settings->credentials_selected_id = ui_strdup(settings->obj->ctx, id);
+    }
+    
+    if(!pwd->isdecrypted) {
+        settings_credentials_decrypt(settings);
+        return;
+    }
+    
+    if(settings->credentials_new) {
+        ui_free(settings->obj->ctx, settings->credentials_selected_id);
+        settings->credentials_selected_id = NULL;
+        settings->credentials_selected_index = ui_list_count(settings->credentials_users);
+        ui_list_append(settings->credentials_users, ui_strdup(settings->obj->ctx, "new"));
+        settings->credentials_ignore_selectionevent = TRUE;
+        ui_list_update(settings->credentials_users);
+        ui_list_setselection(settings->credentials_users, settings->credentials_selected_index);
+        settings->credentials_ignore_selectionevent = FALSE;
+        
+        ui_set(settings->credentials_id, "new");
+        ui_set(settings->credentials_user, "");
+        ui_set(settings->credentials_password, "");
+        ui_list_clear(settings->credentials_locations);
+        ui_list_update(settings->credentials_locations);
+    } else {
+        PwdEntry *entry = cxMapGet(pwd->ids, settings->credentials_selected_id);
+        PwdIndexEntry *index = cxMapGet(pwd->index, settings->credentials_selected_id);
+        if(!entry) {
+            fprintf(stderr, "Error: cannot get pwd entry %s\n", settings->credentials_selected_id);
+            return;
+        }
+        if(!index) {
+            fprintf(stderr, "Error: missing PwdIndexEntry. PwdStore may be broken.\n");
+            return;
+        }
+
+        ui_set(settings->credentials_id, entry->id);
+        ui_set(settings->credentials_user, entry->user);
+        ui_set(settings->credentials_password, entry->password);
+
+        ui_list_clear(settings->credentials_locations);
+        if(index->locations) {
+            CxIterator i = cxListIterator(index->locations);
+            cx_foreach(char *, loc, i) {
+                ui_list_append(settings->credentials_locations, ui_strdup(settings->obj->ctx, loc));
+            }
+        }
+    }
+    
+    ui_list_update(settings->credentials_locations);
+    
+    ui_set_group(settings->obj->ctx, SETTINGS_STATE_CREDENTIALS_SELECTED);
+}
+
+void settings_credentials_clear(SettingsWindow *settings) {
+    ui_free(settings->obj->ctx, settings->credentials_selected_id);
+    settings->credentials_selected_id = NULL;
+    settings->credentials_new = FALSE;
+    
+    ui_set(settings->credentials_id, "");
+    ui_set(settings->credentials_user, "");
+    ui_set(settings->credentials_password, "");
+    ui_list_clear(settings->credentials_locations);
+    ui_list_update(settings->credentials_locations);
+    
+    ui_unset_group(settings->obj->ctx, SETTINGS_STATE_CREDENTIALS_SELECTED);
+}
+
+int settings_credentials_save(SettingsWindow *settings) {
+    if(settings->credentials_selected_index == -1) {
+        return 0;
+    }
+    
+    // check if the credentials list contains an element with the same id
+    // if an index is found, it must match the selected index, otherwise
+    // we have a duplicate identifier
+    char *newid = ui_get(settings->credentials_id);
+    if(strlen(newid) == 0) {
+        return 0; // TODO: check if other fields are set
+    }
+    
+    ssize_t index = cxListFind(settings->credentials_users->data, newid);
+    if(index >=0 && index != settings->credentials_selected_index) {
+        cxmutstr s = cx_asprintf("Identifier %s already in use", newid);
+        ui_dialog(settings->obj, .title = "Error", .content = s.ptr, .closebutton_label = "OK");
+        free(s.ptr);
+        return 1;
+    }
+    
+    char *user = ui_get(settings->credentials_user);
+    char *password = ui_get(settings->credentials_password);
+    CxList *ui_locations = settings->credentials_locations->data;
+    size_t numlocations = cxListSize(ui_locations);
+    CxList *locations = NULL;
+    if(numlocations > 0) {
+        locations = cxArrayListCreateSimple(CX_STORE_POINTERS, numlocations);
+        CxIterator i = cxListIterator(ui_locations);
+        cx_foreach(char*, loc, i) {
+            cxListAdd(locations, strdup(loc));
+        }
+    }
+    
+    // if the user id changed, mark the list as changed to update it later
+    settings->credentials_list_needs_update = settings->credentials_new || strcmp(newid, settings->credentials_selected_id);
+    if(settings->credentials_new || strcmp(newid, settings->credentials_selected_id ? settings->credentials_selected_id : "")) {
+        settings->credentials_list_needs_update = TRUE;
+        cxListRemove(settings->credentials_users->data, settings->credentials_selected_index);
+        cxListInsert(settings->credentials_users->data, settings->credentials_selected_index, ui_strdup(settings->obj->ctx, newid));
+    }
+    
+    // if the pwdstore already contains this id, remove it first
+    if(settings->credentials_selected_id) {
+        pwdstore_remove_entry(settings->pwdstore, settings->credentials_selected_id);
+    }
+    
+    pwdstore_put(settings->pwdstore, newid, user, password);
+    pwdstore_put_index(settings->pwdstore, strdup(newid), locations);
+    settings->credentials_modified = TRUE;
+    settings->credentials_new = FALSE;
+    
+    return 0;
+}
--- a/application/settings.h	Mon Oct 28 07:37:45 2024 +0100
+++ b/application/settings.h	Mon Oct 28 15:20:58 2024 +0100
@@ -38,29 +38,79 @@
     
 
 typedef struct SettingsWindow {
+    UiObject *obj;
+    
     DavConfig *config;
+    PwdStore *pwdstore;
     DavBool config_saved;
     
     UiList *repos;
+    /*
+     * 0: repo list page
+     * 1: edit repo page
+     */
     UiInteger *repo_tabview;
     
     DavBool repo_new;
     UiString *repo_name;
     UiString *repo_url;
+    /*
+     * value: char*   credential id
+     * 
+     * '-' entry + list of all credentials
+     * This list contains the same credential pointers as credentials_users
+     * and therefore doesn't need a destructor.
+     * Both lists are updated at the same time
+     */
     UiList *repo_credentials;
     UiString *repo_user;
     UiString *repo_password;
+    /*
+     * checkbox value
+     */
     UiInteger *repo_encryption;
     UiList *repo_keys;
+    /*
+     * value: char*   tls version name
+     * 
+     * static list of TLS version strings
+     */
     UiList *repo_tls_versions;
     UiString *repo_cacert;
     UiInteger *repo_disable_verification;
     
+    DavBool credentials_new;
+    DavBool credentials_modified;
+    DavBool credentials_list_needs_update;
+    DavBool credentials_ignore_selectionevent;
+    char *credentials_selected_id;
+    int credentials_selected_index;
+    int credentials_location_selected_index;
+    /*
+     * value: char*   credentials id
+     * 
+     * List of pwdstore locations and noloc credentials
+     * The value is a copy (allocated by the UiContext)
+     */
+    UiList *credentials_users;
+    UiString *credentials_id;
+    UiString *credentials_user;
+    UiString *credentials_password;
+    /*
+     * value: char*   url
+     * 
+     * List of credential locations (PwdIndexEntry locations)
+     * The value is a copy (allocated by the UiContext)
+     */
+    UiList *credentials_locations;
+    
     int selected_repo;
 } SettingsWindow;
     
 void settings_window_open();
 
+void settings_credentials_decrypt(SettingsWindow *settings);
+
 void settings_ok(UiEvent *event, void *userdata);
 
 void settings_cancel(UiEvent *event, void *userdata);
@@ -93,8 +143,23 @@
 const char* dav_tlsversion2str(int value);
 
 void settings_reload_credentials(SettingsWindow *settings);
+void settings_reload_repo_credentials(SettingsWindow *settings);
 
+/*
+ * select credentials with the specified id and fill the credentials form
+ * 
+ * if the id is specified, settings->credentials_id will be adjusted
+ * id no id is specified, credentials with the id settings->credentials_id
+ * are selected
+ */
+void settings_credentials_select(SettingsWindow *settings, const char *id);
 
+/*
+ * clear the credentials form
+ */
+void settings_credentials_clear(SettingsWindow *settings);
+
+int settings_credentials_save(SettingsWindow *settings);
 
 
 #ifdef __cplusplus
--- a/libidav/pwdstore.c	Mon Oct 28 07:37:45 2024 +0100
+++ b/libidav/pwdstore.c	Mon Oct 28 15:20:58 2024 +0100
@@ -109,6 +109,61 @@
     return p;
 }
 
+PwdStore* pwdstore_clone(PwdStore *p) {
+    CxBuffer *newbuffer = calloc(1, sizeof(CxBuffer));
+    *newbuffer = *p->content;
+    newbuffer->space = malloc(p->content->capacity);
+    memcpy(newbuffer->space, p->content->space, p->content->capacity);
+    
+    DavKey *key = NULL;
+    if(p->key) {
+        key = malloc(sizeof(DavKey));
+        key->data = malloc(p->key->length);
+        memcpy(key->data, p->key->data, p->key->length);
+        key->length = p->key->length;
+        key->type = p->key->type;
+        key->name = NULL;
+    }
+    
+    PwdStore *newp = calloc(1, sizeof(PwdStore));
+    newp->ids = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+    newp->locations = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+    newp->noloc = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+    newp->index = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
+    newp->content = newbuffer;
+    newp->key = key;
+    newp->unlock_cmd = p->unlock_cmd ? strdup(p->unlock_cmd) : NULL;
+    newp->lock_cmd = p->lock_cmd ? strdup(p->lock_cmd) : NULL;
+    newp->encoffset = p->encoffset;
+    newp->isdecrypted = p->isdecrypted;
+    
+    CxIterator i = cxMapIterator(p->ids);
+    cx_foreach(CxMapEntry *, e, i) {
+        PwdEntry *entry = e->value;
+        PwdEntry *new_entry = malloc(sizeof(PwdEntry));
+        new_entry->id = strdup(entry->id);
+        new_entry->user = entry->user ? strdup(entry->user) : NULL;
+        new_entry->password = entry->password ? strdup(entry->password) : NULL;
+        cxMapPut(newp->ids, *e->key, new_entry);
+    }
+    
+    i = cxMapIterator(p->index);
+    cx_foreach(CxMapEntry *, e, i) {
+        PwdIndexEntry *entry = e->value;
+        CxList *locations = NULL;
+        if(entry->locations) {
+            locations = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+            CxIterator li = cxListIterator(entry->locations);
+            cx_foreach(char *, location, li) {
+                cxListAdd(locations, strdup(location));
+            }
+        }        
+        pwdstore_put_index(newp, entry->id, locations);
+    }
+    
+    return newp;
+}
+
 static int readval(CxBuffer *in, char **val, int allowzero) {
     // value  = length string
     // length = uint32
@@ -172,6 +227,9 @@
     
     if(ret) {
         pwdstore_put_index(p, id, locations);
+        if(cxListSize(locations) == 0) {
+            cxListDestroy(locations);
+        }
     } else {
         if(id) free(id);
         cxListDestroy(locations);
@@ -235,7 +293,9 @@
     PwdEntry *e = cxMapRemoveAndGet(s->ids, key);
     
     if(i) {
-        cxListDestroy(i->locations);
+        if(i->locations) {
+            cxListDestroy(i->locations);
+        }
         free(i->id);
         free(i);
     }
@@ -306,6 +366,8 @@
     
     cxBufferFree(content);
     
+    p->isdecrypted = 1;
+    
     return 0;
 }
 
@@ -377,13 +439,12 @@
     }
     PwdIndexEntry *newentry = malloc(sizeof(PwdIndexEntry));
     newentry->id = id;
-    if(cxListSize(locations) > 0) {
+    if(locations && cxListSize(locations) > 0) {
         newentry->locations = locations;
         cxListAdd(p->locations, newentry);
     } else {
         newentry->locations = NULL;
         cxListAdd(p->noloc, newentry);
-        cxListDestroy(locations);
     }
     cxMapPut(p->index, cx_hash_key_str(id), newentry);
 }
@@ -397,13 +458,15 @@
     cxBufferWrite(&netidlen, 1, sizeof(uint32_t), out);
     cxBufferWrite(e->id, 1, idlen, out);
     
-    CxIterator i = cxListIterator(e->locations);
-    cx_foreach(char *, location, i) {
-        uint32_t locationlen = strlen(location);
-        uint32_t netlocationlen = htonl(locationlen);
-        
-        cxBufferWrite(&netlocationlen, 1, sizeof(uint32_t), out);
-        cxBufferWrite(location, 1, locationlen, out);
+    if(e->locations) {
+        CxIterator i = cxListIterator(e->locations);
+        cx_foreach(char *, location, i) {
+            uint32_t locationlen = strlen(location);
+            uint32_t netlocationlen = htonl(locationlen);
+
+            cxBufferWrite(&netlocationlen, 1, sizeof(uint32_t), out);
+            cxBufferWrite(location, 1, locationlen, out);
+        }
     }
     
     uint32_t terminate = 0;
--- a/libidav/pwdstore.h	Mon Oct 28 07:37:45 2024 +0100
+++ b/libidav/pwdstore.h	Mon Oct 28 15:20:58 2024 +0100
@@ -166,6 +166,8 @@
 
 PwdStore* pwdstore_new(void);
 
+PwdStore* pwdstore_clone(PwdStore *p);
+
 /*
  * decrypts the password store with the previously set password
  */
--- a/ui/common/types.c	Mon Oct 28 07:37:45 2024 +0100
+++ b/ui/common/types.c	Mon Oct 28 15:20:58 2024 +0100
@@ -159,7 +159,9 @@
 }
 
 UIEXPORT void ui_list_update(UiList *list) {
-    list->update(list, 0);
+    if(list->update) {
+        list->update(list, 0);
+    }
 }
 
 void ui_list_addobsv(UiList *list, ui_callback f, void *data) {
--- a/ui/gtk/list.c	Mon Oct 28 07:37:45 2024 +0100
+++ b/ui/gtk/list.c	Mon Oct 28 15:20:58 2024 +0100
@@ -202,22 +202,39 @@
     // bind var
     list->update = ui_listview_update;
     list->getselection = ui_listview_getselection;
+    list->setselection = ui_listview_setselection;
     list->obj = listview;
     
     // add callback
+    UiTreeEventData *event = malloc(sizeof(UiTreeEventData));
+    event->obj = obj;
+    event->activate = args.onactivate;
+    event->activatedata = args.onactivatedata;
+    event->selection = args.onselection;
+    event->selectiondata = args.onselectiondata;
+    g_signal_connect(
+            view,
+            "destroy",
+            G_CALLBACK(ui_destroy_userdata),
+            event);
+    
     if(args.onactivate) {
-        UiTreeEventData *event = ui_malloc(obj->ctx, sizeof(UiTreeEventData));
-        event->obj = obj;
-        event->activatedata = args.onactivatedata;
-        event->activate = args.onactivate;
-        event->selection = NULL;
-
         g_signal_connect(
                 view,
                 "row-activated",
                 G_CALLBACK(ui_listview_activate_event),
                 event);
     }
+    if(args.onselection) {
+        GtkTreeSelection *selection = gtk_tree_view_get_selection(
+                GTK_TREE_VIEW(view));
+        g_signal_connect(
+                selection,
+                "changed",
+                G_CALLBACK(ui_listview_selection_event),
+                event);
+    }
+    
     
     // add widget to the current container
     GtkWidget *scroll_area = SCROLLEDWINDOW_NEW();
@@ -492,6 +509,14 @@
     return selection;
 }
 
+void ui_listview_setselection(UiList *list, UiListSelection selection) {
+    UiListView *view = list->obj;
+    GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view->widget));
+    GtkTreePath *path = gtk_tree_path_new_from_indicesv(selection.rows, selection.count);
+    gtk_tree_selection_select_path(sel, path);
+    //g_object_unref(path);
+}
+
 void ui_listview_destroy(GtkWidget *w, UiListView *v) {
     gtk_tree_view_set_model(GTK_TREE_VIEW(w), NULL);
     ui_destroy_boundvar(v->obj->ctx, v->var);
--- a/ui/gtk/window.c	Mon Oct 28 07:37:45 2024 +0100
+++ b/ui/gtk/window.c	Mon Oct 28 15:20:58 2024 +0100
@@ -239,8 +239,14 @@
     }
     
     GtkWidget *entry = NULL;
-    if(args.input) {
+    if(args.input || args.password) {
         entry = gtk_entry_new();
+        if(args.password) {
+            gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
+        }
+        if(args.input_value) {
+            ENTRY_SET_TEXT(entry, args.input_value);
+        }
         adw_alert_dialog_set_extra_child(ADW_ALERT_DIALOG(dialog), entry);
         event->customdata = entry;
     }
@@ -313,8 +319,14 @@
     }
     
     GtkWidget *textfield = NULL;
-    if(args.input) {
+    if(args.input || args.password) {
         textfield = gtk_entry_new();
+        if(args.password) {
+            gtk_entry_set_visibility(GTK_ENTRY(textfield), FALSE);
+        }
+        if(args.input_value) {
+            ENTRY_SET_TEXT(textfield, args.input_value);
+        }
         BOX_ADD(content_area, textfield);
     }
     
--- a/ui/ui/window.h	Mon Oct 28 07:37:45 2024 +0100
+++ b/ui/ui/window.h	Mon Oct 28 15:20:58 2024 +0100
@@ -45,7 +45,9 @@
     const char *button1_label;
     const char *button2_label;
     const char *closebutton_label;
+    const char *input_value;
     UiBool input;
+    UiBool password;
     ui_callback result;
     void *resultdata;
 } UiDialogArgs;

mercurial