From 362eaad5cbdd4d9071d5f09d464724803a362bc7 Mon Sep 17 00:00:00 2001 From: Guenter Obiltschnig Date: Mon, 10 Nov 2014 22:15:02 +0100 Subject: [PATCH] added support for OAuth 1.0A authentication --- Net/Makefile | 3 +- Net/include/Poco/Net/OAuth10Credentials.h | 274 ++++++++++++++ Net/src/HTTPAuthenticationParams.cpp | 6 +- Net/src/OAuth10Credentials.cpp | 364 +++++++++++++++++++ Net/testsuite/Makefile | 3 +- Net/testsuite/src/HTTPTestSuite.cpp | 2 + Net/testsuite/src/OAuth10CredentialsTest.cpp | 266 ++++++++++++++ Net/testsuite/src/OAuth10CredentialsTest.h | 45 +++ 8 files changed, 958 insertions(+), 5 deletions(-) create mode 100644 Net/include/Poco/Net/OAuth10Credentials.h create mode 100644 Net/src/OAuth10Credentials.cpp create mode 100644 Net/testsuite/src/OAuth10CredentialsTest.cpp create mode 100644 Net/testsuite/src/OAuth10CredentialsTest.h diff --git a/Net/Makefile b/Net/Makefile index 3068e8ec6..d4e885f4d 100644 --- a/Net/Makefile +++ b/Net/Makefile @@ -32,7 +32,8 @@ objects = \ ICMPSocket ICMPSocketImpl ICMPv4PacketImpl \ NTPClient NTPEventArgs NTPPacket \ RemoteSyslogChannel RemoteSyslogListener SMTPChannel \ - WebSocket WebSocketImpl + WebSocket WebSocketImpl \ + OAuth10Credentials target = PocoNet target_version = $(LIBVERSION) diff --git a/Net/include/Poco/Net/OAuth10Credentials.h b/Net/include/Poco/Net/OAuth10Credentials.h new file mode 100644 index 000000000..85c8325a0 --- /dev/null +++ b/Net/include/Poco/Net/OAuth10Credentials.h @@ -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 . + + static std::string percentEncode(const std::string& str); + /// Percent-encodes the given string according to Twitter API's rules, + /// given in . + +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 diff --git a/Net/src/HTTPAuthenticationParams.cpp b/Net/src/HTTPAuthenticationParams.cpp index 53eff21fa..b7a89834a 100644 --- a/Net/src/HTTPAuthenticationParams.cpp +++ b/Net/src/HTTPAuthenticationParams.cpp @@ -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; diff --git a/Net/src/OAuth10Credentials.cpp b/Net/src/OAuth10Credentials.cpp new file mode 100644 index 000000000..2ce5979e9 --- /dev/null +++ b/Net/src/OAuth10Credentials.cpp @@ -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 +#include + + +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 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::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 hmacEngine(signingKey); + hmacEngine.update(signatureBase); + Poco::DigestEngine::Digest digest = hmacEngine.digest(); + std::ostringstream digestBase64; + Poco::Base64Encoder base64Encoder(digestBase64); + base64Encoder.write(reinterpret_cast(&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 diff --git a/Net/testsuite/Makefile b/Net/testsuite/Makefile index 0bb0d96fe..ec5fc9602 100644 --- a/Net/testsuite/Makefile +++ b/Net/testsuite/Makefile @@ -27,7 +27,8 @@ objects = \ RawSocketTest ICMPClientTest ICMPSocketTest ICMPClientTestSuite \ NTPClientTest NTPClientTestSuite \ WebSocketTest WebSocketTestSuite \ - SyslogTest + SyslogTest \ + OAuth10CredentialsTest target = testrunner target_version = 1 diff --git a/Net/testsuite/src/HTTPTestSuite.cpp b/Net/testsuite/src/HTTPTestSuite.cpp index cc5d185e4..67df42ff7 100644 --- a/Net/testsuite/src/HTTPTestSuite.cpp +++ b/Net/testsuite/src/HTTPTestSuite.cpp @@ -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; } diff --git a/Net/testsuite/src/OAuth10CredentialsTest.cpp b/Net/testsuite/src/OAuth10CredentialsTest.cpp new file mode 100644 index 000000000..214f28f21 --- /dev/null +++ b/Net/testsuite/src/OAuth10CredentialsTest.cpp @@ -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 + // + // 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 + // and . + // + // 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 + // and . + // + // 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; +} diff --git a/Net/testsuite/src/OAuth10CredentialsTest.h b/Net/testsuite/src/OAuth10CredentialsTest.h new file mode 100644 index 000000000..eb035426f --- /dev/null +++ b/Net/testsuite/src/OAuth10CredentialsTest.h @@ -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