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