Fix previous commit
[debian/pino.git] / src / oauth-client.vala
1 /*
2  * OAuth client implementation using libsoup.
3  *
4  * Copyright (C) 2009 Mark Lee <oauth@lazymalevolence.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 3 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  *
20  * Author : Mark Lee <oauth@lazymalevolence.com>
21  */
22
23 using Soup;
24
25 namespace OAuth
26 {
27   const string VERSION = "1.0";
28   public enum SignatureMethod
29   {
30     UNKNOWN = 0,
31     PLAINTEXT,
32     HMAC_SHA1,
33     RSA_SHA1
34   }
35   public class Client : Object
36   {
37     private Session _session;
38     public Session session
39     {
40       get
41       {
42         return this._session;
43       }
44       construct
45       {
46         this._session = value;
47       }
48     }
49     public string timestamp
50     {
51       owned get
52       {
53         time_t ts = time_t ();
54         return ((long)ts).to_string ();
55       }
56     }
57     public string nonce
58     {
59       owned get
60       {
61         return Random.next_int ().to_string ();
62       }
63     }
64     private HashTable<string,string> _request_token;
65     public HashTable<string,string> request_token
66     {
67       get
68       {
69         return this._request_token;
70       }
71     }
72     private HashTable<string,string> _access_token;
73     public HashTable<string,string> access_token
74     {
75       get
76       {
77         return this._access_token;
78       }
79     }
80     private string _consumer_key;
81     public string consumer_key
82     {
83       get
84       {
85         return this._consumer_key;
86       }
87       construct
88       {
89         this._consumer_key = value;
90       }
91     }
92     private string _consumer_secret;
93     public string consumer_secret
94     {
95       construct
96       {
97         this._consumer_secret = value;
98       }
99     }
100     private SignatureMethod _signature_method;
101     private string _signature_method_str;
102     public SignatureMethod signature_method
103     {
104       get
105       {
106         return this._signature_method;
107       }
108       construct
109       {
110         this._signature_method = value;
111         this._signature_method_str = signature_method_to_string (value);
112       }
113     }
114     
115   public Client(string __consumer_key, string __consumer_secret,
116     SignatureMethod __signature_method, Session __session) {
117       
118     _consumer_key = __consumer_key;
119     _consumer_secret = __consumer_secret;
120     _signature_method = __signature_method;
121     _session = __session;
122   }
123     
124     public static string
125     signature_method_to_string (SignatureMethod method)
126     {
127       string result = null;
128       switch (method)
129       {
130         case SignatureMethod.PLAINTEXT:
131           result = "PLAINTEXT";
132           break;
133         case SignatureMethod.HMAC_SHA1:
134           result = "HMAC-SHA1";
135           break;
136         case SignatureMethod.RSA_SHA1:
137           result = "RSA-SHA1";
138           break;
139         default:
140           result = "HMAC-SHA1";
141           break;
142       }
143       return result;
144     }
145     public static SignatureMethod
146     string_to_signature_method (string repr)
147     {
148       SignatureMethod result;
149       switch (repr)
150       {
151         case "PLAINTEXT":
152           result = SignatureMethod.PLAINTEXT;
153           break;
154         case "HMAC-SHA1":
155           result = SignatureMethod.HMAC_SHA1;
156           break;
157         case "RSA-SHA1":
158           result = SignatureMethod.RSA_SHA1;
159           break;
160         default:
161           result = SignatureMethod.HMAC_SHA1;
162           break;
163       }
164       return result;
165     }
166     /**
167      * Conforms to 5.1, "Parameter Encoding".
168      * (Note: Soup.form_encode_hash does not conform.)
169      */
170     public string
171     encode_parameters (HashTable<string,string> input)
172     {
173       string encoded = "";
174       List<unowned string> keys = input.get_keys ();
175       keys.sort ((CompareFunc)strcmp);
176       foreach (unowned string key in keys)
177       {
178         unowned string? val = input.lookup (key);
179         if (val == null)
180         {
181           debug ("key (%s) is null", key);
182           continue;
183         }
184         if (encoded.len () != 0)
185         {
186           encoded += "&";
187         }
188         encoded += URI.encode (key, "&=") + "=" + URI.encode (val, "&=");
189       }
190       return encoded;
191     }
192     /**
193      * Conforms to Section 9, "Signing Requests".
194      */
195     public string
196     generate_signature (string method, string uri, string token_secret,
197                         HashTable<string,string> sig_params)
198     {
199       // 9.1. Signature Base String
200       string sig_base;
201       // 9.1.1. Normalize Request Parameters
202       // FIXME
203       string request_params = this.encode_parameters (sig_params);
204       debug ("params: %s", request_params);
205       // 9.1.2. Construct Request URL
206       URI soup_uri = new URI (uri);
207       soup_uri.set_host (soup_uri.host.down ());
208       soup_uri.query = null;
209       soup_uri.fragment = null;
210       string request_uri = soup_uri.to_string (false);
211       // 9.1.3. Concatenate Request Elements
212       sig_base = "%s&%s&%s".printf (method, URI.encode (request_uri, null),
213                                     URI.encode (request_params, "=&"));
214       string secrets = "%s&%s".printf (URI.encode (this._consumer_secret, null),
215                                        URI.encode (token_secret, null));
216       debug ("Signature Base: %s", sig_base);
217       string signature = null;
218       switch (this._signature_method)
219       {
220         case SignatureMethod.HMAC_SHA1: // 9.2
221           uchar[] hmac;
222           debug ("secrets: %li; sig: %li", secrets.len (), sig_base.len ());
223           SHA1.hmac (secrets, sig_base, out hmac);
224           debug ("hmac: %d", hmac.length);
225           assert (hmac.length == 20);
226           signature = Base64.encode (hmac);
227           break;
228         case SignatureMethod.RSA_SHA1: // 9.3
229           critical ("Not Implemented.");
230           break;
231         case SignatureMethod.PLAINTEXT: // 9.4
232           signature = secrets;
233           break;
234       }
235       debug ("signature generated: %s", signature);
236       return signature;
237     }
238     public HashTable<string,string>
239     generate_params (string method, string uri,
240                      HashTable<string,string>? extra=null)
241     {
242       HashTable<string,string> result;
243       string oauth_token_secret;
244       string signature;
245       if (extra == null)
246       {
247         result = new HashTable<string,string> (str_hash, str_equal);
248       }
249       else
250       {
251         result = extra;
252       }
253       result.replace ("oauth_consumer_key", this._consumer_key);
254       result.replace ("oauth_signature_method", this._signature_method_str);
255       result.replace ("oauth_version", VERSION);
256       if (this._request_token == null)
257       {
258         oauth_token_secret = "";
259       }
260       else
261       {
262         if (this._access_token == null)
263         {
264           oauth_token_secret = this._request_token.lookup ("oauth_token_secret");
265           result.replace ("oauth_token",
266                           this._request_token.lookup ("oauth_token"));
267         }
268         else
269         {
270           oauth_token_secret = this._access_token.lookup ("oauth_token_secret");
271           result.replace ("oauth_token",
272                           this._access_token.lookup ("oauth_token"));
273         }
274       }
275       result.replace ("oauth_timestamp", this.timestamp);
276       result.replace ("oauth_nonce", this.nonce);
277       signature = this.generate_signature (method, uri, oauth_token_secret,
278                                            result);
279       result.replace ("oauth_signature", signature);
280       return result;
281     }
282     public void
283     fetch_request_token (string method, string uri,
284                          HashTable<string,string>? extra_params=null)
285     {
286       HashTable<string,string> request_params;
287       Message msg;
288       string encoded;
289       
290       request_params = this.generate_params (method, uri, extra_params);
291       msg = new Message (method, uri);
292       encoded = this.encode_parameters (request_params);
293       debug ("encoded params: %s", encoded);
294       msg.request_body.append (MemoryUse.COPY, (void*)encoded, encoded.len ());
295       this._session.send_message (msg);
296       warning((string)msg.response_body.flatten().data);
297       this._request_token = form_decode ((string)msg.response_body.flatten().data);
298     }
299     public string
300     authorize_request_token (string uri)
301     {
302       string full_uri;
303       if (uri.contains ("%s"))
304       {
305         full_uri = uri.printf (this._request_token.lookup ("oauth_token"));
306       }
307       else
308       {
309         full_uri = uri;
310       }
311       return full_uri;
312     }
313     public void
314     fetch_access_token (string method, string uri, string pin)
315     {
316       HashTable<string,string> params;
317       Message msg;
318       
319       HashTable<string, string> extra = new HashTable<string,string> (str_hash, str_equal);
320       extra.insert("oauth_verifier", pin);
321       params = this.generate_params (method, uri, extra);
322       msg = form_request_new_from_hash (method, uri, params);
323       this._session.send_message (msg);
324       warning((string)msg.response_body.flatten().data);
325       this._access_token = form_decode ((string)msg.response_body.flatten().data);
326     }
327     public string generate_authorization (string method, string uri, string realm)
328     {
329       string header = "OAuth";
330       HashTable<string,string> params;
331
332       params = this.generate_params (method, uri);
333       params.insert ("realm", realm);
334       foreach (weak string key in params.get_keys ())
335       {
336         if (header.len () != 5)
337         {
338           header += ",";
339         }
340         string value = URI.encode (params.lookup (key), null);
341         header += " %s=\"%s\"".printf (URI.encode (key, null), value);
342       }
343       return header;
344     }
345   }
346 }
347
348 // vim: set et ts=2 sts=2 sw=2 ai :