30 #include "gtest/gtest.h" 33 #include <boost/algorithm/string/predicate.hpp> 34 #include <boost/algorithm/string/join.hpp> 35 #include <boost/fusion/adapted/std_pair.hpp> 36 #include <boost/range/algorithm/find_if.hpp> 37 #include <boost/range/iterator_range_core.hpp> 38 #include <boost/spirit/include/karma_char.hpp> 39 #include <boost/spirit/include/karma_list.hpp> 40 #include <boost/spirit/include/karma_generate.hpp> 41 #include <boost/spirit/include/karma_right_alignment.hpp> 42 #include <boost/spirit/include/karma_sequence.hpp> 43 #include <boost/spirit/include/karma_string.hpp> 44 #include <boost/spirit/include/karma_uint.hpp> 45 #include <boost/spirit/include/qi_alternative.hpp> 46 #include <boost/spirit/include/qi_char.hpp> 47 #include <boost/spirit/include/qi_char_class.hpp> 48 #include <boost/spirit/include/qi_difference.hpp> 49 #include <boost/spirit/include/qi_eoi.hpp> 50 #include <boost/spirit/include/qi_list.hpp> 51 #include <boost/spirit/include/qi_parse.hpp> 52 #include <boost/spirit/include/qi_plus.hpp> 53 #include <boost/spirit/include/qi_sequence.hpp> 54 #include <boost/spirit/include/qi_string.hpp> 58 #include <unordered_map> 68 using fields = std::unordered_map<std::string, std::string>;
69 using auth_responses = std::vector<fields>;
71 void rng(
size_t len,
uint8_t *ptr)
78 str.insert(str.begin(),
'"');
83 void write_fields(
std::string& out,
const fields& args)
85 namespace karma = boost::spirit::karma;
87 std::back_inserter(out),
95 write_fields(out, args);
102 write_fields(out, args);
106 request.m_header_info.m_etc_fields.push_back(
115 for (
const auto&
choice : choices)
118 write_fields(out,
choice);
120 response.m_header_info.m_etc_fields.push_back(
121 std::make_pair(
u8"WWW-authenticate",
std::move(out))
127 bool has_same_fields(
const auth_responses& in)
129 const std::vector<std::string> check{
u8"nonce",
u8"qop",
u8"realm",
u8"stale"};
131 auto current =
in.begin();
132 const auto end =
in.end();
137 for ( ; current != end; ++current )
139 for (
const auto& field : check)
144 if (expected != actual)
156 return response.m_response_code == 401 &&
157 response.m_response_comment ==
u8"Unauthorized" &&
163 namespace qi = boost::spirit::qi;
166 const bool rc = qi::parse(
168 qi::lit(
u8"Digest ") >> ((
171 (qi::lit(
'"') >> +(qi::ascii::char_ -
'"') >> qi::lit(
'"')) |
172 +(qi::ascii::graph - qi::ascii::char_(
u8"()<>@,;:\\\"/[]?={}"))
179 throw std::runtime_error{
"Bad field given in HTTP header"};
186 auth_responses result{};
188 const auto end =
response.m_additional_fields.end();
189 for (
auto current =
response.m_additional_fields.begin();; ++current)
191 current = std::find_if(current, end, [] (
const std::pair<std::string, std::string>& field) {
192 return boost::equals(
u8"WWW-authenticate", field.first);
198 result.push_back(parse_fields(current->second));
206 md5::MD5Init(std::addressof(ctx));
209 reinterpret_cast<const std::uint8_t*>(
in.data()),
213 std::array<std::uint8_t, 16> digest{{}};
214 md5::MD5Final(digest.data(), std::addressof(ctx));
228 return get_a1(user, responses.at(0));
235 std::vector<std::string>{md5_hex(get_a1(user, responses)), nonce, cnonce},
u8":" 241 return boost::join(std::vector<std::string>{
"NOP", uri},
u8":");
246 namespace karma = boost::spirit::karma;
249 std::back_inserter(out),
250 karma::right_align(8,
'0')[karma::uint_generator<std::uint32_t, 16>{}],
258 TEST(HTTP_Server_Auth, NotRequired)
264 TEST(HTTP_Server_Auth, MissingAuth)
275 TEST(HTTP_Server_Auth, BadSyntax)
278 EXPECT_TRUE(
bool(auth.get_response(make_request({{
u8"algorithm",
"fo\xFF"}}))));
279 EXPECT_TRUE(
bool(auth.get_response(make_request({{
u8"cnonce",
"\"000\xFF\""}}))));
280 EXPECT_TRUE(
bool(auth.get_response(make_request({{
u8"cnonce \xFF =",
"\"000\xFF\""}}))));
281 EXPECT_TRUE(
bool(auth.get_response(make_request({{
u8" \xFF cnonce",
"\"000\xFF\""}}))));
289 const auto response = auth.get_response(make_request(fields{}));
293 const auto fields = parse_response(*
response);
306 boost::join(std::vector<std::string>{md5_hex(a1), nonce, md5_hex(a2)},
u8":")
309 const auto request = make_request({
310 {
u8"nonce", quoted(nonce)},
311 {
u8"realm", quoted(fields[0].at(
u8"realm"))},
312 {
u8"response", quoted(auth_code)},
313 {
u8"uri", quoted(uri)},
319 const auto response2 = auth.get_response(request);
323 const auto fields2 = parse_response(*response2);
331 TEST(HTTP_Server_Auth, MD5_sess)
333 constexpr
const char cnonce[] =
"not a good cnonce";
338 const auto response = auth.get_response(make_request(fields{}));
342 const auto fields = parse_response(*
response);
351 const std::string a1 = get_a1_sess(user, cnonce, fields);
355 boost::join(std::vector<std::string>{md5_hex(a1), nonce, md5_hex(a2)},
u8":")
358 const auto request = make_request({
359 {
u8"algorithm",
u8"md5-sess"},
360 {
u8"cnonce", quoted(cnonce)},
361 {
u8"nonce", quoted(nonce)},
362 {
u8"realm", quoted(fields[0].at(
u8"realm"))},
363 {
u8"response", quoted(auth_code)},
364 {
u8"uri", quoted(uri)},
370 const auto response2 = auth.get_response(request);
374 const auto fields2 = parse_response(*response2);
382 TEST(HTTP_Server_Auth, MD5_auth)
384 constexpr
const char cnonce[] =
"not a nonce";
385 constexpr
const char qop[] =
"auth";
390 const auto response = auth.get_response(make_request(fields{}));
394 const auto parsed = parse_response(*
response);
407 const auto generate_auth = [&] {
410 std::vector<std::string>{md5_hex(a1), nonce, nc, cnonce, qop, md5_hex(a2)},
u8":" 416 {
u8"algorithm", quoted(
u8"md5")},
417 {
u8"cnonce", quoted(cnonce)},
419 {
u8"nonce", quoted(nonce)},
420 {
u8"qop", quoted(qop)},
421 {
u8"realm", quoted(parsed[0].at(
u8"realm"))},
422 {
u8"response", quoted(generate_auth())},
423 {
u8"uri", quoted(uri)},
427 const auto request = make_request(args);
430 for (
unsigned i = 2; i < 20; ++i)
433 args.at(
u8"nc") = nc;
434 args.at(
u8"response") = quoted(generate_auth());
438 const auto replay = auth.get_response(request);
442 const auto parsed_replay = parse_response(*replay);
450 TEST(HTTP_Server_Auth, MD5_sess_auth)
452 constexpr
const char cnonce[] =
"not a nonce";
453 constexpr
const char qop[] =
"auth";
458 const auto response = auth.get_response(make_request(fields{}));
462 const auto parsed = parse_response(*
response);
471 const std::string a1 = get_a1_sess(user, cnonce, parsed);
475 const auto generate_auth = [&] {
478 std::vector<std::string>{md5_hex(a1), nonce, nc, cnonce, qop, md5_hex(a2)},
u8":" 484 {
u8"algorithm",
u8"md5-sess"},
485 {
u8"cnonce", quoted(cnonce)},
487 {
u8"nonce", quoted(nonce)},
489 {
u8"realm", quoted(parsed[0].at(
u8"realm"))},
490 {
u8"response", quoted(generate_auth())},
491 {
u8"uri", quoted(uri)},
495 const auto request = make_request(args);
498 for (
unsigned i = 2; i < 20; ++i)
501 args.at(
u8"nc") = nc;
502 args.at(
u8"response") = quoted(generate_auth());
506 const auto replay = auth.get_response(request);
510 const auto parsed_replay = parse_response(*replay);
531 const http::login user{
"some_user",
"ultimate password"};
538 request.
m_URI =
"/FOO";
540 auto response = server.get_response(request);
550 for (
unsigned i = 0; i < 1000; ++i)
564 auto response2 = server.get_response(request);
567 EXPECT_TRUE(response2->m_header_info.m_etc_fields.empty());
568 response2->m_header_info.m_etc_fields = response2->m_additional_fields;
570 const auth_responses parsed1 = parse_response(*
response);
571 const auth_responses parsed2 = parse_response(*response2);
574 EXPECT_NE(parsed1[0].at(
u8"nonce"), parsed2[0].at(
u8"nonce"));
586 TEST(HTTP_Client_Auth, Unavailable)
590 EXPECT_FALSE(
bool(auth.get_auth_field(
"GET",
"/file")));
593 TEST(HTTP_Client_Auth, MissingAuthenticate)
597 EXPECT_FALSE(
bool(auth.get_auth_field(
"POST",
"/\xFFname")));
600 response.m_additional_fields.push_back({
"\xFF",
"\xFF"});
603 EXPECT_FALSE(
bool(auth.get_auth_field(
"DELETE",
"/file/does/not/exist")));
606 TEST(HTTP_Client_Auth, BadSyntax)
618 constexpr
char method[] =
"NOP";
619 constexpr
char nonce[] =
"some crazy nonce";
620 constexpr
char realm[] =
"the only realm";
621 constexpr
char uri[] =
"/some_file";
628 {
u8"domain", quoted(
"ignored")},
629 {
u8"nonce", quoted(nonce)},
630 {
u8"REALM", quoted(realm)}
633 {
u8"algorithm",
"null"},
634 {
u8"domain", quoted(
"ignored")},
641 const auto auth_field = auth.get_auth_field(method, uri);
644 const auto parsed = parse_fields(auth_field->second);
658 boost::join(std::vector<std::string>{md5_hex(a1), nonce, md5_hex(a2)},
u8":")
660 EXPECT_TRUE(boost::iequals(auth_code, parsed.at(
u8"response")));
662 const auto auth_field_dup = auth.get_auth_field(method, uri);
669 response.m_header_info.m_etc_fields.front().second.append(
u8"," + write_fields({{
u8"stale",
u8"TRUE"}}));
673 TEST(HTTP_Client_Auth, MD5_auth)
675 constexpr
char cnonce[] =
"";
676 constexpr
char method[] =
"NOP";
677 constexpr
char nonce[] =
"some crazy nonce";
678 constexpr
char opaque[] =
"this is the opaque";
679 constexpr
char qop[] =
u8"ignore,auth,ignore";
680 constexpr
char realm[] =
"the only realm";
681 constexpr
char uri[] =
"/some_file";
688 {
u8"algorithm",
u8"MD5"},
689 {
u8"domain", quoted(
"ignored")},
692 {
u8"qop", quoted(
"some,thing,to,ignore")}
695 {
u8"algorIthm", quoted(
u8"md5")},
696 {
u8"domain", quoted(
"ignored")},
697 {
u8"noNce", quoted(nonce)},
698 {
u8"opaque", quoted(opaque)},
699 {
u8"realm", quoted(realm)},
700 {
u8"QoP", quoted(qop)}
706 for (
unsigned i = 1; i < 1000; ++i)
710 const auto auth_field = auth.get_auth_field(method, uri);
713 const auto parsed = parse_fields(auth_field->second);
727 boost::join(std::vector<std::string>{md5_hex(a1), nonce, nc, cnonce,
u8"auth", md5_hex(a2)},
u8":")
729 EXPECT_TRUE(boost::iequals(auth_code, parsed.at(
u8"response")));
733 response.m_header_info.m_etc_fields.back().second.append(
u8"," + write_fields({{
u8"stale",
u8"trUe"}}));
741 epee::net_utils::http::add_field(str,
"foo",
"bar");
743 epee::net_utils::http::add_field(str, {
"moarbars",
"moarfoo"});
745 EXPECT_STREQ(
"leading textfoo: bar\r\nbar: foo\r\nmoarbars: moarfoo\r\n", str.c_str());
size_t size() const noexcept
#define EXPECT_TRUE(condition)
#define EXPECT_STREQ(s1, s2)
http_header_info m_header_info
Implements RFC 2617 digest auth. Digests from RFC 7616 can be added.
epee::misc_utils::struct_init< response_t > response
mdb_size_t count(MDB_cursor *cur)
void rand(size_t N, uint8_t *bytes)
TEST(HTTP_Server_Auth, NotRequired)
std::string m_http_method_str
#define EXPECT_NE(val1, val2)
const T & move(const T &t)
const GenericPointer< typename T::ValueType > T2 value
#define ASSERT_TRUE(condition)
#define EXPECT_FALSE(condition)
std::string to_string(t_connection_type type)
#define ASSERT_LE(val1, val2)
const char * data() const noexcept
Implements RFC 2617 digest auth. Digests from RFC 7616 can be added.
#define EXPECT_EQ(val1, val2)