1 /*
2  * A pseudo-plugin that stores/fetches accounts in/from the SSO via libaccounts
3  *
4  * Copyright © 2010-2011 Nokia Corporation
5  * Copyright © 2010-2011 Collabora Ltd.
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21 
22 #include "config.h"
23 #include "mcd-account-manager-sso.h"
24 #include "mcd-debug.h"
25 
26 #include <telepathy-glib/telepathy-glib.h>
27 
28 #include <libaccounts-glib/ag-account.h>
29 #include <libaccounts-glib/ag-service.h>
30 
31 #include <string.h>
32 #include <ctype.h>
33 
34 #define PLUGIN_PRIORITY (MCP_ACCOUNT_STORAGE_PLUGIN_PRIO_KEYRING + 10)
35 #define PLUGIN_NAME "maemo-libaccounts"
36 #define PLUGIN_DESCRIPTION \
37   "Account storage in the Maemo SSO store via libaccounts-glib API"
38 #define PLUGIN_PROVIDER "org.maemo.Telepathy.Account.Storage.LibAccounts"
39 
40 #define MCPP "param-"
41 #define AGPP "parameters/"
42 #define LIBACCT_ID_KEY  "libacct-uid"
43 
44 #define MC_ENABLED_KEY "Enabled"
45 #define AG_ENABLED_KEY "enabled"
46 
47 #define AG_LABEL_KEY   "name"
48 #define MC_LABEL_KEY   "DisplayName"
49 
50 #define AG_ACCOUNT_KEY "username"
51 #define MC_ACCOUNT_KEY "account"
52 #define PASSWORD_KEY   "password"
53 #define AG_ACCOUNT_ALT_KEY AGPP "account"
54 
55 #define MC_CMANAGER_KEY "manager"
56 #define MC_PROTOCOL_KEY "protocol"
57 #define MC_IDENTITY_KEY "tmc-uid"
58 
59 #define SERVICES_KEY    "sso-services"
60 
61 #define MC_SERVICE_KEY  "Service"
62 
63 #define AG_ACCOUNT_WRITE_INTERVAL 5
64 
65 static const gchar *exported_settings[] = { "CredentialsId", NULL };
66 
67 typedef enum {
68   DELAYED_CREATE,
69   DELAYED_DELETE,
70 } DelayedSignal;
71 
72 typedef struct {
73   gchar *mc_name;
74   gchar *ag_name;
75   gboolean global;   /* global ag setting or service specific? */
76   gboolean readable; /* does the _standard_ read method copy this into MC?  */
77   gboolean writable; /* does the _standard_ write method copy this into AG? */
78   gboolean freeable; /* should clear_setting_data deallocate the names? */
79 } Setting;
80 
81 #define GLOBAL     TRUE
82 #define SERVICE    FALSE
83 #define READABLE   TRUE
84 #define UNREADABLE FALSE
85 #define WRITABLE   TRUE
86 #define UNWRITABLE FALSE
87 
88 typedef enum {
89   SETTING_MC,
90   SETTING_AG,
91 } SettingType;
92 
93 /* IMPORTANT IMPLEMENTATION NOTE:
94  *
95  * The mapping between telepathy settings and parameter names
96  * and ag account (libaccounts) settings, and whether those settings
97  * are stored in the global or service specific ag section is a
98  * finicky beast - the mapping below has been arrived at empirically
99  * Take care when altering it.
100  *
101  * Settings not mentioned explicitly are:
102  * • given the same name on both MC and AG sides
103  * • assigned to the service specific section
104  * • automatically prefixed (param- vs parameters/) for each side if necessary
105  *
106  * So if your setting fits these criteria, you do not need to add it at all.
107  */
108 Setting setting_map[] = {
109   { MC_ENABLED_KEY     , AG_ENABLED_KEY , GLOBAL , UNREADABLE, UNWRITABLE },
110   { MCPP MC_ACCOUNT_KEY, AG_ACCOUNT_KEY , GLOBAL , READABLE  , UNWRITABLE },
111   { MCPP PASSWORD_KEY  , PASSWORD_KEY   , GLOBAL , READABLE  , WRITABLE   },
112   { MC_LABEL_KEY       , AG_LABEL_KEY   , GLOBAL , READABLE  , WRITABLE   },
113   { LIBACCT_ID_KEY     , LIBACCT_ID_KEY , GLOBAL , UNREADABLE, UNWRITABLE },
114   { MC_IDENTITY_KEY    , MC_IDENTITY_KEY, SERVICE, READABLE  , WRITABLE   },
115   { MC_CMANAGER_KEY    , MC_CMANAGER_KEY, SERVICE, READABLE  , UNWRITABLE },
116   { MC_PROTOCOL_KEY    , MC_PROTOCOL_KEY, SERVICE, READABLE  , UNWRITABLE },
117   { MC_SERVICE_KEY     , MC_SERVICE_KEY , SERVICE, UNREADABLE, UNWRITABLE },
118   { SERVICES_KEY       , SERVICES_KEY   , GLOBAL , UNREADABLE, UNWRITABLE },
119   { NULL }
120 };
121 
122 typedef struct {
123   DelayedSignal signal;
124   AgAccountId account_id;
125 } DelayedSignalData;
126 
127 typedef struct {
128   McdAccountManagerSso *sso;
129   struct {
130     AgAccountWatch service;
131     AgAccountWatch global;
132   } watch;
133 } WatchData;
134 
135 static Setting *
setting_data(const gchar * name,SettingType type)136 setting_data (const gchar *name, SettingType type)
137 {
138   guint i = 0;
139   static Setting parameter = { NULL, NULL, SERVICE, READABLE, WRITABLE, TRUE };
140   const gchar *prefix;
141 
142   for (; setting_map[i].mc_name != NULL; i++)
143     {
144       const gchar *setting_name = NULL;
145 
146       if (type == SETTING_MC)
147         setting_name = setting_map[i].mc_name;
148       else
149         setting_name = setting_map[i].ag_name;
150 
151       if (g_strcmp0 (name, setting_name) == 0)
152         return &setting_map[i];
153     }
154 
155   prefix = (type == SETTING_MC) ? MCPP : AGPP;
156 
157   if (!g_str_has_prefix (name, prefix))
158     { /* a non-parameter setting */
159       parameter.mc_name = g_strdup (name);
160       parameter.ag_name = g_strdup (name);
161     }
162   else
163     { /* a setting that is a parameter on both sides (AG & MC) */
164       const guint plength = strlen (prefix);
165 
166       parameter.mc_name = g_strdup_printf ("%s%s", MCPP, name + plength);
167       parameter.ag_name = g_strdup_printf ("%s%s", AGPP, name + plength);
168     }
169 
170   return &parameter;
171 }
172 
173 static void
clear_setting_data(Setting * setting)174 clear_setting_data (Setting *setting)
175 {
176   if (setting == NULL)
177     return;
178 
179   if (!setting->freeable)
180     return;
181 
182   g_free (setting->mc_name);
183   g_free (setting->ag_name);
184   setting->mc_name = NULL;
185   setting->ag_name = NULL;
186 }
187 
188 static gboolean _sso_account_enabled (
189     McdAccountManagerSso *self,
190     AgAccount *account,
191     AgService *service);
192 
193 static void account_storage_iface_init (McpAccountStorageIface *,
194     gpointer);
195 
196 static gchar *
197 _ag_accountid_to_mc_key (McdAccountManagerSso *sso,
198     AgAccountId id,
199     gboolean create);
200 
201 static void _ag_account_stored_cb (AgAccount *acct,
202     const GError *err,
203     gpointer ignore);
204 
205 static void _sso_created (GObject *object,
206     AgAccountId id,
207     gpointer user_data);
208 
209 static void _sso_toggled (GObject *object,
210     AgAccountId id,
211     gpointer data);
212 
213 static gboolean save_setting (
214     McdAccountManagerSso *self,
215     AgAccount *account,
216     const Setting *setting,
217     const gchar *val);
218 
219 G_DEFINE_TYPE_WITH_CODE (McdAccountManagerSso, mcd_account_manager_sso,
220     G_TYPE_OBJECT,
221     G_IMPLEMENT_INTERFACE (MCP_TYPE_ACCOUNT_STORAGE,
222         account_storage_iface_init));
223 
224 static gchar *
_gvalue_to_string(const GValue * val)225 _gvalue_to_string (const GValue *val)
226 {
227   switch (G_VALUE_TYPE (val))
228     {
229       case G_TYPE_STRING:
230         return g_value_dup_string (val);
231       case G_TYPE_BOOLEAN:
232         return g_strdup (g_value_get_boolean (val) ? "true" : "false");
233       case G_TYPE_CHAR:
234         return g_strdup_printf ("%c", g_value_get_uchar (val));
235       case G_TYPE_UCHAR:
236         return g_strdup_printf ("%c", g_value_get_char (val));
237       case G_TYPE_INT:
238         return g_strdup_printf ("%i", g_value_get_int (val));
239       case G_TYPE_UINT:
240         return g_strdup_printf ("%u", g_value_get_uint (val));
241       case G_TYPE_LONG:
242         return g_strdup_printf ("%ld", g_value_get_long (val));
243       case G_TYPE_ULONG:
244         return g_strdup_printf ("%lu", g_value_get_ulong (val));
245       case G_TYPE_INT64:
246         return g_strdup_printf ("%" G_GINT64_FORMAT, g_value_get_int64 (val));
247       case G_TYPE_UINT64:
248         return g_strdup_printf ("%" G_GUINT64_FORMAT, g_value_get_uint64 (val));
249       case G_TYPE_ENUM:
250         return g_strdup_printf ("%d" , g_value_get_enum (val));
251       case G_TYPE_FLAGS:
252         return g_strdup_printf ("%u", g_value_get_flags (val));
253       case G_TYPE_FLOAT:
254         return g_strdup_printf ("%f", g_value_get_float (val));
255       case G_TYPE_DOUBLE:
256         return g_strdup_printf ("%g", g_value_get_double (val));
257       default:
258         DEBUG ("Unsupported type %s", G_VALUE_TYPE_NAME (val));
259         return NULL;
260     }
261 }
262 
263 static const gchar *
account_manager_sso_get_service_type(McdAccountManagerSso * self)264 account_manager_sso_get_service_type (McdAccountManagerSso *self)
265 {
266   McdAccountManagerSsoClass *klass = MCD_ACCOUNT_MANAGER_SSO_GET_CLASS (self);
267 
268   g_assert (klass->service_type != NULL);
269 
270   return klass->service_type;
271 }
272 
273 static gboolean
_ag_account_select_default_im_service(McdAccountManagerSso * self,AgAccount * account)274 _ag_account_select_default_im_service (
275     McdAccountManagerSso *self,
276     AgAccount *account)
277 {
278   const gchar *service_type = account_manager_sso_get_service_type (self);
279   gboolean have_service = FALSE;
280   GList *first = ag_account_list_services_by_type (account, service_type);
281 
282   if (first != NULL && first->data != NULL)
283     {
284       have_service = TRUE;
285       DEBUG ("default %s service %s", service_type,
286           ag_service_get_name (first->data));
287       ag_account_select_service (account, first->data);
288     }
289 
290   ag_service_list_free (first);
291 
292   return have_service;
293 }
294 
295 static AgSettingSource
_ag_account_global_value(AgAccount * account,const gchar * key,GValue * value)296 _ag_account_global_value (AgAccount *account,
297     const gchar *key,
298     GValue *value)
299 {
300   AgSettingSource src = AG_SETTING_SOURCE_NONE;
301   AgService *service = ag_account_get_selected_service (account);
302 
303   if (service != NULL)
304     {
305       ag_account_select_service (account, NULL);
306       src = ag_account_get_value (account, key, value);
307       ag_account_select_service (account, service);
308     }
309   else
310     {
311       src = ag_account_get_value (account, key, value);
312     }
313 
314   return src;
315 }
316 
317 static AgSettingSource
_ag_account_local_value(McdAccountManagerSso * self,AgAccount * account,const gchar * key,GValue * value)318 _ag_account_local_value (
319     McdAccountManagerSso *self,
320     AgAccount *account,
321     const gchar *key,
322     GValue *value)
323 {
324   AgSettingSource src = AG_SETTING_SOURCE_NONE;
325   AgService *service = ag_account_get_selected_service (account);
326 
327   if (service != NULL)
328     {
329       src = ag_account_get_value (account, key, value);
330     }
331   else
332     {
333       _ag_account_select_default_im_service (self, account);
334       src = ag_account_get_value (account, key, value);
335       ag_account_select_service (account, NULL);
336     }
337 
338   return src;
339 }
340 
341 /* AG_ACCOUNT_ALT_KEY from service overrides global AG_ACCOUNT_KEY if set */
342 static void
_maybe_set_account_param_from_service(McdAccountManagerSso * self,const McpAccountManager * am,AgAccount * ag_account,const gchar * mc_account)343 _maybe_set_account_param_from_service (
344     McdAccountManagerSso *self,
345     const McpAccountManager *am,
346     AgAccount *ag_account,
347     const gchar *mc_account)
348 {
349   Setting *setting = setting_data (AG_ACCOUNT_KEY, SETTING_AG);
350   AgSettingSource source = AG_SETTING_SOURCE_NONE;
351   GValue ag_value = G_VALUE_INIT;
352 
353   g_return_if_fail (setting != NULL);
354   g_return_if_fail (ag_account != NULL);
355 
356   g_value_init (&ag_value, G_TYPE_STRING);
357 
358   source = _ag_account_local_value (self, ag_account, AG_ACCOUNT_ALT_KEY,
359       &ag_value);
360 
361   if (source != AG_SETTING_SOURCE_NONE)
362     {
363       gchar *value = _gvalue_to_string (&ag_value);
364 
365       DEBUG ("overriding global %s param with %s: %s",
366           AG_ACCOUNT_KEY, AG_ACCOUNT_ALT_KEY, value);
367       mcp_account_manager_set_value (am, mc_account, setting->mc_name, value);
368       g_free (value);
369     }
370 
371   g_value_unset (&ag_value);
372   clear_setting_data (setting);
373 }
374 
375 static WatchData *
make_watch_data(McdAccountManagerSso * sso)376 make_watch_data (McdAccountManagerSso *sso)
377 {
378   WatchData *data = g_slice_new0 (WatchData);
379 
380   data->sso = g_object_ref (sso);
381 
382   return data;
383 }
384 
385 static void
free_watch_data(gpointer data)386 free_watch_data (gpointer data)
387 {
388   WatchData *wd = data;
389 
390   if (wd == NULL)
391     return;
392 
393   tp_clear_object (&wd->sso);
394   g_slice_free (WatchData, wd);
395 }
396 
unwatch_account_keys(McdAccountManagerSso * sso,AgAccountId id)397 static void unwatch_account_keys (McdAccountManagerSso *sso,
398     AgAccountId id)
399 {
400   gpointer watch_key = GUINT_TO_POINTER (id);
401   WatchData *wd = g_hash_table_lookup (sso->watches, watch_key);
402   AgAccount *account = ag_manager_get_account (sso->ag_manager, id);
403 
404   if (wd != NULL && account != NULL)
405     {
406       ag_account_remove_watch (account, wd->watch.global);
407       ag_account_remove_watch (account, wd->watch.service);
408     }
409 
410   g_hash_table_remove (sso->watches, watch_key);
411 }
412 
413 /* There are two types of ag watch: ag_account_watch_key and                *
414  * ag_account_watch_dir. _key passees us the watched key when invoking this *
415  * callback, dir watches only a prefix, and passes the watched prefix       *
416  * (not the actual updated setting) - we now watch with _dir since _key     *
417  * doesn't allow us to watch for keys-that-are-not-set at creation time     *
418  * (since those cannot be known in advance): This means that in this        *
419  * callback we must compare what we have in MC with what's in AG and issue  *
420  * update notices accordingly (and remember to handle deleted keys).        *
421  * It also means the const gchar *what-was-updated parameter is not useful  */
_sso_updated(AgAccount * account,const gchar * unused,gpointer data)422 static void _sso_updated (AgAccount *account,
423     const gchar *unused,
424     gpointer data)
425 {
426   WatchData *wd = data;
427   McdAccountManagerSso *sso = wd->sso;
428   McpAccountManager *am = sso->manager_interface;
429   McpAccountStorage *mcpa = MCP_ACCOUNT_STORAGE (sso);
430   gpointer id = GUINT_TO_POINTER (account->id);
431   const gchar *name = g_hash_table_lookup (sso->id_name_map, id);
432   AgService *service = ag_account_get_selected_service (account);
433   GStrv keys = NULL;
434   GHashTable *unseen = NULL;
435   GHashTableIter deleted_iter = { 0 };
436   const gchar *deleted_key;
437   guint i;
438   gboolean params_updated = FALSE;
439   const gchar *immutables[] = { MC_SERVICE_KEY, SERVICES_KEY, NULL };
440 
441   /* account has no name yet: might be time to create it */
442   if (name == NULL)
443     return _sso_created (G_OBJECT (sso->ag_manager), account->id, sso);
444 
445   DEBUG ("update for account %s", name);
446 
447   /* list the keys we know about so we can tell if one has been deleted */
448   keys = mcp_account_manager_list_keys (am, name);
449   unseen = g_hash_table_new (g_str_hash, g_str_equal);
450 
451   for (i = 0; keys != NULL && keys[i] != NULL; i++)
452     g_hash_table_insert (unseen, keys[i], GUINT_TO_POINTER (TRUE));
453 
454   /* now iterate over ag settings, global then service specific: */
455   ag_account_select_service (account, NULL);
456 
457   for (i = 0; i < 2; i++)
458     {
459       AgAccountSettingIter iter = { 0 };
460       const gchar *ag_key = NULL;
461       const GValue *ag_val = NULL;
462 
463       if (i == 1)
464         _ag_account_select_default_im_service (sso, account);
465 
466       ag_account_settings_iter_init (account, &iter, NULL);
467 
468       while (ag_account_settings_iter_next (&iter, &ag_key, &ag_val))
469         {
470           Setting *setting = setting_data (ag_key, SETTING_AG);
471           const gchar *mc_key;
472           gchar *ag_str;
473           gchar *mc_str;
474 
475           if (setting == NULL)
476             continue;
477 
478           mc_key = setting->mc_name;
479           mc_str = mcp_account_manager_get_value (am, name, mc_key);
480           ag_str = _gvalue_to_string (ag_val);
481           g_hash_table_remove (unseen, mc_key);
482 
483           if (tp_strdiff (ag_str, mc_str))
484             {
485               mcp_account_manager_set_value (am, name, mc_key, ag_str);
486 
487               if (sso->ready)
488                 {
489                   if (g_str_has_prefix (mc_key, MCPP))
490                     params_updated = TRUE;
491                   else
492                     mcp_account_storage_emit_altered_one (mcpa, name, mc_key);
493                 }
494             }
495 
496           g_free (mc_str);
497           g_free (ag_str);
498           clear_setting_data (setting);
499         }
500     }
501 
502   /* special case values always exist and therefore cannot be deleted */
503   for (i = 0; immutables[i] != NULL; i++)
504     {
505       Setting *immutable = setting_data (immutables[i], SETTING_AG);
506 
507       g_hash_table_remove (unseen, immutable->ag_name);
508       clear_setting_data (immutable);
509     }
510 
511   /* signal (and update) deleted settings: */
512   g_hash_table_iter_init (&deleted_iter, unseen);
513 
514   while (g_hash_table_iter_next (&deleted_iter, (gpointer *)&deleted_key, NULL))
515     {
516       mcp_account_manager_set_value (am, name, deleted_key, NULL);
517 
518       if (g_str_has_prefix (deleted_key, MCPP))
519         params_updated = TRUE;
520       else
521         mcp_account_storage_emit_altered_one (mcpa, name, deleted_key);
522     }
523 
524   g_hash_table_unref (unseen);
525   g_strfreev (keys);
526 
527   if (params_updated)
528     mcp_account_storage_emit_altered_one (mcpa, name, "Parameters");
529 
530   /* put the selected service back the way it was when we found it */
531   ag_account_select_service (account, service);
532 }
533 
watch_for_updates(McdAccountManagerSso * sso,AgAccount * account)534 static void watch_for_updates (McdAccountManagerSso *sso,
535     AgAccount *account)
536 {
537   WatchData *data;
538   gpointer id = GUINT_TO_POINTER (account->id);
539   AgService *service;
540 
541   /* already watching account? let's be idempotent */
542   if (g_hash_table_lookup (sso->watches, id) != NULL)
543     return;
544 
545   DEBUG ("watching AG ID %u for updates", account->id);
546 
547   service = ag_account_get_selected_service (account);
548 
549   data = make_watch_data (sso);
550 
551   ag_account_select_service (account, NULL);
552   data->watch.global = ag_account_watch_dir (account, "", _sso_updated, data);
553 
554   _ag_account_select_default_im_service (sso, account);
555   data->watch.service = ag_account_watch_dir (account, "", _sso_updated, data);
556 
557   g_hash_table_insert (sso->watches, id, data);
558   ag_account_select_service (account, service);
559 }
560 
_sso_toggled(GObject * object,AgAccountId id,gpointer data)561 static void _sso_toggled (GObject *object,
562     AgAccountId id,
563     gpointer data)
564 {
565   AgManager *manager = AG_MANAGER (object);
566   McdAccountManagerSso *sso = MCD_ACCOUNT_MANAGER_SSO (data);
567   McpAccountStorage *mcpa = MCP_ACCOUNT_STORAGE (sso);
568   AgAccount *account = NULL;
569   gboolean on = FALSE;
570   const gchar *name = NULL;
571 
572   /* If the account manager isn't ready, account state changes are of no   *
573    * interest to us: it will pick up the then-current state of the account *
574    * when it does become ready, and anything that happens between now and  *
575    * then is not important:                                                */
576   if (!sso->ready)
577     return;
578 
579   account = ag_manager_get_account (manager, id);
580 
581   if (account != NULL)
582     {
583       on = _sso_account_enabled (sso, account, NULL);
584       name = g_hash_table_lookup (sso->id_name_map, GUINT_TO_POINTER (id));
585     }
586 
587   if (name != NULL)
588     {
589       const gchar *value = on ? "true" : "false";
590       McpAccountManager *am = sso->manager_interface;
591 
592       mcp_account_manager_set_value (am, name, "Enabled", value);
593       mcp_account_storage_emit_toggled (mcpa, name, on);
594     }
595   else
596     {
597       DEBUG ("received enabled=%u signal for unknown SSO account %u", on, id);
598     }
599 }
600 
_sso_deleted(GObject * object,AgAccountId id,gpointer user_data)601 static void _sso_deleted (GObject *object,
602     AgAccountId id,
603     gpointer user_data)
604 {
605   McdAccountManagerSso *sso = MCD_ACCOUNT_MANAGER_SSO (user_data);
606 
607   if (sso->ready)
608     {
609       const gchar *name =
610         g_hash_table_lookup (sso->id_name_map, GUINT_TO_POINTER (id));
611 
612       /* if the account was in our cache, then this was a 3rd party delete *
613        * op that someone did behind our back: fire the signal and clean up */
614       if (name != NULL)
615         {
616           McpAccountStorage *mcpa = MCP_ACCOUNT_STORAGE (sso);
617           gchar *signalled_name = g_strdup (name);
618 
619           /* forget id->name map first, so the signal can't start a loop */
620           g_hash_table_remove (sso->id_name_map, GUINT_TO_POINTER (id));
621           g_hash_table_remove (sso->accounts, signalled_name);
622 
623           /* stop watching for updates */
624           unwatch_account_keys (sso, id);
625 
626           mcp_account_storage_emit_deleted (mcpa, signalled_name);
627 
628           g_free (signalled_name);
629         }
630     }
631   else
632     {
633       DelayedSignalData *sig_data = g_slice_new0 (DelayedSignalData);
634 
635       sig_data->signal = DELAYED_DELETE;
636       sig_data->account_id = id;
637       g_queue_push_tail (sso->pending_signals, sig_data);
638     }
639 }
640 
641 /* return TRUE if we actually changed any state, FALSE otherwise */
_sso_account_enable(McdAccountManagerSso * self,AgAccount * account,AgService * service,gboolean on)642 static gboolean _sso_account_enable (
643     McdAccountManagerSso *self,
644     AgAccount *account,
645     AgService *service,
646     gboolean on)
647 {
648   AgService *original = ag_account_get_selected_service (account);
649 
650   /* the setting account is already in one of the global+service
651      configurations that corresponds to our current state: don't touch it */
652   if (_sso_account_enabled (self, account, service) == on)
653     return FALSE;
654 
655   /* turn the local enabled flag on/off as required */
656   if (service != NULL)
657     ag_account_select_service (account, service);
658   else
659     _ag_account_select_default_im_service (self, account);
660 
661   ag_account_set_enabled (account, on);
662 
663   /* if we are turning the account on, the global flag must also be set *
664    * NOTE: this isn't needed when turning the account off               */
665   if (on)
666     {
667       ag_account_select_service (account, NULL);
668       ag_account_set_enabled (account, on);
669     }
670 
671   ag_account_select_service (account, original);
672 
673   return TRUE;
674 }
675 
_sso_account_enabled(McdAccountManagerSso * self,AgAccount * account,AgService * service)676 static gboolean _sso_account_enabled (
677     McdAccountManagerSso *self,
678     AgAccount *account,
679     AgService *service)
680 {
681   gboolean local  = FALSE;
682   gboolean global = FALSE;
683   AgService *original = ag_account_get_selected_service (account);
684 
685   if (service == NULL)
686     {
687       _ag_account_select_default_im_service (self, account);
688       local = ag_account_get_enabled (account);
689     }
690   else
691     {
692       if (original != service)
693         ag_account_select_service (account, service);
694 
695       local = ag_account_get_enabled (account);
696     }
697 
698   ag_account_select_service (account, NULL);
699   global = ag_account_get_enabled (account);
700 
701   ag_account_select_service (account, original);
702 
703   DEBUG ("_sso_account_enabled: global:%d && local:%d", global, local);
704 
705   return local && global;
706 }
707 
_sso_created(GObject * object,AgAccountId id,gpointer user_data)708 static void _sso_created (GObject *object,
709     AgAccountId id,
710     gpointer user_data)
711 {
712   AgManager *ag_manager = AG_MANAGER (object);
713   McdAccountManagerSso *sso = MCD_ACCOUNT_MANAGER_SSO (user_data);
714   gchar *name =
715     g_hash_table_lookup (sso->id_name_map, GUINT_TO_POINTER (id));
716 
717   if (sso->ready)
718     {
719       /* if we already know the account's name, we shouldn't fire the new *
720        * account signal as it is one we (and our superiors) already have  *
721        * This could happen as a result of multiple updates being set off  *
722        * before we are ready, for example                                 */
723       if (name == NULL)
724         {
725           McpAccountStorage *mcpa = MCP_ACCOUNT_STORAGE (sso);
726           AgAccount *account = ag_manager_get_account (ag_manager, id);
727 
728           if (account != NULL)
729             {
730               /* this will be owned by the ag account hash, do not free it */
731               name = _ag_accountid_to_mc_key (sso, id, TRUE);
732 
733               if (name != NULL)
734                 {
735                   Setting *setting = setting_data (MC_IDENTITY_KEY, SETTING_MC);
736 
737                   g_hash_table_insert (sso->accounts, name, account);
738                   g_hash_table_insert (sso->id_name_map, GUINT_TO_POINTER (id),
739                       g_strdup (name));
740 
741                   save_setting (sso, account, setting, name);
742 
743                   ag_account_store (account, _ag_account_stored_cb, sso);
744 
745                   mcp_account_storage_emit_created (mcpa, name);
746 
747                   clear_setting_data (setting);
748                 }
749               else
750                 {
751                   /* not enough data to name the account: wait for an update */
752                   DEBUG ("SSO account #%u is currently unnameable", id);
753                 }
754 
755               /* in either case, add the account to the watched list */
756               watch_for_updates (sso, account);
757             }
758         }
759     }
760   else
761     {
762       DelayedSignalData *sig_data = g_slice_new0 (DelayedSignalData);
763 
764       sig_data->signal = DELAYED_CREATE;
765       sig_data->account_id = id;
766       g_queue_push_tail (sso->pending_signals, sig_data);
767     }
768 }
769 
770 static void
mcd_account_manager_sso_init(McdAccountManagerSso * self)771 mcd_account_manager_sso_init (McdAccountManagerSso *self)
772 {
773   self->accounts =
774     g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
775   self->id_name_map =
776     g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
777   self->watches =
778     g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
779         (GDestroyNotify) free_watch_data);
780   self->pending_signals = g_queue_new ();
781 
782 }
783 
784 static void
mcd_account_manager_sso_constructed(GObject * object)785 mcd_account_manager_sso_constructed (GObject *object)
786 {
787   McdAccountManagerSso *self = MCD_ACCOUNT_MANAGER_SSO (object);
788   GObjectClass *parent_class =
789       G_OBJECT_CLASS (mcd_account_manager_sso_parent_class);
790   const gchar *service_type = account_manager_sso_get_service_type (self);
791 
792   if (parent_class->constructed != NULL)
793     parent_class->constructed (object);
794 
795   DEBUG ("Watching for services of type '%s'", service_type);
796   self->ag_manager = ag_manager_new_for_service_type (service_type);
797 
798   g_signal_connect(self->ag_manager, "enabled-event",
799       G_CALLBACK (_sso_toggled), self);
800   g_signal_connect(self->ag_manager, "account-deleted",
801       G_CALLBACK (_sso_deleted), self);
802   g_signal_connect(self->ag_manager, "account-created",
803       G_CALLBACK (_sso_created), self);
804 }
805 
806 static void
mcd_account_manager_sso_class_init(McdAccountManagerSsoClass * klass)807 mcd_account_manager_sso_class_init (McdAccountManagerSsoClass *klass)
808 {
809   GObjectClass *object_class = G_OBJECT_CLASS (klass);
810 
811   object_class->constructed = mcd_account_manager_sso_constructed;
812 
813   klass->service_type = "IM";
814 }
815 
816 static void
_ag_account_stored_cb(AgAccount * account,const GError * err,gpointer user_data)817 _ag_account_stored_cb (
818     AgAccount *account,
819     const GError *err,
820     gpointer user_data)
821 {
822   McdAccountManagerSso *self = MCD_ACCOUNT_MANAGER_SSO (user_data);
823   GValue uid = G_VALUE_INIT;
824   const gchar *name = NULL;
825   AgSettingSource src = AG_SETTING_SOURCE_NONE;
826 
827   g_value_init (&uid, G_TYPE_STRING);
828 
829   src = _ag_account_local_value (self, account, MC_IDENTITY_KEY, &uid);
830 
831   if (src != AG_SETTING_SOURCE_NONE && G_VALUE_HOLDS_STRING (&uid))
832     {
833       name = g_value_get_string (&uid);
834       DEBUG ("%p:%s stored: %s", account, name, err ? err->message : "-");
835       g_value_unset (&uid);
836     }
837   else
838     {
839       DEBUG ("%p:%s not stored? %s", acct,
840           ag_account_get_display_name (account), err ? err->message : "-");
841     }
842 }
843 
844 static gchar *
_ag_accountid_to_mc_key(McdAccountManagerSso * sso,AgAccountId id,gboolean create)845 _ag_accountid_to_mc_key (McdAccountManagerSso *sso,
846     AgAccountId id,
847     gboolean create)
848 {
849   AgAccount *account = ag_manager_get_account (sso->ag_manager, id);
850   AgSettingSource src = AG_SETTING_SOURCE_NONE;
851   AgService *service = NULL;
852   GValue value = G_VALUE_INIT;
853 
854   if (account == NULL)
855     {
856       DEBUG ("AG Account ID %u invalid", id);
857       return NULL;
858     }
859 
860   service = ag_account_get_selected_service (account);
861 
862   DEBUG ("AG Account ID: %u", id);
863 
864   g_value_init (&value, G_TYPE_STRING);
865 
866   /* first look for the stored TMC uid */
867   src = _ag_account_local_value (sso, account, MC_IDENTITY_KEY, &value);
868 
869   /* if we found something, our work here is done: */
870   if (src != AG_SETTING_SOURCE_NONE)
871     {
872       gchar *uid = g_value_dup_string (&value);
873       g_value_unset (&value);
874       return uid;
875     }
876 
877   if (!create)
878     {
879       g_value_unset (&value);
880       return NULL;
881     }
882 
883   DEBUG ("no " MC_IDENTITY_KEY " found, synthesising one:");
884 
885   src = _ag_account_global_value (account, AG_ACCOUNT_KEY, &value);
886 
887   /* fall back to the alernative account-naming setting if necessary: */
888   if (src == AG_SETTING_SOURCE_NONE)
889     {
890       _ag_account_select_default_im_service (sso, account);
891       src = _ag_account_local_value (sso, account, AG_ACCOUNT_ALT_KEY, &value);
892     }
893 
894   if (src != AG_SETTING_SOURCE_NONE && G_VALUE_HOLDS_STRING (&value))
895     {
896       AgAccountSettingIter iter;
897       const gchar *k;
898       const GValue *v;
899       GValue cmanager = G_VALUE_INIT;
900       GValue protocol = G_VALUE_INIT;
901       const gchar *cman, *proto;
902       McpAccountManager *am = sso->manager_interface;
903       GHashTable *params = g_hash_table_new_full (g_str_hash, g_str_equal,
904           g_free, NULL);
905       gchar *name = NULL;
906 
907       g_value_init (&cmanager, G_TYPE_STRING);
908       g_value_init (&protocol, G_TYPE_STRING);
909 
910       /* if we weren't on a service when got here, pick the most likely one: */
911       if (service == NULL)
912         _ag_account_select_default_im_service (sso, account);
913 
914       ag_account_get_value (account, MC_CMANAGER_KEY, &cmanager);
915       cman = g_value_get_string (&cmanager);
916 
917       if (cman == NULL)
918         goto cleanup;
919 
920       ag_account_get_value (account, MC_PROTOCOL_KEY, &protocol);
921       proto = g_value_get_string (&protocol);
922 
923       if (proto == NULL)
924         goto cleanup;
925 
926       /* prepare the hash of MC param keys -> GValue */
927       /* NOTE: some AG bare settings map to MC parameters,   *
928        * so we must iterate over all AG settings, parameters *
929        * and bare settings included                          */
930 
931       /* first any matching global values: */
932       ag_account_select_service (account, NULL);
933       ag_account_settings_iter_init (account, &iter, NULL);
934 
935       while (ag_account_settings_iter_next (&iter, &k, &v))
936         {
937           Setting *setting = setting_data (k, SETTING_AG);
938 
939           if (setting != NULL && g_str_has_prefix (setting->mc_name, MCPP))
940             {
941               gchar *param_key = g_strdup (setting->mc_name + strlen (MCPP));
942 
943               g_hash_table_insert (params, param_key, (gpointer) v);
944             }
945 
946           clear_setting_data (setting);
947         }
948 
949       /* then any service specific settings */
950       if (service != NULL)
951         ag_account_select_service (account, service);
952       else
953         _ag_account_select_default_im_service (sso, account);
954 
955       ag_account_settings_iter_init (account, &iter, NULL);
956       while (ag_account_settings_iter_next (&iter, &k, &v))
957         {
958           Setting *setting = setting_data (k, SETTING_AG);
959 
960           if (setting != NULL && g_str_has_prefix (setting->mc_name, MCPP))
961             {
962               gchar *param_key = g_strdup (setting->mc_name + strlen (MCPP));
963 
964               g_hash_table_insert (params, param_key, (gpointer) v);
965             }
966 
967           clear_setting_data (setting);
968         }
969 
970       /* we want this to override any other settings for uid generation */
971       g_hash_table_insert (params, g_strdup (MC_ACCOUNT_KEY), &value);
972 
973       name = mcp_account_manager_get_unique_name (am, cman, proto, params);
974 
975     cleanup:
976       ag_account_select_service (account, service);
977       g_hash_table_unref (params);
978       g_value_unset (&value);
979       g_value_unset (&cmanager);
980       g_value_unset (&protocol);
981 
982       DEBUG (MC_IDENTITY_KEY " value %p:%s synthesised", name, name);
983       return name;
984     }
985   else
986     {
987       g_value_unset (&value);
988     }
989 
990   DEBUG (MC_IDENTITY_KEY " not synthesised, returning NULL");
991   return NULL;
992 }
993 
994 static AgAccount *
get_ag_account(const McdAccountManagerSso * sso,const McpAccountManager * am,const gchar * name,AgAccountId * id)995 get_ag_account (const McdAccountManagerSso *sso,
996     const McpAccountManager *am,
997     const gchar *name,
998     AgAccountId *id)
999 {
1000   AgAccount *account;
1001 
1002   g_return_val_if_fail (id != NULL, NULL);
1003 
1004   /* we have a cached account, just return that */
1005   account = g_hash_table_lookup (sso->accounts, name);
1006   if (account != NULL)
1007     {
1008       *id = account->id;
1009       return account;
1010     }
1011 
1012   *id = 0;
1013 
1014   return NULL;
1015 }
1016 
1017 /* returns true if it actually changed an account's state */
1018 static gboolean
save_setting(McdAccountManagerSso * self,AgAccount * account,const Setting * setting,const gchar * val)1019 save_setting (
1020     McdAccountManagerSso *self,
1021     AgAccount *account,
1022     const Setting *setting,
1023     const gchar *val)
1024 {
1025   gboolean changed = FALSE;
1026   AgService *service = ag_account_get_selected_service (account);
1027 
1028   if (!setting->writable)
1029     return FALSE;
1030 
1031   if (setting->global)
1032     ag_account_select_service (account, NULL);
1033   else if (service == NULL)
1034     _ag_account_select_default_im_service (self, account);
1035 
1036   if (setting->readable)
1037     {
1038       GValue old = G_VALUE_INIT;
1039       AgSettingSource src = AG_SETTING_SOURCE_NONE;
1040 
1041       g_value_init (&old, G_TYPE_STRING);
1042 
1043       if (setting->global)
1044         src = _ag_account_global_value (account, setting->ag_name, &old);
1045       else
1046         src = _ag_account_local_value (self, account, setting->ag_name, &old);
1047 
1048       /* unsetting an already unset value, bail out */
1049       if (val == NULL && src == AG_SETTING_SOURCE_NONE)
1050         goto done;
1051 
1052       /* assigning a value to one which _is_ set: check it actually changed */
1053       if (val != NULL && src != AG_SETTING_SOURCE_NONE)
1054         {
1055           gchar *str = _gvalue_to_string (&old);
1056           gboolean noop = g_strcmp0 (str, val) == 0;
1057 
1058           g_value_unset (&old);
1059           g_free (str);
1060 
1061           if (noop)
1062             goto done;
1063         }
1064     }
1065 
1066   /* if we got this far, we're changing the stored state: */
1067   changed = TRUE;
1068 
1069   if (val != NULL)
1070     {
1071       GValue value = G_VALUE_INIT;
1072 
1073       g_value_init (&value, G_TYPE_STRING);
1074       g_value_set_string (&value, val);
1075       ag_account_set_value (account, setting->ag_name, &value);
1076       g_value_unset (&value);
1077     }
1078   else
1079     {
1080       ag_account_set_value (account, setting->ag_name, NULL);
1081     }
1082 
1083   /* leave the selected service as we found it: */
1084  done:
1085   ag_account_select_service (account, service);
1086 
1087   return changed;
1088 }
1089 
1090 static gboolean
_set(const McpAccountStorage * self,const McpAccountManager * am,const gchar * account_suffix,const gchar * key,const gchar * val)1091 _set (const McpAccountStorage *self,
1092     const McpAccountManager *am,
1093     const gchar *account_suffix,
1094     const gchar *key,
1095     const gchar *val)
1096 {
1097   AgAccountId id;
1098   McdAccountManagerSso *sso = MCD_ACCOUNT_MANAGER_SSO (self);
1099   AgAccount *account = get_ag_account (sso, am, account_suffix, &id);
1100   Setting *setting = NULL;
1101   gboolean updated = FALSE;
1102 
1103   /* can't store a setting with no name */
1104   g_return_val_if_fail (key != NULL, FALSE);
1105 
1106   /* we no longer create accounts in libaccount: either an account exists *
1107    * in libaccount as a result of some 3rd party intervention, or it is   *
1108    * not an account that this plugin should ever concern itself with      */
1109 
1110   if (account != NULL)
1111     setting = setting_data (key, SETTING_MC);
1112   else
1113     return FALSE;
1114 
1115   if (setting != NULL)
1116     {
1117       /* Enabled is both a global and a local value, for extra fun: */
1118       if (g_str_equal (setting->mc_name, MC_ENABLED_KEY))
1119         {
1120           gboolean on = g_str_equal (val, "true");
1121 
1122           DEBUG ("setting enabled flag: '%d'", on);
1123           updated = _sso_account_enable (sso, account, NULL, on);
1124         }
1125       else
1126         {
1127           updated = save_setting (sso, account, setting, val);
1128         }
1129 
1130       if (updated)
1131         sso->save = TRUE;
1132 
1133       clear_setting_data (setting);
1134     }
1135 
1136   /* whether or not we stored this value, if we got this far it's our *
1137    * setting and no-one else is allowed to claim it: so return TRUE   */
1138   return TRUE;
1139 }
1140 
1141 /* Implements the half of the _get method where key is not NULL. */
1142 static void
account_manager_sso_get_one(McdAccountManagerSso * sso,const McpAccountManager * am,const gchar * account_suffix,const gchar * key,AgAccount * account,AgService * service)1143 account_manager_sso_get_one (
1144     McdAccountManagerSso *sso,
1145     const McpAccountManager *am,
1146     const gchar *account_suffix,
1147     const gchar *key,
1148     AgAccount *account,
1149     AgService *service)
1150 {
1151   if (g_str_equal (key, MC_ENABLED_KEY))
1152     {
1153       const gchar *v = NULL;
1154 
1155       v = _sso_account_enabled (sso, account, service) ? "true" : "false";
1156       mcp_account_manager_set_value (am, account_suffix, key, v);
1157     }
1158   else if (g_str_equal (key, SERVICES_KEY))
1159     {
1160       GString *result = g_string_new ("");
1161       AgManager * agm = ag_account_get_manager (account);
1162       GList *services = ag_manager_list_services (agm);
1163       GList *item = NULL;
1164 
1165       for (item = services; item != NULL; item = g_list_next (item))
1166         {
1167           const gchar *name = ag_service_get_name (item->data);
1168 
1169           g_string_append_printf (result, "%s;", name);
1170         }
1171 
1172       mcp_account_manager_set_value (am, account_suffix, key, result->str);
1173 
1174       ag_service_list_free (services);
1175       g_string_free (result, TRUE);
1176     }
1177   else if (g_str_equal (key, MC_SERVICE_KEY))
1178     {
1179       const gchar *service_name = NULL;
1180       AgService *im_service = NULL;
1181 
1182       _ag_account_select_default_im_service (sso, account);
1183       im_service = ag_account_get_selected_service (account);
1184       service_name = ag_service_get_name (im_service);
1185       mcp_account_manager_set_value (am, account_suffix, key, service_name);
1186     }
1187   else
1188     {
1189       GValue v = G_VALUE_INIT;
1190       AgSettingSource src = AG_SETTING_SOURCE_NONE;
1191       Setting *setting = setting_data (key, SETTING_MC);
1192 
1193       if (setting == NULL)
1194         return;
1195 
1196       g_value_init (&v, G_TYPE_STRING);
1197 
1198       if (setting->global)
1199         src = _ag_account_global_value (account, setting->ag_name, &v);
1200       else
1201         src = _ag_account_local_value (sso, account, setting->ag_name, &v);
1202 
1203       if (src != AG_SETTING_SOURCE_NONE)
1204         {
1205           gchar *val = _gvalue_to_string (&v);
1206 
1207           mcp_account_manager_set_value (am, account_suffix, key, val);
1208 
1209           g_free (val);
1210         }
1211 
1212       if (g_str_equal (key, MCPP MC_ACCOUNT_KEY))
1213         _maybe_set_account_param_from_service (sso, am, account,
1214             account_suffix);
1215 
1216       g_value_unset (&v);
1217       clear_setting_data (setting);
1218     }
1219 }
1220 
1221 /* Implements the half of the _get method where key == NULL, which is an
1222  * instruction from MC that we should look up all of this account's properties
1223  * and stash them with mcp_account_manager_set_value().
1224  */
1225 static void
account_manager_sso_get_all(McdAccountManagerSso * sso,const McpAccountManager * am,const gchar * account_suffix,AgAccount * account,AgService * service)1226 account_manager_sso_get_all (
1227     McdAccountManagerSso *sso,
1228     const McpAccountManager *am,
1229     const gchar *account_suffix,
1230     AgAccount *account,
1231     AgService *service)
1232 {
1233   AgAccountSettingIter ag_setting;
1234   const gchar *k;
1235   const GValue *v;
1236   const gchar *on = NULL;
1237   AgService *im_service = NULL;
1238 
1239   /* pick the IM service if we haven't got one set */
1240   if (service == NULL)
1241     _ag_account_select_default_im_service (sso, account);
1242 
1243   /* special case, not stored as a normal setting */
1244   im_service = ag_account_get_selected_service (account);
1245   mcp_account_manager_set_value (am, account_suffix, MC_SERVICE_KEY,
1246       ag_service_get_name (im_service));
1247 
1248   ag_account_settings_iter_init (account, &ag_setting, NULL);
1249   while (ag_account_settings_iter_next (&ag_setting, &k, &v))
1250     {
1251       Setting *setting = setting_data (k, SETTING_AG);
1252 
1253       if (setting != NULL && setting->readable && !setting->global)
1254         {
1255           gchar *value = _gvalue_to_string (v);
1256 
1257           mcp_account_manager_set_value (am, account_suffix,
1258               setting->mc_name, value);
1259 
1260           g_free (value);
1261         }
1262 
1263       clear_setting_data (setting);
1264     }
1265 
1266   /* deselect any service we may have to get global settings */
1267   ag_account_select_service (account, NULL);
1268   ag_account_settings_iter_init (account, &ag_setting, NULL);
1269 
1270   while (ag_account_settings_iter_next (&ag_setting, &k, &v))
1271     {
1272       Setting *setting = setting_data (k, SETTING_AG);
1273 
1274       if (setting != NULL && setting->readable && setting->global)
1275         {
1276           gchar *value  = _gvalue_to_string (v);
1277 
1278           mcp_account_manager_set_value (am, account_suffix,
1279               setting->mc_name, value);
1280 
1281           g_free (value);
1282         }
1283 
1284       clear_setting_data (setting);
1285     }
1286 
1287   /* special case, actually two separate but related flags in SSO */
1288   on = _sso_account_enabled (sso, account, NULL) ? "true" : "false";
1289   mcp_account_manager_set_value (am, account_suffix, MC_ENABLED_KEY, on);
1290 
1291   _maybe_set_account_param_from_service (sso, am, account, account_suffix);
1292 }
1293 
1294 gboolean
_mcd_account_manager_sso_get(const McpAccountStorage * self,const McpAccountManager * am,const gchar * account_suffix,const gchar * key)1295 _mcd_account_manager_sso_get (
1296     const McpAccountStorage *self,
1297     const McpAccountManager *am,
1298     const gchar *account_suffix,
1299     const gchar *key)
1300 {
1301   AgAccountId id;
1302   McdAccountManagerSso *sso = MCD_ACCOUNT_MANAGER_SSO (self);
1303   AgAccount *account = get_ag_account (sso, am, account_suffix, &id);
1304   AgService *service = ag_account_get_selected_service (account);
1305 
1306   if (account == NULL)
1307     return FALSE;
1308 
1309   /* Delegate to one of the two relatively-orthogonal meanings of this
1310    * method... */
1311   if (key != NULL)
1312     account_manager_sso_get_one (sso, am, account_suffix, key, account,
1313         service);
1314   else
1315     account_manager_sso_get_all (sso, am, account_suffix, account, service);
1316 
1317   /* leave the selected service as we found it */
1318   ag_account_select_service (account, service);
1319   return TRUE;
1320 }
1321 
1322 static gboolean
_delete(const McpAccountStorage * self,const McpAccountManager * am,const gchar * account_suffix,const gchar * key)1323 _delete (const McpAccountStorage *self,
1324       const McpAccountManager *am,
1325       const gchar *account_suffix,
1326       const gchar *key)
1327 {
1328   AgAccountId id;
1329   McdAccountManagerSso *sso = MCD_ACCOUNT_MANAGER_SSO (self);
1330   AgAccount *account = get_ag_account (sso, am, account_suffix, &id);
1331   gboolean updated = FALSE;
1332 
1333   /* have no values for this account, nothing to do here: */
1334   if (account == NULL)
1335     return TRUE;
1336 
1337   if (key == NULL)
1338     {
1339       ag_account_delete (account);
1340       g_hash_table_remove (sso->accounts, account_suffix);
1341       g_hash_table_remove (sso->id_name_map, GUINT_TO_POINTER (id));
1342 
1343       /* stop watching for updates */
1344       unwatch_account_keys (sso, id);
1345       updated = TRUE;
1346     }
1347   else
1348     {
1349       Setting *setting = setting_data (key, SETTING_MC);
1350 
1351       if (setting != NULL)
1352         updated = save_setting (sso, account, setting, NULL);
1353 
1354       clear_setting_data (setting);
1355     }
1356 
1357   if (updated)
1358     sso->save = TRUE;
1359 
1360   return TRUE;
1361 }
1362 
1363 static gboolean
_commit_real(gpointer user_data)1364 _commit_real (gpointer user_data)
1365 {
1366   McpAccountStorage *self = MCP_ACCOUNT_STORAGE (user_data);
1367   McdAccountManagerSso *sso = MCD_ACCOUNT_MANAGER_SSO (self);
1368   GHashTableIter iter;
1369   gchar *key;
1370   AgAccount *account;
1371 
1372   g_hash_table_iter_init (&iter, sso->accounts);
1373 
1374   /* for each account, set its telepathy uid MC_IDENTITY_KEY in the  *
1375    * AgAccount structure, and then flush any changes to said account *
1376    * to long term storage with ag_account_store()                    *
1377    * The actual changes are those pushed into the AgAccount in _set  *
1378    * and _delete                                                     */
1379   while (g_hash_table_iter_next (&iter, (gpointer) &key, (gpointer) &account))
1380     {
1381       Setting *setting = setting_data (MC_IDENTITY_KEY, SETTING_MC);
1382       /* this value ties MC accounts to SSO accounts */
1383       save_setting (sso, account, setting, key);
1384       ag_account_store (account, _ag_account_stored_cb, sso);
1385     }
1386 
1387   sso->commit_source = 0;
1388 
1389   /* any pending changes should now have been pushed, clear the save-me flag */
1390   sso->save = FALSE;
1391 
1392   return FALSE;
1393 }
1394 
1395 static gboolean
_commit(const McpAccountStorage * self,const McpAccountManager * am)1396 _commit (const McpAccountStorage *self,
1397     const McpAccountManager *am)
1398 {
1399   McdAccountManagerSso *sso = MCD_ACCOUNT_MANAGER_SSO (self);
1400 
1401   if (!sso->save)
1402     return TRUE;
1403 
1404   if (sso->commit_source == 0)
1405     {
1406       DEBUG ("Deferring commit for %d seconds", AG_ACCOUNT_WRITE_INTERVAL);
1407       sso->commit_source = g_timeout_add_seconds_full (G_PRIORITY_DEFAULT,
1408           AG_ACCOUNT_WRITE_INTERVAL,
1409           _commit_real, g_object_ref (sso), g_object_unref);
1410     }
1411   else
1412     {
1413       DEBUG ("Already deferred commit");
1414     }
1415 
1416   return TRUE;
1417 }
1418 
1419 static void
_load_from_libaccounts(McdAccountManagerSso * sso,const McpAccountManager * am)1420 _load_from_libaccounts (McdAccountManagerSso *sso,
1421     const McpAccountManager *am)
1422 {
1423   GList *ag_ids = ag_manager_list_by_service_type (sso->ag_manager,
1424       account_manager_sso_get_service_type (sso));
1425   GList *ag_id;
1426 
1427   for (ag_id = ag_ids; ag_id != NULL; ag_id = g_list_next (ag_id))
1428     {
1429       const gchar *key;
1430       const GValue *val;
1431       AgAccountSettingIter iter;
1432       AgAccountId id = GPOINTER_TO_UINT (ag_id->data);
1433       AgAccount *account = ag_manager_get_account (sso->ag_manager, id);
1434 
1435       if (account != NULL)
1436         {
1437           AgService *service = ag_account_get_selected_service (account);
1438           gchar *name = _ag_accountid_to_mc_key (sso, id, FALSE);
1439 
1440           if (name != NULL)
1441             {
1442               AgService *im_service = NULL;
1443               gchar *ident = g_strdup_printf ("%u", id);
1444               GStrv mc_id = g_strsplit (name, "/", 3);
1445               gboolean enabled;
1446 
1447               /* cache the account object, and the ID->name maping: the  *
1448                * latter is required because we might receive an async    *
1449                * delete signal with the ID after libaccounts-glib has    *
1450                * purged all its account data, so we couldn't rely on the *
1451                * MC_IDENTITY_KEY setting.                                */
1452               g_hash_table_insert (sso->accounts, name, account);
1453               g_hash_table_insert (sso->id_name_map, GUINT_TO_POINTER (id),
1454                   g_strdup (name));
1455 
1456               if (service == NULL)
1457                 _ag_account_select_default_im_service (sso, account);
1458 
1459               /* special case, not stored as a normal setting */
1460               im_service = ag_account_get_selected_service (account);
1461               mcp_account_manager_set_value (am, name, MC_SERVICE_KEY,
1462                   ag_service_get_name (im_service));
1463 
1464               ag_account_settings_iter_init (account, &iter, NULL);
1465 
1466               while (ag_account_settings_iter_next (&iter, &key, &val))
1467                 {
1468                   Setting *setting = setting_data (key, SETTING_AG);
1469 
1470                   if (setting != NULL && !setting->global && setting->readable)
1471                     {
1472                       gchar *value = _gvalue_to_string (val);
1473 
1474                       mcp_account_manager_set_value (am, name, setting->mc_name,
1475                           value);
1476 
1477                       g_free (value);
1478                     }
1479 
1480                   clear_setting_data (setting);
1481                 }
1482 
1483               ag_account_select_service (account, NULL);
1484               ag_account_settings_iter_init (account, &iter, NULL);
1485 
1486               while (ag_account_settings_iter_next (&iter, &key, &val))
1487                 {
1488                   Setting *setting = setting_data (key, SETTING_AG);
1489 
1490                   if (setting != NULL && setting->global && setting->readable)
1491                     {
1492                       gchar *value = _gvalue_to_string (val);
1493 
1494                       mcp_account_manager_set_value (am, name, setting->mc_name,
1495                           value);
1496 
1497                       g_free (value);
1498                     }
1499 
1500                   clear_setting_data (setting);
1501                 }
1502 
1503               /* special case, actually two separate but related flags in SSO */
1504               enabled = _sso_account_enabled (sso, account, NULL);
1505 
1506               mcp_account_manager_set_value (am, name, MC_ENABLED_KEY,
1507                   enabled ? "true" : "false");
1508               mcp_account_manager_set_value (am, name, LIBACCT_ID_KEY, ident);
1509               mcp_account_manager_set_value (am, name, MC_CMANAGER_KEY, mc_id[0]);
1510               mcp_account_manager_set_value (am, name, MC_PROTOCOL_KEY, mc_id[1]);
1511               mcp_account_manager_set_value (am, name, MC_IDENTITY_KEY, name);
1512               _maybe_set_account_param_from_service (sso, am, account, name);
1513 
1514               /* force the services value to be synthesised + cached */
1515               _mcd_account_manager_sso_get (MCP_ACCOUNT_STORAGE (sso), am,
1516                   name, SERVICES_KEY);
1517 
1518               ag_account_select_service (account, service);
1519 
1520               watch_for_updates (sso, account);
1521 
1522               g_strfreev (mc_id);
1523               g_free (ident);
1524             }
1525         }
1526     }
1527 
1528   sso->loaded = TRUE;
1529   ag_manager_list_free (ag_ids);
1530 }
1531 
1532 static GList *
_list(const McpAccountStorage * self,const McpAccountManager * am)1533 _list (const McpAccountStorage *self,
1534     const McpAccountManager *am)
1535 {
1536   GList *rval = NULL;
1537   McdAccountManagerSso *sso = MCD_ACCOUNT_MANAGER_SSO (self);
1538   GList *ag_ids = NULL;
1539   GList *ag_id;
1540 
1541   if (!sso->loaded)
1542     _load_from_libaccounts (sso, am);
1543 
1544   ag_ids = ag_manager_list_by_service_type (sso->ag_manager,
1545       account_manager_sso_get_service_type (sso));
1546 
1547   for (ag_id = ag_ids; ag_id != NULL; ag_id = g_list_next (ag_id))
1548     {
1549       AgAccountId id = GPOINTER_TO_UINT (ag_id->data);
1550       gchar *name = NULL;
1551 
1552       name = _ag_accountid_to_mc_key (sso, id, FALSE);
1553 
1554       if (name != NULL)
1555         {
1556           DEBUG ("account %s listed", name);
1557           rval = g_list_prepend (rval, name);
1558         }
1559       else
1560         {
1561           DelayedSignalData *data = g_slice_new0 (DelayedSignalData);
1562 
1563           DEBUG ("account %u delayed", id);
1564           data->signal = DELAYED_CREATE;
1565           data->account_id = id;
1566           g_queue_push_tail (sso->pending_signals, data);
1567         }
1568     }
1569 
1570   ag_manager_list_free (ag_ids);
1571 
1572   return rval;
1573 }
1574 
1575 static void
_ready(const McpAccountStorage * self,const McpAccountManager * am)1576 _ready (const McpAccountStorage *self,
1577     const McpAccountManager *am)
1578 {
1579   McdAccountManagerSso *sso = MCD_ACCOUNT_MANAGER_SSO (self);
1580 
1581   if (sso->ready)
1582     return;
1583 
1584   g_assert (sso->manager_interface == NULL);
1585   sso->manager_interface = g_object_ref (G_OBJECT (am));
1586   sso->ready = TRUE;
1587 
1588   while (g_queue_get_length (sso->pending_signals) > 0)
1589     {
1590       DelayedSignalData *data = g_queue_pop_head (sso->pending_signals);
1591       GObject *signal_source = G_OBJECT (sso->ag_manager);
1592 
1593       switch (data->signal)
1594         {
1595           case DELAYED_CREATE:
1596             _sso_created (signal_source, data->account_id, sso);
1597             break;
1598           case DELAYED_DELETE:
1599             _sso_deleted (signal_source, data->account_id, sso);
1600             break;
1601           default:
1602             g_assert_not_reached ();
1603         }
1604 
1605       g_slice_free (DelayedSignalData, data);
1606     }
1607 
1608   g_queue_free (sso->pending_signals);
1609   sso->pending_signals = NULL;
1610 }
1611 
1612 static gboolean
_find_account(McdAccountManagerSso * sso,const gchar * account_name,AgAccountId * account_id)1613 _find_account (McdAccountManagerSso *sso,
1614     const gchar *account_name,
1615     AgAccountId *account_id)
1616 {
1617   GList *ag_ids = NULL;
1618   GList *ag_id;
1619   gboolean found = FALSE;
1620 
1621   g_return_val_if_fail (account_id != NULL, found);
1622 
1623   ag_ids = ag_manager_list_by_service_type (sso->ag_manager,
1624       account_manager_sso_get_service_type (sso));
1625 
1626   for (ag_id = ag_ids; ag_id != NULL; ag_id = g_list_next (ag_id))
1627     {
1628       AgAccountId id = GPOINTER_TO_UINT (ag_id->data);
1629       gchar *name = NULL;
1630 
1631       name = _ag_accountid_to_mc_key (sso, id, FALSE);
1632 
1633       if (g_strcmp0 (name, account_name) == 0)
1634         {
1635           found = TRUE;
1636           *account_id = id;
1637         }
1638 
1639       g_free (name);
1640 
1641       if (found)
1642         break;
1643     }
1644 
1645   ag_manager_list_free (ag_ids);
1646 
1647   return found;
1648 }
1649 
1650 static void
_get_identifier(const McpAccountStorage * self,const gchar * account,GValue * identifier)1651 _get_identifier (const McpAccountStorage *self,
1652     const gchar *account,
1653     GValue *identifier)
1654 {
1655   AgAccountId account_id = 0;
1656 
1657   if (!_find_account (MCD_ACCOUNT_MANAGER_SSO (self), account, &account_id))
1658     g_warning ("Didn't find account %s in %s", account, PLUGIN_NAME);
1659 
1660   g_value_init (identifier, G_TYPE_UINT);
1661 
1662   g_value_set_uint (identifier, account_id);
1663 }
1664 
1665 static GHashTable *
_get_additional_info(const McpAccountStorage * self,const gchar * account_suffix)1666 _get_additional_info (const McpAccountStorage *self,
1667     const gchar *account_suffix)
1668 {
1669   AgAccountId account_id = 0;
1670   McdAccountManagerSso *sso = MCD_ACCOUNT_MANAGER_SSO (self);
1671   GHashTable *additional_info = NULL;
1672   AgAccount *account;
1673   AgService *service;
1674   AgAccountSettingIter iter;
1675   const GValue *val;
1676   const gchar *key;
1677 
1678   if (!_find_account (sso, account_suffix, &account_id))
1679     {
1680       g_warning ("Didn't find account %s in %s", account_suffix, PLUGIN_NAME);
1681       return NULL;
1682     }
1683 
1684   account = ag_manager_get_account (sso->ag_manager, account_id);
1685 
1686   g_return_val_if_fail (account != NULL, NULL);
1687 
1688   service = ag_account_get_selected_service (account);
1689 
1690   additional_info = g_hash_table_new_full (g_str_hash, g_str_equal,
1691       (GDestroyNotify) g_free, (GDestroyNotify) tp_g_value_slice_free);
1692 
1693   if (service == NULL)
1694     _ag_account_select_default_im_service (sso, account);
1695 
1696   ag_account_settings_iter_init (account, &iter, NULL);
1697 
1698   while (ag_account_settings_iter_next (&iter, &key, &val))
1699     {
1700       if (tp_strv_contains (exported_settings, key))
1701           g_hash_table_insert (additional_info, g_strdup (key),
1702               tp_g_value_slice_dup (val));
1703     }
1704 
1705   ag_account_select_service (account, NULL);
1706   ag_account_settings_iter_init (account, &iter, NULL);
1707 
1708   while (ag_account_settings_iter_next (&iter, &key, &val))
1709     {
1710       if (tp_strv_contains (exported_settings, key))
1711           g_hash_table_insert (additional_info, g_strdup (key),
1712               tp_g_value_slice_dup (val));
1713     }
1714 
1715   ag_account_select_service (account, service);
1716 
1717   g_object_unref (account);
1718 
1719   return additional_info;
1720 }
1721 
1722 static void
account_storage_iface_init(McpAccountStorageIface * iface,gpointer unused G_GNUC_UNUSED)1723 account_storage_iface_init (McpAccountStorageIface *iface,
1724     gpointer unused G_GNUC_UNUSED)
1725 {
1726   iface->name = PLUGIN_NAME;
1727   iface->desc = PLUGIN_DESCRIPTION;
1728   iface->priority = PLUGIN_PRIORITY;
1729   iface->provider = PLUGIN_PROVIDER;
1730 
1731   iface->get = _mcd_account_manager_sso_get;
1732   iface->set = _set;
1733   iface->delete = _delete;
1734   iface->commit = _commit;
1735   iface->list = _list;
1736   iface->ready = _ready;
1737   iface->get_identifier = _get_identifier;
1738   iface->get_additional_info = _get_additional_info;
1739 }
1740 
1741 McdAccountManagerSso *
mcd_account_manager_sso_new(void)1742 mcd_account_manager_sso_new (void)
1743 {
1744   return g_object_new (MCD_TYPE_ACCOUNT_MANAGER_SSO, NULL);
1745 }
1746