1 /*
2 * e-soup-auth-bearer.c
3 *
4 * This library is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation.
7 *
8 * This library is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
11 * for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this library. If not, see <http://www.gnu.org/licenses/>.
15 *
16 */
17
18 /**
19 * SECTION: e-soup-auth-bearer
20 * @include: libedataserver/libedataserver.h
21 * @short_description: OAuth 2.0 support for libsoup
22 *
23 * #ESoupAuthBearer adds libsoup support for the use of bearer tokens in
24 * HTTP requests to access OAuth 2.0 protected resources, as defined in
25 * <ulink url="http://tools.ietf.org/html/rfc6750">RFC 6750</ulink>.
26 *
27 * An #EBackend should integrate #ESoupAuthBearer first by adding it as a
28 * feature to a #SoupSession's #SoupAuthManager, then from a #SoupSession
29 * #SoupSession::authenticate handler call e_source_get_oauth2_access_token()
30 * and pass the results to e_soup_auth_bearer_set_access_token().
31 **/
32
33 #include "evolution-data-server-config.h"
34
35 #include "e-soup-auth-bearer.h"
36
37 #include <time.h>
38
39 #define AUTH_STRENGTH 1
40
41 #define EXPIRY_INVALID ((time_t) -1)
42
43 struct _ESoupAuthBearerPrivate {
44 GMutex property_lock;
45 gchar *access_token;
46 time_t expiry;
47 };
48
G_DEFINE_TYPE_WITH_PRIVATE(ESoupAuthBearer,e_soup_auth_bearer,SOUP_TYPE_AUTH)49 G_DEFINE_TYPE_WITH_PRIVATE (
50 ESoupAuthBearer,
51 e_soup_auth_bearer,
52 SOUP_TYPE_AUTH)
53
54 static gboolean
55 e_soup_auth_bearer_is_expired_locked (ESoupAuthBearer *bearer)
56 {
57 gboolean expired = TRUE;
58
59 if (bearer->priv->expiry != EXPIRY_INVALID)
60 expired = (bearer->priv->expiry <= time (NULL));
61
62 return expired;
63 }
64
65 static void
e_soup_auth_bearer_finalize(GObject * object)66 e_soup_auth_bearer_finalize (GObject *object)
67 {
68 ESoupAuthBearerPrivate *priv;
69
70 priv = E_SOUP_AUTH_BEARER (object)->priv;
71
72 g_mutex_clear (&priv->property_lock);
73 g_free (priv->access_token);
74
75 /* Chain up to parent's finalize() method. */
76 G_OBJECT_CLASS (e_soup_auth_bearer_parent_class)->finalize (object);
77 }
78
79 static gboolean
e_soup_auth_bearer_update(SoupAuth * auth,SoupMessage * message,GHashTable * auth_header)80 e_soup_auth_bearer_update (SoupAuth *auth,
81 SoupMessage *message,
82 GHashTable *auth_header)
83 {
84 if (message && message->status_code == SOUP_STATUS_UNAUTHORIZED) {
85 ESoupAuthBearer *bearer;
86
87 g_return_val_if_fail (E_IS_SOUP_AUTH_BEARER (auth), FALSE);
88
89 bearer = E_SOUP_AUTH_BEARER (auth);
90
91 g_mutex_lock (&bearer->priv->property_lock);
92
93 /* Expire the token, it's likely to be invalid. */
94 bearer->priv->expiry = EXPIRY_INVALID;
95
96 g_mutex_unlock (&bearer->priv->property_lock);
97
98 return FALSE;
99 }
100
101 return TRUE;
102 }
103
104 static GSList *
e_soup_auth_bearer_get_protection_space(SoupAuth * auth,SoupURI * source_uri)105 e_soup_auth_bearer_get_protection_space (SoupAuth *auth,
106 SoupURI *source_uri)
107 {
108 /* XXX Not sure what to do here. Need to return something. */
109
110 return g_slist_prepend (NULL, g_strdup (""));
111 }
112
113 static gboolean
e_soup_auth_bearer_is_authenticated(SoupAuth * auth)114 e_soup_auth_bearer_is_authenticated (SoupAuth *auth)
115 {
116 ESoupAuthBearer *bearer;
117 gboolean authenticated = FALSE;
118
119 bearer = E_SOUP_AUTH_BEARER (auth);
120
121 g_mutex_lock (&bearer->priv->property_lock);
122
123 authenticated = (bearer->priv->access_token != NULL);
124
125 g_mutex_unlock (&bearer->priv->property_lock);
126
127 return authenticated;
128 }
129
130 static gchar *
e_soup_auth_bearer_get_authorization(SoupAuth * auth,SoupMessage * message)131 e_soup_auth_bearer_get_authorization (SoupAuth *auth,
132 SoupMessage *message)
133 {
134 ESoupAuthBearer *bearer;
135 gchar *res;
136
137 bearer = E_SOUP_AUTH_BEARER (auth);
138
139 g_mutex_lock (&bearer->priv->property_lock);
140
141 res = g_strdup_printf ("Bearer %s", bearer->priv->access_token);
142
143 g_mutex_unlock (&bearer->priv->property_lock);
144
145 return res;
146 }
147
148 static void
e_soup_auth_bearer_class_init(ESoupAuthBearerClass * class)149 e_soup_auth_bearer_class_init (ESoupAuthBearerClass *class)
150 {
151 GObjectClass *object_class;
152 SoupAuthClass *auth_class;
153
154 /* Keep the "e" prefix on private methods
155 * so we don't step on libsoup's namespace. */
156
157 object_class = G_OBJECT_CLASS (class);
158 object_class->finalize = e_soup_auth_bearer_finalize;
159
160 auth_class = SOUP_AUTH_CLASS (class);
161 auth_class->scheme_name = "Bearer";
162 auth_class->strength = AUTH_STRENGTH;
163 auth_class->update = e_soup_auth_bearer_update;
164 auth_class->get_protection_space = e_soup_auth_bearer_get_protection_space;
165 auth_class->is_authenticated = e_soup_auth_bearer_is_authenticated;
166 auth_class->get_authorization = e_soup_auth_bearer_get_authorization;
167 }
168
169 static void
e_soup_auth_bearer_init(ESoupAuthBearer * bearer)170 e_soup_auth_bearer_init (ESoupAuthBearer *bearer)
171 {
172 bearer->priv = e_soup_auth_bearer_get_instance_private (bearer);
173 bearer->priv->expiry = EXPIRY_INVALID;
174
175 g_mutex_init (&bearer->priv->property_lock);
176 }
177
178 /**
179 * e_soup_auth_bearer_set_access_token:
180 * @bearer: an #ESoupAuthBearer
181 * @access_token: an OAuth 2.0 access token
182 * @expires_in_seconds: expiry for @access_token, or 0 if unknown
183 *
184 * This function is analogous to soup_auth_authenticate() for "Basic" HTTP
185 * authentication, except it takes an OAuth 2.0 access token instead of a
186 * username and password.
187 *
188 * If @expires_in_seconds is greater than zero, soup_auth_is_authenticated()
189 * will return %FALSE after the given number of seconds have elapsed.
190 *
191 * Since: 3.10
192 **/
193 void
e_soup_auth_bearer_set_access_token(ESoupAuthBearer * bearer,const gchar * access_token,gint expires_in_seconds)194 e_soup_auth_bearer_set_access_token (ESoupAuthBearer *bearer,
195 const gchar *access_token,
196 gint expires_in_seconds)
197 {
198 gboolean was_authenticated;
199 gboolean now_authenticated;
200
201 g_return_if_fail (E_IS_SOUP_AUTH_BEARER (bearer));
202
203 was_authenticated = soup_auth_is_authenticated (SOUP_AUTH (bearer));
204
205 g_mutex_lock (&bearer->priv->property_lock);
206
207 if (g_strcmp0 (bearer->priv->access_token, access_token) == 0) {
208 g_mutex_unlock (&bearer->priv->property_lock);
209 return;
210 }
211
212 g_free (bearer->priv->access_token);
213 bearer->priv->access_token = g_strdup (access_token);
214
215 if (expires_in_seconds > 0)
216 bearer->priv->expiry = time (NULL) + expires_in_seconds - 5;
217 else
218 bearer->priv->expiry = EXPIRY_INVALID;
219
220 g_mutex_unlock (&bearer->priv->property_lock);
221
222 now_authenticated = soup_auth_is_authenticated (SOUP_AUTH (bearer));
223
224 if (was_authenticated != now_authenticated)
225 g_object_notify (
226 G_OBJECT (bearer),
227 SOUP_AUTH_IS_AUTHENTICATED);
228 }
229
230 /**
231 * e_soup_auth_bearer_is_expired:
232 * @bearer: an #ESoupAuthBearer
233 *
234 * Returns: Whether the set token is expired. It is considered expired even
235 * if the e_soup_auth_bearer_set_access_token() was called set yet.
236 *
237 * Since: 3.24
238 **/
239 gboolean
e_soup_auth_bearer_is_expired(ESoupAuthBearer * bearer)240 e_soup_auth_bearer_is_expired (ESoupAuthBearer *bearer)
241 {
242 gboolean expired;
243
244 g_return_val_if_fail (E_IS_SOUP_AUTH_BEARER (bearer), TRUE);
245
246 g_mutex_lock (&bearer->priv->property_lock);
247 expired = e_soup_auth_bearer_is_expired_locked (bearer);
248 g_mutex_unlock (&bearer->priv->property_lock);
249
250 return expired;
251 }
252