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:
adrian 2020-06-07 08:43:05 +02:00 committed by GitHub
parent bbef8780fd
commit fd2a612945
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 327 additions and 23 deletions

View File

@ -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"} ]
}
}

View File

@ -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})

View File

@ -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;
}

View File

@ -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;
};

View File

@ -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");

View File

@ -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

View File

@ -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_);
}

View File

@ -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

View File

@ -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)

View File

@ -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)