add digest filter in examples (#462)
* add digest filter in examples * Add getMd5() function to the public API Co-authored-by: Adrián Ortiz Gutiérrez <aortiz@MacBook-Pro.local> Co-authored-by: antao <antao2002@gmail.com>
This commit is contained in:
parent
bbef8780fd
commit
fd2a612945
|
@ -226,5 +226,9 @@
|
|||
}
|
||||
}],
|
||||
//custom_config: custom configuration for users. This object can be get by the app().getCustomConfig() method.
|
||||
"custom_config": {}
|
||||
"custom_config": {
|
||||
"realm" : "drogonRealm",
|
||||
"opaque" : "drogonOpaque",
|
||||
"credentials" : [ {"user" : "drogon", "password": "dr0g0n"} ]
|
||||
}
|
||||
}
|
|
@ -31,6 +31,7 @@ set(simple_example_sources
|
|||
simple_example/api_Attachment.cc
|
||||
simple_example/api_v1_ApiTest.cc
|
||||
simple_example/TimeFilter.cc
|
||||
simple_example/DigestAuthFilter.cc
|
||||
simple_example/main.cc)
|
||||
|
||||
add_executable(webapp ${simple_example_sources} ${VIEWSRC})
|
||||
|
|
|
@ -0,0 +1,215 @@
|
|||
#include "DigestAuthFilter.h"
|
||||
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <string>
|
||||
|
||||
std::string method2String(HttpMethod m)
|
||||
{
|
||||
switch (m)
|
||||
{
|
||||
case Get:
|
||||
return "GET";
|
||||
case Post:
|
||||
return "POST";
|
||||
case Head:
|
||||
return "HEAD";
|
||||
case Put:
|
||||
return "PUT";
|
||||
case Delete:
|
||||
return "DELETE";
|
||||
case Options:
|
||||
return "OPTIONS";
|
||||
default:
|
||||
return "INVALID";
|
||||
}
|
||||
}
|
||||
|
||||
std::string toLower(const std::string &in)
|
||||
{
|
||||
std::string out = in;
|
||||
std::transform(out.begin(), out.end(), out.begin(), [](unsigned char c) {
|
||||
return std::tolower(c);
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
bool DigestAuthFilter::isEndOfAttributeName(size_t pos,
|
||||
size_t len,
|
||||
const char *data)
|
||||
{
|
||||
if (pos >= len)
|
||||
return true;
|
||||
if (isspace(static_cast<unsigned char>(data[pos])))
|
||||
return true;
|
||||
// The reason for this complexity is that some attributes may contain
|
||||
// trailing equal signs (like base64 tokens in Negotiate auth headers)
|
||||
if ((pos + 1 < len) && (data[pos] == '=') &&
|
||||
!isspace(static_cast<unsigned char>(data[pos + 1])) &&
|
||||
(data[pos + 1] != '='))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void DigestAuthFilter::httpParseAttributes(const char *data,
|
||||
size_t len,
|
||||
HttpAttributeList &attributes)
|
||||
{
|
||||
size_t pos = 0;
|
||||
while (true)
|
||||
{
|
||||
// Skip leading whitespace
|
||||
while ((pos < len) && isspace(static_cast<unsigned char>(data[pos])))
|
||||
{
|
||||
++pos;
|
||||
}
|
||||
|
||||
// End of attributes?
|
||||
if (pos >= len)
|
||||
return;
|
||||
|
||||
// Find end of attribute name
|
||||
size_t start = pos;
|
||||
while (!isEndOfAttributeName(pos, len, data))
|
||||
{
|
||||
++pos;
|
||||
}
|
||||
|
||||
HttpAttribute attribute;
|
||||
attribute.first.assign(data + start, data + pos);
|
||||
|
||||
// Attribute has value?
|
||||
if ((pos < len) && (data[pos] == '='))
|
||||
{
|
||||
++pos; // Skip '='
|
||||
// Check if quoted value
|
||||
if ((pos < len) && (data[pos] == '"'))
|
||||
{
|
||||
while (++pos < len)
|
||||
{
|
||||
if (data[pos] == '"')
|
||||
{
|
||||
++pos;
|
||||
break;
|
||||
}
|
||||
if ((data[pos] == '\\') && (pos + 1 < len))
|
||||
++pos;
|
||||
attribute.second.append(1, data[pos]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while ((pos < len) &&
|
||||
!isspace(static_cast<unsigned char>(data[pos])) &&
|
||||
(data[pos] != ','))
|
||||
{
|
||||
attribute.second.append(1, data[pos++]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
attributes.push_back(attribute);
|
||||
if ((pos < len) && (data[pos] == ','))
|
||||
++pos; // Skip ','
|
||||
}
|
||||
}
|
||||
|
||||
bool DigestAuthFilter::httpHasAttribute(const HttpAttributeList &attributes,
|
||||
const std::string &name,
|
||||
std::string *value)
|
||||
{
|
||||
for (HttpAttributeList::const_iterator it = attributes.begin();
|
||||
it != attributes.end();
|
||||
++it)
|
||||
{
|
||||
if (it->first == name)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
*value = it->second;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
DigestAuthFilter::DigestAuthFilter(
|
||||
const std::map<std::string, std::string> &credentials,
|
||||
const std::string &realm,
|
||||
const std::string &opaque)
|
||||
: credentials(credentials), realm(realm), opaque(opaque)
|
||||
{
|
||||
}
|
||||
|
||||
void DigestAuthFilter::doFilter(const HttpRequestPtr &req,
|
||||
FilterCallback &&cb,
|
||||
FilterChainCallback &&ccb)
|
||||
{
|
||||
if (!req->session())
|
||||
{
|
||||
// no session support by framework,pls enable session
|
||||
auto resp = HttpResponse::newNotFoundResponse();
|
||||
cb(resp);
|
||||
return;
|
||||
}
|
||||
|
||||
auto auth_header = req->getHeader("Authorization");
|
||||
if (!auth_header.empty())
|
||||
{
|
||||
HttpAttributeList att_list;
|
||||
httpParseAttributes(auth_header.c_str(), auth_header.size(), att_list);
|
||||
std::string username, realm, nonce, uri, opaque, response;
|
||||
if (httpHasAttribute(att_list, "username", &username) &&
|
||||
httpHasAttribute(att_list, "realm", &realm) &&
|
||||
httpHasAttribute(att_list, "nonce", &nonce) &&
|
||||
httpHasAttribute(att_list, "uri", &uri) &&
|
||||
httpHasAttribute(att_list, "opaque", &opaque) &&
|
||||
httpHasAttribute(att_list, "response", &response))
|
||||
{
|
||||
if (credentials.find(username) != credentials.end())
|
||||
{
|
||||
std::string A1 =
|
||||
username + ":" + realm + ":" + credentials.at(username);
|
||||
std::string A2 = method2String(req->getMethod()) + ":" + uri;
|
||||
std::string A1_middle_A2 = toLower(utils::getMd5(A1)) + ":" +
|
||||
nonce + ":" +
|
||||
toLower(utils::getMd5(A2));
|
||||
std::string calculated_response =
|
||||
toLower(utils::getMd5(A1_middle_A2));
|
||||
if (response == calculated_response)
|
||||
{
|
||||
// Passed
|
||||
ccb();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_DEBUG << "invalid response " << response
|
||||
<< ", calculated " << calculated_response;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_DEBUG << "invalid username " << username;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_DEBUG << "missing attributes in WWW-Authenticate header"
|
||||
<< auth_header;
|
||||
}
|
||||
}
|
||||
// not Passed
|
||||
auto resp = HttpResponse::newHttpResponse();
|
||||
resp->setStatusCode(k401Unauthorized);
|
||||
resp->addHeader("WWW-Authenticate",
|
||||
" Digest realm=\"" + realm + "\", nonce=\"" +
|
||||
toLower(utils::getMd5(std::to_string(time(0)))) +
|
||||
"\", opaque=\"" + opaque + "\"");
|
||||
cb(resp);
|
||||
return;
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
#pragma once
|
||||
|
||||
#include <drogon/HttpFilter.h>
|
||||
using namespace drogon;
|
||||
|
||||
typedef std::pair<std::string, std::string> HttpAttribute;
|
||||
typedef std::vector<HttpAttribute> HttpAttributeList;
|
||||
typedef std::map<std::string /*username*/, std::string /*password*/>
|
||||
CredentialsMap;
|
||||
|
||||
class DigestAuthFilter : public drogon::HttpFilter<DigestAuthFilter, false>
|
||||
{
|
||||
const std::map<std::string, std::string> credentials;
|
||||
const std::string realm;
|
||||
const std::string opaque;
|
||||
|
||||
static bool isEndOfAttributeName(size_t pos, size_t len, const char *data);
|
||||
static void httpParseAttributes(const char *data,
|
||||
size_t len,
|
||||
HttpAttributeList &attributes);
|
||||
|
||||
static bool httpHasAttribute(const HttpAttributeList &attributes,
|
||||
const std::string &name,
|
||||
std::string *value);
|
||||
|
||||
public:
|
||||
explicit DigestAuthFilter(const CredentialsMap &credentials,
|
||||
const std::string &realm,
|
||||
const std::string &opaque);
|
||||
virtual void doFilter(const HttpRequestPtr &req,
|
||||
FilterCallback &&cb,
|
||||
FilterChainCallback &&ccb) override;
|
||||
};
|
|
@ -1,6 +1,7 @@
|
|||
|
||||
#include "CustomCtrl.h"
|
||||
#include "CustomHeaderFilter.h"
|
||||
#include "DigestAuthFilter.h"
|
||||
#include <drogon/drogon.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
@ -69,6 +70,22 @@ class B : public DrObjectBase
|
|||
callback(res);
|
||||
}
|
||||
};
|
||||
|
||||
class C : public drogon::HttpController<C>
|
||||
{
|
||||
public:
|
||||
METHOD_LIST_BEGIN
|
||||
ADD_METHOD_TO(C::priv, "/priv/resource", Get, "DigestAuthFilter");
|
||||
METHOD_LIST_END
|
||||
void priv(const HttpRequestPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback) const
|
||||
{
|
||||
auto resp = HttpResponse::newHttpResponse();
|
||||
resp->setBody("<P>private content, only for authenticated users</P>");
|
||||
callback(resp);
|
||||
}
|
||||
};
|
||||
|
||||
namespace api
|
||||
{
|
||||
namespace v1
|
||||
|
@ -192,6 +209,9 @@ int main()
|
|||
app().setDocumentRoot("./");
|
||||
app().enableSession(60);
|
||||
|
||||
std::map<std::string, std::string> config_credentials;
|
||||
std::string realm("drogonRealm");
|
||||
std::string opaque("drogonOpaque");
|
||||
// Load configuration
|
||||
app().loadConfigFile("config.example.json");
|
||||
auto &json = app().getCustomConfig();
|
||||
|
@ -199,6 +219,27 @@ int main()
|
|||
{
|
||||
std::cout << "empty custom config!" << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!json["realm"].empty())
|
||||
{
|
||||
realm = json["realm"].asString();
|
||||
}
|
||||
if (!json["opaque"].empty())
|
||||
{
|
||||
opaque = json["opaque"].asString();
|
||||
}
|
||||
for (auto &&i : json["credentials"])
|
||||
{
|
||||
config_credentials[i["user"].asString()] = i["password"].asString();
|
||||
}
|
||||
}
|
||||
|
||||
// Install Digest Authentication Filter using custom config credentials,
|
||||
// used by C HttpController (/C/priv/resource)
|
||||
auto auth_filter =
|
||||
std::make_shared<DigestAuthFilter>(config_credentials, realm, opaque);
|
||||
app().registerFilter(auth_filter);
|
||||
|
||||
// Install custom controller
|
||||
auto ctrlPtr = std::make_shared<CustomCtrl>("Hi");
|
||||
|
|
|
@ -97,6 +97,9 @@ inline std::string urlDecode(const string_view &szToDecode)
|
|||
std::string urlEncode(const std::string &);
|
||||
std::string urlEncodeComponent(const std::string &);
|
||||
|
||||
/// Get the MD5 digest of a string.
|
||||
std::string getMd5(const std::string &originalString);
|
||||
|
||||
/// Commpress or decompress data using gzip lib.
|
||||
/**
|
||||
* @param data the input data
|
||||
|
|
|
@ -18,11 +18,6 @@
|
|||
#include <drogon/MultiPart.h>
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include <drogon/config.h>
|
||||
#ifdef OpenSSL_FOUND
|
||||
#include <openssl/md5.h>
|
||||
#else
|
||||
#include "ssl_funcs/Md5.h"
|
||||
#endif
|
||||
#include <algorithm>
|
||||
#include <fcntl.h>
|
||||
#include <fstream>
|
||||
|
@ -226,14 +221,5 @@ int HttpFile::saveTo(const std::string &pathAndFilename) const
|
|||
}
|
||||
std::string HttpFile::getMd5() const
|
||||
{
|
||||
#ifdef OpenSSL_FOUND
|
||||
MD5_CTX c;
|
||||
unsigned char md5[16] = {0};
|
||||
MD5_Init(&c);
|
||||
MD5_Update(&c, fileContent_.c_str(), fileContent_.size());
|
||||
MD5_Final(md5, &c);
|
||||
return utils::binaryStringToHex(md5, 16);
|
||||
#else
|
||||
return Md5Encode::encode(fileContent_);
|
||||
#endif
|
||||
return utils::getMd5(fileContent_);
|
||||
}
|
||||
|
|
|
@ -14,6 +14,12 @@
|
|||
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <drogon/config.h>
|
||||
#ifdef OpenSSL_FOUND
|
||||
#include <openssl/md5.h>
|
||||
#else
|
||||
#include "ssl_funcs/Md5.h"
|
||||
#endif
|
||||
#ifdef USE_BROTLI
|
||||
#include <brotli/decode.h>
|
||||
#include <brotli/encode.h>
|
||||
|
@ -1138,5 +1144,19 @@ std::string brotliDecompress(const char *data, const size_t ndata)
|
|||
}
|
||||
#endif
|
||||
|
||||
std::string getMd5(const std::string &originalString)
|
||||
{
|
||||
#ifdef OpenSSL_FOUND
|
||||
MD5_CTX c;
|
||||
unsigned char md5[16] = {0};
|
||||
MD5_Init(&c);
|
||||
MD5_Update(&c, originalString.c_str(), originalString.size());
|
||||
MD5_Final(md5, &c);
|
||||
return utils::binaryStringToHex(md5, 16);
|
||||
#else
|
||||
return Md5Encode::encode(originalString);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace utils
|
||||
} // namespace drogon
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
add_executable(drogon_msgbuffer_unittest MsgBufferUnittest.cpp)
|
||||
add_executable(drobject_unittest DrObjectUnittest.cpp)
|
||||
add_executable(gzip_unittest GzipUnittest.cpp)
|
||||
add_executable(md5_unittest MD5Unittest.cpp ../lib/src/ssl_funcs/Md5.cc)
|
||||
add_executable(md5_unittest MD5Unittest.cpp)
|
||||
add_executable(sha1_unittest SHA1Unittest.cpp ../lib/src/ssl_funcs/Sha1.cc)
|
||||
add_executable(ostringstream_unittest OStringStreamUnitttest.cpp)
|
||||
add_executable(base64_unittest Base64Unittest.cpp)
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
#include "../lib/src/ssl_funcs/Md5.h"
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <string>
|
||||
|
||||
TEST(Md5Test, md5)
|
||||
{
|
||||
// EXPECT_EQ(Md5Encode::encode("123456789012345678901234567890123456789012345"
|
||||
// "678901234567890123456789012345678901234567890"
|
||||
// "1234567890"),
|
||||
// "49CB3608E2B33FAD6B65DF8CB8F49668");
|
||||
EXPECT_EQ(Md5Encode::encode("1"), "C4CA4238A0B923820DCC509A6F75849B");
|
||||
EXPECT_EQ(drogon::utils::getMd5(
|
||||
"123456789012345678901234567890123456789012345"
|
||||
"678901234567890123456789012345678901234567890"
|
||||
"1234567890"),
|
||||
"49CB3608E2B33FAD6B65DF8CB8F49668");
|
||||
EXPECT_EQ(drogon::utils::getMd5("1"), "C4CA4238A0B923820DCC509A6F75849B");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
|
|
Loading…
Reference in New Issue