a76cdc8be9aba2e124970f02014fdec208f4a1ac
[milk] / src / milk-auth.c
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License as
4  * published by the Free Software Foundation; either version 2 of the
5  * License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
10  * General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public
13  * License along with this program; if not, write to the
14  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
15  * Boston, MA  02110-1301  USA
16  *
17  * Authors: Travis Reitter <treitter@gmail.com>
18  */
19
20 #include <config.h>
21
22 #include <glib.h>
23 #include <glib/gi18n.h>
24 #include <gtk/gtk.h>
25 #include <hildon/hildon.h>
26
27 #include <rtm-glib/rtm-error.h>
28 #include <rtm-glib/rtm-glib.h>
29
30 #include "milk-auth.h"
31 #include "milk-dialogs.h"
32
33 G_DEFINE_TYPE (MilkAuth, milk_auth, G_TYPE_OBJECT);
34
35 /* less expensive than G_TYPE_INSTANCE_GET_PRIVATE */
36 #define MILK_AUTH_PRIVATE(o) ((MILK_AUTH ((o)))->priv)
37
38 #define RTM_API_KEY "81f5c6c904aeafbbc914d9845d250ea8"
39 #define RTM_SHARED_SECRET "b08b15419378f913"
40
41 struct _MilkAuthPrivate
42 {
43         RtmGlib *rtm_glib;
44         char *api_key;
45         char *shared_secret;
46         char *frob;
47         MilkAuthState state;
48 };
49
50 enum {
51         PROP_API_KEY = 1,
52         PROP_SHARED_SECRET,
53         PROP_STATE,
54 };
55
56 static MilkAuth *default_auth = NULL;
57
58
59 MilkAuthState
60 milk_auth_get_state (MilkAuth *auth)
61 {
62         g_return_val_if_fail (auth, MILK_AUTH_STATE_DISCONNECTED);
63         g_return_val_if_fail (MILK_IS_AUTH (auth),
64                         MILK_AUTH_STATE_DISCONNECTED);
65
66         return MILK_AUTH_PRIVATE (auth)->state;
67 }
68
69 static void
70 milk_auth_get_property (GObject    *object,
71                         guint       property_id,
72                         GValue     *value,
73                         GParamSpec *pspec)
74 {
75         MilkAuthPrivate *priv = MILK_AUTH_PRIVATE (object);
76
77         switch (property_id)
78         {
79                 case PROP_API_KEY:
80                         g_value_set_string (value, priv->api_key);
81                 break;
82
83                 case PROP_SHARED_SECRET:
84                         g_value_set_string (value, priv->shared_secret);
85                 break;
86
87                 case PROP_STATE:
88                         g_value_set_int (value, priv->state);
89                 break;
90
91                 default:
92                         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id,
93                                         pspec);
94         }
95 }
96
97 static void
98 milk_auth_set_property (GObject      *object,
99                         guint         property_id,
100                         const GValue *value,
101                         GParamSpec   *pspec)
102 {
103         MilkAuthPrivate *priv;
104         MilkAuth *auth;
105
106         auth = MILK_AUTH (object);
107         priv = MILK_AUTH_PRIVATE (auth);
108
109         switch (property_id)
110         {
111                 case PROP_API_KEY:
112                         priv->api_key = g_value_dup_string (value);
113                 break;
114
115                 case PROP_SHARED_SECRET:
116                         priv->shared_secret = g_value_dup_string (value);
117                 break;
118
119                 case PROP_STATE:
120                         priv->state = g_value_get_int (value);
121                 break;
122
123                 default:
124                         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id,
125                                         pspec);
126         }
127 }
128
129 static void
130 auth_response_cb (GtkWidget *dialog,
131                   int        response,
132                   MilkAuth  *auth)
133
134 {
135         MilkAuthPrivate *priv;
136         GError *error = NULL;
137         gchar *auth_token;
138         gchar *username;
139         MilkAuthState previous_state;
140
141         priv = MILK_AUTH_PRIVATE (auth);
142
143         previous_state = priv->state;
144         auth_token = rtm_glib_auth_get_token (priv->rtm_glib, priv->frob,
145                         &error);
146         if (error != NULL) {
147                 g_warning ("%s", rtm_error_get_message (error));
148                 goto auth_response_cb_error_OUT;
149         }
150
151         if (!rtm_glib_auth_check_token (priv->rtm_glib, auth_token, NULL)) {
152                 g_warning ("auth_token not valid!\n");
153                 goto auth_response_cb_error_OUT;
154         }
155         if (error != NULL) {
156                 g_warning ("%s", rtm_error_get_message (error));
157                 goto auth_response_cb_error_OUT;
158         }
159         username = rtm_glib_test_login (priv->rtm_glib, auth_token, &error);
160
161         g_free (auth_token);
162
163         if (error != NULL) {
164                 g_warning ("%s", rtm_error_get_message (error));
165                 goto auth_response_cb_error_OUT;
166         }
167
168         priv->state = MILK_AUTH_STATE_CONNECTED;
169         goto auth_response_cb_OUT;
170
171 auth_response_cb_error_OUT:
172         priv->state = MILK_AUTH_STATE_DISCONNECTED;
173         /* FIXME: make it possible to re-try, etc. */
174         gtk_main_quit ();
175
176 auth_response_cb_OUT:
177         if (priv->state != previous_state)
178                 g_object_notify (G_OBJECT (auth), "state");
179
180         gtk_widget_destroy (dialog);
181 }
182
183 GList *
184 milk_auth_get_tasks (MilkAuth    *auth,
185                      const char  *last_sync,
186                      GError     **error)
187 {
188         MilkAuthPrivate *priv;
189         GList *rtm_tasks;
190
191         g_return_val_if_fail (auth, NULL);
192         g_return_val_if_fail (MILK_IS_AUTH (auth), NULL);
193         g_return_val_if_fail (
194                         milk_auth_get_state (auth) == MILK_AUTH_STATE_CONNECTED,
195                         NULL);
196
197         priv = MILK_AUTH_PRIVATE (auth);
198
199         /* FIXME: cache this */
200         rtm_tasks = rtm_glib_tasks_get_list (priv->rtm_glib, NULL, NULL,
201                         (char*) last_sync, error);
202
203         return rtm_tasks;
204 }
205
206 char*
207 milk_auth_timeline_create (MilkAuth  *auth,
208                            GError   **error)
209 {
210         MilkAuthPrivate *priv;
211
212         g_return_val_if_fail (MILK_IS_AUTH (auth), NULL);
213
214         priv = MILK_AUTH_PRIVATE (auth);
215
216         return rtm_glib_timelines_create (priv->rtm_glib, error);
217 }
218
219 /* FIXME: we probably really want this to be async (but sequencable) */
220 GList*
221 milk_auth_tasks_add (MilkAuth  *auth,
222                      char      *timeline,
223                      GList     *names)
224 {
225         gboolean success = TRUE;
226         MilkAuthPrivate *priv;
227         GList *l;
228         GList *tasks = NULL;
229         GError *error = NULL;
230
231         g_return_val_if_fail (MILK_IS_AUTH (auth), NULL);
232         g_return_val_if_fail (names, NULL);
233
234         priv = MILK_AUTH_PRIVATE (auth);
235
236         for (l = names; l; l = l->next) {
237                 RtmTask *task;
238
239                 /* FIXME: cut this */
240                 g_debug ("trying to send task with name '%s'", l->data);
241
242                 /* XXX: this uses Smart Add parsing; make this user-settable? */
243                 /* XXX: the cast to char* is actually a bug in the rtm-glib API
244                  */
245                 task = rtm_glib_tasks_add (priv->rtm_glib, timeline,
246                                 l->data, NULL, TRUE, &error);
247                 if (task) {
248                         /* FIXME: cut this */
249                         g_debug (G_STRLOC ": added task with ID '%s'",
250                                         rtm_task_get_id (task));
251
252                         tasks = g_list_prepend (tasks, task);
253                 } else {
254                         g_warning ("failed to add some tasks: %s",
255                                         error->message);
256                         g_clear_error (&error);
257                 }
258         }
259
260         return tasks;
261 }
262
263 RtmTask*
264 milk_auth_task_add (MilkAuth    *auth,
265                     char        *timeline,
266                     const char  *name,
267                     GError     **error)
268 {
269         MilkAuthPrivate *priv;
270
271         g_return_val_if_fail (MILK_IS_AUTH (auth), NULL);
272
273         priv = MILK_AUTH_PRIVATE (auth);
274
275         /* XXX: this uses Smart Add parsing; make this user-settable? */
276         /* XXX: the cast to char* is actually a bug in the rtm-glib API */
277         return rtm_glib_tasks_add (priv->rtm_glib, timeline, (char*) name, NULL,
278                         TRUE, error);
279 }
280
281 /* FIXME: we probably really want this to be async (but sequencable) */
282 GList*
283 milk_auth_tasks_send_changes (MilkAuth *auth,
284                               char     *timeline,
285                               GList    *tasks)
286 {
287         gboolean success = TRUE;
288         MilkAuthPrivate *priv;
289         GList *l;
290         GList *tasks_sent = NULL;
291         GError *error = NULL;
292
293         g_return_val_if_fail (MILK_IS_AUTH (auth), NULL);
294         g_return_val_if_fail (tasks, NULL);
295
296         priv = MILK_AUTH_PRIVATE (auth);
297
298         for (l = tasks; l; l = l->next) {
299                 RtmTask *task = l->data;
300
301                 GTimeVal *tv;
302
303                 /* If any of these conditions fail, libsoup ends up exploding
304                  * in a segfault (blindly de-reffing NULL); better to be safe
305                  * than sorry */
306                 if (!rtm_task_get_list_id (task)) {
307                         g_warning (G_STRLOC ": task doesn't have a list ID; "
308                                         "skipping...");
309                         continue;
310                 }
311
312                 if (!rtm_task_get_taskseries_id (task)) {
313                         g_warning (G_STRLOC ": task doesn't have a taskseries "
314                                         "ID; skipping...");
315                         continue;
316                 }
317
318                 if (!rtm_task_get_id (task)) {
319                         g_warning (G_STRLOC ": task doesn't have an ID; "
320                                         "skipping...");
321                         continue;
322                 }
323
324                 tv = rtm_task_get_due_date (task);
325                 if (tv) {
326                         char *due_str;
327                         char *tid;
328
329                         due_str = g_time_val_to_iso8601 (tv);
330
331                         /* FIXME: cut this */
332                         g_debug ("going to set due string: '%s'", due_str);
333
334                         /* XXX: this uses Smart Add parsing; make this
335                          * user-settable? */
336                         /* XXX: the cast to char* is actually a bug in the
337                          * rtm-glib API */
338                         tid = rtm_glib_tasks_set_due_date (priv->rtm_glib, timeline,
339                                         task, due_str,
340                                         /* FIXME: set this appropriately */
341                                         FALSE,
342                                         FALSE, &error);
343
344                         if (tid) {
345                                 /* FIXME: this should be a set, not a list --
346                                  * we'll add each task to the set if it's been
347                                  * changed since the last send */
348                                 tasks_sent = g_list_prepend (tasks_sent, task);
349                         } else {
350                                 g_warning ("failed to add some tasks: %s",
351                                                 error->message);
352                                 g_clear_error (&error);
353                         }
354
355                         g_free (tid);
356                 }
357
358                 /* FIXME: handle all the other attributes, not just the date */
359         }
360
361         return tasks_sent;
362 }
363
364 char*
365 milk_auth_task_complete (MilkAuth  *auth,
366                          char      *timeline,
367                          RtmTask   *task,
368                          GError   **error)
369 {
370         MilkAuthPrivate *priv;
371
372         g_return_val_if_fail (MILK_IS_AUTH (auth), NULL);
373
374         priv = MILK_AUTH_PRIVATE (auth);
375
376         return rtm_glib_tasks_complete (priv->rtm_glib, timeline, task, error);
377 }
378
379 char*
380 milk_auth_task_delete (MilkAuth  *auth,
381                        char      *timeline,
382                        RtmTask   *task,
383                        GError   **error)
384 {
385         MilkAuthPrivate *priv;
386
387         g_return_val_if_fail (MILK_IS_AUTH (auth), NULL);
388
389         priv = MILK_AUTH_PRIVATE (auth);
390
391         return rtm_glib_tasks_delete (priv->rtm_glib, timeline, task, error);
392 }
393
394 char*
395 milk_auth_task_set_priority (MilkAuth    *auth,
396                              char        *timeline,
397                              RtmTask     *task,
398                              const char  *priority,
399                              GError     **error)
400 {
401         MilkAuthPrivate *priv;
402
403         g_return_val_if_fail (MILK_IS_AUTH (auth), NULL);
404
405         priv = MILK_AUTH_PRIVATE (auth);
406
407         return rtm_glib_tasks_set_priority (priv->rtm_glib, timeline, task,
408                         priority, error);
409 }
410
411 /* FIXME: why does this (or something above it) totally fail if we don't have a
412  * working Internet connection / resolv.conf is mangled? */
413 /* FIXME: instead of this manual call, listen to the connection manager
414  * transitions -- see this:
415  * http://wiki.maemo.org/Documentation/Maemo_5_Developer_Guide/Using_Connectivity_Components/Maemo_Connectivity#Libconic_Usage
416  */
417 void
418 milk_auth_log_in (MilkAuth *auth)
419 {
420         MilkAuthPrivate *priv;
421         GError *error = NULL;
422         gchar *url;
423         GtkDialog *dialog;
424
425         g_return_if_fail (auth);
426         g_return_if_fail (MILK_IS_AUTH (auth));
427
428         priv = MILK_AUTH_PRIVATE (auth);
429
430         if (rtm_glib_test_echo (priv->rtm_glib, &error)) {
431                 g_print ("Test echo OK!\n");
432         } else {
433                 g_print ("Test echo FAIL!\n");
434                 return;
435         }
436         if (error != NULL) {
437                 g_error ("%s", rtm_error_get_message (error));
438                 return;
439         }
440
441         /* FIXME: relocate this */
442         if (priv->frob)
443                 g_free (priv->frob);
444
445         priv->frob = rtm_glib_auth_get_frob (priv->rtm_glib, &error);
446         if (error != NULL) {
447                 g_error ("%s", rtm_error_get_message (error));
448                 return;
449         }
450         g_print ("Frob: %s\n", priv->frob);
451
452         url = rtm_glib_auth_get_login_url (priv->rtm_glib, priv->frob,
453                         "delete");
454         g_print ("URL: %s\n", url);
455
456         dialog = milk_dialogs_auth_prompt (NULL, url);
457
458         g_signal_connect (dialog, "response", G_CALLBACK (auth_response_cb),
459                         auth);
460 }
461
462 static void
463 milk_auth_constructed (GObject *object)
464 {
465         MilkAuthPrivate *priv = MILK_AUTH_PRIVATE (object);
466
467         priv->rtm_glib = rtm_glib_new (priv->api_key, priv->shared_secret);
468 }
469
470 static void
471 milk_auth_finalize (GObject *object)
472 {
473         MilkAuthPrivate *priv = MILK_AUTH_PRIVATE (object);
474
475         g_object_unref (priv->rtm_glib);
476
477         g_free (priv->api_key);
478         g_free (priv->shared_secret);
479         g_free (priv->frob);
480 }
481
482 static void
483 milk_auth_class_init (MilkAuthClass *klass)
484 {
485         GObjectClass *object_class = G_OBJECT_CLASS (klass);
486
487         g_type_class_add_private (klass, sizeof (MilkAuthPrivate));
488
489         object_class->get_property = milk_auth_get_property;
490         object_class->set_property = milk_auth_set_property;
491         object_class->constructed = milk_auth_constructed;
492         object_class->finalize = milk_auth_finalize;
493
494         /* FIXME: make these read-only */
495         g_object_class_install_property
496                 (object_class,
497                  PROP_API_KEY,
498                  g_param_spec_string
499                          ("api-key",
500                           "API authentication key",
501                           "Milk's API authentication key.",
502                           RTM_API_KEY,
503                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
504                           G_PARAM_STATIC_STRINGS));
505
506         g_object_class_install_property
507                 (object_class,
508                  PROP_SHARED_SECRET,
509                  g_param_spec_string
510                          ("shared-secret",
511                           "Shared secret",
512                           "Milk's shared secret with the server.",
513                           RTM_SHARED_SECRET,
514                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
515                           G_PARAM_STATIC_STRINGS));
516
517         g_object_class_install_property
518                 (object_class,
519                  PROP_STATE,
520                  g_param_spec_int
521                          ("state",
522                           "Authentication state",
523                           "Authentication/connection state with the server.",
524                           0, (NUM_MILK_AUTH_STATES-1),
525                           MILK_AUTH_STATE_DISCONNECTED,
526                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
527                           G_PARAM_STATIC_STRINGS));
528 }
529
530 static void
531 milk_auth_init (MilkAuth *self)
532 {
533         MilkAuthPrivate *priv;
534
535         self->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (
536                         self, MILK_TYPE_AUTH, MilkAuthPrivate);
537 }
538
539 MilkAuth*
540 milk_auth_get_default ()
541 {
542         if (!default_auth)
543                 default_auth = g_object_new (MILK_TYPE_AUTH, NULL);
544
545         return default_auth;
546 }