/* This file is part of anastasis-gtk. Copyright (C) 2020, 2021, 2022 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** * @file src/anastasis/anastasis-gtk_action.c * @brief Handle redux action results * @author Christian Grothoff * @author Dennis Neufeld */ #include #include #include "anastasis-gtk_action.h" #include "anastasis-gtk_attributes.h" #include "anastasis-gtk_dispatch.h" #include "anastasis-gtk_helper.h" #include "anastasis-gtk_handle-identity-changed.h" #include "anastasis-gtk_progress.h" #include #include #include /** * After how long does our long-poller time out? */ #define LP_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 30) /** * Are we currently processing an action? */ bool AG_in_action; /** * Are we currently editing the secret? */ bool AG_in_secret_editing; /** * Are we currently editing the secret name? */ bool AG_in_secret_name_editing; #define DEBUG 0 /** * Lookup provider name by provider URL in our state. * * @param provider_url URL to lookup name for * @return provider name, or if unknown, the @a provider_url */ static const char * lookup_provider_name (const char *provider_url) { json_t *providers; json_t *provider; const char *name = NULL; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("business_name", &name), NULL), GNUNET_JSON_spec_end () }; providers = json_object_get (AG_redux_state, "authentication_providers"); provider = json_object_get (providers, provider_url); if (NULL == provider) return provider_url; if (GNUNET_OK != GNUNET_JSON_parse (provider, spec, NULL, NULL)) { GNUNET_break (0); json_dumpf (provider, stderr, JSON_INDENT (2)); return provider_url; } return name; } /** * Prepare window for selection of the continent. */ static void action_continent_selecting (void) { GtkListStore *country_liststore = GTK_LIST_STORE ( GCG_get_main_window_object ("country_liststore")); AG_hide_all_frames (); gtk_list_store_clear (country_liststore); { GtkListStore *continent_liststore; json_t *continents; continent_liststore = GTK_LIST_STORE ( GCG_get_main_window_object ("continent_liststore")); gtk_list_store_clear (continent_liststore); continents = json_object_get (AG_redux_state, "continents"); if (NULL != continents) { json_t *continent; size_t index; json_array_foreach (continents, index, continent) { const char *name; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("name", &name), GNUNET_JSON_spec_end () }; if (GNUNET_OK != GNUNET_JSON_parse (continent, spec, NULL, NULL)) { GNUNET_break (0); GNUNET_JSON_parse_free (spec); continue; } gtk_list_store_insert_with_values (continent_liststore, NULL, -1, AG_CMC_CONTINENT_NAME, name, AG_CMC_CONTINENT_NAME_I18N, dgettext ("anastasis", name), -1); GNUNET_JSON_parse_free (spec); } } } AG_sensitive ("anastasis_gtk_main_window_prev_button"); AG_insensitive ("anastasis_gtk_main_window_forward_button"); AG_show ("anastasis_gtk_progress_vbox"); AG_progress_update (); if (NULL != json_object_get (AG_redux_state, "backup_state")) { AG_show ("anastasis_gtk_backup_progress_scrolled_window"); AG_hide ("anastasis_gtk_recovery_progress_scrolled_window"); } else { AG_show ("anastasis_gtk_recovery_progress_scrolled_window"); AG_hide ("anastasis_gtk_backup_progress_scrolled_window"); } AG_show ("anastasis_gtk_main_window_prev_button"); AG_enable_next (); AG_show ("anastasis_gtk_main_control_vbox"); AG_show ("anastasis_gtk_continent_frame"); } /** * Prepare window for selection of the country. */ static void action_country_selecting (void) { GtkListStore *country_liststore; json_t *countries; const char *selected_country; AG_hide_all_frames (); countries = json_object_get (AG_redux_state, "countries"); selected_country = json_string_value (json_object_get (AG_redux_state, "selected_country")); country_liststore = GTK_LIST_STORE ( GCG_get_main_window_object ("country_liststore")); gtk_list_store_clear (country_liststore); { json_t *country; size_t index; json_array_foreach (countries, index, country) { GtkTreeIter iter; const char *code; const char *name; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("code", &code), GNUNET_JSON_spec_string ("name", &name), GNUNET_JSON_spec_end () }; if (GNUNET_OK != GNUNET_JSON_parse (country, spec, NULL, NULL)) { GNUNET_break (0); GNUNET_JSON_parse_free (spec); continue; } gtk_list_store_insert_with_values ( country_liststore, &iter, -1, AG_CCMC_COUNTRY_NAME, dgettext ("anastasis", name), AG_CCMC_COUNTRY_CODE, code, -1); if ( (NULL != selected_country) && (NULL != code) && (0 == strcmp (code, selected_country)) ) { GtkTreeView *tv; GtkTreeSelection *sel; tv = GTK_TREE_VIEW (GCG_get_main_window_object ( "anastasis_gtk_country_treeview")); sel = gtk_tree_view_get_selection (tv); gtk_tree_selection_select_iter (sel, &iter); } GNUNET_JSON_parse_free (spec); } } AG_sensitive ("anastasis_gtk_main_window_prev_button"); AG_insensitive ("anastasis_gtk_main_window_forward_button"); AG_show ("anastasis_gtk_main_control_vbox"); AG_show ("anastasis_gtk_progress_vbox"); AG_progress_update (); if (NULL != json_object_get (AG_redux_state, "backup_state")) { AG_show ("anastasis_gtk_backup_progress_scrolled_window"); AG_hide ("anastasis_gtk_recovery_progress_scrolled_window"); } else { AG_show ("anastasis_gtk_recovery_progress_scrolled_window"); AG_hide ("anastasis_gtk_backup_progress_scrolled_window"); } AG_show ("anastasis_gtk_main_window_prev_button"); AG_enable_next (); AG_show ("anastasis_gtk_continent_frame"); } /** * Create widget for "string" type user attributes. * * @param details not used * @return widget to be used for string entry */ static GtkWidget * ctor_entry (const json_t *details) { (void) details; return gtk_entry_new (); } /** * Create widget for "date" type user attributes. * * @param details not used * @return widget to be used for date entry */ static GtkWidget * ctor_date (const json_t *details) { (void) details; return gtk_calendar_new (); } /** * Create widget of @a type under @a uuid with @a label and @a tooltip * for the identity attribute editing dialog. Stores all created widgets * in the #AG_entry_attributes and ensures that we never create the same * widget (by @a uuid) twice. * * @param uh hash of unique ID of the widget, only create one per UUID * @param type type of the widget to create * @param label label to use * @param tooltip tooltip to use * @param id_attr potential additional inputs for the widget creation * @return created widget */ static GtkWidget * create_attribute_widget (const struct GNUNET_HashCode *uh, const char *type, const char *label, const char *tooltip, const json_t *id_attr) { static struct { const char *type; GtkWidget *(*ctor)(const json_t *details); } type_map [] = { { .type = "string", .ctor = &ctor_entry }, { .type = "date", .ctor = &ctor_date }, { .type = NULL, .ctor = NULL } }; GtkWidget *w; w = GNUNET_CONTAINER_multihashmap_get (AG_entry_attributes, uh); if (NULL != w) { GtkWidget *p; gtk_widget_show (w); p = gtk_widget_get_parent (w); gtk_widget_show (p); p = gtk_widget_get_parent (p); gtk_widget_show (p); return w; } for (unsigned int i = 0; NULL != type_map[i].type; i++) { GtkBox *box; GtkBox *vbox; if (0 != strcmp (type_map[i].type, type)) continue; w = type_map[i].ctor (id_attr); GNUNET_assert (NULL != w); gtk_widget_show (w); box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5 /* spacing in pixels */)); vbox = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 5 /* spacing in pixels */)); { GtkWidget *glabel; glabel = gtk_label_new (label); gtk_box_pack_start (box, /* parent */ glabel, /* child */ false, /* expand */ false, /* fill */ 5); /* padding */ gtk_widget_show (glabel); } GNUNET_assert (0 < g_signal_connect (w, "changed", G_CALLBACK (&AG_identity_changed), NULL)); GNUNET_assert (0 < g_signal_connect (w, "style-updated", G_CALLBACK (>k_widget_queue_draw), NULL)); gtk_widget_set_tooltip_text (w, tooltip); gtk_box_pack_start (box, /* parent */ w, /* child */ false, /* expand */ false, /* fill */ 5); /* padding */ gtk_widget_show (GTK_WIDGET (box)); gtk_box_pack_start (vbox, /* parent */ GTK_WIDGET (box), /* child */ false, /* expand */ false, /* fill */ 5); /* padding */ { GtkWidget *private_widget; GtkBuilder *builder; GtkBin *bin; builder = GNUNET_GTK_get_new_builder ("this_stays_private.glade", NULL); GNUNET_break (NULL != builder); /* load frame */ bin = GTK_BIN (gtk_builder_get_object (builder, "private_dummy_window")); GNUNET_break (NULL != bin); private_widget = gtk_bin_get_child (bin); GNUNET_break (NULL != private_widget); g_object_ref (private_widget); gtk_container_remove (GTK_CONTAINER (bin), private_widget); gtk_widget_destroy (GTK_WIDGET (bin)); g_object_unref (G_OBJECT (builder)); gtk_box_pack_start (vbox, /* parent */ private_widget, /* child */ false, /* expand */ false, /* fill */ 5); /* padding */ } gtk_widget_show (GTK_WIDGET (vbox)); GNUNET_assert (GNUNET_OK == GNUNET_CONTAINER_multihashmap_put (AG_entry_attributes, uh, w, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); { GtkBox *pbox; pbox = GTK_BOX (GCG_get_main_window_object ( "anastasis_gtk_identity_vbox")); gtk_box_pack_start (pbox, /* parent */ GTK_WIDGET (vbox), /* child */ false, /* expand */ false, /* fill */ 5); /* padding */ } return w; } GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "FATAL: required attribute type `%s' not supported\n", type); GNUNET_assert (0); return NULL; } /** * Update GtkLabel @a name, setting text to @a value. * * @param name Glade-name of widget to update * @param value value to set */ static void update_label (const char *name, const char *value) { GtkLabel *label; label = GTK_LABEL (GCG_get_main_window_object (name)); if (NULL == label) return; if (NULL == value) { gtk_widget_hide (GTK_WIDGET (label)); } else { gtk_label_set_text (label, value); gtk_widget_show (GTK_WIDGET (label)); } } /** * We are entering the tab where the user has to enter their personal * attributes. Show widgets applicable to the selected country, and * dynamically generate widgets if necessary. Furthermore, restore * widget contents from our state (if stored attribute values are * present in our state). */ static void action_user_attributes_collecting (void) { const json_t *id_attributes; AG_hide_all_frames (); id_attributes = json_object_get (AG_redux_state, "required_attributes"); GNUNET_assert (NULL != id_attributes); AG_hide_children ("anastasis_gtk_identity_vbox"); { size_t index; json_t *id_attr; json_array_foreach (id_attributes, index, id_attr) { const char *widget_name = NULL; const char *attr_tooltip = NULL; const char *attr_label = NULL; const char *attr_type; const char *attr_uuid; const char *attr_name; bool optional = false; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("widget", &widget_name), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_bool ("optional", &optional), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("tooltip", &attr_tooltip), NULL), GNUNET_JSON_spec_string ("type", &attr_type), GNUNET_JSON_spec_string ("uuid", &attr_uuid), GNUNET_JSON_spec_string ("name", &attr_name), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("label", &attr_label), NULL), GNUNET_JSON_spec_end () }; struct GNUNET_HashCode uh; GtkWidget *w = NULL; char *l = NULL; GNUNET_assert (GNUNET_OK == GNUNET_JSON_parse (id_attr, spec, NULL, NULL)); GNUNET_CRYPTO_hash (attr_uuid, strlen (attr_uuid), &uh); if (NULL != widget_name) { char *data_name; data_name = AG_expand_name (widget_name, attr_type); w = GTK_WIDGET (GCG_get_main_window_object (data_name)); if (NULL == w) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Widget `%s' not found, will try to create dynamic replacement\n", data_name); } GNUNET_free (data_name); } if (optional) GNUNET_asprintf (&l, _ ("%s (optional)"), dgettext ("anastasis", attr_label)); else l = GNUNET_strdup (dgettext ("anastasis", attr_label)); if ( (NULL != widget_name) && (NULL != w) ) { char *box_widget; GObject *box; GNUNET_asprintf (&box_widget, "%s_box", widget_name); box = GCG_get_main_window_object (box_widget); if (NULL == box) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Widget `%s' not found, cannot show entry element. BAD.\n", box_widget); } else { AG_show (box_widget); AG_show_children (box_widget); } GNUNET_free (box_widget); } if ( (NULL != w) && (! GNUNET_CONTAINER_multihashmap_contains (AG_entry_attributes, &uh)) ) { GNUNET_assert (GNUNET_OK == GNUNET_CONTAINER_multihashmap_put (AG_entry_attributes, &uh, w, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); } if (NULL == w) w = create_attribute_widget (&uh, attr_type, l, attr_tooltip, id_attr); GNUNET_free (l); if (NULL != w) { json_t *ia; json_t *val; ia = json_object_get (AG_redux_state, "identity_attributes"); val = json_object_get (ia, attr_name); if ( (NULL != val) && (! json_is_null (val)) ) AG_import_attribute_data (w, attr_type, val); gtk_widget_show (w); } GNUNET_JSON_parse_free (spec); } } AG_sensitive ("anastasis_gtk_main_window_prev_button"); AG_identity_changed (); AG_show ("anastasis_gtk_progress_vbox"); AG_progress_update (); if (NULL != json_object_get (AG_redux_state, "backup_state")) { AG_show ("anastasis_gtk_backup_progress_scrolled_window"); AG_hide ("anastasis_gtk_recovery_progress_scrolled_window"); } else { AG_show ("anastasis_gtk_recovery_progress_scrolled_window"); AG_hide ("anastasis_gtk_backup_progress_scrolled_window"); } AG_show ("anastasis_gtk_main_window_save_as_button"); AG_show ("anastasis_gtk_main_control_vbox"); AG_show ("anastasis_gtk_main_window_prev_button"); AG_enable_next (); AG_show ("anastasis_gtk_identity_frame"); AG_focus ("anastasis_gtk_ia_full_name_entry"); } static void activate_by_method (json_t *methods) { size_t index; const json_t *method; json_array_foreach (methods, index, method) { const char *type; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("type", &type), GNUNET_JSON_spec_end () }; if (GNUNET_OK != GNUNET_JSON_parse (method, spec, NULL, NULL)) { GNUNET_break (0); json_dumpf (method, stderr, JSON_INDENT (2)); continue; } { char btn[64]; GNUNET_snprintf (btn, sizeof (btn), "anastasis_gtk_btn_add_auth_%s", type); AG_sensitive (btn); } } } static void action_authentications_editing (void) { json_t *aps; bool have_auth; AG_hide_all_frames (); AG_insensitive_children ("anastasis_gtk_auth_button_grid"); aps = json_object_get (AG_redux_state, "authentication_providers"); { const json_t *ap; const char *provider_url; json_object_foreach (aps, provider_url, ap) { uint32_t ec = 0; uint32_t hc = 0; const char *status; json_t *methods = NULL; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("status", &status), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_uint32 ("error_code", &ec), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_json ("methods", &methods), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_uint32 ("http_status", &hc), NULL), GNUNET_JSON_spec_end () }; if (GNUNET_OK != GNUNET_JSON_parse (ap, spec, NULL, NULL)) { GNUNET_break (0); continue; } if (0 == strcmp (status, "disabled")) continue; switch (hc) { case MHD_HTTP_OK: if (NULL == methods) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Provider `%s' has no authentication methods?\n", provider_url); break; } activate_by_method (methods); break; default: GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Status of provider `%s' is %u/%u\n", provider_url, (unsigned int) ec, (unsigned int) hc); break; } GNUNET_JSON_parse_free (spec); } } have_auth = false; { GtkListStore *ls; json_t *ams; size_t index; json_t *am; ls = GTK_LIST_STORE (GCG_get_main_window_object ( "authentication_methods_liststore")); gtk_list_store_clear (ls); ams = json_object_get (AG_redux_state, "authentication_methods"); json_array_foreach (ams, index, am) { const char *type; const char *instructions; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("type", &type), GNUNET_JSON_spec_string ("instructions", &instructions), GNUNET_JSON_spec_end () }; GNUNET_assert (GNUNET_OK == GNUNET_JSON_parse (am, spec, NULL, NULL)); gtk_list_store_insert_with_values ( ls, NULL, -1, AG_AMMC_TYPE, type, AG_AMMC_VISUALIZATION, instructions, AG_AMMC_INDEX, (guint) index, -1); have_auth = true; } } AG_sensitive ("anastasis_gtk_main_window_prev_button"); if (have_auth) AG_enable_next (); else AG_insensitive ("anastasis_gtk_main_window_forward_button"); AG_show ("anastasis_gtk_progress_vbox"); AG_progress_update (); AG_show ("anastasis_gtk_backup_progress_scrolled_window"); AG_hide ("anastasis_gtk_recovery_progress_scrolled_window"); AG_show ("anastasis_gtk_main_control_vbox"); AG_show ("anastasis_gtk_main_window_prev_button"); AG_enable_next (); AG_show ("anastasis_gtk_b_authentication_frame"); } /** * Lookup @a method_cost of authentication method @a type at @a provider in our * #AG_redux_state. * * @param provider URL of provider * @param type authentication method to look for * @param[out] method_cost cost to return * @return #GNUNET_OK on success, #GNUNET_NO if * the provider is down, #GNUNET_SYSERR on other failures */ static enum GNUNET_GenericReturnValue lookup_recovery_cost (const char *provider, const char *type, struct TALER_Amount *method_cost) { json_t *aps; json_t *ap; json_t *methods; size_t index; json_t *method; memset (method_cost, 0, sizeof (struct TALER_Amount)); aps = json_object_get (AG_redux_state, "authentication_providers"); GNUNET_assert (NULL != aps); ap = json_object_get (aps, provider); if (NULL == ap) { GNUNET_break (0); json_dumpf (aps, stderr, JSON_INDENT (2)); return GNUNET_SYSERR; } methods = json_object_get (ap, "methods"); if (NULL == methods) return GNUNET_NO; /* provider down */ json_array_foreach (methods, index, method) { struct TALER_Amount fee; const char *mtype; struct GNUNET_JSON_Specification spec[] = { TALER_JSON_spec_amount_any ("usage_fee", &fee), GNUNET_JSON_spec_string ("type", &mtype), GNUNET_JSON_spec_end () }; if (GNUNET_OK != GNUNET_JSON_parse (method, spec, NULL, NULL)) { GNUNET_break (0); json_dumpf (method, stderr, JSON_INDENT (2)); continue; } if (0 == strcmp (mtype, type)) { *method_cost = fee; return GNUNET_OK; } } GNUNET_break (0); json_dumpf (methods, stderr, JSON_INDENT (2)); return GNUNET_SYSERR; } static void action_policies_reviewing (void) { json_t *policies; size_t pindex; json_t *policy; GtkTreeStore *ts; AG_hide_all_frames (); ts = GTK_TREE_STORE (GCG_get_main_window_object ("policy_review_treestore")); gtk_tree_store_clear (ts); policies = json_object_get (AG_redux_state, "policies"); GNUNET_assert (NULL != policies); json_array_foreach (policies, pindex, policy) { GtkTreeIter piter; json_t *methods; struct GNUNET_JSON_Specification pspec[] = { GNUNET_JSON_spec_json ("methods", &methods), GNUNET_JSON_spec_end () }; size_t mindex; json_t *method; char *summary; if (GNUNET_OK != GNUNET_JSON_parse (policy, pspec, NULL, NULL)) { GNUNET_break (0); continue; } gtk_tree_store_insert_with_values (ts, &piter, NULL, /* no parent */ -1, /* append */ -1); summary = NULL; json_array_foreach (methods, mindex, method) { uint32_t imethod; const char *provider_url; struct GNUNET_JSON_Specification mspec[] = { GNUNET_JSON_spec_string ("provider", &provider_url), GNUNET_JSON_spec_uint32 ("authentication_method", &imethod), GNUNET_JSON_spec_end () }; json_t *jmethods; json_t *jmethod; if (GNUNET_OK != GNUNET_JSON_parse (method, mspec, NULL, NULL)) { json_dumpf (method, stderr, JSON_INDENT (2)); GNUNET_break (0); continue; } jmethods = json_object_get (AG_redux_state, "authentication_methods"); jmethod = json_array_get (jmethods, imethod); { GtkTreeIter miter; const char *instructions; const char *type; struct GNUNET_JSON_Specification tspec[] = { GNUNET_JSON_spec_string ("instructions", &instructions), GNUNET_JSON_spec_string ("type", &type), GNUNET_JSON_spec_end () }; struct TALER_Amount method_cost; const char *provider_name; if (GNUNET_OK != GNUNET_JSON_parse (jmethod, tspec, NULL, NULL)) { GNUNET_break (0); continue; } if (GNUNET_OK != lookup_recovery_cost (provider_url, type, &method_cost)) { GNUNET_break (0); continue; } provider_name = lookup_provider_name (provider_url); gtk_tree_store_insert_with_values ( ts, &miter, &piter, /* parent */ -1, /* append */ AG_PRMC_POLICY_NAME, instructions, AG_PRMC_METHOD_TYPE, type, AG_PRMC_COST, TALER_amount2s (&method_cost), AG_PRMC_PROVIDER_URL, provider_url, AG_PRMC_EXPIRATION_TIME_STR, "N/A", AG_PRMC_POLICY_INDEX, (guint) pindex, AG_PRMC_IS_CHALLENGE, TRUE, AG_PRMC_METHOD_INDEX, (guint) mindex, AG_PRMC_PROVIDER_NAME, provider_name, -1); if (NULL == summary) { summary = GNUNET_strdup (type); } else { char *tmp; GNUNET_asprintf (&tmp, "%s + %s", summary, type); GNUNET_free (summary); summary = tmp; } } GNUNET_JSON_parse_free (mspec); } if (NULL != summary) { gtk_tree_store_set (ts, &piter, AG_PRMC_POLICY_NAME, summary, AG_PRMC_EXPIRATION_TIME_STR, "N/A", AG_PRMC_POLICY_INDEX, (guint) pindex, AG_PRMC_IS_CHALLENGE, FALSE, -1); GNUNET_free (summary); } GNUNET_JSON_parse_free (pspec); } { GtkTreeView *tv; tv = GTK_TREE_VIEW (GCG_get_main_window_object ( "anastasis_gtk_review_policy_treeview")); gtk_tree_view_expand_all (tv); } AG_sensitive ("anastasis_gtk_main_window_prev_button"); AG_show ("anastasis_gtk_progress_vbox"); AG_progress_update (); AG_show ("anastasis_gtk_backup_progress_scrolled_window"); AG_hide ("anastasis_gtk_recovery_progress_scrolled_window"); AG_show ("anastasis_gtk_b_policy_frame"); AG_show ("anastasis_gtk_main_control_vbox"); AG_show ("anastasis_gtk_main_window_prev_button"); AG_enable_next (); } /** * Update GtkEntry @a name, setting text to @a value. * * @param name Glade-name of widget to update * @param value value to set */ static void update_entry (const char *name, const char *value) { GtkEntry *entry; const char *old; if (NULL == value) value = ""; entry = GTK_ENTRY (GCG_get_main_window_object (name)); if (NULL == entry) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "`%s' is not a GtkEntry!\n", name); return; } old = gtk_entry_get_text (entry); if (NULL == old) old = ""; if (0 != strcmp (old, value)) gtk_entry_set_text (entry, value); } /** * Function called when we begin editing the secret. */ static void action_secret_editing (void) { struct GNUNET_TIME_Timestamp exp_time; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_timestamp ("expiration", &exp_time), GNUNET_JSON_spec_end () }; struct tm tv; bool is_free = false; AG_hide_all_frames (); if (GNUNET_OK != GNUNET_JSON_parse (AG_redux_state, spec, NULL, NULL)) { GNUNET_break (0); AG_error ("State did not parse correctly: lacks expiration"); return; } { time_t t; t = exp_time.abs_time.abs_value_us / GNUNET_TIME_UNIT_SECONDS.rel_value_us; GNUNET_assert (NULL != localtime_r (&t, &tv)); } { json_t *fees; fees = json_object_get (AG_redux_state, "upload_fees"); if (0 == json_array_size (fees)) { update_label ("backup_fee_value_label", _ ("gratis")); is_free = true; } else { char *val = GNUNET_strdup (""); size_t pos; json_t *fee; struct TALER_Amount a; json_array_foreach (fees, pos, fee) { struct GNUNET_JSON_Specification spec[] = { TALER_JSON_spec_amount_any ("fee", &a), GNUNET_JSON_spec_end () }; char *tmp; if (GNUNET_OK != GNUNET_JSON_parse (fee, spec, NULL, NULL)) { GNUNET_break (0); json_dumpf (fees, stderr, JSON_INDENT (2)); continue; } GNUNET_asprintf (&tmp, "%s%s%llu.%u %s", val, strlen (val) > 0 ? "\n" : "", (unsigned long long) a.value, (unsigned int) a.fraction, a.currency); GNUNET_free (val); val = tmp; } update_label ("backup_fee_value_label", val); GNUNET_free (val); } } { char estr[128]; if (is_free) GNUNET_assert (sizeof (estr) > strftime (estr, sizeof (estr), "%d %B %Y", &tv)); else GNUNET_assert (sizeof (estr) > strftime (estr, sizeof (estr), "%d %B", &tv)); update_label ("expiration_date_without_year_label", estr); } { GtkSpinButton *sb; unsigned int this_year; unsigned int exp_year; sb = GTK_SPIN_BUTTON (GCG_get_main_window_object ( "expiration_year_spin_button")); if (is_free) gtk_widget_hide (GTK_WIDGET (sb)); else gtk_widget_show (GTK_WIDGET (sb)); this_year = GNUNET_TIME_get_current_year (); /* We allow at most 5 years into the future */ gtk_spin_button_set_range (sb, this_year + 1, this_year + 6); exp_year = tv.tm_year + 1900; gtk_spin_button_set_value (sb, (double) exp_year); } AG_insensitive ("anastasis_gtk_main_window_forward_button"); AG_sensitive ("anastasis_gtk_enter_secret_open_button"); AG_sensitive ("anastasis_gtk_enter_secret_textview"); AG_hide ("anastasis_gtk_secret_clear_file_button"); AG_hide ("anastasis_gtk_secret_clear_text_button"); AG_hide ("anastasis_gtk_secret_file_name_hbox"); AG_show ("anastasis_gtk_secret_file_chooser_hbox"); { const char *name = ""; json_t *jsecret = NULL; const char *filename = NULL; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_json ("core_secret", &jsecret), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("secret_name", &name), NULL), GNUNET_JSON_spec_end () }; if (GNUNET_OK != GNUNET_JSON_parse (AG_redux_state, spec, NULL, NULL)) { GNUNET_break (0); json_dumpf (AG_redux_state, stderr, JSON_INDENT (2)); AG_error ("State did not parse correctly: invalid secret data"); return; } if (! AG_in_secret_name_editing) update_entry ("anastasis_gtk_secret_name_entry", name); if (NULL != jsecret) { const char *mime = NULL; const char *text = NULL; struct GNUNET_JSON_Specification sspec[] = { GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("text", &text), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("mime", &mime), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("filename", &filename), NULL), GNUNET_JSON_spec_end () }; if (GNUNET_OK != GNUNET_JSON_parse (jsecret, sspec, NULL, NULL)) { GNUNET_break (0); json_dumpf (AG_redux_state, stderr, JSON_INDENT (2)); AG_error ("State did not parse correctly: invalid secret data"); return; } if ( (NULL != text) && (0 == strlen (text)) ) text = NULL; if (! AG_in_secret_editing) { GtkTextBuffer *tb = GTK_TEXT_BUFFER (GCG_get_main_window_object ( "anastasis_gtk_enter_secret_textbuffer")); const char *old; GtkTextIter start; GtkTextIter end; gtk_text_buffer_get_start_iter (tb, &start); gtk_text_buffer_get_end_iter (tb, &end); old = gtk_text_buffer_get_text (tb, &start, &end, true); if (0 != strcmp (old, text)) gtk_text_buffer_set_text (tb, text, -1); } update_label ("anastasis_gtk_secret_file_name_label", filename); if ( (NULL != text) || (NULL != filename) ) { AG_enable_next (); } if (NULL != text) { AG_insensitive ("anastasis_gtk_enter_secret_open_button"); AG_show ("anastasis_gtk_secret_clear_text_button"); } if (NULL != filename) { AG_insensitive ("anastasis_gtk_enter_secret_textview"); AG_show ("anastasis_gtk_secret_clear_file_button"); AG_show ("anastasis_gtk_secret_file_name_hbox"); AG_hide ("anastasis_gtk_secret_file_chooser_hbox"); } GNUNET_JSON_parse_free (sspec); } else { /* secret is NULL */ GtkTextBuffer *tb = GTK_TEXT_BUFFER (GCG_get_main_window_object ( "anastasis_gtk_enter_secret_textbuffer")); gtk_text_buffer_set_text (tb, "", -1); } if ( (NULL == name) || (0 == strlen (name) ) ) AG_focus ("anastasis_gtk_secret_name_entry"); else if (NULL == filename) AG_focus ("anastasis_gtk_enter_secret_textview"); GNUNET_JSON_parse_free (spec); } AG_sensitive ("anastasis_gtk_main_window_prev_button"); AG_show ("anastasis_gtk_progress_vbox"); AG_progress_update (); AG_show ("anastasis_gtk_backup_progress_scrolled_window"); AG_hide ("anastasis_gtk_recovery_progress_scrolled_window"); AG_show ("anastasis_gtk_enter_secret_frame"); AG_show ("anastasis_gtk_main_control_vbox"); AG_show ("anastasis_gtk_main_window_prev_button"); AG_enable_next (); } static void action_truths_paying (void) { json_t *pprs; size_t index; json_t *pt; GtkListStore *ls; AG_hide_all_frames (); ls = GTK_LIST_STORE (GCG_get_main_window_object ( "unpaid_qrcodes_liststore")); gtk_list_store_clear (ls); pprs = json_object_get (AG_redux_state, "payments"); json_array_foreach (pprs, index, pt) { const char *payto = json_string_value (pt); GdkPixbuf *pb; GtkWidget *w; w = GTK_WIDGET (GCG_get_main_window_object ("unpaid_qr_treeview")); if (NULL == w) { GNUNET_break (0); continue; } if (NULL == payto) { GNUNET_break (0); continue; } pb = AG_setup_qrcode (w, payto, strlen (payto)); if (NULL == pb) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, _ ("Failed to initialize QR-code pixbuf for `%s'\n"), payto); continue; } gtk_list_store_insert_with_values (ls, NULL, -1, /* append */ AG_UQRMC_QR_IMAGE, pb, AG_UQRMC_URL, payto, AG_UQRMC_PROVIDER, "", -1); g_object_unref (pb); } { json_t *args; struct GNUNET_TIME_Relative timeout; timeout = GNUNET_TIME_UNIT_MINUTES; GNUNET_assert (NULL == AG_ra); args = GNUNET_JSON_PACK ( GNUNET_JSON_pack_time_rel ("timeout", timeout)); AG_ra = ANASTASIS_redux_action (AG_redux_state, "pay", args, &AG_action_cb, NULL); json_decref (args); } AG_show ("anastasis_gtk_pay_frame"); AG_show ("anastasis_gtk_main_control_vbox"); AG_show ("anastasis_gtk_progress_vbox"); AG_progress_update (); AG_show ("anastasis_gtk_backup_progress_scrolled_window"); AG_hide ("anastasis_gtk_recovery_progress_scrolled_window"); AG_hide ("anastasis_gtk_main_window_prev_button"); AG_hide ("anastasis_gtk_main_window_forward_button"); } static void action_policies_paying (void) { json_t *pprs; size_t index; json_t *ppr; GtkListStore *ls; AG_hide_all_frames (); ls = GTK_LIST_STORE (GCG_get_main_window_object ( "unpaid_qrcodes_liststore")); gtk_list_store_clear (ls); pprs = json_object_get (AG_redux_state, "policy_payment_requests"); json_array_foreach (pprs, index, ppr) { const char *provider; const char *payto; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("provider", &provider), GNUNET_JSON_spec_string ("payto", &payto), GNUNET_JSON_spec_end () }; GdkPixbuf *pb; GtkWidget *w; w = GTK_WIDGET (GCG_get_main_window_object ("unpaid_qr_treeview")); if (NULL == w) { GNUNET_break (0); continue; } if (GNUNET_OK != GNUNET_JSON_parse (ppr, spec, NULL, NULL)) { GNUNET_break (0); continue; } pb = AG_setup_qrcode (w, payto, strlen (payto)); if (NULL == pb) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, _ ("Failed to initialize QR-code pixbuf for `%s'\n"), payto); continue; } gtk_list_store_insert_with_values (ls, NULL, -1, /* append */ AG_UQRMC_QR_IMAGE, pb, AG_UQRMC_URL, payto, AG_UQRMC_PROVIDER, provider, -1); g_object_unref (pb); } { json_t *args; struct GNUNET_TIME_Relative timeout; timeout = GNUNET_TIME_UNIT_MINUTES; GNUNET_assert (NULL == AG_ra); args = GNUNET_JSON_PACK ( GNUNET_JSON_pack_time_rel ("timeout", timeout)); AG_ra = ANASTASIS_redux_action (AG_redux_state, "pay", args, &AG_action_cb, NULL); json_decref (args); } AG_show ("anastasis_gtk_pay_frame"); AG_show ("anastasis_gtk_main_control_vbox"); AG_show ("anastasis_gtk_progress_vbox"); AG_progress_update (); AG_show ("anastasis_gtk_backup_progress_scrolled_window"); AG_hide ("anastasis_gtk_recovery_progress_scrolled_window"); AG_hide ("anastasis_gtk_main_window_prev_button"); AG_hide ("anastasis_gtk_main_window_forward_button"); } /** * The backup has finished, show the providers, policy version and * expiration dates. */ static void action_backup_finished (void) { json_t *det; json_t *se; const char *url; GtkListStore *ls; struct GNUNET_TIME_Timestamp mexp; AG_hide_all_frames (); det = json_object_get (AG_redux_state, "success_details"); ls = GTK_LIST_STORE (GCG_get_main_window_object ( "backup_provider_liststore")); gtk_list_store_clear (ls); mexp = GNUNET_TIME_UNIT_FOREVER_TS; json_object_foreach (det, url, se) { struct GNUNET_TIME_Timestamp pexp; uint64_t version; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_uint64 ("policy_version", &version), GNUNET_JSON_spec_timestamp ("policy_expiration", &pexp), GNUNET_JSON_spec_end () }; if (GNUNET_OK != GNUNET_JSON_parse (se, spec, NULL, NULL)) { GNUNET_break_op (0); AG_error ("State did not parse correctly"); return; } mexp = GNUNET_TIME_timestamp_min (mexp, pexp); gtk_list_store_insert_with_values ( ls, NULL, -1, /* append */ AG_BPC_PROVIDER_URL, url, AG_BPC_BACKUP_VERSION, (guint64) version, AG_BPC_EXPIRATION_TIME_STR, GNUNET_TIME_timestamp2s (pexp), AG_BPC_SUCCESS_FLAG, true, AG_BPC_PROVIDER_NAME, lookup_provider_name (url), -1); } { struct tm tv; char estr[128]; time_t t; struct GNUNET_TIME_Absolute sexp; /* be more conservative in what we show */ sexp = GNUNET_TIME_absolute_subtract (mexp.abs_time, GNUNET_TIME_UNIT_DAYS); t = sexp.abs_value_us / GNUNET_TIME_UNIT_SECONDS.rel_value_us; GNUNET_assert (NULL != localtime_r (&t, &tv)); GNUNET_assert (sizeof (estr) > strftime (estr, sizeof (estr), "%d %B %Y", &tv)); update_label ("backup_expiration_date_label", GNUNET_TIME_absolute2s (sexp)); } AG_hide ("anastasis_gtk_progress_vbox"); AG_hide ("anastasis_gtk_backup_progress_scrolled_window"); AG_show ("anastasis_gtk_completed_frame"); AG_show ("anastasis_gtk_backup_complete_box"); AG_hide ("anastasis_gtk_success_recovery_box"); AG_show ("anastasis_gtk_success_backup_label"); AG_hide ("anastasis_gtk_success_recovery_box"); AG_show ("anastasis_gtk_main_control_vbox"); AG_hide ("anastasis_gtk_main_window_save_as_button"); AG_show ("anastasis_gtk_restart_button"); AG_show ("anastasis_gtk_main_window_quit_button"); AG_hide ("anastasis_gtk_main_window_prev_button"); AG_hide ("anastasis_gtk_main_window_forward_button"); } static const json_t * find_challenge_by_uuid (const char *uuid) { json_t *rd; json_t *cs; size_t index; json_t *c; rd = json_object_get (AG_redux_state, "recovery_document"); cs = json_object_get (rd, "challenges"); json_array_foreach (cs, index, c) { const char *u; u = json_string_value (json_object_get (c, "uuid")); if (NULL == u) { GNUNET_break (0); continue; } if (0 == strcmp (u, uuid)) return c; } return NULL; } /** * Update the list store with the challenge feedback. * * @param uuid the UUID to render feedback for * @param builder the builder to use to grab display widgets */ static void show_challenge_feedback (const char *uuid, GtkBuilder *builder) { json_t *cf; const json_t *f; const char *state; const char *hint = NULL; const char *taler_pay_uri = NULL; uint32_t ec = TALER_EC_NONE; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("state", &state), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("taler_pay_uri", &taler_pay_uri), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("display_hint", &hint), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_uint32 ("error_code", &ec), NULL), GNUNET_JSON_spec_end () }; cf = json_object_get (AG_redux_state, "challenge_feedback"); f = json_object_get (cf, uuid); if (NULL == f) return; /* no feedback */ if (GNUNET_OK != GNUNET_JSON_parse (f, spec, NULL, NULL)) { GNUNET_break (0); json_dumpf (f, stderr, JSON_INDENT (2)); return; } if (0) { /* TODO: do we want to show the payment request here at all? */ if (NULL != taler_pay_uri) { GdkPixbuf *qr; GtkWidget *w = NULL; qr = AG_setup_qrcode (w, taler_pay_uri, strlen (taler_pay_uri)); g_object_unref (qr); } } if (TALER_EC_NONE != ec) { const char *emsg; GtkLabel *l; char *msg; emsg = TALER_ErrorCode_get_hint (ec); GNUNET_asprintf (&msg, "#%u: %s", (unsigned int) ec, emsg); l = GTK_LABEL (gtk_builder_get_object (builder, "error_label")); gtk_label_set_text (l, msg); GNUNET_free (msg); gtk_widget_show (GTK_WIDGET (l)); } if (NULL != hint) { GtkLabel *l; l = GTK_LABEL (gtk_builder_get_object (builder, "hint_label")); gtk_label_set_text (l, hint); gtk_widget_show (GTK_WIDGET (l)); } GNUNET_JSON_parse_free (spec); } /** * Function called on each discovered recovery policy. Called * with all arguments NULL if we have received all policies that * we could possibly receive for the current operation. * * The client can then start a new policy discovery process, using the * smallest (also most recent) @a version received per @a provider_url * in the cursor to resume. Note that in this case, the application * logic is responsible for de-duplication using @a hcpd, or it may show * policies again if they are at different providers under versions not * queried up to the cursor. * * @param cls closure with the `GtkListStore` to update. * @param hcpd hash of the compressed policy document (unique per policy) * @param provider_url which provider claims to have this policy * @param version version of the policy at this provider * @param attribute_mask combination of optional identity attributes * present in the state that was used to locate this version * @param server_time when did the provider receive the upload * @param secret_name name the user assigned to the backup * @param providers json array of providers offering this policy */ static void expand_policy_list (void *cls, const struct GNUNET_HashCode *hcpd, const char *provider_url, uint32_t version, json_int_t attribute_mask, struct GNUNET_TIME_Timestamp server_time, const char *secret_name, const json_t *providers) { GtkListStore *ls = cls; GtkTreeIter iter; bool have_first; have_first = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (ls), &iter); gtk_list_store_insert_with_values ( ls, &iter, -1, /* append */ AG_SSMC_PROVIDER_URL, provider_url, AG_SSMC_POLICY_VERSION, (gint) version, AG_SSMC_ATTRIBUTE_MASK, (gint) attribute_mask, AG_SSMC_SECRET_NAME, secret_name, AG_SSMC_POLICY_DATE_STRING, GNUNET_TIME_timestamp2s (server_time), AG_SSMC_POLICY_DATE_NUMERIC, (guint64) server_time.abs_time.abs_value_us, AG_SSMC_POLICY_PROVIDER_JSON, (gpointer) providers, -1); if (! have_first) { GtkTreeSelection *selection; selection = GTK_TREE_SELECTION ( GCG_get_main_window_object ( "anastasis_gtk_secret_selection_treeselection")); gtk_tree_selection_select_iter (selection, &iter); } } /** * Function called with the results of #ANASTASIS_redux_action on "poll". * * @param cls NULL * @param error_code Error code * @param response new state as result or config information of a provider */ static void long_poll_challenges_cb (void *cls, enum TALER_ErrorCode error_code, json_t *response); /** * Schedules the "poll" challenges action. * * @param cls NULL */ static void long_poll_challenges_task (void *cls) { struct ANASTASIS_LongAction *la = &AG_lacs[ANASTASIS_LP_POLL_CHALLENGES]; json_t *tspec; (void) cls; la->task = NULL; if (GNUNET_TIME_absolute_is_future (la->next_time)) { la->task = GNUNET_SCHEDULER_add_at (la->next_time, &long_poll_challenges_task, NULL); return; } la->next_time = GNUNET_TIME_relative_to_absolute (LP_TIMEOUT); tspec = GNUNET_JSON_PACK ( GNUNET_JSON_pack_time_rel ("timeout", LP_TIMEOUT)); la->ra = ANASTASIS_redux_action (AG_redux_state, "poll", tspec, &long_poll_challenges_cb, NULL); json_decref (tspec); } static void long_poll_challenges_cb (void *cls, enum TALER_ErrorCode error_code, json_t *response) { struct ANASTASIS_LongAction *la = &AG_lacs[ANASTASIS_LP_POLL_CHALLENGES]; (void) cls; la->ra = NULL; switch (error_code) { case TALER_EC_NONE: /* continued below */ break; default: AG_error (_ ("poll failed: %s (#%u)"), TALER_ErrorCode_get_hint (error_code), (unsigned int) error_code); /* simply try again */ la->task = GNUNET_SCHEDULER_add_now (&long_poll_challenges_task, NULL); return; } AG_action_cb (NULL, TALER_EC_NONE, response); } /** * Function called with the results of #ANASTASIS_redux_action on "sync_providers". * * @param cls NULL * @param error_code Error code * @param response new state as result or config information of a provider */ static void long_poll_sync_action_cb (void *cls, enum TALER_ErrorCode error_code, json_t *response); /** * Schedules the "sync_providers" action. * * @param cls NULL */ static void long_poll_sync_task (void *cls) { struct ANASTASIS_LongAction *la = &AG_lacs[ANASTASIS_LP_SYNC_PROVIDERS]; json_t *tspec; (void) cls; la->task = NULL; if (GNUNET_TIME_absolute_is_future (la->next_time)) { la->task = GNUNET_SCHEDULER_add_at (la->next_time, &long_poll_sync_task, NULL); return; } la->next_time = GNUNET_TIME_relative_to_absolute (LP_TIMEOUT); tspec = GNUNET_JSON_PACK ( GNUNET_JSON_pack_time_rel ("timeout", LP_TIMEOUT)); la->ra = ANASTASIS_redux_action (AG_redux_state, "sync_providers", tspec, &long_poll_sync_action_cb, NULL); json_decref (tspec); } static void long_poll_sync_action_cb (void *cls, enum TALER_ErrorCode error_code, json_t *response) { struct ANASTASIS_LongAction *la = &AG_lacs[ANASTASIS_LP_SYNC_PROVIDERS]; (void) cls; la->ra = NULL; switch (error_code) { case TALER_EC_NONE: /* continued below */ break; case TALER_EC_ANASTASIS_REDUCER_ACTION_INVALID: /* we are in sync, nothing left to do */ return; default: AG_error (_ ("Synchronizing with providers failed: %s (#%u)"), TALER_ErrorCode_get_hint (error_code), (unsigned int) error_code); /* simply try again */ la->task = GNUNET_SCHEDULER_add_now (&long_poll_sync_task, NULL); return; } AG_action_cb (NULL, TALER_EC_NONE, response); } /** * Function called with the results of #ANASTASIS_redux_action on "poll_providers". * * @param cls NULL * @param error_code Error code * @param response new state as result or config information of a provider */ static void long_poll_providers_action_cb (void *cls, enum TALER_ErrorCode error_code, json_t *response); /** * Schedules the specified action. * * @param cls NULL */ static void long_poll_providers_task (void *cls) { struct ANASTASIS_LongAction *la = &AG_lacs[ANASTASIS_LP_POLL_PROVIDERS]; json_t *tspec; (void) cls; la->task = NULL; if (GNUNET_TIME_absolute_is_future (la->next_time)) { la->task = GNUNET_SCHEDULER_add_at (la->next_time, &long_poll_providers_task, NULL); return; } la->next_time = GNUNET_TIME_relative_to_absolute (LP_TIMEOUT); tspec = GNUNET_JSON_PACK ( GNUNET_JSON_pack_time_rel ("timeout", LP_TIMEOUT)); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Starting long polling task for provider configurations\n"); la->ra = ANASTASIS_redux_action (AG_redux_state, "poll_providers", tspec, &long_poll_providers_action_cb, NULL); json_decref (tspec); } /** * Start policy discovery process. Triggers download(s) * of the various provider configurations and (once we * have any) starts the policy discovery process. */ static void begin_discovery (void); static void long_poll_providers_action_cb (void *cls, enum TALER_ErrorCode error_code, json_t *response) { struct ANASTASIS_LongAction *la = &AG_lacs[ANASTASIS_LP_POLL_PROVIDERS]; json_t *ap; const char *url; json_t *obj; la->ra = NULL; switch (error_code) { case TALER_EC_NONE: /* continued below */ break; default: AG_error (_ ("Polling for providers failed: %s (#%u)"), TALER_ErrorCode_get_hint (error_code), (unsigned int) error_code); /* simply try again */ la->task = GNUNET_SCHEDULER_add_now (&long_poll_providers_task, NULL); return; } GNUNET_assert (NULL != AG_pd); /* Find out which provider is new, merge that one! */ ap = json_object_get (response, "authentication_providers"); GNUNET_assert (NULL != ap); json_object_foreach (ap, url, obj) { struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt; if (GNUNET_OK == ANASTASIS_reducer_lookup_salt (AG_redux_state, url, &provider_salt)) continue; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Expanding policy discovery to recently discovered live provider `%s'\n", url); ANASTASIS_policy_discovery_more (AG_pd, url, obj); } /* update our state to the new state, but without going through the usual action loop */ json_decref (AG_redux_state); AG_redux_state = json_incref (response); GNUNET_assert (NULL != AG_redux_state); } static void begin_discovery (void) { struct ANASTASIS_LongAction *la = &AG_lacs[ANASTASIS_LP_POLL_PROVIDERS]; GtkListStore *ls; json_t *ap; const char *url; json_t *obj; bool ready = false; bool poll = false; ls = GTK_LIST_STORE (GCG_get_main_window_object ( "secret_selection_liststore")); GNUNET_assert (NULL != ls); if (NULL == AG_pd) gtk_list_store_clear (ls); ap = json_object_get (AG_redux_state, "authentication_providers"); json_object_foreach (ap, url, obj) { struct ANASTASIS_CRYPTO_ProviderSaltP provider_salt; enum GNUNET_GenericReturnValue ret; ret = ANASTASIS_reducer_lookup_salt (AG_redux_state, url, &provider_salt); switch (ret) { case GNUNET_OK: ready = true; break; case GNUNET_NO: poll = true; break; case GNUNET_SYSERR: GNUNET_break (0); break; } } if (poll) { la->next_time = GNUNET_TIME_UNIT_ZERO_ABS; la->task = GNUNET_SCHEDULER_add_now (&long_poll_providers_task, NULL); } if (NULL == AG_pd) AG_pd = ANASTASIS_policy_discovery_start (AG_redux_state, NULL, &expand_policy_list, ls); if ( (! ready) && (! poll) ) { AG_error (_ ("No available providers! Try to add one!")); } } /** * Function called when it is time for the user to select a secret * from the list of secrets. Builds the respective tree model. */ static void action_secret_selecting (void) { AG_hide ("anastasis_gtk_start_frame"); AG_stop_long_action (); if (AG_have_error) AG_show ("anastasis_gtk_error_label"); AG_hide ("anastasis_gtk_challenge_frame"); AG_hide ("anastasis_gtk_identity_frame"); if (NULL == AG_pd) begin_discovery (); AG_show ("anastasis_gtk_progress_vbox"); AG_progress_update (); AG_show ("anastasis_gtk_recovery_progress_scrolled_window"); AG_hide ("anastasis_gtk_backup_progress_scrolled_window"); AG_show ("anastasis_gtk_main_control_vbox"); AG_show ("anastasis_gtk_main_window_save_as_button"); AG_show ("anastasis_gtk_select_secret_frame"); AG_show ("anastasis_gtk_main_window_prev_button"); AG_hide ("anastasis_gtk_main_window_quit_button"); AG_insensitive ("anastasis_gtk_main_window_forward_button"); } /** * Callback invoked if the a "challenge"-button is clicked. * * @param object * @param user_data points to the UUID of the challenge */ static void challenge_button_clicked_cb (GObject *object, gpointer user_data) { const char *uuid = user_data; json_t *args; (void) object; args = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("uuid", uuid)); AG_freeze (); AG_ra = ANASTASIS_redux_action (AG_redux_state, "select_challenge", args, &AG_action_cb, NULL); json_decref (args); } /** * Generate widget about a @a challenge and add it to the * @a challenge_box. * * @param challenge_box box to expand * @param rd our recovery document (to use) * @param challenge the challenge to add * @return #GNUNET_OK on success, #GNUNET_NO * if the provider is down, #GNUNET_SYSERR on harder * internal failures */ static enum GNUNET_GenericReturnValue add_challenge (GtkBox *challenge_box, json_t *rd, json_t *uchallenge) { GtkBuilder *builder; GtkWindow *window; GtkWidget *hbox; GtkLabel *label; GtkButton *button; const char *instructions; const char *provider; const char *type; const char *uuid; struct TALER_Amount cost; bool async = false; bool solved = false; struct GNUNET_JSON_Specification uspec[] = { GNUNET_JSON_spec_string ("uuid", &uuid), GNUNET_JSON_spec_end () }; const char *iuuid; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("instructions", &instructions), GNUNET_JSON_spec_string ("type", &type), GNUNET_JSON_spec_string ("url", &provider), GNUNET_JSON_spec_string ("uuid", &iuuid), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_bool ("async", &async), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_bool ("solved", &solved), NULL), GNUNET_JSON_spec_end () }; bool ok = true; enum GNUNET_GenericReturnValue ret; if (GNUNET_OK != GNUNET_JSON_parse (uchallenge, uspec, NULL, NULL)) { GNUNET_break (0); return GNUNET_SYSERR; } { json_t *challenges; size_t index; json_t *challenge; challenges = json_object_get (rd, "challenges"); /* TODO: change data structure to have 'uuid' as the index into the 'challenges' object, instead of this 'challenges' being an array */ json_array_foreach (challenges, index, challenge) { if (GNUNET_OK != GNUNET_JSON_parse (challenge, spec, NULL, NULL)) { GNUNET_break (0); return GNUNET_SYSERR; } if (0 == strcmp (uuid, iuuid)) break; } } ret = lookup_recovery_cost (provider, type, &cost); if (GNUNET_SYSERR == ret) { GNUNET_break (0); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Could not find cost of %s at %s\n", type, provider); json_dumpf (AG_redux_state, stderr, JSON_INDENT (2)); return GNUNET_SYSERR; } if (GNUNET_NO == ret) ok = false; builder = GNUNET_GTK_get_new_builder ( "anastasis_gtk_challenge_template.glade", NULL); window = GTK_WINDOW (gtk_builder_get_object (builder, "challenge_template_window")); hbox = GTK_WIDGET (gtk_builder_get_object (builder, "challenge_hbox")); label = GTK_LABEL (gtk_builder_get_object (builder, "challenge_label")); button = GTK_BUTTON (gtk_builder_get_object (builder, "solve_challenge_button")); if (! ok) gtk_widget_set_sensitive (GTK_WIDGET (button), false); if (solved) { GtkImage *solved_img; solved_img = GTK_IMAGE (gtk_builder_get_object (builder, "challenge_solved_image")); gtk_widget_show (GTK_WIDGET (solved_img)); gtk_widget_hide (GTK_WIDGET (button)); } else { g_signal_connect (button, "clicked", G_CALLBACK (challenge_button_clicked_cb), (void *) uuid); if (ok) gtk_widget_set_tooltip_text (GTK_WIDGET (button), TALER_amount2s (&cost)); else gtk_widget_set_tooltip_text (GTK_WIDGET (button), _ ("unknown")); } { GtkImage *icon; char *lab; GNUNET_asprintf (&lab, "challenge_category_%s_image", type); icon = GTK_IMAGE (gtk_builder_get_object (builder, lab)); GNUNET_free (lab); if (NULL != icon) gtk_widget_show (GTK_WIDGET (icon)); } show_challenge_feedback (uuid, builder); if (ok) gtk_label_set_text (label, instructions); else gtk_label_set_text (label, _ ("provider down")); g_object_ref (hbox); gtk_container_remove (GTK_CONTAINER (window), hbox); g_object_unref (builder); gtk_box_pack_start (challenge_box, hbox, false, false, 15); if (! ok) return GNUNET_NO; return GNUNET_OK; } /** * Generate widget about a @a policy and add it to the * @a policy_box. * * @param policy_box box to expand * @param rd our recovery document (to use) * @param pindex policy index (for the label) * @param policy policy to add * @return true if we should long poll "sync_providers" */ static bool add_policy (GtkBox *policy_box, json_t *rd, size_t pindex, json_t *policy) { GtkBuilder *builder; GtkWindow *window; GtkWidget *widget; GtkLabel *label; GtkBox *vbox; char *txt; json_t *challenges; bool ok; challenges = json_object_get (policy, "challenges"); if (NULL == challenges) { GNUNET_break_op (0); AG_error ("Policy did not parse correctly"); return false; } builder = GNUNET_GTK_get_new_builder ("anastasis_gtk_policy_template.glade", NULL); window = GTK_WINDOW (gtk_builder_get_object (builder, "policy_template_window")); widget = GTK_WIDGET (gtk_builder_get_object (builder, "policy_frame")); label = GTK_LABEL (gtk_builder_get_object (builder, "policy_label")); vbox = GTK_BOX (gtk_builder_get_object (builder, "policy_vbox")); ok = true; { size_t index; json_t *challenge; json_array_foreach (challenges, index, challenge) { enum GNUNET_GenericReturnValue ret; ret = add_challenge (vbox, rd, challenge); if (GNUNET_SYSERR == ret) { GNUNET_break (0); g_object_unref (builder); GNUNET_asprintf (&txt, "Failed to process challenge #%u of policy #%u", (unsigned int) (1 + index), (unsigned int) (1 + pindex)); AG_error (txt); GNUNET_free (txt); return false; } if (GNUNET_NO == ret) { gtk_widget_set_sensitive (GTK_WIDGET (vbox), false); ok = false; } } } if (ok) { GNUNET_asprintf (&txt, _ ("Policy #%u"), (unsigned int) (1 + pindex)); } else { GNUNET_asprintf (&txt, _ ("Policy #%u (unavailable, provider down)"), (unsigned int) (1 + pindex)); } gtk_label_set_text (label, txt); GNUNET_free (txt); g_object_ref (widget); gtk_container_remove (GTK_CONTAINER (window), widget); g_object_unref (builder); gtk_box_pack_start (policy_box, widget, false, false, 15); return ! ok; } /** * The user must select the next challenge to solve * during the recovery process. */ static void action_challenge_selecting (void) { json_t *rd; json_t *policies; GtkBox *policy_box; bool sp = false; AG_hide_all_frames (); AG_stop_long_action (); rd = json_object_get (AG_redux_state, "recovery_document"); policies = json_object_get (rd, "decryption_policies"); GNUNET_assert (NULL != policies); policy_box = GTK_BOX (GCG_get_main_window_object ( "anastasis_gtk_policy_vbox")); /* remove all existing entries from the box */ { GtkContainer *pb; GList *children; pb = GTK_CONTAINER (policy_box); children = gtk_container_get_children (pb); for (GList *iter = children; iter != NULL; iter = g_list_next (iter)) gtk_container_remove (pb, GTK_WIDGET (iter->data)); g_list_free (children); } { size_t pindex; json_t *policy; json_array_foreach (policies, pindex, policy) { sp |= add_policy (policy_box, rd, pindex, policy); } } if (sp) { struct ANASTASIS_LongAction *la = &AG_lacs[ANASTASIS_LP_SYNC_PROVIDERS]; la->next_time = GNUNET_TIME_UNIT_ZERO_ABS; GNUNET_assert (NULL == la->task); la->task = GNUNET_SCHEDULER_add_now (&long_poll_sync_task, NULL); } { struct ANASTASIS_LongAction *la = &AG_lacs[ANASTASIS_LP_POLL_CHALLENGES]; json_t *rd; json_t *challenges; size_t index; json_t *challenge; rd = json_object_get (AG_redux_state, "recovery_document"); GNUNET_assert (NULL != rd); challenges = json_object_get (rd, "challenges"); GNUNET_assert (NULL != challenges); json_array_foreach (challenges, index, challenge) { bool async = false; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_bool ("async", &async), NULL), GNUNET_JSON_spec_end () }; const json_t *ks; ks = json_object_get (challenge, "key_share"); if ( (NULL != ks) && (! json_is_null (ks)) ) continue; /* already solved */ if (GNUNET_OK != GNUNET_JSON_parse (challenge, spec, NULL, NULL)) { GNUNET_break (0); json_dumpf (challenge, stderr, JSON_INDENT (2)); continue; } if (async && (NULL == la->task) ) { la->next_time = GNUNET_TIME_UNIT_ZERO_ABS; la->task = GNUNET_SCHEDULER_add_now (&long_poll_challenges_task, NULL); } } } AG_sensitive ("anastasis_gtk_review_policy_treeview"); AG_show ("anastasis_gtk_progress_vbox"); AG_progress_update (); AG_show ("anastasis_gtk_recovery_progress_scrolled_window"); AG_hide ("anastasis_gtk_backup_progress_scrolled_window"); AG_show ("anastasis_gtk_main_control_vbox"); AG_show ("anastasis_gtk_main_window_save_as_button"); AG_show ("anastasis_gtk_challenge_frame"); AG_show ("anastasis_gtk_main_window_prev_button"); AG_hide ("anastasis_gtk_main_window_quit_button"); AG_hide ("anastasis_gtk_main_window_forward_button"); } /** * An Anastasis provider requires payment for a challenge. * Give opportunity to the user to pay. */ static void action_challenge_paying (void) { json_t *pprs; json_t *ppr; GtkListStore *ls; const char *uuid; bool found = false; const char *ps; AG_hide_all_frames (); ls = GTK_LIST_STORE (GCG_get_main_window_object ( "unpaid_qrcodes_liststore")); gtk_list_store_clear (ls); pprs = json_object_get (AG_redux_state, "challenge_feedback"); json_object_foreach (pprs, uuid, ppr) { const char *state; const char *payto = NULL; const char *provider = NULL; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("state", &state), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("taler_pay_uri", &payto), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("provider", &provider), NULL), GNUNET_JSON_spec_string ("payment_secret", &ps), GNUNET_JSON_spec_end () }; GdkPixbuf *pb; GtkWidget *w; if (GNUNET_OK != GNUNET_JSON_parse (ppr, spec, NULL, NULL)) { GNUNET_break (0); json_dumpf (ppr, stderr, JSON_INDENT (2)); continue; } if (NULL == payto) continue; if (0 != strcmp (state, "payment")) continue; found = true; w = GTK_WIDGET (GCG_get_main_window_object ("unpaid_qr_treeview")); pb = AG_setup_qrcode (w, payto, strlen (payto)); if (NULL == pb) GNUNET_log (GNUNET_ERROR_TYPE_WARNING, _ ("Failed to initialize QR-code pixbuf for `%s'\n"), payto); gtk_list_store_insert_with_values (ls, NULL, -1, /* append */ AG_UQRMC_QR_IMAGE, pb, AG_UQRMC_URL, payto, AG_UQRMC_PROVIDER, provider, -1); g_object_unref (pb); break; } if (found) { json_t *args; struct GNUNET_TIME_Relative timeout; timeout = GNUNET_TIME_UNIT_MINUTES; GNUNET_assert (NULL == AG_ra); args = GNUNET_JSON_PACK ( GNUNET_JSON_pack_time_rel ("timeout", timeout), GNUNET_JSON_pack_string ("payment_secret", ps)); AG_ra = ANASTASIS_redux_action (AG_redux_state, "pay", args, &AG_action_cb, NULL); json_decref (args); } else { AG_error ("ERROR: Internal error: should pay, but do not know what"); } AG_show ("anastasis_gtk_progress_vbox"); AG_progress_update (); AG_show ("anastasis_gtk_recovery_progress_scrolled_window"); AG_hide ("anastasis_gtk_backup_progress_scrolled_window"); AG_show ("anastasis_gtk_pay_frame"); AG_show ("anastasis_gtk_main_control_vbox"); AG_sensitive ("anastasis_gtk_main_window_prev_button"); AG_show ("anastasis_gtk_main_window_prev_button"); AG_hide ("anastasis_gtk_main_window_forward_button"); } /** * Render challenge feedback for challenge @a uuid_str in a dialog of * @a builder in the label under @a target_widget. * * Useful in case the operation previously failed at the * server and we have some useful information to return * to the user. * * FIXME: only do it AFTER the first attempt of the * user to enter a code, and/or change what the * server returns so we do NOT render a confusing * error message on first use! * * @param builder a builder to get widgets from * @param target_widget the widget to update * @param uuid_str the UUID to render feedback for */ static void render_feedback (GtkBuilder *builder, const char *target_widget, const char *uuid_str) { json_t *cf; json_t *cs; const char *state; const char *redirect_url = NULL; const char *hint = NULL; json_t *details = NULL; const char *taler_pay_uri = NULL; uint32_t ec = TALER_EC_NONE; uint32_t http_status = 0; bool hide = false; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("state", &state), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("taler_pay_uri", &taler_pay_uri), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_json ("details", &details), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("redirect_url", &redirect_url), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("hint", &hint), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_uint32 ("http_status", &http_status), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_uint32 ("error_code", &ec), NULL), GNUNET_JSON_spec_end () }; GtkLabel *elabel; char *msg; cf = json_object_get (AG_redux_state, "challenge_feedback"); cs = json_object_get (cf, uuid_str); if (NULL == cs) return; elabel = GTK_LABEL (gtk_builder_get_object (builder, target_widget)); if (NULL == elabel) { GNUNET_break (0); return; } if (GNUNET_OK != GNUNET_JSON_parse (cs, spec, NULL, NULL)) { GNUNET_break (0); gtk_label_set_text (elabel, _ ("INTERNAL ERROR: could not parse state")); gtk_widget_show (GTK_WIDGET (elabel)); return; } if ( (0 == strcmp (state, "hint")) && (NULL != hint) ) { GNUNET_asprintf (&msg, _ ("Hint (#%u): %s"), (unsigned int) http_status, dgettext ("taler-exchange", hint)); } else if ( (0 == strcmp (state, "details")) && (NULL != details) ) { uint32_t code; const char *hint = NULL; const char *detail = NULL; struct GNUNET_JSON_Specification ispec[] = { GNUNET_JSON_spec_uint32 ("code", &code), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("hint", &hint), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("detail", &detail), NULL), GNUNET_JSON_spec_end () }; if (GNUNET_OK != GNUNET_JSON_parse (details, ispec, NULL, NULL)) { GNUNET_break (0); json_dumpf (details, stderr, JSON_INDENT (2)); msg = GNUNET_strdup ( _ ("ERROR: failed to parse server JSON instructions")); } else { const char *ihint; const char *type = "Error"; switch (code) { case TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED: hide = true; type = "Hint"; break; default: break; } ihint = TALER_ErrorCode_get_hint (code); if ( (NULL != hint) && ( (NULL == ihint) || ('<' == ihint[0])) ) ihint = hint; /* use server hint */ ihint = dgettext ("taler-exchange", ihint); if (NULL == detail) { if (NULL == ihint) GNUNET_asprintf (&msg, "%s #%u", type, (unsigned int) code); else GNUNET_asprintf (&msg, "%s #%u: %s", type, (unsigned int) code, ihint); } else { if (NULL == ihint) GNUNET_asprintf (&msg, "%s #%u (%s)", type, (unsigned int) code, detail); else GNUNET_asprintf (&msg, "%s #%u: %s (%s)", type, (unsigned int) code, ihint, detail); } } } else { GNUNET_asprintf (&msg, "ERROR: state `%s` with HTTP Status %u", state, (unsigned int) http_status); } gtk_label_set_text (elabel, msg); GNUNET_free (msg); if (hide) gtk_widget_hide (GTK_WIDGET (elabel)); else gtk_widget_show (GTK_WIDGET (elabel)); GNUNET_JSON_parse_free (spec); } /** * Open dialog to allow user to answer security question. * * @param details details about the challenge * @return the dialog object, or NULL on error */ static GtkDialog * diag_question (const json_t *details) { GtkBuilder *builder; GtkDialog *ad; builder = GNUNET_GTK_get_new_builder ( "anastasis_gtk_challenge_question.glade", NULL); if (NULL == builder) { GNUNET_break (0); return NULL; } ad = GTK_DIALOG (gtk_builder_get_object (builder, "anastasis_gtk_c_question_dialog")); { GtkLabel *label; const char *instructions; label = GTK_LABEL (gtk_builder_get_object (builder, "security_question_label")); instructions = json_string_value (json_object_get (details, "instructions")); gtk_label_set_text (label, instructions); } { const char *uuid_str; uuid_str = json_string_value (json_object_get (details, "uuid-display")); render_feedback (builder, "anastasis_gtk_c_question_error_label", uuid_str); } return ad; } /** * Create a dialog for the user to enter a PIN code. * * @param details details about the dialog to render * @return dialog object */ static GtkDialog * diag_code (const json_t *details) { GtkBuilder *builder; const char *instructions; const char *uuid_str; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("instructions", &instructions), GNUNET_JSON_spec_string ("uuid-display", &uuid_str), GNUNET_JSON_spec_end () }; if (GNUNET_OK != GNUNET_JSON_parse (details, spec, NULL, NULL)) { GNUNET_break (0); json_dumpf (details, stderr, JSON_INDENT (2)); return NULL; } builder = GNUNET_GTK_get_new_builder ("anastasis_gtk_challenge_code.glade", NULL); if (NULL == builder) { GNUNET_break (0); return NULL; } { GtkLabel *label; label = GTK_LABEL (gtk_builder_get_object (builder, "challenge_instructions_label")); gtk_label_set_text (label, instructions); label = GTK_LABEL (gtk_builder_get_object (builder, "anastasis_gtk_c_challenge_label")); gtk_label_set_text (label, uuid_str); render_feedback (builder, "anastasis_gtk_c_code_error_label", uuid_str); } { GtkDialog *ad; ad = GTK_DIALOG (gtk_builder_get_object (builder, "anastasis_gtk_c_code_dialog")); return ad; } } /** * Create a dialog for the user to enter an TOTP code. * * @param details details about the dialog to render * @return dialog object */ static GtkDialog * diag_totp (const json_t *details) { GtkBuilder *builder; const char *instructions; const char *uuid_str; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("instructions", &instructions), GNUNET_JSON_spec_string ("uuid-display", &uuid_str), GNUNET_JSON_spec_end () }; if (GNUNET_OK != GNUNET_JSON_parse (details, spec, NULL, NULL)) { GNUNET_break (0); json_dumpf (details, stderr, JSON_INDENT (2)); return NULL; } builder = GNUNET_GTK_get_new_builder ("anastasis_gtk_challenge_totp.glade", NULL); if (NULL == builder) { GNUNET_break (0); return NULL; } { GtkLabel *label; label = GTK_LABEL (gtk_builder_get_object (builder, "challenge_instructions_label")); gtk_label_set_text (label, instructions); } render_feedback (builder, "anastasis_gtk_c_totp_error_label", uuid_str); { GtkDialog *ad; ad = GTK_DIALOG (gtk_builder_get_object (builder, "anastasis_gtk_c_totp_dialog")); return ad; } } /** * Create a dialog for the user to make an IBAN transfer. * * @param details details about the dialog to render * @return dialog object */ static GtkDialog * diag_iban (const json_t *details) { GtkBuilder *builder; struct TALER_Amount amount; const char *credit_iban; const char *business; const char *subject; const char *uuid_str; const char *debit_iban_hint; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("uuid", &uuid_str), GNUNET_JSON_spec_string ("instructions", &debit_iban_hint), GNUNET_JSON_spec_end () }; if (GNUNET_OK != GNUNET_JSON_parse (details, spec, NULL, NULL)) { GNUNET_break (0); json_dumpf (details, stderr, JSON_INDENT (2)); return NULL; } { json_t *cf = json_object_get (AG_redux_state, "challenge_feedback"); json_t *ci = json_object_get (cf, uuid_str); json_t *cd = json_object_get (ci, "details"); struct GNUNET_JSON_Specification ispec[] = { TALER_JSON_spec_amount_any ("challenge_amount", &amount), GNUNET_JSON_spec_string ("credit_iban", &credit_iban), GNUNET_JSON_spec_string ("business_name", &business), GNUNET_JSON_spec_string ("wire_transfer_subject", &subject), GNUNET_JSON_spec_end () }; if ( (NULL == cd) || (GNUNET_OK != GNUNET_JSON_parse (cd, ispec, NULL, NULL)) ) { GNUNET_break (0); json_dumpf (AG_redux_state, stderr, JSON_INDENT (2)); return NULL; } } builder = GNUNET_GTK_get_new_builder ("anastasis_gtk_challenge_iban.glade", NULL); if (NULL == builder) { GNUNET_break (0); return NULL; } { GtkLabel *label; label = GTK_LABEL (gtk_builder_get_object (builder, "debit_account_label")); gtk_label_set_text (label, debit_iban_hint); label = GTK_LABEL (gtk_builder_get_object (builder, "credit_account_label")); gtk_label_set_text (label, credit_iban); label = GTK_LABEL (gtk_builder_get_object (builder, "provider_name_label")); gtk_label_set_text (label, business); label = GTK_LABEL (gtk_builder_get_object (builder, "wire_transfer_subject_label")); gtk_label_set_text (label, subject); label = GTK_LABEL (gtk_builder_get_object (builder, "amount_label")); gtk_label_set_text (label, TALER_amount2s (&amount)); } { GtkDialog *ad; ad = GTK_DIALOG (gtk_builder_get_object (builder, "anastasis_gtk_c_iban_dialog")); return ad; } } /** * The user wants to solve the selected challenge. Launch the * dialog to allow the user to enter the solution. */ static void action_challenge_solving (void) { struct { const char *type; GtkDialog *(*ctor)(const json_t *details); } type_map [] = { { .type = gettext_noop ("question"), .ctor = &diag_question }, { .type = gettext_noop ("sms"), .ctor = &diag_code }, { .type = gettext_noop ("post"), .ctor = &diag_code }, { .type = gettext_noop ("email"), .ctor = &diag_code }, { .type = gettext_noop ("iban"), .ctor = &diag_iban }, { .type = gettext_noop ("totp"), .ctor = &diag_totp }, { .type = NULL, .ctor = NULL } }; const char *type; GtkDialog *diag; const char *uuid; const json_t *challenge; uuid = json_string_value (json_object_get (AG_redux_state, "selected_challenge_uuid")); if (NULL == uuid) { GNUNET_break (0); return; } challenge = find_challenge_by_uuid (uuid); if (NULL == challenge) { GNUNET_break (0); return; } type = json_string_value (json_object_get (challenge, "type")); if (NULL == type) { GNUNET_break (0); return; } /* create dialog based on challenge type */ diag = NULL; for (unsigned int i = 0; NULL != type_map[i].type; i++) { if (0 != strcmp (type_map[i].type, type)) continue; diag = type_map[i].ctor (challenge); break; } if (NULL == diag) { GNUNET_break (0); return; } /* show dialog */ { GtkWidget *toplevel; GtkWidget *box; box = GTK_WIDGET (GCG_get_main_window_object ( "anastasis_gtk_policy_vbox")); toplevel = gtk_widget_get_toplevel (box); gtk_window_set_transient_for (GTK_WINDOW (diag), GTK_WINDOW (toplevel)); gtk_window_present (GTK_WINDOW (diag)); } } /** * The recovery process was finished. Show the recovered secret to the * user. */ static void action_recovery_finished (void) { const char *mime = NULL; const char *text = NULL; const char *name = NULL; void *data = NULL; size_t data_size = 0; const json_t *cs; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("mime", &mime), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("text", &text), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_varsize ("value", &data, &data_size), NULL), GNUNET_JSON_spec_end () }; GdkPixbuf *pb; GtkImage *img; AG_hide_all_frames (); name = json_string_value (json_object_get (json_object_get (AG_redux_state, "recovery_information"), "secret_name")); cs = json_object_get (AG_redux_state, "core_secret"); GNUNET_assert (NULL != cs); GNUNET_assert (GNUNET_OK == GNUNET_JSON_parse (cs, spec, NULL, NULL)); AG_hide ("anastasis_gtk_secret_copy_button"); update_label ("anastasis_gtk_secret_value_label", text); if ( (NULL != name) && (0 != strlen (name)) ) update_label ("recovery_secret_name_value_label", name); else update_label ("recovery_secret_name_value_label", _ ("You did not name this secret")); if ( (0 == strncasecmp (mime, "text/", strlen ("text/"))) || (0 == strncasecmp (mime, "image/", strlen ("image/"))) || (NULL != text) ) { /* images and text can be copied */ AG_show ("anastasis_gtk_secret_copy_button"); } pb = NULL; img = GTK_IMAGE (GCG_get_main_window_object ( "anastasis_gtk_secret_qr_image")); if (NULL != text) { pb = AG_setup_qrcode (GTK_WIDGET (img), text, strlen (text)); } else { pb = AG_setup_qrcode (GTK_WIDGET (img), data, data_size); } if (NULL != pb) { gtk_image_set_from_pixbuf (img, pb); g_object_unref (pb); } else { AG_hide ("anastasis_gtk_secret_qr_image"); } GNUNET_JSON_parse_free (spec); AG_hide ("anastasis_gtk_progress_vbox"); AG_hide ("anastasis_gtk_recovery_progress_scrolled_window"); AG_hide ("anastasis_gtk_backup_progress_scrolled_window"); AG_show ("anastasis_gtk_completed_frame"); AG_hide ("anastasis_gtk_backup_complete_box"); AG_hide ("anastasis_gtk_success_backup_label"); AG_show ("anastasis_gtk_success_recovery_box"); AG_show ("anastasis_gtk_main_control_vbox"); AG_hide ("anastasis_gtk_main_window_save_as_button"); AG_show ("anastasis_gtk_restart_button"); AG_show ("anastasis_gtk_main_window_quit_button"); AG_hide ("anastasis_gtk_main_window_prev_button"); AG_hide ("anastasis_gtk_main_window_forward_button"); } /** * Function called with the results of #ANASTASIS_redux_action. * * @param cls closure * @param error_code Error code * @param response new state as result or config information of a provider */ void AG_action_cb (void *cls, enum TALER_ErrorCode error_code, json_t *response) { struct DispatchItem actions[] = { { .state = "CONTINENT_SELECTING", .action = &action_continent_selecting }, { .state = "COUNTRY_SELECTING", .action = &action_country_selecting }, { .state = "USER_ATTRIBUTES_COLLECTING", .action = &action_user_attributes_collecting }, { .state = "AUTHENTICATIONS_EDITING", .action = &action_authentications_editing }, { .state = "POLICIES_REVIEWING", .action = &action_policies_reviewing }, { .state = "SECRET_EDITING", .action = &action_secret_editing }, { .state = "TRUTHS_PAYING", .action = &action_truths_paying }, { .state = "POLICIES_PAYING", .action = &action_policies_paying }, { .state = "BACKUP_FINISHED", .action = &action_backup_finished }, { .state = "SECRET_SELECTING", .action = &action_secret_selecting }, { .state = "CHALLENGE_SELECTING", .action = &action_challenge_selecting }, { .state = "CHALLENGE_PAYING", .action = &action_challenge_paying }, { .state = "CHALLENGE_SOLVING", .action = &action_challenge_solving }, { .state = "RECOVERY_FINISHED", .action = &action_recovery_finished }, { .state = NULL, .action = NULL } }; (void) cls; AG_ra = NULL; AG_thaw (); #if DEBUG fprintf (stderr, "Action result %d\n", error_code); json_dumpf (response, stderr, JSON_INDENT (2)); fprintf (stderr, "END action result %d\n", error_code); #endif if (TALER_EC_NONE != error_code) { /* TODO: maybe also render 'detail' if present in state? See for example ~ anastasis_api_backup_redux.c:3082 */ AG_error ("Error #%d: %s\n", (int) error_code, TALER_ErrorCode_get_hint (error_code)); if (AG_in_action) { GNUNET_break (0); return; } } if ( (NULL != json_object_get (response, "backup_state")) || (NULL != json_object_get (response, "recovery_state")) ) { json_decref (AG_redux_state); AG_stop_long_action (); AG_redux_state = json_incref (response); } if ( (TALER_EC_ANASTASIS_TRUTH_UNKNOWN == error_code) || (TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED == error_code) ) { /* special case: do not remain in previous (challenge selected) state but revert to challenge selecting */ GNUNET_assert (0 == json_object_set_new (AG_redux_state, "recovery_state", json_string ("CHALLENGE_SELECTING"))); } if ( (TALER_EC_ANASTASIS_REDUCER_NETWORK_FAILED == error_code) || (TALER_EC_ANASTASIS_REDUCER_POLICY_MALFORMED == error_code) || (TALER_EC_ANASTASIS_REDUCER_POLICY_LOOKUP_FAILED == error_code) ) { /* special case: do not remain in previous (enter identity) state but advance to secret selecting */ GNUNET_assert (0 == json_object_set_new (AG_redux_state, "recovery_state", json_string ("SECRET_SELECTING"))); } AG_in_action = true; if (GNUNET_OK == AG_dispatch (actions)) { AG_in_action = false; return; } AG_in_action = false; AG_error ("Unhandled state `%s/%s'", json_string_value (json_object_get (AG_redux_state, "backup_state")), json_string_value (json_object_get (AG_redux_state, "recovery_state"))); json_dumpf (AG_redux_state, stderr, JSON_INDENT (2)); json_decref (AG_redux_state); AG_redux_state = NULL; AG_hide_all_frames (); AG_show ("anastasis_gtk_start_frame"); }