anastasis-gtk_handle-auth-edit-provider-clicked.c (17436B)
1 /* 2 This file is part of anastasis-gtk. 3 Copyright (C) 2021 Anastasis SARL 4 5 Anastasis is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published 7 by the Free Software Foundation; either version 3, or (at your 8 option) any later version. 9 10 Anastasis is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with Anastasis; see the file COPYING. If not, write to the 17 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 Boston, MA 02110-1301, USA. 19 */ 20 21 /** 22 * @file src/anastasis/anastasis-gtk_backup.c 23 * @brief Main function of anastasis-gtk 24 * @author Christian Grothoff 25 * @author Dennis Neufeld 26 */ 27 #include <gnunet/gnunet_util_lib.h> 28 #include "anastasis_gtk_util.h" 29 #include "anastasis-gtk_action.h" 30 #include "anastasis-gtk_helper.h" 31 #include "anastasis-gtk_handle-main-window-forward-clicked.h" 32 #include "anastasis-gtk_rows.h" 33 #include <sys/types.h> 34 #include <sys/wait.h> 35 #include <jansson.h> 36 #include <microhttpd.h> 37 38 39 /** 40 * Context for menu callbacks. 41 */ 42 struct MenuContext 43 { 44 /** 45 * Base URL of the selected provider. 46 */ 47 char *url; 48 49 }; 50 51 52 /** 53 * Free a `struct MenuContext`. Called when the action group holding it goes 54 * away, which happens on the next right-click at the latest. 55 * 56 * @param cls the `struct MenuContext` to free 57 */ 58 static void 59 free_menu_context (gpointer cls) 60 { 61 struct MenuContext *ctx = cls; 62 63 GNUNET_free (ctx->url); 64 GNUNET_free (ctx); 65 } 66 67 68 /** 69 * Open @a url in a browser. 70 * 71 * @param url the URL to open 72 */ 73 static void 74 xdg_open (const char *url) 75 { 76 pid_t chld; 77 int status; 78 79 chld = fork (); 80 if (-1 == chld) 81 { 82 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, 83 "fork"); 84 return; 85 } 86 if (0 == chld) 87 { 88 pid_t c2; 89 90 c2 = fork (); 91 if (-1 == c2) 92 _exit (EXIT_FAILURE); 93 if (0 != c2) 94 _exit (EXIT_SUCCESS); 95 execlp ("xdg-open", 96 "xdg-open", 97 url, 98 NULL); 99 execlp ("open", 100 "open", 101 url, 102 NULL); 103 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 104 "exec", 105 "open"); 106 _exit (EXIT_FAILURE); 107 } 108 waitpid (chld, &status, 0); 109 } 110 111 112 /** 113 * Return the provider the user right-clicked on. 114 * 115 * @param view the list the context menu belongs to 116 * @return the context, NULL if there is none 117 */ 118 static struct MenuContext * 119 current_context (gpointer view) 120 { 121 return g_object_get_data (G_OBJECT (view), 122 "anastasis-menu-context"); 123 } 124 125 126 /** 127 * The user selected the 'view terms of service' menu item. 128 * 129 * @param action the activated action 130 * @param parameter unused 131 * @param user_data the tree view holding the `struct MenuContext` 132 */ 133 static void 134 view_terms_of_service (GSimpleAction *action, 135 GVariant *parameter, 136 gpointer user_data) 137 { 138 struct MenuContext *ctx = current_context (user_data); 139 char *tos; 140 141 (void) action; 142 (void) parameter; 143 if (NULL == ctx) 144 { 145 GNUNET_break (0); 146 return; 147 } 148 GNUNET_asprintf (&tos, 149 "%sterms", 150 ctx->url); 151 xdg_open (tos); 152 GNUNET_free (tos); 153 } 154 155 156 /** 157 * The user selected the 'view privacy policy' menu item. 158 * 159 * @param action the activated action 160 * @param parameter unused 161 * @param user_data the tree view holding the `struct MenuContext` 162 */ 163 static void 164 view_privacy_policy (GSimpleAction *action, 165 GVariant *parameter, 166 gpointer user_data) 167 { 168 struct MenuContext *ctx = current_context (user_data); 169 char *pp; 170 171 (void) action; 172 (void) parameter; 173 if (NULL == ctx) 174 { 175 GNUNET_break (0); 176 return; 177 } 178 GNUNET_asprintf (&pp, 179 "%sprivacy", 180 ctx->url); 181 xdg_open (pp); 182 GNUNET_free (pp); 183 } 184 185 186 /** 187 * Return the context menu of @a tv, creating it on first use. A single 188 * popover is reused for the lifetime of the list. 189 * 190 * @param tv the list 191 * @return the popover to show 192 */ 193 static GtkPopover * 194 get_popover (GtkWidget *tv) 195 { 196 static const GActionEntry entries[] = { 197 { .name = "privacy", 198 .activate = &view_privacy_policy }, 199 { .name = "terms", 200 .activate = &view_terms_of_service } 201 }; 202 GtkWidget *popover; 203 204 popover = g_object_get_data (G_OBJECT (tv), 205 "anastasis-context-menu"); 206 if (NULL != popover) 207 return GTK_POPOVER (popover); 208 { 209 GMenu *menu = g_menu_new (); 210 211 g_menu_append (menu, 212 _ ("View _privacy policy..."), 213 "provider.privacy"); 214 g_menu_append (menu, 215 _ ("View _terms of service..."), 216 "provider.terms"); 217 popover = gtk_popover_menu_new_from_model (G_MENU_MODEL (menu)); 218 g_object_unref (menu); 219 } 220 gtk_popover_set_has_arrow (GTK_POPOVER (popover), 221 FALSE); 222 gtk_widget_set_halign (popover, 223 GTK_ALIGN_START); 224 gtk_widget_set_parent (popover, 225 tv); 226 g_object_set_data_full (G_OBJECT (tv), 227 "anastasis-context-menu", 228 popover, 229 (GDestroyNotify) & gtk_widget_unparent); 230 { 231 GSimpleActionGroup *actions; 232 233 actions = g_simple_action_group_new (); 234 g_action_map_add_action_entries (G_ACTION_MAP (actions), 235 entries, 236 G_N_ELEMENTS (entries), 237 tv); 238 gtk_widget_insert_action_group (tv, 239 "provider", 240 G_ACTION_GROUP (actions)); 241 g_object_unref (actions); 242 } 243 return GTK_POPOVER (popover); 244 } 245 246 247 /** 248 * User right-clicked on the list: show the context menu that lets them 249 * view the provider's privacy policy or terms of service. 250 * 251 * @param gesture the click gesture, restricted to button 3 by the UI file 252 * @param n_press number of presses 253 * @param x horizontal position of the click, in widget coordinates 254 * @param y vertical position of the click, in widget coordinates 255 * @param user_data the builder 256 */ 257 void 258 provider_tree_view_pressed_cb (GtkGestureClick *gesture, 259 gint n_press, 260 gdouble x, 261 gdouble y, 262 gpointer user_data) 263 { 264 GtkWidget *widget 265 = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)); 266 struct MenuContext *ctx; 267 AGRow *row; 268 269 (void) n_press; 270 (void) user_data; 271 row = AG_row_at (widget, 272 y); 273 if (NULL == row) 274 return; /* the user clicked past the last provider */ 275 ctx = GNUNET_new (struct MenuContext); 276 ctx->url = GNUNET_strdup (AG_row_string (row, 277 "url")); 278 /* the list owns the context of the row currently pointed at; the 279 previous one is released here */ 280 g_object_set_data_full (G_OBJECT (widget), 281 "anastasis-menu-context", 282 ctx, 283 &free_menu_context); 284 { 285 GtkPopover *popover = get_popover (widget); 286 const GdkRectangle at = { 287 .x = (int) x, 288 .y = (int) y, 289 .width = 1, 290 .height = 1 291 }; 292 293 gtk_popover_set_pointing_to (popover, 294 &at); 295 gtk_popover_popup (popover); 296 } 297 } 298 299 300 /** 301 * The user clicked the "add" button to add a new provider to the list. 302 * 303 * @param button the button object 304 * @param user_data the builder 305 */ 306 void 307 url_add_button_clicked_cb (GtkButton *button, 308 gpointer user_data) 309 { 310 GtkBuilder *builder = GTK_BUILDER (user_data); 311 GListStore *ls; 312 GtkEntry *entry; 313 const char *url; 314 315 ls = AG_list_of (builder, 316 "provider_liststore"); 317 GNUNET_assert (NULL != ls); 318 entry = GTK_ENTRY (gtk_builder_get_object (builder, 319 "url_entry")); 320 url = gtk_editable_get_text (GTK_EDITABLE (entry)); 321 AG_list_append (ls, 322 GNUNET_JSON_PACK ( 323 GNUNET_JSON_pack_string ("url", 324 url), 325 GNUNET_JSON_pack_string ("status", 326 _ ("new")), 327 GNUNET_JSON_pack_string ("color", 328 "blue"), 329 GNUNET_JSON_pack_bool ("enabled", 330 true), 331 GNUNET_JSON_pack_bool ("sensitive", 332 false), 333 GNUNET_JSON_pack_bool ("not_sensitive", 334 true), 335 GNUNET_JSON_pack_string ("name", 336 url))); 337 gtk_editable_set_text (GTK_EDITABLE (entry), 338 ""); 339 } 340 341 342 /** 343 * The user changed the URL of a provider to be possibly added. 344 * Validate the syntax and if it is a valid URL, enable the button. 345 * 346 * @param entry the edited widget 347 * @param user_data the dialog builder 348 */ 349 void 350 url_entry_changed_cb (GtkEntry *entry, 351 gpointer user_data) 352 { 353 GtkBuilder *builder = GTK_BUILDER (user_data); 354 GtkWidget *button; 355 const char *url; 356 357 button = GTK_WIDGET (gtk_builder_get_object (builder, 358 "add_button")); 359 url = gtk_editable_get_text (GTK_EDITABLE (entry)); 360 gtk_widget_set_sensitive (button, 361 (0 == strncasecmp (url, 362 "http://", 363 strlen ("http://"))) || 364 (0 == strncasecmp (url, 365 "https://", 366 strlen ("https://")))); 367 } 368 369 370 /** 371 * Function called from the edit-provider dialog upon completion. 372 * 373 * @param dialog the pseudonym selection dialog 374 * @param response_id response code from the dialog 375 * @param user_data the builder of the dialog 376 */ 377 void 378 edit_provider_dialog_response_cb (GtkDialog *dialog, 379 gint response_id, 380 gpointer user_data) 381 { 382 GtkBuilder *builder = GTK_BUILDER (user_data); 383 GListModel *lm; 384 json_t *args; 385 guint n; 386 387 if (GTK_RESPONSE_APPLY != response_id) 388 { 389 gtk_window_destroy (GTK_WINDOW (dialog)); 390 g_object_unref (G_OBJECT (builder)); 391 return; 392 } 393 lm = G_LIST_MODEL (AG_list_of (builder, 394 "provider_liststore")); 395 if (NULL == lm) 396 { 397 GNUNET_break (0); 398 return; 399 } 400 args = json_object (); 401 n = g_list_model_get_n_items (lm); 402 for (guint i = 0; i < n; i++) 403 { 404 AGRow *row = g_list_model_get_item (lm, 405 i); 406 407 GNUNET_assert (0 == 408 json_object_set_new ( 409 args, 410 AG_row_string (row, 411 "url"), 412 GNUNET_JSON_PACK ( 413 GNUNET_JSON_pack_string ("status", 414 AG_row_bool (row, 415 "enabled") 416 ? "not-contacted" 417 : "disabled")))); 418 g_object_unref (row); 419 } 420 gtk_window_destroy (GTK_WINDOW (dialog)); 421 g_object_unref (G_OBJECT (builder)); 422 AG_freeze (); 423 AG_ra = ANASTASIS_redux_action (AG_redux_state, 424 "add_provider", 425 args, 426 &AG_action_cb, 427 NULL); 428 json_decref (args); 429 } 430 431 432 /** 433 * Callback invoked if the the "Edit"-provider list button is clicked. 434 * 435 * @param object 436 * @param user_data unused 437 */ 438 void 439 anastasis_gtk_edit_provider_list_clicked_cb (GtkButton *object, 440 gpointer user_data) 441 { 442 static const struct AG_SortSpec sorts[] = { 443 { .column = "enabled_column", 444 .field = "not_sensitive", 445 .numeric = true }, 446 { .column = "status_column", 447 .field = "not_sensitive", 448 .numeric = true }, 449 { .column = NULL } 450 }; 451 GtkWidget *ad; 452 GtkBuilder *builder; 453 GListStore *ls; 454 json_t *providers; 455 456 builder = ANASTASIS_GTK_get_new_builder ( 457 ANASTASIS_GTK_project_data (), 458 "anastasis_gtk_edit_providers.ui", 459 NULL); 460 if (NULL == builder) 461 { 462 GNUNET_break (0); 463 return; 464 } 465 AG_list_make_sortable (builder, 466 "provider_tree_view", 467 "provider_sorted", 468 sorts); 469 ls = AG_list_of (builder, 470 "provider_liststore"); 471 providers = json_object_get (AG_redux_state, 472 "authentication_providers"); 473 { 474 const char *url; 475 const json_t *provider; 476 json_object_foreach (providers, url, provider) 477 { 478 uint32_t http_code = 0; 479 uint32_t ec = TALER_EC_NONE; 480 struct TALER_Amount ll; 481 const char *status; 482 const char *name = NULL; 483 struct GNUNET_JSON_Specification spec[] = { 484 GNUNET_JSON_spec_string ("status", 485 &status), 486 GNUNET_JSON_spec_mark_optional ( 487 GNUNET_JSON_spec_uint32 ("http_status", 488 &http_code), 489 NULL), 490 GNUNET_JSON_spec_mark_optional ( 491 GNUNET_JSON_spec_string ("business_name", 492 &name), 493 NULL), 494 GNUNET_JSON_spec_mark_optional ( 495 TALER_JSON_spec_amount_any ("liability_limit", 496 &ll), 497 NULL), 498 GNUNET_JSON_spec_mark_optional ( 499 GNUNET_JSON_spec_uint32 ("error_code", 500 &ec), 501 NULL), 502 GNUNET_JSON_spec_end () 503 }; 504 char *status_str; 505 const char *color; 506 bool sensitive = false; 507 const char *ll_s = NULL; 508 509 memset (&ll, 510 0, 511 sizeof (ll)); 512 if (GNUNET_OK != 513 GNUNET_JSON_parse (provider, 514 spec, 515 NULL, NULL)) 516 { 517 GNUNET_break (0); 518 json_dumpf (provider, 519 stderr, 520 JSON_INDENT (2)); 521 continue; 522 } 523 if ( (MHD_HTTP_OK == http_code) && 524 (0 != strcmp (status, 525 "disabled")) ) 526 { 527 status_str = GNUNET_strdup (_ ("available")); 528 color = "green"; 529 sensitive = true; 530 if (GNUNET_OK == 531 TALER_amount_is_valid (&ll)) 532 ll_s = TALER_amount2s (&ll); 533 else 534 GNUNET_break (0); 535 } 536 else if ( (0 == http_code) && 537 (0 != strcmp (status, 538 "disabled")) ) 539 { 540 GNUNET_asprintf (&status_str, 541 _ ("Network failure: %s (#%u)"), 542 TALER_ErrorCode_get_hint (ec), 543 (unsigned int) ec); 544 color = "red"; 545 } 546 else if (0 == strcmp (status, 547 "disabled")) 548 { 549 GNUNET_asprintf (&status_str, 550 _ ("disabled")); 551 color = "blue"; 552 sensitive = true; 553 } 554 else 555 { 556 GNUNET_asprintf (&status_str, 557 _ ("HTTP %s (%u): %s (#%u)"), 558 MHD_get_reason_phrase_for (http_code), 559 (unsigned int) http_code, 560 TALER_ErrorCode_get_hint (ec), 561 (unsigned int) ec); 562 color = "red"; 563 } 564 if (NULL == name) 565 name = url; 566 AG_list_append ( 567 ls, 568 GNUNET_JSON_PACK ( 569 GNUNET_JSON_pack_string ("url", 570 url), 571 GNUNET_JSON_pack_string ("status", 572 status_str), 573 GNUNET_JSON_pack_string ("color", 574 color), 575 GNUNET_JSON_pack_allow_null ( 576 GNUNET_JSON_pack_string ("liability", 577 ll_s)), 578 GNUNET_JSON_pack_bool ("enabled", 579 0 != strcmp (status, 580 "disabled")), 581 GNUNET_JSON_pack_bool ("sensitive", 582 sensitive), 583 GNUNET_JSON_pack_bool ("not_sensitive", 584 ! sensitive), 585 GNUNET_JSON_pack_string ("name", 586 name))); 587 GNUNET_free (status_str); 588 } 589 } 590 ad = GTK_WIDGET (gtk_builder_get_object (builder, 591 "edit_provider_dialog")); 592 { 593 GtkRoot *toplevel; 594 595 toplevel = gtk_widget_get_root (GTK_WIDGET (object)); 596 gtk_window_set_transient_for (GTK_WINDOW (ad), 597 GTK_WINDOW (toplevel)); 598 gtk_window_present (GTK_WINDOW (ad)); 599 } 600 }