Support for uncontactable authenticator 83/309883/7
authorKrzysztof Jackiewicz <k.jackiewicz@samsung.com>
Wed, 17 Apr 2024 07:29:00 +0000 (09:29 +0200)
committerKrzysztof Małysa <k.malysa@samsung.com>
Mon, 29 Jul 2024 14:50:54 +0000 (14:50 +0000)
Change-Id: Idaed2c684dbfcefbde2ddbde62e612d2fdf59b51

srcs/exception.h
srcs/tunnel.cpp
srcs/tunnel.h
srcs/websockets.cpp
srcs/websockets.h
tests/tunnel/auto_tests.cpp
tests/tunnel/auto_tests.h
tests/tunnel/common_tests.cpp
tests/tunnel/constants.cpp
tests/tunnel/constants.h
tests/tunnel/manual_tests.py

index 779044eea6842cdee8249d0f864d25bb2101806e..071a376af39f4398dc0a3a41db78dadd139b9270 100644 (file)
@@ -56,6 +56,7 @@ using AccessDenied = Exception<WAUTHN_ERROR_ACCESS_DENIED>;
 using MemoryError = Exception<WAUTHN_ERROR_OUT_OF_MEMORY>;
 using Cancelled = Exception<WAUTHN_ERROR_CANCELED>;
 using Timeout = Exception<WAUTHN_ERROR_TIMED_OUT>;
+using Uncontactable = Exception<WAUTHN_ERROR_CONNECTION_REFUSED>;
 
 } // namespace Exception
 
@@ -68,5 +69,7 @@ using Timeout = Exception<WAUTHN_ERROR_TIMED_OUT>;
 #define THROW_ENCODING(...) ERROR_LOGGED_THROW(::Exception::EncodingFailed, __VA_ARGS__)
 #define THROW_MEMORY() ERROR_LOGGED_THROW(::Exception::MemoryError, "Memory error")
 #define THROW_TIMEOUT(...) ERROR_LOGGED_THROW(::Exception::Timeout, __VA_ARGS__)
+#define THROW_UNCONTACTABLE() \
+    ERROR_LOGGED_THROW(::Exception::Uncontactable, "Authenticator is uncontactable")
 
 #define THROW_CANCELLED() DEBUG_LOGGED_THROW(::Exception::Cancelled, "Operation cancelled")
index 52f965df018337dc996fcf6cfd30ef289971505d..0955c4a9c2eba15522f9ce9aeb877ffe076da914 100644 (file)
@@ -123,6 +123,7 @@ void Tunnel::Connect(const std::string &url, std::optional<ExtraHttpHeader> extr
             THROW_UNKNOWN("Creating libwebsocket context failed");
     }
 
+    m_uncontactable = false;
     m_connection = m_ws->ClientConnect(m_context, url);
     if (!m_connection)
         DisconnectOnError();
@@ -223,7 +224,6 @@ bool Tunnel::HandleEvent(Lws *wsi, enum lws_callback_reasons reason, void *in, s
         case LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED:
         case LWS_CALLBACK_WSI_CREATE:
         case LWS_CALLBACK_OPENSSL_PERFORM_SERVER_CERT_VERIFICATION:
-        case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
         case LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH:
             if (m_state != State::DISCONNECTED) {
                 LogError("Unexpected event");
@@ -232,6 +232,20 @@ bool Tunnel::HandleEvent(Lws *wsi, enum lws_callback_reasons reason, void *in, s
             }
             break;
 
+        case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
+            if (m_state != State::DISCONNECTED) {
+                LogError("Unexpected event");
+                m_state = State::FAILED;
+                return true;
+            }
+            if (HTTP_STATUS_GONE == m_ws->HttpClientResponse(wsi)) {
+                LogError("Uncontactable authenticactor");
+                m_state = State::FAILED;
+                m_uncontactable = true;
+                return true;
+            }
+            break;
+
         case LWS_CALLBACK_VHOST_CERT_AGING:
             // Was seen happening as soon as just after LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED,
             // as well as after the connection is fully connected. Therefore ignore this event
@@ -491,6 +505,8 @@ void Tunnel::DisconnectOnError()
         throw ExceptionTunnelClosed{};
     } else if (m_state != State::CONNECTED) {
         DestroyContext(lock);
+        if (m_uncontactable)
+            THROW_UNCONTACTABLE();
         THROW_INVALID_STATE("Invalid tunnel state");
     }
 }
