mirror of
https://github.com/pocoproject/poco.git
synced 2025-10-23 16:48:06 +02:00
added support for OAuth 1.0A authentication
This commit is contained in:
@@ -32,7 +32,8 @@ objects = \
|
||||
ICMPSocket ICMPSocketImpl ICMPv4PacketImpl \
|
||||
NTPClient NTPEventArgs NTPPacket \
|
||||
RemoteSyslogChannel RemoteSyslogListener SMTPChannel \
|
||||
WebSocket WebSocketImpl
|
||||
WebSocket WebSocketImpl \
|
||||
OAuth10Credentials
|
||||
|
||||
target = PocoNet
|
||||
target_version = $(LIBVERSION)
|
||||
|
274
Net/include/Poco/Net/OAuth10Credentials.h
Normal file
274
Net/include/Poco/Net/OAuth10Credentials.h
Normal file
@@ -0,0 +1,274 @@
|
||||
//
|
||||
// OAuth10Credentials.h
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Library: Net
|
||||
// Package: OAuth
|
||||
// Module: OAuth10Credentials
|
||||
//
|
||||
// Definition of the OAuth10Credentials class.
|
||||
//
|
||||
// Copyright (c) 2014, Applied Informatics Software Engineering GmbH.
|
||||
// and Contributors.
|
||||
//
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
//
|
||||
|
||||
|
||||
#ifndef Net_OAuth10Credentials_INCLUDED
|
||||
#define Net_OAuth10Credentials_INCLUDED
|
||||
|
||||
|
||||
#include "Poco/Net/Net.h"
|
||||
#include "Poco/URI.h"
|
||||
|
||||
|
||||
namespace Poco {
|
||||
namespace Net {
|
||||
|
||||
|
||||
class HTTPRequest;
|
||||
class HTMLForm;
|
||||
|
||||
|
||||
class Net_API OAuth10Credentials
|
||||
/// This class implements OAuth 1.0A authentication for HTTP requests,
|
||||
/// according to RFC 5849.
|
||||
///
|
||||
/// Only PLAINTEXT and HMAC-SHA1 signature methods are
|
||||
/// supported. The RSA-SHA1 signature method is not supported.
|
||||
///
|
||||
/// The OAuth10Credentials can be used to sign a client request, as
|
||||
/// well as to verify the signature of a request on the server.
|
||||
///
|
||||
/// To sign a client request, using a known consumer (client) key, consumer (client) secret,
|
||||
/// OAuth token and token secret:
|
||||
///
|
||||
/// 1. Create an OAuth10Credentials object using all four credentials, either using
|
||||
/// the four argument constructor, or by using the default constructor and setting
|
||||
/// the credentials using the setter methods.
|
||||
/// 2. Create a URI containing the full request URI.
|
||||
/// 3. Create an appropriate HTTPRequest object.
|
||||
/// 4. Optionally, create a HTMLForm object containing additional parameters to sign.
|
||||
/// 5. Sign the request by calling authenticate(). This will add an OAuth
|
||||
/// Authorization header to the request.
|
||||
/// 6. Send the request using a HTTPClientSession.
|
||||
///
|
||||
/// To request the OAuth request token from a server, using only the consumer (client) key
|
||||
/// and consumer (client) secret:
|
||||
///
|
||||
/// 1. Create an OAuth10Credentials object using the two consumer credentials, either using
|
||||
/// the two argument constructor, or by using the default constructor and setting
|
||||
/// the credentials using the setter methods.
|
||||
/// 2. Specify the callback URI using setCallback().
|
||||
/// 3. Create a URI containing the full request URI to obtain the token.
|
||||
/// 4. Create an appropriate HTTPRequest object.
|
||||
/// 5. Sign the request by calling authenticate(). This will add an OAuth
|
||||
/// Authorization header to the request.
|
||||
/// 6. Send the request using a HTTPClientSession.
|
||||
/// 7. The response will contain the request token and request token secret.
|
||||
/// These can be extracted from the response body using a HTMLForm object.
|
||||
///
|
||||
/// Requesting the access token and secret (temporary credentials) from the server
|
||||
/// is analogous to signing a client request using consumer key, consumer secret,
|
||||
/// request token and request token secret.
|
||||
/// The server response will contain the access token and access token secret,
|
||||
/// which can again be extracted from the response body using a HTMLForm object.
|
||||
///
|
||||
/// To verify a request on the server:
|
||||
///
|
||||
/// 1. Create an OAuth10Credentials object using the constructor taking a
|
||||
/// HTTPRequest object. This will extract the consumer key and token (if
|
||||
/// provided).
|
||||
/// 2. Provide the consumer secret and token secret (if required) matching the
|
||||
/// consumer key and token to the OAuth10Credentials object using the
|
||||
/// setter methods.
|
||||
/// 3. Create an URI object containing the full request URI.
|
||||
/// 4. Call verify() to verify the signature.
|
||||
/// 5. If verification was successful, and the request was a request for
|
||||
/// a request (temporary) token, call getCallback() to
|
||||
/// obtain the callback URI provided by the client.
|
||||
{
|
||||
public:
|
||||
enum SignatureMethod
|
||||
/// OAuth 1.0A Signature Method.
|
||||
{
|
||||
SIGN_PLAINTEXT, /// OAuth 1.0A PLAINTEXT signature method
|
||||
SIGN_HMAC_SHA1 /// OAuth 1.0A HMAC-SHA1 signature method
|
||||
};
|
||||
|
||||
OAuth10Credentials();
|
||||
/// Creates an empty OAuth10Credentials object.
|
||||
|
||||
OAuth10Credentials(const std::string& consumerKey, const std::string& consumerSecret);
|
||||
/// Creates an HTTPCredentials object with the given consumer key and consumer secret.
|
||||
///
|
||||
/// The token and tokenSecret will be left empty.
|
||||
|
||||
OAuth10Credentials(const std::string& consumerKey, const std::string& consumerSecret, const std::string& token, const std::string& tokenSecret);
|
||||
/// Creates an HTTPCredentials object with the given consumer key and
|
||||
/// consumer secret, as well as token and token secret.
|
||||
|
||||
explicit OAuth10Credentials(const HTTPRequest& request);
|
||||
/// Creates an OAuth10Credentials object from a HTTPRequest object.
|
||||
///
|
||||
/// Extracts consumer key and token (if available) from the Authorization header.
|
||||
///
|
||||
/// Throws a NotAuthenticatedException if the request does
|
||||
/// not contain OAuth 1.0 credentials.
|
||||
|
||||
~OAuth10Credentials();
|
||||
/// Destroys the HTTPCredentials.
|
||||
|
||||
void setConsumerKey(const std::string& consumerKey);
|
||||
/// Sets the consumer key.
|
||||
|
||||
const std::string& getConsumerKey() const;
|
||||
/// Returns the consumer key.
|
||||
|
||||
void setConsumerSecret(const std::string& consumerSecret);
|
||||
/// Sets the consumer secret.
|
||||
|
||||
const std::string& getConsumerSecret() const;
|
||||
/// Returns the consumer secret.
|
||||
|
||||
void setToken(const std::string& token);
|
||||
/// Sets the token.
|
||||
|
||||
const std::string& getToken() const;
|
||||
/// Returns the token.
|
||||
|
||||
void setTokenSecret(const std::string& tokenSecret);
|
||||
/// Sets the token.
|
||||
|
||||
const std::string& getTokenSecret() const;
|
||||
/// Returns the token secret.
|
||||
|
||||
void setRealm(const std::string& realm);
|
||||
/// Sets the optional realm to be included in the Authorization header.
|
||||
|
||||
const std::string& getRealm() const;
|
||||
/// Returns the optional realm to be included in the Authorization header.
|
||||
|
||||
void setCallback(const std::string& uri);
|
||||
/// Sets the callback URI.
|
||||
|
||||
const std::string& getCallback() const;
|
||||
/// Returns the callback URI.
|
||||
|
||||
void authenticate(HTTPRequest& request, const Poco::URI& uri, SignatureMethod method = SIGN_HMAC_SHA1);
|
||||
/// Adds an OAuth 1.0A Authentication header to the given request, using
|
||||
/// the given signature method.
|
||||
|
||||
void authenticate(HTTPRequest& request, const Poco::URI& uri, const Poco::Net::HTMLForm& params, SignatureMethod method = SIGN_HMAC_SHA1);
|
||||
/// Adds an OAuth 1.0A Authentication header to the given request, using
|
||||
/// the given signature method.
|
||||
|
||||
bool verify(const HTTPRequest& request, const Poco::URI& uri);
|
||||
/// Verifies the signature of the given request.
|
||||
///
|
||||
/// The consumer key, consumer secret, token and token secret must have been set.
|
||||
///
|
||||
/// Returns true if the signature is valid, otherwise false.
|
||||
///
|
||||
/// Throws a NotAuthenticatedException if the request does not contain OAuth
|
||||
/// credentials, or in case of an unsupported OAuth version or signature method.
|
||||
|
||||
bool verify(const HTTPRequest& request, const Poco::URI& uri, const Poco::Net::HTMLForm& params);
|
||||
/// Verifies the signature of the given request.
|
||||
///
|
||||
/// The consumer key, consumer secret, token and token secret must have been set.
|
||||
///
|
||||
/// Returns true if the signature is valid, otherwise false.
|
||||
///
|
||||
/// Throws a NotAuthenticatedException if the request does not contain OAuth
|
||||
/// credentials, or in case of an unsupported OAuth version or signature method.
|
||||
|
||||
void nonceAndTimestampForTesting(const std::string& nonce, const std::string& timestamp);
|
||||
/// Sets the nonce and timestamp to a wellknown value.
|
||||
///
|
||||
/// For use by testsuite only, to test the signature
|
||||
/// algorithm with wellknown inputs.
|
||||
///
|
||||
/// In normal operation, the nonce is a random value
|
||||
/// computed by createNonce() and the timestamp is taken
|
||||
/// from the system time.
|
||||
|
||||
protected:
|
||||
void signPlaintext(Poco::Net::HTTPRequest& request) const;
|
||||
/// Signs the given HTTP request according to OAuth 1.0A PLAINTEXT signature method.
|
||||
|
||||
void signHMACSHA1(Poco::Net::HTTPRequest& request, const std::string& uri, const Poco::Net::HTMLForm& params) const;
|
||||
/// Signs the given HTTP request according to OAuth 1.0A HMAC-SHA1 signature method.
|
||||
|
||||
std::string createNonce() const;
|
||||
/// Creates a nonce, which is basically a Base64-encoded 32 character random
|
||||
/// string, with non-alphanumeric characters removed.
|
||||
|
||||
std::string createSignature(const Poco::Net::HTTPRequest& request, const std::string& uri, const Poco::Net::HTMLForm& params, const std::string& nonce, const std::string& timestamp) const;
|
||||
/// Creates a OAuth signature for the given request and its parameters, according
|
||||
/// to <https://dev.twitter.com/docs/auth/creating-signature>.
|
||||
|
||||
static std::string percentEncode(const std::string& str);
|
||||
/// Percent-encodes the given string according to Twitter API's rules,
|
||||
/// given in <https://dev.twitter.com/docs/auth/percent-encoding-parameters>.
|
||||
|
||||
private:
|
||||
OAuth10Credentials(const OAuth10Credentials&);
|
||||
OAuth10Credentials& operator = (const OAuth10Credentials&);
|
||||
|
||||
std::string _consumerKey;
|
||||
std::string _consumerSecret;
|
||||
std::string _token;
|
||||
std::string _tokenSecret;
|
||||
std::string _callback;
|
||||
std::string _realm;
|
||||
std::string _nonce;
|
||||
std::string _timestamp;
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// inlines
|
||||
//
|
||||
inline const std::string& OAuth10Credentials::getConsumerKey() const
|
||||
{
|
||||
return _consumerKey;
|
||||
}
|
||||
|
||||
|
||||
inline const std::string& OAuth10Credentials::getConsumerSecret() const
|
||||
{
|
||||
return _consumerSecret;
|
||||
}
|
||||
|
||||
|
||||
inline const std::string& OAuth10Credentials::getToken() const
|
||||
{
|
||||
return _token;
|
||||
}
|
||||
|
||||
|
||||
inline const std::string& OAuth10Credentials::getTokenSecret() const
|
||||
{
|
||||
return _tokenSecret;
|
||||
}
|
||||
|
||||
|
||||
inline const std::string& OAuth10Credentials::getRealm() const
|
||||
{
|
||||
return _realm;
|
||||
}
|
||||
|
||||
|
||||
inline const std::string& OAuth10Credentials::getCallback() const
|
||||
{
|
||||
return _callback;
|
||||
}
|
||||
|
||||
|
||||
} } // namespace Poco::Net
|
||||
|
||||
|
||||
#endif // Net_OAuth10Credentials_INCLUDED
|
@@ -203,7 +203,7 @@ void HTTPAuthenticationParams::parse(std::string::const_iterator first, std::str
|
||||
switch (state)
|
||||
{
|
||||
case STATE_SPACE:
|
||||
if (Ascii::isAlphaNumeric(*it))
|
||||
if (Ascii::isAlphaNumeric(*it) || *it == '_')
|
||||
{
|
||||
token += *it;
|
||||
state = STATE_TOKEN;
|
||||
@@ -220,7 +220,7 @@ void HTTPAuthenticationParams::parse(std::string::const_iterator first, std::str
|
||||
{
|
||||
state = STATE_EQUALS;
|
||||
}
|
||||
else if (Ascii::isAlphaNumeric(*it))
|
||||
else if (Ascii::isAlphaNumeric(*it) || *it == '_')
|
||||
{
|
||||
token += *it;
|
||||
}
|
||||
@@ -228,7 +228,7 @@ void HTTPAuthenticationParams::parse(std::string::const_iterator first, std::str
|
||||
break;
|
||||
|
||||
case STATE_EQUALS:
|
||||
if (Ascii::isAlphaNumeric(*it))
|
||||
if (Ascii::isAlphaNumeric(*it) || *it == '_')
|
||||
{
|
||||
value += *it;
|
||||
state = STATE_VALUE;
|
||||
|
364
Net/src/OAuth10Credentials.cpp
Normal file
364
Net/src/OAuth10Credentials.cpp
Normal file
@@ -0,0 +1,364 @@
|
||||
//
|
||||
// OAuth10Credentials.cpp
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// 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/HTTPResponse.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 <map>
|
||||
#include <sstream>
|
||||
|
||||
|
||||
namespace Poco {
|
||||
namespace Net {
|
||||
|
||||
|
||||
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 (authScheme == "OAuth")
|
||||
{
|
||||
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 (authScheme == "OAuth")
|
||||
{
|
||||
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 (method == "PLAINTEXT")
|
||||
{
|
||||
refSignature = percentEncode(_consumerSecret);
|
||||
refSignature += '&';
|
||||
refSignature += percentEncode(_tokenSecret);
|
||||
}
|
||||
else if (method == "HMAC-SHA1")
|
||||
{
|
||||
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("OAuth");
|
||||
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("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("OAuth");
|
||||
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("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 (Poco::Net::HTMLForm::ConstIterator it = params.begin(); it != params.end(); ++it)
|
||||
{
|
||||
paramsMap[percentEncode(it->first)] = percentEncode(it->second);
|
||||
}
|
||||
|
||||
std::string paramsString;
|
||||
for (std::map<std::string, std::string>::const_iterator 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
|
@@ -27,7 +27,8 @@ objects = \
|
||||
RawSocketTest ICMPClientTest ICMPSocketTest ICMPClientTestSuite \
|
||||
NTPClientTest NTPClientTestSuite \
|
||||
WebSocketTest WebSocketTestSuite \
|
||||
SyslogTest
|
||||
SyslogTest \
|
||||
OAuth10CredentialsTest
|
||||
|
||||
target = testrunner
|
||||
target_version = 1
|
||||
|
@@ -15,6 +15,7 @@
|
||||
#include "HTTPResponseTest.h"
|
||||
#include "HTTPCookieTest.h"
|
||||
#include "HTTPCredentialsTest.h"
|
||||
#include "OAuth10CredentialsTest.h"
|
||||
|
||||
|
||||
CppUnit::Test* HTTPTestSuite::suite()
|
||||
@@ -25,6 +26,7 @@ CppUnit::Test* HTTPTestSuite::suite()
|
||||
pSuite->addTest(HTTPResponseTest::suite());
|
||||
pSuite->addTest(HTTPCookieTest::suite());
|
||||
pSuite->addTest(HTTPCredentialsTest::suite());
|
||||
pSuite->addTest(OAuth10CredentialsTest::suite());
|
||||
|
||||
return pSuite;
|
||||
}
|
||||
|
266
Net/testsuite/src/OAuth10CredentialsTest.cpp
Normal file
266
Net/testsuite/src/OAuth10CredentialsTest.cpp
Normal file
@@ -0,0 +1,266 @@
|
||||
//
|
||||
// OAuth10CredentialsTest.cpp
|
||||
//
|
||||
// $Id: //poco/1.4/Net/testsuite/src/HTTPCredentialsTest.cpp#3 $
|
||||
//
|
||||
// Copyright (c) 2014, Applied Informatics Software Engineering GmbH.
|
||||
// and Contributors.
|
||||
//
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
//
|
||||
|
||||
|
||||
#include "OAuth10CredentialsTest.h"
|
||||
#include "CppUnit/TestCaller.h"
|
||||
#include "CppUnit/TestSuite.h"
|
||||
#include "Poco/Net/HTTPRequest.h"
|
||||
#include "Poco/Net/HTTPResponse.h"
|
||||
#include "Poco/Net/OAuth10Credentials.h"
|
||||
#include "Poco/Net/NetException.h"
|
||||
#include "Poco/Net/HTMLForm.h"
|
||||
#include "Poco/URI.h"
|
||||
|
||||
|
||||
using Poco::Net::HTTPRequest;
|
||||
using Poco::Net::HTTPResponse;
|
||||
using Poco::Net::OAuth10Credentials;
|
||||
using Poco::Net::NotAuthenticatedException;
|
||||
using Poco::Net::HTMLForm;
|
||||
using Poco::URI;
|
||||
|
||||
|
||||
OAuth10CredentialsTest::OAuth10CredentialsTest(const std::string& name): CppUnit::TestCase(name)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
OAuth10CredentialsTest::~OAuth10CredentialsTest()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void OAuth10CredentialsTest::testCallback()
|
||||
{
|
||||
// Note: Request taken from <https://dev.twitter.com/web/sign-in/implementing>
|
||||
//
|
||||
// POST /oauth/request_token HTTP/1.1
|
||||
// Host: api.twitter.com
|
||||
// Authorization:
|
||||
// OAuth oauth_callback="http%3A%2F%2Flocalhost%2Fsign-in-with-twitter%2F",
|
||||
// oauth_consumer_key="cChZNFj6T5R0TigYB9yd1w",
|
||||
// oauth_nonce="ea9ec8429b68d6b77cd5600adbbb0456",
|
||||
// oauth_signature="F1Li3tvehgcraF8DMJ7OyxO4w9Y%3D",
|
||||
// oauth_signature_method="HMAC-SHA1",
|
||||
// oauth_timestamp="1318467427",
|
||||
// oauth_version="1.0"
|
||||
|
||||
|
||||
URI uri("https://api.twitter.com/oauth/request_token");
|
||||
OAuth10Credentials creds("cChZNFj6T5R0TigYB9yd1w", "L8qq9PZyRg6ieKGEKhZolGC0vJWLw8iEJ88DRdyOg");
|
||||
creds.setCallback("http://localhost/sign-in-with-twitter/");
|
||||
creds.nonceAndTimestampForTesting("ea9ec8429b68d6b77cd5600adbbb0456", "1318467427");
|
||||
HTTPRequest request(HTTPRequest::HTTP_POST, uri.getPathEtc());
|
||||
|
||||
creds.authenticate(request, uri);
|
||||
|
||||
std::string auth = request.get("Authorization");
|
||||
assert (auth == "OAuth"
|
||||
" oauth_consumer_key=\"cChZNFj6T5R0TigYB9yd1w\","
|
||||
" oauth_nonce=\"ea9ec8429b68d6b77cd5600adbbb0456\","
|
||||
" oauth_signature=\"F1Li3tvehgcraF8DMJ7OyxO4w9Y%3D\","
|
||||
" oauth_signature_method=\"HMAC-SHA1\","
|
||||
" oauth_timestamp=\"1318467427\","
|
||||
" oauth_callback=\"http%3A%2F%2Flocalhost%2Fsign-in-with-twitter%2F\","
|
||||
" oauth_version=\"1.0\"");
|
||||
}
|
||||
|
||||
|
||||
void OAuth10CredentialsTest::testParams()
|
||||
{
|
||||
// Note: Request taken from <https://dev.twitter.com/oauth/overview/authorizing-requests>
|
||||
// and <https://dev.twitter.com/oauth/overview/creating-signatures>.
|
||||
//
|
||||
// POST /1/statuses/update.json?include_entities=true HTTP/1.1
|
||||
// Content-Type: application/x-www-form-urlencoded
|
||||
// Authorization:
|
||||
// OAuth oauth_consumer_key="xvz1evFS4wEEPTGEFPHBog",
|
||||
// oauth_nonce="kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg",
|
||||
// oauth_signature="tnnArxj06cWHq44gCs1OSKk%2FjLY%3D",
|
||||
// oauth_signature_method="HMAC-SHA1",
|
||||
// oauth_timestamp="1318622958",
|
||||
// oauth_token="370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb",
|
||||
// oauth_version="1.0"
|
||||
// Content-Length: 76
|
||||
// Host: api.twitter.com
|
||||
//
|
||||
// status=Hello%20Ladies%20%2b%20Gentlemen%2c%20a%20signed%20OAuth%20request%21
|
||||
|
||||
URI uri("https://api.twitter.com/1/statuses/update.json?include_entities=true");
|
||||
OAuth10Credentials creds(
|
||||
"xvz1evFS4wEEPTGEFPHBog",
|
||||
"kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw",
|
||||
"370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb",
|
||||
"LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE"
|
||||
);
|
||||
creds.nonceAndTimestampForTesting("kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg", "1318622958");
|
||||
HTTPRequest request(HTTPRequest::HTTP_POST, uri.getPathEtc());
|
||||
|
||||
HTMLForm params;
|
||||
params.set("include_entities", "true");
|
||||
params.set("status", "Hello Ladies + Gentlemen, a signed OAuth request!");
|
||||
|
||||
creds.authenticate(request, uri, params);
|
||||
|
||||
std::string auth = request.get("Authorization");
|
||||
assert (auth == "OAuth"
|
||||
" oauth_consumer_key=\"xvz1evFS4wEEPTGEFPHBog\","
|
||||
" oauth_nonce=\"kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg\","
|
||||
" oauth_signature=\"tnnArxj06cWHq44gCs1OSKk%2FjLY%3D\","
|
||||
" oauth_signature_method=\"HMAC-SHA1\","
|
||||
" oauth_timestamp=\"1318622958\","
|
||||
" oauth_token=\"370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb\","
|
||||
" oauth_version=\"1.0\"");
|
||||
}
|
||||
|
||||
|
||||
void OAuth10CredentialsTest::testRealm()
|
||||
{
|
||||
// Note: Request taken from <https://dev.twitter.com/oauth/overview/authorizing-requests>
|
||||
// and <https://dev.twitter.com/oauth/overview/creating-signatures>.
|
||||
//
|
||||
// POST /1/statuses/update.json?include_entities=true HTTP/1.1
|
||||
// Content-Type: application/x-www-form-urlencoded
|
||||
// Authorization:
|
||||
// OAuth realm="Twitter API"
|
||||
// oauth_consumer_key="xvz1evFS4wEEPTGEFPHBog",
|
||||
// oauth_nonce="kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg",
|
||||
// oauth_signature="tnnArxj06cWHq44gCs1OSKk%2FjLY%3D",
|
||||
// oauth_signature_method="HMAC-SHA1",
|
||||
// oauth_timestamp="1318622958",
|
||||
// oauth_token="370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb",
|
||||
// oauth_version="1.0"
|
||||
// Content-Length: 76
|
||||
// Host: api.twitter.com
|
||||
//
|
||||
// status=Hello%20Ladies%20%2b%20Gentlemen%2c%20a%20signed%20OAuth%20request%21
|
||||
|
||||
URI uri("https://api.twitter.com/1/statuses/update.json?include_entities=true");
|
||||
OAuth10Credentials creds(
|
||||
"xvz1evFS4wEEPTGEFPHBog",
|
||||
"kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw",
|
||||
"370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb",
|
||||
"LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE"
|
||||
);
|
||||
creds.setRealm("Twitter API");
|
||||
creds.nonceAndTimestampForTesting("kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg", "1318622958");
|
||||
HTTPRequest request(HTTPRequest::HTTP_POST, uri.getPathEtc());
|
||||
|
||||
HTMLForm params;
|
||||
params.set("include_entities", "true");
|
||||
params.set("status", "Hello Ladies + Gentlemen, a signed OAuth request!");
|
||||
|
||||
creds.authenticate(request, uri, params);
|
||||
|
||||
std::string auth = request.get("Authorization");
|
||||
assert (auth == "OAuth"
|
||||
" realm=\"Twitter API\","
|
||||
" oauth_consumer_key=\"xvz1evFS4wEEPTGEFPHBog\","
|
||||
" oauth_nonce=\"kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg\","
|
||||
" oauth_signature=\"tnnArxj06cWHq44gCs1OSKk%2FjLY%3D\","
|
||||
" oauth_signature_method=\"HMAC-SHA1\","
|
||||
" oauth_timestamp=\"1318622958\","
|
||||
" oauth_token=\"370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb\","
|
||||
" oauth_version=\"1.0\"");
|
||||
}
|
||||
|
||||
|
||||
void OAuth10CredentialsTest::testPlaintext()
|
||||
{
|
||||
URI uri("https://api.twitter.com/oauth/request_token");
|
||||
OAuth10Credentials creds("consumerKey", "consumerSecret");
|
||||
creds.setCallback("http://localhost/sign-in-with-twitter/");
|
||||
HTTPRequest request(HTTPRequest::HTTP_POST, uri.getPathEtc());
|
||||
|
||||
creds.authenticate(request, uri, OAuth10Credentials::SIGN_PLAINTEXT);
|
||||
|
||||
std::string auth = request.get("Authorization");
|
||||
|
||||
assert (auth == "OAuth"
|
||||
" oauth_consumer_key=\"consumerKey\","
|
||||
" oauth_signature=\"consumerSecret%26\","
|
||||
" oauth_signature_method=\"PLAINTEXT\","
|
||||
" oauth_callback=\"http%3A%2F%2Flocalhost%2Fsign-in-with-twitter%2F\","
|
||||
" oauth_version=\"1.0\"");
|
||||
}
|
||||
|
||||
|
||||
void OAuth10CredentialsTest::testVerify()
|
||||
{
|
||||
URI uri("https://api.twitter.com/1/statuses/update.json?include_entities=true");
|
||||
HTTPRequest request(HTTPRequest::HTTP_POST, uri.getPathEtc());
|
||||
request.set("Authorization", "OAuth"
|
||||
" oauth_consumer_key=\"xvz1evFS4wEEPTGEFPHBog\","
|
||||
" oauth_nonce=\"kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg\","
|
||||
" oauth_signature=\"tnnArxj06cWHq44gCs1OSKk%2FjLY%3D\","
|
||||
" oauth_signature_method=\"HMAC-SHA1\","
|
||||
" oauth_timestamp=\"1318622958\","
|
||||
" oauth_token=\"370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb\","
|
||||
" oauth_version=\"1.0\"");
|
||||
|
||||
OAuth10Credentials creds(request);
|
||||
assert (creds.getConsumerKey() == "xvz1evFS4wEEPTGEFPHBog");
|
||||
assert (creds.getToken() == "370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb");
|
||||
creds.setConsumerSecret("kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw");
|
||||
creds.setTokenSecret("LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE");
|
||||
|
||||
HTMLForm params;
|
||||
params.read(uri.getRawQuery());
|
||||
params.read("status=Hello%20Ladies%20%2b%20Gentlemen%2c%20a%20signed%20OAuth%20request%21");
|
||||
|
||||
assert (creds.verify(request, uri, params));
|
||||
}
|
||||
|
||||
|
||||
void OAuth10CredentialsTest::testVerifyPlaintext()
|
||||
{
|
||||
URI uri("https://api.twitter.com/oauth/request_token");
|
||||
HTTPRequest request(HTTPRequest::HTTP_POST, uri.getPathEtc());
|
||||
request.set("Authorization", "OAuth"
|
||||
" oauth_consumer_key=\"consumerKey\","
|
||||
" oauth_signature=\"consumerSecret%26\","
|
||||
" oauth_signature_method=\"PLAINTEXT\","
|
||||
" oauth_callback=\"http%3A%2F%2Flocalhost%2Fsign-in-with-twitter%2F\","
|
||||
" oauth_version=\"1.0\"");
|
||||
|
||||
OAuth10Credentials creds(request);
|
||||
assert (creds.getConsumerKey() == "consumerKey");
|
||||
creds.setConsumerSecret("consumerSecret");
|
||||
|
||||
assert (creds.verify(request, uri));
|
||||
assert (creds.getCallback() == "http://localhost/sign-in-with-twitter/");
|
||||
}
|
||||
|
||||
|
||||
void OAuth10CredentialsTest::setUp()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void OAuth10CredentialsTest::tearDown()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
CppUnit::Test* OAuth10CredentialsTest::suite()
|
||||
{
|
||||
CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("OAuth10CredentialsTest");
|
||||
|
||||
CppUnit_addTest(pSuite, OAuth10CredentialsTest, testCallback);
|
||||
CppUnit_addTest(pSuite, OAuth10CredentialsTest, testParams);
|
||||
CppUnit_addTest(pSuite, OAuth10CredentialsTest, testRealm);
|
||||
CppUnit_addTest(pSuite, OAuth10CredentialsTest, testPlaintext);
|
||||
CppUnit_addTest(pSuite, OAuth10CredentialsTest, testVerify);
|
||||
CppUnit_addTest(pSuite, OAuth10CredentialsTest, testVerifyPlaintext);
|
||||
|
||||
return pSuite;
|
||||
}
|
45
Net/testsuite/src/OAuth10CredentialsTest.h
Normal file
45
Net/testsuite/src/OAuth10CredentialsTest.h
Normal file
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// OAuth10CredentialsTest.h
|
||||
//
|
||||
// $Id$
|
||||
//
|
||||
// Definition of the OAuth10CredentialsTest class.
|
||||
//
|
||||
// Copyright (c) 2014, Applied Informatics Software Engineering GmbH.
|
||||
// and Contributors.
|
||||
//
|
||||
// SPDX-License-Identifier: BSL-1.0
|
||||
//
|
||||
|
||||
|
||||
#ifndef OAuth10CredentialsTest_INCLUDED
|
||||
#define OAuth10CredentialsTest_INCLUDED
|
||||
|
||||
|
||||
#include "Poco/Net/Net.h"
|
||||
#include "CppUnit/TestCase.h"
|
||||
|
||||
|
||||
class OAuth10CredentialsTest: public CppUnit::TestCase
|
||||
{
|
||||
public:
|
||||
OAuth10CredentialsTest(const std::string& name);
|
||||
~OAuth10CredentialsTest();
|
||||
|
||||
void testCallback();
|
||||
void testParams();
|
||||
void testRealm();
|
||||
void testPlaintext();
|
||||
void testVerify();
|
||||
void testVerifyPlaintext();
|
||||
|
||||
void setUp();
|
||||
void tearDown();
|
||||
|
||||
static CppUnit::Test* suite();
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
|
||||
#endif // OAuth10CredentialsTest_INCLUDED
|
Reference in New Issue
Block a user