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 ¶meter;
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