1 /**
2  * ae.net.oauth.common
3  *
4  * I have no idea what I'm doing.
5  * Please don't use this module.
6  *
7  * License:
8  *   This Source Code Form is subject to the terms of
9  *   the Mozilla Public License, v. 2.0. If a copy of
10  *   the MPL was not distributed with this file, You
11  *   can obtain one at http://mozilla.org/MPL/2.0/.
12  *
13  * Authors:
14  *   Vladimir Panteleev <vladimir@thecybershadow.net>
15  */
16 
17 module ae.net.oauth.common;
18 
19 import std.algorithm.sorting;
20 import std.base64;
21 import std.conv;
22 import std.datetime;
23 import std.digest.hmac;
24 import std.digest.sha;
25 
26 import ae.net.ietf.url;
27 import ae.utils.text;
28 
29 debug(OAUTH) import std.stdio : stderr;
30 
31 struct OAuthConfig
32 {
33 	string consumerKey;
34 	string consumerSecret;
35 }
36 
37 struct OAuthSession
38 {
39 	OAuthConfig config;
40 
41 	string token, tokenSecret;
42 
43 	UrlParameters prepareRequest(string requestUrl, string method, UrlParameters[] parameters...)
44 	{
45 		UrlParameters oauthParams;
46 		oauthParams["oauth_consumer_key"] = config.consumerKey;
47 		oauthParams["oauth_token"] = token;
48 		oauthParams["oauth_timestamp"] = Clock.currTime().toUnixTime().text();
49 		oauthParams["oauth_nonce"] = randomString();
50 		oauthParams["oauth_version"] = "1.0";
51 		oauthParams["oauth_signature_method"] = "HMAC-SHA1";
52 		oauthParams["oauth_signature"] = signRequest(method, requestUrl, parameters ~ oauthParams);
53 		return oauthParams;
54 	}
55 
56 	string signRequest(string method, string requestUrl, UrlParameters[] parameters...)
57 	{
58 		string paramStr;
59 		bool[string] keys;
60 
61 		foreach (set; parameters)
62 			foreach (key, value; set)
63 				keys[key] = true;
64 
65 		foreach (key; keys.keys.sort())
66 		{
67 			string[] values;
68 			foreach (set; parameters)
69 				foreach (value; set.getAll(key).sort())
70 					values ~= value;
71 
72 			foreach (value; values.sort())
73 			{
74 				if (paramStr.length)
75 					paramStr ~= '&';
76 				paramStr ~= encode(key) ~ "=" ~ encode(value);
77 			}
78 		}
79 
80 		auto str = encode(method) ~ "&" ~ encode(requestUrl) ~ "&" ~ encode(paramStr);
81 		debug(OAUTH) stderr.writeln("Signature base string: ", str);
82 
83 		auto key = encode(config.consumerSecret) ~ "&" ~ encode(tokenSecret);
84 		debug(OAUTH) stderr.writeln("Signing key: ", key);
85 		auto digest = hmac!SHA1(cast(ubyte[])str, cast(ubyte[])key);
86 		return Base64.encode(digest);
87 	}
88 
89 	unittest
90 	{
91 		// Example from https://dev.twitter.com/oauth/overview/creating-signatures
92 
93 		OAuthSession session;
94 		session.config.consumerSecret = "kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw";
95 		session.tokenSecret = "LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE";
96 
97 		UrlParameters getVars, postVars, oauthVars;
98 		getVars["include_entities"] = "true";
99 		postVars["status"] = "Hello Ladies + Gentlemen, a signed OAuth request!";
100 
101 		oauthVars["oauth_consumer_key"] = "xvz1evFS4wEEPTGEFPHBog";
102 		oauthVars["oauth_nonce"] = "kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg";
103 		oauthVars["oauth_signature_method"] = "HMAC-SHA1";
104 		oauthVars["oauth_timestamp"] = "1318622958";
105 		oauthVars["oauth_token"] = "370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb";
106 		oauthVars["oauth_version"] = "1.0";
107 
108 		auto signature = session.signRequest("POST", "https://api.twitter.com/1/statuses/update.json", getVars, postVars, oauthVars);
109 		assert(signature == "tnnArxj06cWHq44gCs1OSKk/jLY=");
110 	}
111 
112 	alias encode = oauthEncode;
113 }
114 
115 /// Converts OAuth parameters into a string suitable for the "Authorization" header.
116 string oauthHeader(UrlParameters oauthParams)
117 {
118 	string s;
119 	foreach (key, value; oauthParams)
120 		s ~= (s.length ? ", " : "") ~ key ~ `="` ~ oauthEncode(value) ~ `"`;
121 	return "OAuth " ~ s;
122 }
123 
124 static import std.ascii;
125 static alias oauthEncode = encodeUrlPart!(c => std.ascii.isAlphaNum(c) || c=='-' || c=='.' || c=='_' || c=='~');