anastasis-gtk

Demonstrator GUI for Anastasis
Log | Files | Refs | README | LICENSE

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 }