poco/Net/src/OAuth10Credentials.cpp
2022-07-07 04:18:20 -05:00

366 lines
9.9 KiB
C++

//
// OAuth10Credentials.cpp
//
// Library: Net
// Package: OAuth
// Module: OAuth10Credentials
//
// Copyright (c) 2014, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
//
#include "Poco/Net/OAuth10Credentials.h"
#include "Poco/Net/HTTPRequest.h"
#include "Poco/Net/HTMLForm.h"
#include "Poco/Net/NetException.h"
#include "Poco/Net/HTTPAuthenticationParams.h"
#include "Poco/SHA1Engine.h"
#include "Poco/HMACEngine.h"
#include "Poco/Base64Encoder.h"
#include "Poco/RandomStream.h"
#include "Poco/Timestamp.h"
#include "Poco/NumberParser.h"
#include "Poco/NumberFormatter.h"
#include "Poco/Format.h"
#include "Poco/String.h"
#include <map>
#include <sstream>
namespace Poco {
namespace Net {
const std::string OAuth10Credentials::SCHEME = "OAuth";
OAuth10Credentials::OAuth10Credentials()
{
}
OAuth10Credentials::OAuth10Credentials(const std::string& consumerKey, const std::string& consumerSecret):
_consumerKey(consumerKey),
_consumerSecret(consumerSecret)
{
}
OAuth10Credentials::OAuth10Credentials(const std::string& consumerKey, const std::string& consumerSecret, const std::string& token, const std::string& tokenSecret):
_consumerKey(consumerKey),
_consumerSecret(consumerSecret),
_token(token),
_tokenSecret(tokenSecret)
{
}
OAuth10Credentials::OAuth10Credentials(const Poco::Net::HTTPRequest& request)
{
if (request.hasCredentials())
{
std::string authScheme;
std::string authParams;
request.getCredentials(authScheme, authParams);
if (icompare(authScheme, SCHEME) == 0)
{
HTTPAuthenticationParams params(authParams);
std::string consumerKey = params.get("oauth_consumer_key", "");
URI::decode(consumerKey, _consumerKey);
std::string token = params.get("oauth_token", "");
URI::decode(token, _token);
std::string callback = params.get("oauth_callback", "");
URI::decode(callback, _callback);
}
else throw NotAuthenticatedException("No OAuth credentials in Authorization header", authScheme);
}
else throw NotAuthenticatedException("No Authorization header found");
}
OAuth10Credentials::~OAuth10Credentials()
{
}
void OAuth10Credentials::setConsumerKey(const std::string& consumerKey)
{
_consumerKey = consumerKey;
}
void OAuth10Credentials::setConsumerSecret(const std::string& consumerSecret)
{
_consumerSecret = consumerSecret;
}
void OAuth10Credentials::setToken(const std::string& token)
{
_token = token;
}
void OAuth10Credentials::setTokenSecret(const std::string& tokenSecret)
{
_tokenSecret = tokenSecret;
}
void OAuth10Credentials::setRealm(const std::string& realm)
{
_realm = realm;
}
void OAuth10Credentials::setCallback(const std::string& callback)
{
_callback = callback;
}
void OAuth10Credentials::authenticate(HTTPRequest& request, const Poco::URI& uri, SignatureMethod method)
{
HTMLForm emptyParams;
authenticate(request, uri, emptyParams, method);
}
void OAuth10Credentials::authenticate(HTTPRequest& request, const Poco::URI& uri, const Poco::Net::HTMLForm& params, SignatureMethod method)
{
if (method == SIGN_PLAINTEXT)
{
signPlaintext(request);
}
else
{
URI uriWithoutQuery(uri);
uriWithoutQuery.setQuery("");
uriWithoutQuery.setFragment("");
signHMACSHA1(request, uriWithoutQuery.toString(), params);
}
}
bool OAuth10Credentials::verify(const HTTPRequest& request, const Poco::URI& uri)
{
HTMLForm params;
return verify(request, uri, params);
}
bool OAuth10Credentials::verify(const HTTPRequest& request, const Poco::URI& uri, const Poco::Net::HTMLForm& params)
{
if (request.hasCredentials())
{
std::string authScheme;
std::string authParams;
request.getCredentials(authScheme, authParams);
if (icompare(authScheme, SCHEME) == 0)
{
HTTPAuthenticationParams oauthParams(authParams);
std::string version = oauthParams.get("oauth_version", "1.0");
if (version != "1.0") throw NotAuthenticatedException("Unsupported OAuth version", version);
_consumerKey.clear();
std::string consumerKey = oauthParams.get("oauth_consumer_key", "");
URI::decode(consumerKey, _consumerKey);
_token.clear();
std::string token = oauthParams.get("oauth_token", "");
URI::decode(token, _token);
_callback.clear();
std::string callback = oauthParams.get("oauth_callback", "");
URI::decode(callback, _callback);
std::string nonceEnc = oauthParams.get("oauth_nonce", "");
std::string nonce;
URI::decode(nonceEnc, nonce);
std::string timestamp = oauthParams.get("oauth_timestamp", "");
std::string method = oauthParams.get("oauth_signature_method", "");
std::string signatureEnc = oauthParams.get("oauth_signature", "");
std::string signature;
URI::decode(signatureEnc, signature);
std::string refSignature;
if (icompare(method, "PLAINTEXT") == 0)
{
refSignature = percentEncode(_consumerSecret);
refSignature += '&';
refSignature += percentEncode(_tokenSecret);
}
else if (icompare(method, "HMAC-SHA1") == 0)
{
URI uriWithoutQuery(uri);
uriWithoutQuery.setQuery("");
uriWithoutQuery.setFragment("");
refSignature = createSignature(request, uriWithoutQuery.toString(), params, nonce, timestamp);
}
else throw NotAuthenticatedException("Unsupported OAuth signature method", method);
return refSignature == signature;
}
else throw NotAuthenticatedException("No OAuth credentials found in Authorization header");
}
else throw NotAuthenticatedException("No Authorization header found");
}
void OAuth10Credentials::nonceAndTimestampForTesting(const std::string& nonce, const std::string& timestamp)
{
_nonce = nonce;
_timestamp = timestamp;
}
void OAuth10Credentials::signPlaintext(Poco::Net::HTTPRequest& request) const
{
std::string signature(percentEncode(_consumerSecret));
signature += '&';
signature += percentEncode(_tokenSecret);
std::string authorization(SCHEME);
if (!_realm.empty())
{
Poco::format(authorization, " realm=\"%s\",", _realm);
}
Poco::format(authorization, " oauth_consumer_key=\"%s\"", percentEncode(_consumerKey));
Poco::format(authorization, ", oauth_signature=\"%s\"", percentEncode(signature));
authorization += ", oauth_signature_method=\"PLAINTEXT\"";
if (!_token.empty())
{
Poco::format(authorization, ", oauth_token=\"%s\"", percentEncode(_token));
}
if (!_callback.empty())
{
Poco::format(authorization, ", oauth_callback=\"%s\"", percentEncode(_callback));
}
authorization += ", oauth_version=\"1.0\"";
request.set(HTTPRequest::AUTHORIZATION, authorization);
}
void OAuth10Credentials::signHMACSHA1(Poco::Net::HTTPRequest& request, const std::string& uri, const Poco::Net::HTMLForm& params) const
{
std::string nonce(_nonce);
if (nonce.empty())
{
nonce = createNonce();
}
std::string timestamp(_timestamp);
if (timestamp.empty())
{
timestamp = Poco::NumberFormatter::format(Poco::Timestamp().epochTime());
}
std::string signature(createSignature(request, uri, params, nonce, timestamp));
std::string authorization(SCHEME);
if (!_realm.empty())
{
Poco::format(authorization, " realm=\"%s\",", _realm);
}
Poco::format(authorization, " oauth_consumer_key=\"%s\"", percentEncode(_consumerKey));
Poco::format(authorization, ", oauth_nonce=\"%s\"", percentEncode(nonce));
Poco::format(authorization, ", oauth_signature=\"%s\"", percentEncode(signature));
authorization += ", oauth_signature_method=\"HMAC-SHA1\"";
Poco::format(authorization, ", oauth_timestamp=\"%s\"", timestamp);
if (!_token.empty())
{
Poco::format(authorization, ", oauth_token=\"%s\"", percentEncode(_token));
}
if (!_callback.empty())
{
Poco::format(authorization, ", oauth_callback=\"%s\"", percentEncode(_callback));
}
authorization += ", oauth_version=\"1.0\"";
request.set(HTTPRequest::AUTHORIZATION, authorization);
}
std::string OAuth10Credentials::createNonce() const
{
std::ostringstream base64Nonce;
Poco::Base64Encoder base64Encoder(base64Nonce);
Poco::RandomInputStream randomStream;
for (int i = 0; i < 32; i++)
{
base64Encoder.put(randomStream.get());
}
base64Encoder.close();
std::string nonce = base64Nonce.str();
return Poco::translate(nonce, "+/=", "");
}
std::string OAuth10Credentials::createSignature(const Poco::Net::HTTPRequest& request, const std::string& uri, const Poco::Net::HTMLForm& params, const std::string& nonce, const std::string& timestamp) const
{
std::map<std::string, std::string> paramsMap;
paramsMap["oauth_version"] = "1.0";
paramsMap["oauth_consumer_key"] = percentEncode(_consumerKey);
paramsMap["oauth_nonce"] = percentEncode(nonce);
paramsMap["oauth_signature_method"] = "HMAC-SHA1";
paramsMap["oauth_timestamp"] = timestamp;
if (!_token.empty())
{
paramsMap["oauth_token"] = percentEncode(_token);
}
if (!_callback.empty())
{
paramsMap["oauth_callback"] = percentEncode(_callback);
}
for (const auto& p: params)
{
paramsMap[percentEncode(p.first)] = percentEncode(p.second);
}
std::string paramsString;
for (auto it = paramsMap.begin(); it != paramsMap.end(); ++it)
{
if (it != paramsMap.begin()) paramsString += '&';
paramsString += it->first;
paramsString += "=";
paramsString += it->second;
}
std::string signatureBase = request.getMethod();
signatureBase += '&';
signatureBase += percentEncode(uri);
signatureBase += '&';
signatureBase += percentEncode(paramsString);
std::string signingKey;
signingKey += percentEncode(_consumerSecret);
signingKey += '&';
signingKey += percentEncode(_tokenSecret);
Poco::HMACEngine<Poco::SHA1Engine> hmacEngine(signingKey);
hmacEngine.update(signatureBase);
Poco::DigestEngine::Digest digest = hmacEngine.digest();
std::ostringstream digestBase64;
Poco::Base64Encoder base64Encoder(digestBase64);
base64Encoder.write(reinterpret_cast<char*>(&digest[0]), digest.size());
base64Encoder.close();
return digestBase64.str();
}
std::string OAuth10Credentials::percentEncode(const std::string& str)
{
std::string encoded;
Poco::URI::encode(str, "!?#/'\",;:$&()[]*+=@", encoded);
return encoded;
}
} } // namespace Poco::Net