PageRenderTime 48ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/evolution-data-server-3.5.4/addressbook/backends/google/e-gdata-goa-authorizer.c

#
C | 524 lines | 371 code | 109 blank | 44 comment | 22 complexity | 426bf7ac9570d70121a52503ef881362 MD5 | raw file
Possible License(s): LGPL-2.0
  1. /*
  2. * e-gdata-goa-authorizer.c
  3. *
  4. * This program is free software; you can redistribute it and/or
  5. * modify it under the terms of the GNU Lesser General Public
  6. * License as published by the Free Software Foundation; either
  7. * version 2 of the License, or (at your option) version 3.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. * Lesser General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU Lesser General Public
  15. * License along with the program; if not, see <http://www.gnu.org/licenses/>
  16. *
  17. */
  18. #include "e-gdata-goa-authorizer.h"
  19. #include <string.h>
  20. #include <oauth.h>
  21. #define E_GDATA_GOA_AUTHORIZER_GET_PRIVATE(obj) \
  22. (G_TYPE_INSTANCE_GET_PRIVATE \
  23. ((obj), E_TYPE_GDATA_GOA_AUTHORIZER, EGDataGoaAuthorizerPrivate))
  24. struct _EGDataGoaAuthorizerPrivate {
  25. /* GoaObject is already thread-safe. */
  26. GoaObject *goa_object;
  27. /* These members are protected by the global mutex. */
  28. gchar *access_token;
  29. gchar *access_token_secret;
  30. GHashTable *authorization_domains;
  31. };
  32. enum {
  33. PROP_0,
  34. PROP_GOA_OBJECT
  35. };
  36. /* GDataAuthorizer methods must be thread-safe. */
  37. static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
  38. /* Forward Declarations */
  39. static void e_gdata_goa_authorizer_interface_init
  40. (GDataAuthorizerInterface *interface);
  41. G_DEFINE_TYPE_WITH_CODE (
  42. EGDataGoaAuthorizer,
  43. e_gdata_goa_authorizer,
  44. G_TYPE_OBJECT,
  45. G_IMPLEMENT_INTERFACE (
  46. GDATA_TYPE_AUTHORIZER,
  47. e_gdata_goa_authorizer_interface_init))
  48. static GHashTable *
  49. gdata_goa_authorizer_get_parameters (SoupMessage *message,
  50. const gchar *consumer_key,
  51. const gchar *consumer_secret,
  52. const gchar *access_token,
  53. const gchar *access_token_secret)
  54. {
  55. GString *query;
  56. GString *base_string;
  57. GString *signing_key;
  58. GHashTable *parameters;
  59. GHashTableIter iter;
  60. SoupURI *soup_uri;
  61. GList *keys, *i;
  62. gchar *string;
  63. gchar *request_uri;
  64. gpointer key;
  65. parameters = g_hash_table_new_full (
  66. (GHashFunc) g_str_hash,
  67. (GEqualFunc) g_str_equal,
  68. (GDestroyNotify) NULL,
  69. (GDestroyNotify) g_free);
  70. /* soup_form_decode() uses an awkward allocation style for
  71. * its hash table entries, so it's easier to copy its content
  72. * into our own hash table than try to use the returned hash
  73. * table directly. */
  74. soup_uri = soup_message_get_uri (message);
  75. if (soup_uri->query != NULL) {
  76. GHashTable *hash_table;
  77. gpointer value;
  78. hash_table = soup_form_decode (soup_uri->query);
  79. g_hash_table_iter_init (&iter, hash_table);
  80. while (g_hash_table_iter_next (&iter, &key, &value)) {
  81. key = (gpointer) g_intern_string (key);
  82. value = g_strdup (value);
  83. g_hash_table_insert (parameters, key, value);
  84. }
  85. g_hash_table_destroy (hash_table);
  86. }
  87. /* Add OAuth parameters. */
  88. key = (gpointer) "oauth_version";
  89. g_hash_table_insert (parameters, key, g_strdup ("1.0"));
  90. string = oauth_gen_nonce ();
  91. key = (gpointer) "oauth_nonce";
  92. g_hash_table_insert (parameters, key, g_strdup (string));
  93. free (string); /* do not use g_free () */
  94. key = (gpointer) "oauth_timestamp";
  95. string = g_strdup_printf ("%" G_GINT64_FORMAT, (gint64) time (NULL));
  96. g_hash_table_insert (parameters, key, string); /* takes ownership */
  97. key = (gpointer) "oauth_consumer_key";
  98. g_hash_table_insert (parameters, key, g_strdup (consumer_key));
  99. key = (gpointer) "oauth_token";
  100. g_hash_table_insert (parameters, key, g_strdup (access_token));
  101. key = (gpointer) "oauth_signature_method";
  102. g_hash_table_insert (parameters, key, g_strdup ("HMAC-SHA1"));
  103. /* Build the query part of the signature base string. */
  104. query = g_string_sized_new (512);
  105. keys = g_hash_table_get_keys (parameters);
  106. keys = g_list_sort (keys, (GCompareFunc) g_strcmp0);
  107. for (i = keys; i != NULL; i = g_list_next (i)) {
  108. const gchar *_key = i->data;
  109. const gchar *val;
  110. val = g_hash_table_lookup (parameters, _key);
  111. if (i != keys)
  112. g_string_append_c (query, '&');
  113. g_string_append_uri_escaped (query, _key, NULL, FALSE);
  114. g_string_append_c (query, '=');
  115. g_string_append_uri_escaped (query, val, NULL, FALSE);
  116. }
  117. g_list_free (keys);
  118. /* Build the signature base string. */
  119. soup_uri = soup_uri_copy (soup_uri);
  120. soup_uri_set_query (soup_uri, NULL);
  121. soup_uri_set_fragment (soup_uri, NULL);
  122. request_uri = soup_uri_to_string (soup_uri, FALSE);
  123. soup_uri_free (soup_uri);
  124. base_string = g_string_sized_new (512);
  125. g_string_append_uri_escaped (base_string, message->method, NULL, FALSE);
  126. g_string_append_c (base_string, '&');
  127. g_string_append_uri_escaped (base_string, request_uri, NULL, FALSE);
  128. g_string_append_c (base_string, '&');
  129. g_string_append_uri_escaped (base_string, query->str, NULL, FALSE);
  130. /* Build the HMAC-SHA1 signing key. */
  131. signing_key = g_string_sized_new (512);
  132. g_string_append_uri_escaped (
  133. signing_key, consumer_secret, NULL, FALSE);
  134. g_string_append_c (signing_key, '&');
  135. g_string_append_uri_escaped (
  136. signing_key, access_token_secret, NULL, FALSE);
  137. /* Sign the request. */
  138. key = (gpointer) "oauth_signature";
  139. string = oauth_sign_hmac_sha1 (base_string->str, signing_key->str);
  140. g_hash_table_insert (parameters, key, g_strdup (string));
  141. free (string); /* do not use g_free () */
  142. g_free (request_uri);
  143. g_string_free (query, TRUE);
  144. g_string_free (base_string, TRUE);
  145. g_string_free (signing_key, TRUE);
  146. return parameters;
  147. }
  148. static void
  149. gdata_goa_authorizer_add_authorization (GDataAuthorizer *authorizer,
  150. SoupMessage *message)
  151. {
  152. EGDataGoaAuthorizerPrivate *priv;
  153. GoaOAuthBased *goa_oauth_based;
  154. GHashTable *parameters;
  155. GString *authorization;
  156. const gchar *consumer_key;
  157. const gchar *consumer_secret;
  158. guint ii;
  159. const gchar *oauth_keys[] = {
  160. "oauth_version",
  161. "oauth_nonce",
  162. "oauth_timestamp",
  163. "oauth_consumer_key",
  164. "oauth_token",
  165. "oauth_signature_method",
  166. "oauth_signature"
  167. };
  168. /* This MUST be called with the mutex already locked. */
  169. priv = E_GDATA_GOA_AUTHORIZER_GET_PRIVATE (authorizer);
  170. /* We can't add an Authorization header without an access token.
  171. * Let the request fail. GData should refresh us if it gets back
  172. * a "401 Authorization required" response from Google, and then
  173. * automatically retry the request. */
  174. if (priv->access_token == NULL)
  175. return;
  176. goa_oauth_based = goa_object_get_oauth_based (priv->goa_object);
  177. consumer_key = goa_oauth_based_get_consumer_key (goa_oauth_based);
  178. consumer_secret = goa_oauth_based_get_consumer_secret (goa_oauth_based);
  179. parameters = gdata_goa_authorizer_get_parameters (
  180. message,
  181. consumer_key,
  182. consumer_secret,
  183. priv->access_token,
  184. priv->access_token_secret);
  185. authorization = g_string_new ("OAuth ");
  186. for (ii = 0; ii < G_N_ELEMENTS (oauth_keys); ii++) {
  187. const gchar *key;
  188. const gchar *val;
  189. key = oauth_keys[ii];
  190. val = g_hash_table_lookup (parameters, key);
  191. if (ii > 0)
  192. g_string_append (authorization, ", ");
  193. g_string_append (authorization, key);
  194. g_string_append_c (authorization, '=');
  195. g_string_append_c (authorization, '"');
  196. g_string_append_uri_escaped (authorization, val, NULL, FALSE);
  197. g_string_append_c (authorization, '"');
  198. }
  199. /* Use replace here, not append, to make sure
  200. * there's only one "Authorization" header. */
  201. soup_message_headers_replace (
  202. message->request_headers,
  203. "Authorization", authorization->str);
  204. g_string_free (authorization, TRUE);
  205. g_hash_table_destroy (parameters);
  206. g_object_unref (goa_oauth_based);
  207. }
  208. static gboolean
  209. gdata_goa_authorizer_is_authorized (GDataAuthorizer *authorizer,
  210. GDataAuthorizationDomain *domain)
  211. {
  212. EGDataGoaAuthorizerPrivate *priv;
  213. /* This MUST be called with the mutex already locked. */
  214. if (domain == NULL)
  215. return TRUE;
  216. priv = E_GDATA_GOA_AUTHORIZER_GET_PRIVATE (authorizer);
  217. domain = g_hash_table_lookup (priv->authorization_domains, domain);
  218. return (domain != NULL);
  219. }
  220. static void
  221. gdata_goa_authorizer_set_goa_object (EGDataGoaAuthorizer *authorizer,
  222. GoaObject *goa_object)
  223. {
  224. g_return_if_fail (GOA_IS_OBJECT (goa_object));
  225. g_return_if_fail (authorizer->priv->goa_object == NULL);
  226. authorizer->priv->goa_object = g_object_ref (goa_object);
  227. }
  228. static void
  229. gdata_goa_authorizer_set_property (GObject *object,
  230. guint property_id,
  231. const GValue *value,
  232. GParamSpec *pspec)
  233. {
  234. switch (property_id) {
  235. case PROP_GOA_OBJECT:
  236. gdata_goa_authorizer_set_goa_object (
  237. E_GDATA_GOA_AUTHORIZER (object),
  238. g_value_get_object (value));
  239. return;
  240. default:
  241. g_assert_not_reached ();
  242. }
  243. G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
  244. }
  245. static void
  246. gdata_goa_authorizer_get_property (GObject *object,
  247. guint property_id,
  248. GValue *value,
  249. GParamSpec *pspec)
  250. {
  251. switch (property_id) {
  252. case PROP_GOA_OBJECT:
  253. g_value_set_object (
  254. value,
  255. e_gdata_goa_authorizer_get_goa_object (
  256. E_GDATA_GOA_AUTHORIZER (object)));
  257. return;
  258. default:
  259. g_assert_not_reached ();
  260. }
  261. G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
  262. }
  263. static void
  264. gdata_goa_authorizer_dispose (GObject *object)
  265. {
  266. EGDataGoaAuthorizerPrivate *priv;
  267. priv = E_GDATA_GOA_AUTHORIZER_GET_PRIVATE (object);
  268. if (priv->goa_object != NULL) {
  269. g_object_unref (priv->goa_object);
  270. priv->goa_object = NULL;
  271. }
  272. g_hash_table_remove_all (priv->authorization_domains);
  273. /* Chain up to parent's dispose() method. */
  274. G_OBJECT_CLASS (e_gdata_goa_authorizer_parent_class)->dispose (object);
  275. }
  276. static void
  277. gdata_goa_authorizer_finalize (GObject *object)
  278. {
  279. EGDataGoaAuthorizerPrivate *priv;
  280. priv = E_GDATA_GOA_AUTHORIZER_GET_PRIVATE (object);
  281. g_free (priv->access_token);
  282. g_free (priv->access_token_secret);
  283. g_hash_table_destroy (priv->authorization_domains);
  284. /* Chain up to parent's finalize() method. */
  285. G_OBJECT_CLASS (e_gdata_goa_authorizer_parent_class)->finalize (object);
  286. }
  287. static void
  288. gdata_goa_authorizer_constructed (GObject *object)
  289. {
  290. EGDataGoaAuthorizerPrivate *priv;
  291. GType service_type;
  292. GList *domains;
  293. /* Chain up to parent's constructed() method. */
  294. G_OBJECT_CLASS (e_gdata_goa_authorizer_parent_class)->
  295. constructed (object);
  296. priv = E_GDATA_GOA_AUTHORIZER_GET_PRIVATE (object);
  297. /* XXX We would need to generalize this to make the class
  298. * reusable for other service types, probably by adding
  299. * a "service-type" constructor property. */
  300. service_type = GDATA_TYPE_CONTACTS_SERVICE;
  301. domains = gdata_service_get_authorization_domains (service_type);
  302. while (domains != NULL) {
  303. g_hash_table_insert (
  304. priv->authorization_domains,
  305. g_object_ref (domains->data),
  306. domains->data);
  307. domains = g_list_delete_link (domains, domains);
  308. }
  309. }
  310. static void
  311. gdata_goa_authorizer_process_request (GDataAuthorizer *authorizer,
  312. GDataAuthorizationDomain *domain,
  313. SoupMessage *message)
  314. {
  315. g_static_mutex_lock (&mutex);
  316. if (gdata_goa_authorizer_is_authorized (authorizer, domain))
  317. gdata_goa_authorizer_add_authorization (authorizer, message);
  318. g_static_mutex_unlock (&mutex);
  319. }
  320. static gboolean
  321. gdata_goa_authorizer_is_authorized_for_domain (GDataAuthorizer *authorizer,
  322. GDataAuthorizationDomain *domain)
  323. {
  324. gboolean authorized;
  325. g_static_mutex_lock (&mutex);
  326. authorized = gdata_goa_authorizer_is_authorized (authorizer, domain);
  327. g_static_mutex_unlock (&mutex);
  328. return authorized;
  329. }
  330. static gboolean
  331. gdata_goa_authorizer_refresh_authorization (GDataAuthorizer *authorizer,
  332. GCancellable *cancellable,
  333. GError **error)
  334. {
  335. EGDataGoaAuthorizerPrivate *priv;
  336. GoaOAuthBased *goa_oauth_based;
  337. GoaAccount *goa_account;
  338. gboolean success = TRUE;
  339. priv = E_GDATA_GOA_AUTHORIZER_GET_PRIVATE (authorizer);
  340. g_static_mutex_lock (&mutex);
  341. g_free (priv->access_token);
  342. priv->access_token = NULL;
  343. g_free (priv->access_token_secret);
  344. priv->access_token_secret = NULL;
  345. goa_account = goa_object_get_account (priv->goa_object);
  346. goa_oauth_based = goa_object_get_oauth_based (priv->goa_object);
  347. success &= goa_account_call_ensure_credentials_sync (
  348. goa_account, NULL, cancellable, error);
  349. success &= goa_oauth_based_call_get_access_token_sync (
  350. goa_oauth_based, &priv->access_token,
  351. &priv->access_token_secret, NULL, cancellable, error);
  352. g_object_unref (goa_account);
  353. g_object_unref (goa_oauth_based);
  354. g_static_mutex_unlock (&mutex);
  355. return success;
  356. }
  357. static void
  358. e_gdata_goa_authorizer_class_init (EGDataGoaAuthorizerClass *class)
  359. {
  360. GObjectClass *object_class;
  361. g_type_class_add_private (class, sizeof (EGDataGoaAuthorizerPrivate));
  362. object_class = G_OBJECT_CLASS (class);
  363. object_class->set_property = gdata_goa_authorizer_set_property;
  364. object_class->get_property = gdata_goa_authorizer_get_property;
  365. object_class->dispose = gdata_goa_authorizer_dispose;
  366. object_class->finalize = gdata_goa_authorizer_finalize;
  367. object_class->constructed = gdata_goa_authorizer_constructed;
  368. g_object_class_install_property (
  369. object_class,
  370. PROP_GOA_OBJECT,
  371. g_param_spec_object (
  372. "goa-object",
  373. "GoaObject",
  374. "The GOA account to authenticate",
  375. GOA_TYPE_OBJECT,
  376. G_PARAM_READWRITE |
  377. G_PARAM_CONSTRUCT_ONLY |
  378. G_PARAM_STATIC_STRINGS));
  379. }
  380. static void
  381. e_gdata_goa_authorizer_interface_init (GDataAuthorizerInterface *interface)
  382. {
  383. interface->process_request =
  384. gdata_goa_authorizer_process_request;
  385. interface->is_authorized_for_domain =
  386. gdata_goa_authorizer_is_authorized_for_domain;
  387. interface->refresh_authorization =
  388. gdata_goa_authorizer_refresh_authorization;
  389. }
  390. static void
  391. e_gdata_goa_authorizer_init (EGDataGoaAuthorizer *authorizer)
  392. {
  393. GHashTable *authorization_domains;
  394. authorization_domains = g_hash_table_new_full (
  395. (GHashFunc) g_direct_hash,
  396. (GEqualFunc) g_direct_equal,
  397. (GDestroyNotify) g_object_unref,
  398. (GDestroyNotify) NULL);
  399. authorizer->priv = E_GDATA_GOA_AUTHORIZER_GET_PRIVATE (authorizer);
  400. authorizer->priv->authorization_domains = authorization_domains;
  401. }
  402. EGDataGoaAuthorizer *
  403. e_gdata_goa_authorizer_new (GoaObject *goa_object)
  404. {
  405. g_return_val_if_fail (GOA_IS_OBJECT (goa_object), NULL);
  406. return g_object_new (
  407. E_TYPE_GDATA_GOA_AUTHORIZER,
  408. "goa-object", goa_object, NULL);
  409. }
  410. GoaObject *
  411. e_gdata_goa_authorizer_get_goa_object (EGDataGoaAuthorizer *authorizer)
  412. {
  413. g_return_val_if_fail (E_IS_GDATA_GOA_AUTHORIZER (authorizer), NULL);
  414. return authorizer->priv->goa_object;
  415. }