/* Copyright © 2012-24 Miranda NG team Copyright © 2009 Jim Porter This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "stdafx.h" static CMStringA OAuthNormalizeUrl(const CMStringA &url) { CMStringA normalUrl = url; int idx = normalUrl.Find('#'); if (idx != -1) normalUrl.Truncate(idx); idx = normalUrl.Find('?'); if (idx != -1) normalUrl.Truncate(idx); return normalUrl; } static int CompareCMString(const CMStringA *p1, const CMStringA *p2) { return mir_strcmp(p1->c_str(), p2->c_str()); } static CMStringA OAuthNormalizeRequestParameters(const CMStringA &requestParameters, bool bShort) { OBJLIST params(10); Split(requestParameters, params, '&'); LIST sorted(params.getCount(), CompareCMString); for (auto &it : params) sorted.insert(it); CMStringA res; for (auto &it : sorted) { if (!res.IsEmpty()) res.AppendChar(bShort ? '&' : ','); if (!bShort) { it->Replace("=", "=\""); it->AppendChar('\"'); } res += *it; } return res; } CMStringA CTwitterProto::BuildSignedOAuthParameters(const CMStringA &request, const CMStringA &url, const char *httpMethod, const char *postData) { CMStringA timestamp(FORMAT, "%lld", _time64(0)); CMStringA nonce = OAuthCreateNonce(); // create oauth requestParameters auto *req = new AsyncHttpRequest(!mir_strcmp(httpMethod, "GET") ? REQUEST_GET : REQUEST_POST, url); req << CHAR_PARAM("oauth_timestamp", timestamp) << CHAR_PARAM("oauth_nonce", nonce) << CHAR_PARAM("oauth_version", "1.0") << CHAR_PARAM("oauth_signature_method", "HMAC-SHA1") << CHAR_PARAM("oauth_consumer_key", OAUTH_CONSUMER_KEY) << CHAR_PARAM("oauth_callback", "oob"); // add the request token if found if (!m_szAccessToken.IsEmpty()) req << CHAR_PARAM("oauth_token", m_szAccessToken); // add the authorization pin if found if (!m_szPin.IsEmpty()) req << CHAR_PARAM("oauth_verifier", m_szPin); // create a parameter list containing both oauth and original parameters // this will be used to create the parameter signature if (!request.IsEmpty()) { req->m_szParam.AppendChar('&'); req->m_szParam.Append(request); } if (!mir_strlen(postData)) { req->m_szParam.AppendChar('&'); req->m_szParam.Append(postData); } // prepare a signature base, a carefully formatted string containing // all of the necessary information needed to generate a valid signature CMStringA normalUrl = OAuthNormalizeUrl(url); CMStringA normalizedParameters = OAuthNormalizeRequestParameters(req->m_szParam, true); CMStringA signatureBase = CMStringA(httpMethod) + "&" + mir_urlEncode(normalUrl) + "&" + mir_urlEncode(normalizedParameters); // obtain a signature and add it to header requestParameters CMStringA signature = OAuthCreateSignature(signatureBase, OAUTH_CONSUMER_SECRET, m_szAccessTokenSecret); req << CHAR_PARAM("oauth_signature", signature); CMStringA ret = OAuthNormalizeRequestParameters(req->m_szParam, false); delete req; return ret; } CMStringA CTwitterProto::UrlGetQuery(const CMStringA &url) { CMStringA query = url; int idx = query.Find('?'); if (idx == -1) return ""; query.Delete(0, idx+1); idx = query.Find('#'); if (idx != -1) query.Truncate(idx); return query; } // OAuthWebRequest used for all OAuth related queries // // consumerKey and consumerSecret - must be provided for every call, they identify the application // oauthToken and oauthTokenSecret - need to be provided for every call, except for the first token request before authorizing // pin - only used during authorization, when the user enters the PIN they received from the CTwitterProto website CMStringA CTwitterProto::OAuthWebRequestSubmit(const CMStringA &url, const char *httpMethod, const char *postData) { CMStringA query = UrlGetQuery(url); CMStringA oauthSignedParameters = BuildSignedOAuthParameters(query, url, httpMethod, postData); oauthSignedParameters.Insert(0, "OAuth "); return oauthSignedParameters; } static char ALPHANUMERIC[62+1] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; CMStringA CTwitterProto::OAuthCreateNonce() { CMStringA nonce; for (int i = 0; i <= 16; ++i) nonce.AppendChar(ALPHANUMERIC[rand() % 62]); return nonce; } CMStringA CTwitterProto::OAuthCreateSignature(const CMStringA &signatureBase, const CMStringA &consumerSecret, const CMStringA &requestTokenSecret) { // URL encode key elements CMStringA key = mir_urlEncode(consumerSecret) + "&" + mir_urlEncode(requestTokenSecret); uint8_t digest[MIR_SHA1_HASH_SIZE]; unsigned int len; HMAC(EVP_sha1(), key.c_str(), (int)key.GetLength(), (uint8_t*)signatureBase.c_str(), signatureBase.GetLength(), digest, &len); return CMStringA(ptrA(mir_base64_encode(digest, sizeof(digest)))); } ///////////////////////////////////////////////////////////////////////////////////////// void CTwitterProto::Oauth2RequestToken(MHttpResponse *pResp, AsyncHttpRequest *) { if (pResp->resultCode != 200) { OnLoggedFail(); return; } // StringPairs response = ParseQueryString(resp.data); // szOauthToken = response[L"oauth_token"]; // szOauthTokenSecret = response[L"oauth_token_secret"]; if (m_szAccessToken.IsEmpty()) { ShowPopup("OAuth token not received, check your internet connection?", 1); debugLogA("**NegotiateConnection - OAuth tokens not received, stopping before we open the web browser.."); return; } // write those bitches to the db foe latta setString(TWITTER_KEY_OAUTH_TOK, m_szAccessToken); setString(TWITTER_KEY_OAUTH_TOK_SEC, m_szAccessTokenSecret); } void CTwitterProto::RequestOauthToken(const char *szPin) { auto *pReq = new AsyncHttpRequest(REQUEST_POST, "/oauth2/token", &CTwitterProto::Oauth2RequestToken); pReq << CHAR_PARAM("grant_type", "authorization_code") << CHAR_PARAM("code_verifier", "zzzzzzz") << CHAR_PARAM("client_id", OAUTH_CONSUMER_KEY) << CHAR_PARAM("callback", "https://miranda-ng.org/oauth") << CHAR_PARAM("code", szPin); Push(pReq); } ///////////////////////////////////////////////////////////////////////////////////////// void CTwitterProto::Oauth2RequestAuth(MHttpResponse *pResp, AsyncHttpRequest *) { if (pResp->resultCode != 200) { OnLoggedFail(); return; } } void CTwitterProto::RequestOauthAuth() { Utils_GetRandom(code_verifier, sizeof(code_verifier)); uint8_t hash[32]; mir_sha256_hash(code_verifier, sizeof(code_verifier), hash); code_challenge = ptrA(mir_base64_encode(hash, sizeof(hash))); auto *pReq = new AsyncHttpRequest(REQUEST_PATCH, "https://twitter.com/i/oauth2/authorize", &CTwitterProto::Oauth2RequestAuth); pReq->flags |= NLHRF_REDIRECT; pReq << CHAR_PARAM("client_id", OAUTH_CONSUMER_KEY) << CHAR_PARAM("scope", "tweet.read tweet.write users.read offline.access") << CHAR_PARAM("response_type", "code") << CHAR_PARAM("callback", "https://oauth.miranda-ng.org") << CHAR_PARAM("state", "state") << CHAR_PARAM("code_challenge_method", "s256") << CHAR_PARAM("code_challenge", code_challenge); Push(pReq); }