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.valuesOf(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=='~');