diff -r 98d0e2516f4e -r 48f43130b4a2 application/settings.c --- 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 +#include +#include #include + #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; +}