index 996d538e4495a332b1fb04b61e523b8f695ddaa4..b65cebf07bf838d535242b88afb0028ec4b3be33 100644 (file)
@@ -102,6 +102,7 @@ protected:
     size_t m_writtenBytesNum = 0;
     State m_state = State::DISCONNECTED;
     bool m_cancelled = false;
+    bool m_uncontactable = false;
 
     /*
      * Protects m_cancelled and m_context.
index 04f7a2d0ed989e6a1bcb20c70adcfdf34fa312ea..dadfb9e4b8b660d339805a457f686cfb078fc447 100644 (file)
@@ -186,6 +186,11 @@ void Websockets::ContextDestroy(LwsContext *context) noexcept
     lws_context_destroy(Context2Native(context));
 }
 
+unsigned int Websockets::HttpClientResponse(Lws *wsi) noexcept
+{
+    return lws_http_client_http_response(Lws2Native(wsi));
+}
+
 int Websockets::FidoCallback(struct lws *wsi,
                              enum lws_callback_reasons reason,
                              void * /*user*/,
index 4b80b76af6450b57810353f70fd8d91c1d497e89..736468641c8d7a97c1df6cf3c50a66bc5e088665 100644 (file)
@@ -173,6 +173,14 @@ public:
      */
     virtual void ContextDestroy(LwsContext *context) noexcept = 0;
 
+    /*
+     * @brief  Returns the last server response code, eg, 200 for client http connections. If there
+     *         is no valid response, it will return 0.
+     *
+     * @param[in] wsi  The connection to get the response code from
+     */
+    virtual unsigned int HttpClientResponse(Lws *wsi) noexcept = 0;
+
     /*
      * @brief  Sets the listener of websocket events
      *
@@ -202,6 +210,7 @@ public:
     int WriteBinary(Lws *wsi, unsigned char *buf, size_t len) noexcept override;
     void SetTimer(Lws *wsi, lws_usec_t usecs) noexcept override;
     void ContextDestroy(LwsContext *context) noexcept override;
+    unsigned int HttpClientResponse(Lws *wsi) noexcept override;
 
     void SetListener(IWebsocketsListener *listener) noexcept override { m_listener = listener; }
 
index 1b7db2abf818660d337559f4675ff515476d0728..cd243b25b1cb103fd8a61dfff47cfadc38728ee3 100644 (file)
@@ -386,6 +386,15 @@ void MockedSockets::ContextDestroy(LwsContext *lwsContext) noexcept
     }
 }
 
+unsigned int MockedSockets::HttpClientResponse(Lws *lws) noexcept
+{
+    auto mockedLws = Lws2Mocked(lws);
+    if (mockedLws->m_url == UncontactableTestUrl())
+        return HTTP_STATUS_GONE;
+
+    return HTTP_STATUS_OK;
+}
+
 /*
  * Tests below are executed using fully mocked websockets implementation only (MockedSockets)
  */
index 3d46c0127cb3a824daf87231b74b568d6f221855..d19080d69378bbb17e6cab656c06ac62d0620206 100644 (file)
@@ -53,6 +53,8 @@ protected:
 
     void ContextDestroy(LwsContext *lwsContext) noexcept override;
 
+    unsigned int HttpClientResponse(Lws *lws) noexcept override;
+
     void SetListener(IWebsocketsListener *listener) noexcept override { m_listener = listener; }
 
     IWebsocketsListener *m_listener = nullptr;
