Auto merge of #2529 - str4d:2444-rpc-www-authenticate, r=daira
[rpc] Add WWW-Authenticate header to 401 response Fix cherry-picked from upstream PR bitcoin/bitcoin#7472. Closes #2444.
This commit is contained in:
commit
e88ab1a920
|
@ -21,6 +21,7 @@ zcash_gtest_SOURCES += \
|
||||||
gtest/test_tautology.cpp \
|
gtest/test_tautology.cpp \
|
||||||
gtest/test_deprecation.cpp \
|
gtest/test_deprecation.cpp \
|
||||||
gtest/test_equihash.cpp \
|
gtest/test_equihash.cpp \
|
||||||
|
gtest/test_httprpc.cpp \
|
||||||
gtest/test_joinsplit.cpp \
|
gtest/test_joinsplit.cpp \
|
||||||
gtest/test_keystore.cpp \
|
gtest/test_keystore.cpp \
|
||||||
gtest/test_noteencryption.cpp \
|
gtest/test_noteencryption.cpp \
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
|
||||||
|
#include "httprpc.cpp"
|
||||||
|
#include "httpserver.h"
|
||||||
|
|
||||||
|
using ::testing::Return;
|
||||||
|
|
||||||
|
class MockHTTPRequest : public HTTPRequest {
|
||||||
|
public:
|
||||||
|
MOCK_METHOD0(GetPeer, CService());
|
||||||
|
MOCK_METHOD0(GetRequestMethod, HTTPRequest::RequestMethod());
|
||||||
|
MOCK_METHOD1(GetHeader, std::pair<bool, std::string>(const std::string& hdr));
|
||||||
|
MOCK_METHOD2(WriteHeader, void(const std::string& hdr, const std::string& value));
|
||||||
|
MOCK_METHOD2(WriteReply, void(int nStatus, const std::string& strReply));
|
||||||
|
|
||||||
|
MockHTTPRequest() : HTTPRequest(nullptr) {}
|
||||||
|
void CleanUp() {
|
||||||
|
// So the parent destructor doesn't try to send a reply
|
||||||
|
replySent = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(HTTPRPC, FailsOnGET) {
|
||||||
|
MockHTTPRequest req;
|
||||||
|
EXPECT_CALL(req, GetRequestMethod())
|
||||||
|
.WillRepeatedly(Return(HTTPRequest::GET));
|
||||||
|
EXPECT_CALL(req, WriteReply(HTTP_BAD_METHOD, "JSONRPC server handles only POST requests"))
|
||||||
|
.Times(1);
|
||||||
|
EXPECT_FALSE(HTTPReq_JSONRPC(&req, ""));
|
||||||
|
req.CleanUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(HTTPRPC, FailsWithoutAuthHeader) {
|
||||||
|
MockHTTPRequest req;
|
||||||
|
EXPECT_CALL(req, GetRequestMethod())
|
||||||
|
.WillRepeatedly(Return(HTTPRequest::POST));
|
||||||
|
EXPECT_CALL(req, GetHeader("authorization"))
|
||||||
|
.WillRepeatedly(Return(std::make_pair(false, "")));
|
||||||
|
EXPECT_CALL(req, WriteHeader("WWW-Authenticate", "Basic realm=\"jsonrpc\""))
|
||||||
|
.Times(1);
|
||||||
|
EXPECT_CALL(req, WriteReply(HTTP_UNAUTHORIZED, ""))
|
||||||
|
.Times(1);
|
||||||
|
EXPECT_FALSE(HTTPReq_JSONRPC(&req, ""));
|
||||||
|
req.CleanUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(HTTPRPC, FailsWithBadAuth) {
|
||||||
|
MockHTTPRequest req;
|
||||||
|
EXPECT_CALL(req, GetRequestMethod())
|
||||||
|
.WillRepeatedly(Return(HTTPRequest::POST));
|
||||||
|
EXPECT_CALL(req, GetHeader("authorization"))
|
||||||
|
.WillRepeatedly(Return(std::make_pair(true, "Basic spam:eggs")));
|
||||||
|
EXPECT_CALL(req, GetPeer())
|
||||||
|
.WillRepeatedly(Return(CService("127.0.0.1:1337")));
|
||||||
|
EXPECT_CALL(req, WriteHeader("WWW-Authenticate", "Basic realm=\"jsonrpc\""))
|
||||||
|
.Times(1);
|
||||||
|
EXPECT_CALL(req, WriteReply(HTTP_UNAUTHORIZED, ""))
|
||||||
|
.Times(1);
|
||||||
|
EXPECT_FALSE(HTTPReq_JSONRPC(&req, ""));
|
||||||
|
req.CleanUp();
|
||||||
|
}
|
|
@ -13,6 +13,9 @@
|
||||||
|
|
||||||
#include <boost/algorithm/string.hpp> // boost::trim
|
#include <boost/algorithm/string.hpp> // boost::trim
|
||||||
|
|
||||||
|
/** WWW-Authenticate to present with 401 Unauthorized response */
|
||||||
|
static const char* WWW_AUTH_HEADER_DATA = "Basic realm=\"jsonrpc\"";
|
||||||
|
|
||||||
/** Simple one-shot callback timer to be used by the RPC mechanism to e.g.
|
/** Simple one-shot callback timer to be used by the RPC mechanism to e.g.
|
||||||
* re-lock the wellet.
|
* re-lock the wellet.
|
||||||
*/
|
*/
|
||||||
|
@ -94,6 +97,7 @@ static bool HTTPReq_JSONRPC(HTTPRequest* req, const std::string &)
|
||||||
// Check authorization
|
// Check authorization
|
||||||
std::pair<bool, std::string> authHeader = req->GetHeader("authorization");
|
std::pair<bool, std::string> authHeader = req->GetHeader("authorization");
|
||||||
if (!authHeader.first) {
|
if (!authHeader.first) {
|
||||||
|
req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA);
|
||||||
req->WriteReply(HTTP_UNAUTHORIZED);
|
req->WriteReply(HTTP_UNAUTHORIZED);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -106,6 +110,7 @@ static bool HTTPReq_JSONRPC(HTTPRequest* req, const std::string &)
|
||||||
shouldn't have their RPC port exposed. */
|
shouldn't have their RPC port exposed. */
|
||||||
MilliSleep(250);
|
MilliSleep(250);
|
||||||
|
|
||||||
|
req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA);
|
||||||
req->WriteReply(HTTP_UNAUTHORIZED);
|
req->WriteReply(HTTP_UNAUTHORIZED);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,11 +56,14 @@ class HTTPRequest
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
struct evhttp_request* req;
|
struct evhttp_request* req;
|
||||||
|
|
||||||
|
// For test access
|
||||||
|
protected:
|
||||||
bool replySent;
|
bool replySent;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
HTTPRequest(struct evhttp_request* req);
|
HTTPRequest(struct evhttp_request* req);
|
||||||
~HTTPRequest();
|
virtual ~HTTPRequest();
|
||||||
|
|
||||||
enum RequestMethod {
|
enum RequestMethod {
|
||||||
UNKNOWN,
|
UNKNOWN,
|
||||||
|
@ -76,17 +79,17 @@ public:
|
||||||
|
|
||||||
/** Get CService (address:ip) for the origin of the http request.
|
/** Get CService (address:ip) for the origin of the http request.
|
||||||
*/
|
*/
|
||||||
CService GetPeer();
|
virtual CService GetPeer();
|
||||||
|
|
||||||
/** Get request method.
|
/** Get request method.
|
||||||
*/
|
*/
|
||||||
RequestMethod GetRequestMethod();
|
virtual RequestMethod GetRequestMethod();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the request header specified by hdr, or an empty string.
|
* Get the request header specified by hdr, or an empty string.
|
||||||
* Return an pair (isPresent,string).
|
* Return an pair (isPresent,string).
|
||||||
*/
|
*/
|
||||||
std::pair<bool, std::string> GetHeader(const std::string& hdr);
|
virtual std::pair<bool, std::string> GetHeader(const std::string& hdr);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read request body.
|
* Read request body.
|
||||||
|
@ -101,7 +104,7 @@ public:
|
||||||
*
|
*
|
||||||
* @note call this before calling WriteErrorReply or Reply.
|
* @note call this before calling WriteErrorReply or Reply.
|
||||||
*/
|
*/
|
||||||
void WriteHeader(const std::string& hdr, const std::string& value);
|
virtual void WriteHeader(const std::string& hdr, const std::string& value);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write HTTP reply.
|
* Write HTTP reply.
|
||||||
|
@ -111,7 +114,7 @@ public:
|
||||||
* @note Can be called only once. As this will give the request back to the
|
* @note Can be called only once. As this will give the request back to the
|
||||||
* main thread, do not call any other HTTPRequest methods after calling this.
|
* main thread, do not call any other HTTPRequest methods after calling this.
|
||||||
*/
|
*/
|
||||||
void WriteReply(int nStatus, const std::string& strReply = "");
|
virtual void WriteReply(int nStatus, const std::string& strReply = "");
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Event handler closure.
|
/** Event handler closure.
|
||||||
|
|
Loading…
Reference in New Issue