1 /*!
2 * @file
3 * @brief The implementation of authentification_handler.
4 */
5
6 #include <arataga/acl_handler/handlers/http/basics.hpp>
7 #include <arataga/acl_handler/handlers/http/factories.hpp>
8 #include <arataga/acl_handler/handlers/http/helpers.hpp>
9 #include <arataga/acl_handler/handlers/http/responses.hpp>
10
11 #include <arataga/utils/overloaded.hpp>
12
13 #include <restinio/helpers/http_field_parsers/authorization.hpp>
14 #include <restinio/helpers/http_field_parsers/basic_auth.hpp>
15 #include <restinio/helpers/http_field_parsers/host.hpp>
16
17 namespace arataga::acl_handler
18 {
19
20 namespace handlers::http
21 {
22
23 //
24 // authentification_handler_t
25 //
26 /*!
27 * @brief Connection-handler that performs authentification.
28 */
29 class authentification_handler_t final : public basic_http_handler_t
30 {
31 //! The result of successful extraction of username/password
32 //! from header fields of HTTP-request.
33 struct username_password_t
34 {
35 std::string m_username;
36 std::string m_password;
37 };
38
39 //! The result for the case when username/password weren't set.
40 struct no_username_password_provided_t {};
41
42 //! The result for the case of an error during username/password
43 //! extraction.
44 struct username_password_extraction_failure_t
45 {
46 //! The description of the error.
47 std::string m_description;
48 };
49
50 //! The generic result of username/password extraction from a HTTP-request.
51 using username_password_extraction_result_t = std::variant<
52 username_password_t,
53 no_username_password_provided_t,
54 username_password_extraction_failure_t
55 >;
56
57 //! The result of successful extraction of host/port from a HTTP-request.
58 struct target_host_and_port_t
59 {
60 std::string m_host;
61 std::uint16_t m_port;
62 };
63
64 //! The result of a failed extraction of host/port from a HTTP-request.
65 struct target_host_and_port_extraction_failure_t
66 {
67 //! The description of the error.
68 std::string m_description;
69 };
70
71 //! Generic result of host/port extraction from a HTTP-request.
72 using target_host_and_port_extraction_result_t = std::variant<
73 target_host_and_port_t,
74 target_host_and_port_extraction_failure_t
75 >;
76
77 //! The result of successful transformation of request-target.
78 struct update_request_target_success_t {};
79
80 //! The result of failed transformation of request-target.
81 struct update_request_target_failure_t
82 {
83 //! The description of the error.
84 std::string m_description;
85 };
86
87 //! Generic result of request-target transformation.
88 using update_request_target_result_t = std::variant<
89 update_request_target_success_t,
90 update_request_target_failure_t
91 >;
92
93 //! The state of HTTP-request parsing.
94 http_handling_state_unique_ptr_t m_request_state;
95
96 //! Additional info for the HTTP-request.
97 /*!
98 * In the case of successful analisys of request-target and Host
99 * header field the actual target-host and target-port will be
100 * stored here.
101 */
102 request_info_t m_request_info;
103
104 //! The timepoint of the start of authentification.
105 std::chrono::steady_clock::time_point m_created_at;
106
107 public:
authentification_handler_t(handler_context_holder_t ctx,handler_context_t::connection_id_t id,asio::ip::tcp::socket connection,http_handling_state_unique_ptr_t request_state,request_info_t request_info)108 authentification_handler_t(
109 handler_context_holder_t ctx,
110 handler_context_t::connection_id_t id,
111 asio::ip::tcp::socket connection,
112 http_handling_state_unique_ptr_t request_state,
113 request_info_t request_info )
114 : basic_http_handler_t{ std::move(ctx), id, std::move(connection) }
115 , m_request_state{ std::move(request_state) }
116 , m_request_info{ std::move(request_info) }
117 , m_created_at{ std::chrono::steady_clock::now() }
118 {
119 }
120
121 protected:
122 void
on_start_impl(delete_protector_t delete_protector)123 on_start_impl( delete_protector_t delete_protector ) override
124 {
125 wrap_action_and_handle_exceptions(
126 delete_protector,
127 [this]( delete_protector_t delete_protector, can_throw_t can_throw )
128 {
129 // If username/password are set, they have to be extracted.
130 auto username_password_extraction_result =
131 try_extract_username_and_password( can_throw );
132 // There is no sense to continue in the case of an error.
133 if( auto * err = std::get_if<username_password_extraction_failure_t>(
134 &username_password_extraction_result); err )
135 {
136 ::arataga::logging::wrap_logging(
137 proxy_logging_mode,
138 spdlog::level::err,
139 [this, can_throw, err]( auto level )
140 {
141 log_message_for_connection(
142 can_throw,
143 level,
144 fmt::format(
145 "username/password extraction failure: {}",
146 err->m_description ) );
147 } );
148
149 send_negative_response_then_close_connection(
150 delete_protector,
151 can_throw,
152 remove_reason_t::protocol_error,
153 response_bad_request_auth_params_extraction_failure );
154
155 return;
156 }
157
158 // Detect the target host and port.
159 auto target_host_and_port_extraction_result =
160 try_extract_target_host_and_port( can_throw );
161 if( auto * err = std::get_if<target_host_and_port_extraction_failure_t>(
162 &target_host_and_port_extraction_result); err )
163 {
164 ::arataga::logging::wrap_logging(
165 proxy_logging_mode,
166 spdlog::level::err,
167 [this, can_throw, err]( auto level )
168 {
169 log_message_for_connection(
170 can_throw,
171 level,
172 fmt::format(
173 "target-host+port extraction failure: {}",
174 err->m_description ) );
175 } );
176
177 send_negative_response_then_close_connection(
178 delete_protector,
179 can_throw,
180 remove_reason_t::protocol_error,
181 response_bad_request_target_host_extraction_failure );
182
183 return;
184 }
185
186 // If request-target is in absolute-form it should be
187 // transformed into origin-form.
188 auto update_request_target_result =
189 try_update_request_target( can_throw );
190 if( auto * err = std::get_if<update_request_target_failure_t>(
191 &update_request_target_result); err )
192 {
193 ::arataga::logging::wrap_logging(
194 proxy_logging_mode,
195 spdlog::level::err,
196 [this, can_throw, err]( auto level )
197 {
198 log_message_for_connection(
199 can_throw,
200 level,
201 fmt::format(
202 "update request-target failure: {}",
203 err->m_description ) );
204 } );
205
206 send_negative_response_then_close_connection(
207 delete_protector,
208 can_throw,
209 remove_reason_t::protocol_error,
210 response_bad_request_invalid_request_target );
211
212 return;
213 }
214
215
216 // Now we can initiate the authentification.
217 initiate_authentification(
218 can_throw,
219 username_password_extraction_result,
220 target_host_and_port_extraction_result );
221 } );
222 }
223
224 void
on_timer_impl(delete_protector_t delete_protector)225 on_timer_impl( delete_protector_t delete_protector ) override
226 {
227 if( std::chrono::steady_clock::now() >= m_created_at +
228 context().config().authentification_timeout() )
229 {
230 wrap_action_and_handle_exceptions(
231 delete_protector,
232 [this]( delete_protector_t delete_protector, can_throw_t can_throw )
233 {
234 ::arataga::logging::wrap_logging(
235 proxy_logging_mode,
236 spdlog::level::warn,
237 [this, can_throw]( auto level )
238 {
239 log_message_for_connection(
240 can_throw,
241 level,
242 "authentification timed out" );
243 } );
244
245 // We can only send the response and close the connection.
246 send_negative_response_then_close_connection(
247 delete_protector,
248 can_throw,
249 remove_reason_t::current_operation_timed_out,
250 response_proxy_auth_required_auth_timeout );
251 } );
252 }
253 }
254
255 public:
256 arataga::utils::string_literal_t
name() const257 name() const noexcept override
258 {
259 using namespace arataga::utils::string_literals;
260 return "http-authenitification-handler"_static_str;
261 }
262
263 private:
264 [[nodiscard]]
265 username_password_extraction_result_t
try_extract_username_and_password(can_throw_t)266 try_extract_username_and_password(
267 can_throw_t /*can_throw*/ )
268 {
269 auto opt_proxy_auth_value = m_request_info.m_headers.opt_value_of(
270 restinio::http_field_t::proxy_authorization );
271 if( !opt_proxy_auth_value )
272 return no_username_password_provided_t{};
273
274 using namespace restinio::http_field_parsers;
275 const auto auth_value_result = authorization_value_t::try_parse(
276 *opt_proxy_auth_value );
277 if( !auth_value_result )
278 return username_password_extraction_failure_t{
279 make_error_description(
280 auth_value_result.error(),
281 *opt_proxy_auth_value )
282 };
283
284 auto & auth_value = *auth_value_result;
285 if( "basic" != auth_value.auth_scheme )
286 return username_password_extraction_failure_t{
287 fmt::format( "unsupported auth-scheme: {}",
288 auth_value.auth_scheme )
289 };
290
291 auto basic_auth_result = basic_auth::try_extract_params(
292 auth_value );
293 if( !basic_auth_result )
294 return username_password_extraction_failure_t{
295 fmt::format( "basic-auth param extraction failed: {}",
296 static_cast<int>(basic_auth_result.error()) )
297 };
298
299 // The Proxy-Authorization header field isn't needed anymore
300 // and should be removed.
301 m_request_info.m_headers.remove_all_of(
302 restinio::http_field_t::proxy_authorization );
303
304 auto & basic_auth = *basic_auth_result;
305 return username_password_t{
306 std::move(basic_auth.username),
307 std::move(basic_auth.password)
308 };
309 }
310
311 [[nodiscard]]
312 target_host_and_port_extraction_result_t
try_extract_target_host_and_port(can_throw_t can_throw)313 try_extract_target_host_and_port( can_throw_t can_throw )
314 {
315 auto extraction_result =
316 try_extract_target_host_and_port_from_request_target( can_throw );
317
318 if( std::holds_alternative<target_host_and_port_extraction_failure_t>(
319 extraction_result ) )
320 {
321 extraction_result =
322 try_extract_target_host_and_port_from_host_field( can_throw );
323 }
324
325 // The Host header field should be removed after the extraction.
326 m_request_info.m_headers.remove_all_of( restinio::http_field_t::host );
327
328 return extraction_result;
329 }
330
331 [[nodiscard]]
332 target_host_and_port_extraction_result_t
try_extract_target_host_and_port_from_request_target(can_throw_t)333 try_extract_target_host_and_port_from_request_target(
334 can_throw_t /*can_throw*/ )
335 {
336 // Try to deconstruct the URL.
337 http_parser_url parser_url;
338 http_parser_url_init( &parser_url );
339
340 const auto & value_to_process = m_request_info.m_request_target;
341
342 const auto parse_url_result = http_parser_parse_url(
343 value_to_process.data(),
344 value_to_process.size(),
345 HTTP_CONNECT == m_request_state->m_parser.method,
346 &parser_url );
347 if( parse_url_result )
348 return target_host_and_port_extraction_failure_t{
349 fmt::format( "unable to parse request-target, "
350 "http_parser_parse_url result: {}",
351 parse_url_result )
352 };
353
354 const auto is_component_present = [&]( unsigned int component ) -> bool
355 {
356 return parser_url.field_set & (1u << component);
357 };
358
359 const auto try_extract_url_component =
360 [&]( unsigned int component ) -> std::string_view
361 {
362 std::string_view result;
363 if( is_component_present(component) )
364 result = std::string_view{
365 value_to_process.data() +
366 parser_url.field_data[component].off,
367 parser_url.field_data[component].len
368 };
369 return result;
370 };
371
372 const auto schema_sv = try_extract_url_component( UF_SCHEMA );
373 const auto host_sv = try_extract_url_component( UF_HOST );
374
375 std::optional< std::uint16_t > opt_port{ 80u };
376 if( is_component_present( UF_PORT ) )
377 opt_port = parser_url.port;
378
379 if( !host_sv.empty() && opt_port )
380 // We already have the result.
381 return target_host_and_port_t{
382 std::string{ host_sv },
383 *opt_port
384 };
385
386 // If there are 'schema' and 'host' then the port number can be detected.
387 if( !schema_sv.empty() && !host_sv.empty() )
388 {
389 if( "http" == schema_sv )
390 return target_host_and_port_t{
391 std::string{ host_sv },
392 80u
393 };
394 else if( "https" == schema_sv )
395 return target_host_and_port_t{
396 std::string{ host_sv },
397 443u
398 };
399 else
400 // Unsupported scheme found.
401 return target_host_and_port_extraction_failure_t{
402 fmt::format( "unsupported schema in request-target: {}",
403 schema_sv )
404 };
405 }
406
407 // target-host and port are not extracted.
408 return target_host_and_port_extraction_failure_t{
409 fmt::format( "no target-host and port in request-target" )
410 };
411 }
412
413 [[nodiscard]]
414 target_host_and_port_extraction_result_t
try_extract_target_host_and_port_from_host_field(can_throw_t)415 try_extract_target_host_and_port_from_host_field(
416 can_throw_t /*can_throw*/ )
417 {
418 // If there are more than one Host header fields then the request
419 // should be rejected. So count the fields.
420 std::optional< std::string_view > opt_host;
421 std::size_t host_occurrences{ 0u };
422
423 m_request_info.m_headers.for_each_value_of(
424 restinio::http_field_t::host,
425 [&]( std::string_view value )
426 {
427 ++host_occurrences;
428 if( 1u == host_occurrences )
429 {
430 opt_host = value;
431 }
432
433 return restinio::http_header_fields_t::continue_enumeration();
434 } );
435
436 if( 0u == host_occurrences )
437 return target_host_and_port_extraction_failure_t{
438 "no Host http-field"
439 };
440 else if( 1u != host_occurrences )
441 return target_host_and_port_extraction_failure_t{
442 fmt::format( "too many Host http-fields: {}",
443 host_occurrences )
444 };
445
446 // We have to parse the value.
447 // The parser from RESTinio is used because http_parser_parse_url
448 // can't handle values like "localhost:9090".
449 using namespace restinio::http_field_parsers;
450
451 auto parse_result = raw_host_value_t::try_parse( opt_host.value() );
452 if( !parse_result )
453 return target_host_and_port_extraction_failure_t{
454 fmt::format( "unable to parse Host http-field: {}",
455 make_error_description(
456 parse_result.error(),
457 opt_host.value() ) )
458 };
459
460 std::string target_host = std::visit(
461 ::arataga::utils::overloaded{
462 []( raw_host_value_t::reg_name_t & n ) -> std::string
463 {
464 return std::move(n.v);
465 },
466 []( raw_host_value_t::ipv4_address_t & n ) -> std::string
467 {
468 return std::move(n.v);
469 },
470 []( raw_host_value_t::ipv6_address_t & n ) -> std::string
471 {
472 return std::move(n.v);
473 }
474 },
475 parse_result->host );
476
477 return target_host_and_port_t{
478 std::move(target_host),
479 parse_result->port ? *(parse_result->port) : std::uint16_t{80u}
480 };
481 }
482
483 [[nodiscard]]
484 update_request_target_result_t
try_update_request_target(can_throw_t)485 try_update_request_target(
486 can_throw_t /*can_throw*/ )
487 {
488 // The value of request-target should be borrowed into
489 // a separate object because we need a reference to that value
490 // during the construction of new m_request_target value.
491 std::string value_to_process{
492 std::move(m_request_info.m_request_target)
493 };
494
495 // Try to deconstruct URL.
496 http_parser_url parser_url;
497 http_parser_url_init( &parser_url );
498
499 const auto parse_url_result = http_parser_parse_url(
500 value_to_process.data(),
501 value_to_process.size(),
502 HTTP_CONNECT == m_request_state->m_parser.method,
503 &parser_url );
504 if( parse_url_result )
505 return update_request_target_failure_t{
506 fmt::format( "unable to parse request-target, "
507 "http_parser_parse_url result: {}",
508 parse_url_result )
509 };
510
511 const auto is_component_present = [&]( unsigned int component ) -> bool
512 {
513 return parser_url.field_set & (1u << component);
514 };
515
516 const auto try_extract_url_component =
517 [&]( unsigned int component ) -> std::string_view
518 {
519 std::string_view result;
520 if( is_component_present(component) )
521 result = std::string_view{
522 value_to_process.data() +
523 parser_url.field_data[component].off,
524 parser_url.field_data[component].len
525 };
526 return result;
527 };
528
529 const auto path = try_extract_url_component( UF_PATH );
530 const auto query = try_extract_url_component( UF_QUERY );
531 const auto fragment = try_extract_url_component( UF_FRAGMENT );
532
533 m_request_info.m_request_target.clear();
534
535 // If request-target is specified in authority-form then 'path'
536 // could be empty after the parsing.
537 if( !path.empty() )
538 m_request_info.m_request_target.append( path.data(), path.size() );
539 else
540 m_request_info.m_request_target += '/';
541
542 if( !query.empty() )
543 {
544 m_request_info.m_request_target += '?';
545 m_request_info.m_request_target.append( query.data(), query.size() );
546 }
547
548 if( !fragment.empty() )
549 {
550 m_request_info.m_request_target += '#';
551 m_request_info.m_request_target.append( fragment.data(), fragment.size() );
552 }
553
554 return update_request_target_success_t{};
555 }
556
557 void
initiate_authentification(can_throw_t,username_password_extraction_result_t & username_password_info,target_host_and_port_extraction_result_t & target_host_and_port_info)558 initiate_authentification(
559 can_throw_t /*can_throw*/,
560 username_password_extraction_result_t & username_password_info,
561 target_host_and_port_extraction_result_t & target_host_and_port_info )
562 {
563 std::optional< std::string > username;
564 std::optional< std::string > password;
565 if( auto * upi = std::get_if<username_password_t>(
566 &username_password_info ) )
567 {
568 username = std::move(upi->m_username);
569 password = std::move(upi->m_password);
570 }
571
572 // Info about target-host and target-port should be stored into
573 // request_info because it'll necessary later.
574 {
575 auto & host_port =
576 std::get< target_host_and_port_t >( target_host_and_port_info );
577 m_request_info.m_target_host = std::move(host_port.m_host);
578 m_request_info.m_target_port = host_port.m_port;
579 }
580
581 context().async_authentificate(
582 m_id,
583 authentification::request_params_t {
584 // We work with IPv4 addresses only so don't expect
585 // something else.
586 m_connection.remote_endpoint().address().to_v4(),
587 std::move(username),
588 std::move(password),
589 m_request_info.m_target_host,
590 m_request_info.m_target_port
591 },
592 with<authentification::result_t>().make_handler(
593 [this](
594 delete_protector_t delete_protector,
595 can_throw_t can_throw,
596 authentification::result_t result )
597 {
598 on_authentification_result(
599 delete_protector, can_throw, result );
600 } )
601 );
602 }
603
604 void
on_authentification_result(delete_protector_t delete_protector,can_throw_t can_throw,authentification::result_t & result)605 on_authentification_result(
606 delete_protector_t delete_protector,
607 can_throw_t can_throw,
608 authentification::result_t & result )
609 {
610 std::visit( ::arataga::utils::overloaded{
611 [&]( authentification::success_t & info )
612 {
613 replace_handler(
614 delete_protector,
615 can_throw,
616 [this, &info]( can_throw_t )
617 {
618 return make_dns_lookup_handler(
619 std::move(m_ctx),
620 m_id,
621 std::move(m_connection),
622 std::move(m_request_state),
623 std::move(m_request_info),
624 std::move(info.m_traffic_limiter) );
625 } );
626 },
627 [&]( const authentification::failure_t & info )
628 {
629 ::arataga::logging::wrap_logging(
630 proxy_logging_mode,
631 spdlog::level::warn,
632 [this, can_throw, &info]( auto level )
633 {
634 log_message_for_connection(
635 can_throw,
636 level,
637 fmt::format(
638 "user is not authentificated, reason: {}",
639 authentification::to_string_literal(
640 info.m_reason ) ) );
641 } );
642
643 send_negative_response_then_close_connection(
644 delete_protector,
645 can_throw,
646 remove_reason_t::access_denied,
647 response_proxy_auth_required_not_authorized );
648 }
649 },
650 result );
651 }
652 };
653
654 //
655 // make_authentification_handler
656 //
657 [[nodiscard]]
658 connection_handler_shptr_t
make_authentification_handler(handler_context_holder_t ctx,handler_context_t::connection_id_t id,asio::ip::tcp::socket connection,http_handling_state_unique_ptr_t request_state,request_info_t request_info)659 make_authentification_handler(
660 handler_context_holder_t ctx,
661 handler_context_t::connection_id_t id,
662 asio::ip::tcp::socket connection,
663 http_handling_state_unique_ptr_t request_state,
664 request_info_t request_info )
665 {
666 return std::make_shared< authentification_handler_t >(
667 std::move(ctx),
668 id,
669 std::move(connection),
670 std::move(request_state),
671 std::move(request_info) );
672 }
673
674 } /* namespace arataga::acl_handler */
675
676 } /* namespace handlers::http */
677
678