index cd885a8545d2c968eefaa50485d6b160a8fcd2c6..d0ba0fa4bfca6fad8eca1e65e6626b60d7dcb10a 100644 (file)
@@ -64,6 +64,7 @@ private:
 using Exception::Cancelled;
 using Exception::InvalidParam;
 using Exception::InvalidState;
+using Exception::Uncontactable;
 using Exception::Unknown;
 
 /*
@@ -409,3 +410,37 @@ TYPED_TEST(TunnelTypedTests, ValidHeaders)
     EXPECT_NO_THROW(
         tunnel.Connect(TestUrl(), ExtraHttpHeader{"unexpected_name:", "unexpected_value"}));
 }
+
+TYPED_TEST(TunnelTypedTests, UncontactableAuthenticator)
+{
+    Tunnel tunnel(std::make_shared<TypeParam>());
+
+    EXPECT_THROW(tunnel.Connect(UncontactableTestUrl()), Uncontactable);
+}
+
+TYPED_TEST(TunnelTypedTests, UncontactableAuthenticator2)
+{
+    static unsigned int responseCode = HTTP_STATUS_OK;
+
+    struct TestSockets : public TypeParam {
+        unsigned int HttpClientResponse(Lws *) noexcept override { return responseCode; }
+    };
+
+    const std::string test = "sdfjisdjkfhdfjkghsdfkghdgkjhd";
+    std::vector<uint8_t> in;
+    std::vector<uint8_t> out;
+    out.assign(test.c_str(), test.c_str() + test.size());
+
+    Tunnel tunnel(std::make_shared<TestSockets>());
+    responseCode = HTTP_STATUS_GONE;
+    ASSERT_THROW(tunnel.Connect(TestUrl()), Uncontactable);
+
+    responseCode = HTTP_STATUS_OK;
+    ASSERT_NO_THROW(tunnel.Connect(TestUrl()));
+    responseCode = HTTP_STATUS_GONE;
+    ASSERT_NO_THROW(tunnel.WriteBinary(out));
+    ASSERT_NO_THROW(in = tunnel.ReadBinary());
+    EXPECT_EQ(in, out);
+
+    EXPECT_NO_THROW(tunnel.Disconnect());
+}
index 19bc0c2dff766db39b3e12cfe6490be5b1fca56a..f7e1cea3bf9ac8377df7f7655cfb22f703366d28 100644 (file)
@@ -21,3 +21,9 @@ const std::string &HeadersTestUrl()
     static const std::string HEADERS_TEST_URL = TestUrl() + "/headers/";
     return HEADERS_TEST_URL;
 }
+
+const std::string &UncontactableTestUrl()
+{
+    static const std::string UNCONTACTABLE_TEST_URL = TestUrl() + "/uncontactable/";
+    return UNCONTACTABLE_TEST_URL;
+}
index 7898b578c466bf5052551db3cca0b7f6cbd6fc21..dae1aefa8b43e04f05c2a98ea4b8c0717acb1090 100644 (file)
@@ -22,6 +22,7 @@
 const std::string &TestUrl();
 
 const std::string &HeadersTestUrl();
+const std::string &UncontactableTestUrl();
 
 constexpr inline char TEST_HEADER_NAME[] = "name:";
 constexpr inline char TEST_HEADER_VALUE[] = "value";
index 9406435a4051f3c37aebf2f6dd7cb5e1f57ec49f..73b21de918292c64b542ee107238659fc17bd3a7 100755 (executable)
@@ -34,6 +34,9 @@ def check_headers(path, request_headers):
         if "name" not in request_headers or request_headers["name"] != "value":
             logging.error("Required header is missing")
             return HTTPStatus.BAD_REQUEST, [], b'Invalid header\n'
+    elif path.startswith("/uncontactable"):
+            logging.error("Uncontactable authenticator")
+            return HTTPStatus.GONE, [], b'Uncontactable authenticator\n'
 
 def get_host_ip():
     # find host ip by sdb port 26101