diff --git a/doc/authentication.md b/doc/authentication.md index 2155e79..6292dc8 100644 --- a/doc/authentication.md +++ b/doc/authentication.md @@ -1,12 +1,21 @@ # Authentication -Webfuse supports token-based authentication using HTTP headers. To activate authentication, two command line option needs to be given: +Webfuse supports two authentications mechanisms: + +- token-based authentication using HTTP headers +- in-protocol authentication + +To activate authentication, two command line option needs to be given: - `--wf-authenticator PATH` allows to specify an executable used for authentication -- `--wf-auth-header HEADER` +- `--wf-auth-header HEADER` _(optional)_ allows to specify the HTTP header used for authentication +When `--wf-auth-header` is not specifiend or the header is not contained +in the HTTP request, the in-protocol solutions is used: Before any other +operation, the credentials are queried via `getcreds`request. + ## Authenticator An authenticator is an executable or script used for token-based diff --git a/doc/protocol.md b/doc/protocol.md index b6cda83..ac4322a 100644 --- a/doc/protocol.md +++ b/doc/protocol.md @@ -313,6 +313,7 @@ _Note that the following numbers are in `hexadecimal` notation._ | rmdir | 0x14 | 0x94 | | statfs | 0x15 | 0x95 | | utimens | 0x16 | 0x96 | +| getcreds | 0x17 | 0x97 | ## Methods @@ -807,6 +808,29 @@ _Note that handle might be invalid (-1), even if the file is open._ | type | u8 | message type (0x96) | | result | result | operation status | +### getcreds + +Query credentials. When authentication is active and the in-protocol +authentication mechanism is used, this is the first request a +webfuse service sends to a provider. + +#### Request + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x17) | + +_Note that handle might be invalid (-1), even if the file is open._ + +#### Response + +| Field | Data Type | Description | +| ------ | --------- | ----------- | +| id | u32 | message id | +| type | u8 | message type (0x97) | +| creds | str | credentials | + ## Examples ### Get file attributes diff --git a/script/provider.py b/script/provider.py index c5cfaba..4cc5a52 100755 --- a/script/provider.py +++ b/script/provider.py @@ -5,6 +5,7 @@ import os import stat import websockets import errno +import getpass INVALID_FD = 0xffffffffffffffff @@ -269,10 +270,12 @@ class FilesystemProvider: 0x14: FilesystemProvider.rmdir, 0x15: FilesystemProvider.statfs, 0x16: FilesystemProvider.utimens, + 0x17: FilesystemProvider.getcreds, } async def run(self): - async with websockets.connect(self.url, extra_headers=[("X-Auth-Token", "user:bob;token=foo")]) as connection: + extra_headers = [("X-Auth-Token", "user:bob;token=foo")] + async with websockets.connect(self.url, extra_headers=extra_headers) as connection: while True: request = await connection.recv() reader = MessageReader(request) @@ -565,7 +568,11 @@ class FilesystemProvider: writer.write_u64(buffer.f_files) writer.write_u64(buffer.f_ffree) writer.write_u64(buffer.f_namemax) - + + def getcreds(self, reader, writer): + credentials = getpass.getpass(prompt="credentials: ") + writer.write_str(credentials) + if __name__ == '__main__': provider = FilesystemProvider('.', 'ws://localhost:8081') diff --git a/src/provider_main.cpp b/src/provider_main.cpp index 6213ac7..c1bcddf 100644 --- a/src/provider_main.cpp +++ b/src/provider_main.cpp @@ -418,6 +418,11 @@ public: return (result == 0) ? 0 : -errno; } + std::string get_credentials() override + { + return getpass("credentials: "); + } + private: std::string get_full_path(std::string const & path) diff --git a/src/webfuse/filesystem.cpp b/src/webfuse/filesystem.cpp index 41bc258..6904f29 100644 --- a/src/webfuse/filesystem.cpp +++ b/src/webfuse/filesystem.cpp @@ -419,4 +419,11 @@ int filesystem::statfs(std::string const & path, struct statvfs * statistics) } } +// get credentials is handled internally +std::string filesystem::get_credentials() +{ + throw std::runtime_error("not implemented"); +} + + } \ No newline at end of file diff --git a/src/webfuse/filesystem.hpp b/src/webfuse/filesystem.hpp index b2f7002..e57c7a2 100644 --- a/src/webfuse/filesystem.hpp +++ b/src/webfuse/filesystem.hpp @@ -47,6 +47,7 @@ public: int statfs(std::string const & path, struct statvfs * statistics) override; + std::string get_credentials() override; private: ws_server &proxy; diff --git a/src/webfuse/filesystem/empty_filesystem.cpp b/src/webfuse/filesystem/empty_filesystem.cpp index e3af5b8..f89f416 100644 --- a/src/webfuse/filesystem/empty_filesystem.cpp +++ b/src/webfuse/filesystem/empty_filesystem.cpp @@ -131,5 +131,11 @@ int empty_filesystem::statfs(std::string const & path, struct statvfs * statisti return -ENOSYS; } +std::string empty_filesystem::get_credentials() +{ + return ""; +} + + } \ No newline at end of file diff --git a/src/webfuse/filesystem/empty_filesystem.hpp b/src/webfuse/filesystem/empty_filesystem.hpp index 16b390a..d4cca3a 100644 --- a/src/webfuse/filesystem/empty_filesystem.hpp +++ b/src/webfuse/filesystem/empty_filesystem.hpp @@ -39,6 +39,8 @@ public: int rmdir(std::string const & path) override; int statfs(std::string const & path, struct statvfs * statistics) override; + + std::string get_credentials() override; }; } diff --git a/src/webfuse/filesystem/filesystem_i.hpp b/src/webfuse/filesystem/filesystem_i.hpp index cf2f0ce..67abb64 100644 --- a/src/webfuse/filesystem/filesystem_i.hpp +++ b/src/webfuse/filesystem/filesystem_i.hpp @@ -48,6 +48,8 @@ public: virtual int rmdir(std::string const & path) = 0; virtual int statfs(std::string const & path, struct statvfs * statistics) = 0; + + virtual std::string get_credentials() = 0; }; } diff --git a/src/webfuse/provider.cpp b/src/webfuse/provider.cpp index 9ca6fee..26fc1be 100644 --- a/src/webfuse/provider.cpp +++ b/src/webfuse/provider.cpp @@ -122,6 +122,9 @@ public: case request_type::statfs: fs_statfs(reader, writer); break; + case request_type::getcreds: + fs_get_credentials(reader, writer); + break; default: std::cout << "unknown request: " << ((int) req_type) << std::endl; break; @@ -371,6 +374,13 @@ private: } } + void fs_get_credentials(messagereader & reader, messagewriter & writer) + { + std::string credentials = fs_.get_credentials(); + writer.write_str(credentials); + } + + filesystem_i & fs_; ws_client client; }; diff --git a/src/webfuse/request_type.cpp b/src/webfuse/request_type.cpp index 0e7b8c0..f8ebe9a 100644 --- a/src/webfuse/request_type.cpp +++ b/src/webfuse/request_type.cpp @@ -29,6 +29,7 @@ request_type get_request_type(uint8_t value) case static_cast(request_type::rmdir): return request_type::rmdir; case static_cast(request_type::statfs): return request_type::statfs; case static_cast(request_type::utimens): return request_type::utimens; + case static_cast(request_type::getcreds): return request_type::getcreds; default: return request_type::unknown; } diff --git a/src/webfuse/request_type.hpp b/src/webfuse/request_type.hpp index b605f0b..6a61119 100644 --- a/src/webfuse/request_type.hpp +++ b/src/webfuse/request_type.hpp @@ -30,7 +30,8 @@ enum class request_type: uint8_t readdir = 0x13, rmdir = 0x14, statfs = 0x15, - utimens = 0x16 + utimens = 0x16, + getcreds =0x17 }; request_type get_request_type(uint8_t value); diff --git a/src/webfuse/response_type.cpp b/src/webfuse/response_type.cpp index cb647e7..a7bb02e 100644 --- a/src/webfuse/response_type.cpp +++ b/src/webfuse/response_type.cpp @@ -29,6 +29,7 @@ response_type get_response_type(request_type value) case request_type::rmdir: return response_type::rmdir; case request_type::statfs: return response_type::statfs; case request_type::utimens: return response_type::utimens; + case request_type::getcreds: return response_type::getcreds; default: return response_type::unknown; } diff --git a/src/webfuse/response_type.hpp b/src/webfuse/response_type.hpp index 46cfebf..6d86715 100644 --- a/src/webfuse/response_type.hpp +++ b/src/webfuse/response_type.hpp @@ -31,7 +31,8 @@ enum class response_type: uint8_t readdir = 0x93, rmdir = 0x94, statfs = 0x95, - utimens = 0x96 + utimens = 0x96, + getcreds = 0x97 }; response_type get_response_type(request_type value); diff --git a/src/webfuse/ws/server.cpp b/src/webfuse/ws/server.cpp index 727d8df..51bd70c 100644 --- a/src/webfuse/ws/server.cpp +++ b/src/webfuse/ws/server.cpp @@ -54,7 +54,7 @@ static int ws_server_callback(struct lws *wsi, enum lws_callback_reasons reason, handler->on_receive(wsi, in, len); break; case LWS_CALLBACK_SERVER_WRITEABLE: - handler->on_writable(); + result = handler->on_writable(); break; default: break; diff --git a/src/webfuse/ws/server_handler.cpp b/src/webfuse/ws/server_handler.cpp index a6ae89c..2475047 100644 --- a/src/webfuse/ws/server_handler.cpp +++ b/src/webfuse/ws/server_handler.cpp @@ -51,6 +51,7 @@ namespace webfuse server_handler::server_handler(std::string const & auth_app, std::string const & auth_hdr) : connection(nullptr) , id(0) +, shutdown_requested(false) , is_authenticated(false) , authenticator(auth_app) , auth_header(auth_hdr) @@ -69,9 +70,26 @@ int server_handler::on_established(lws * wsi) if (nullptr == connection) { connection = wsi; + id = 0; + + if ((!is_authenticated) && (!authenticator.empty())) + { + { + messagewriter writer(request_type::getcreds); + std::lock_guard lock(mut); + + uint32_t id = next_id(); + writer.set_id(id); + requests.emplace(std::move(writer)); + } + + lws_callback_on_writable(wsi); + } + return 0; } + // already connected: refuse return -1; } @@ -86,22 +104,29 @@ void server_handler::on_receive(lws * wsi, void* in, int len) { webfuse::messagereader reader(current_message); uint32_t id = reader.read_u32(); - reader.read_u8(); // read message type: ToDo: use it + auto const message_type = reader.read_u8(); // read message type: ToDo: use it - std::lock_guard lock(mut); - auto it = pending_responses.find(id); - if (it != pending_responses.end()) + if (static_cast(message_type) == response_type::getcreds) { - it->second.set_value(std::move(reader)); - pending_responses.erase(it); + finish_authentication(wsi, std::move(reader)); } else { - // ToDo: log request not found - std::cout << "warning: request not found: id=" << id << std::endl; - for(auto const & entry: pending_responses) + std::lock_guard lock(mut); + auto it = pending_responses.find(id); + if (it != pending_responses.end()) { - std::cout << "\t" << entry.first << std::endl; + it->second.set_value(std::move(reader)); + pending_responses.erase(it); + } + else + { + // ToDo: log request not found + std::cout << "warning: request not found: id=" << id << std::endl; + for(auto const & entry: pending_responses) + { + std::cout << "\t" << entry.first << std::endl; + } } } } @@ -113,8 +138,10 @@ void server_handler::on_receive(lws * wsi, void* in, int len) } } -void server_handler::on_writable() +int server_handler::on_writable() { + if (shutdown_requested) { return -1; } + webfuse::messagewriter writer(webfuse::request_type::unknown); bool has_msg = false; bool has_more = false; @@ -143,6 +170,8 @@ void server_handler::on_writable() { lws_callback_on_writable(connection); } + + return 0; } void server_handler::on_closed(lws * wsi) @@ -258,5 +287,22 @@ uint32_t server_handler::next_id() return id; } +void server_handler::finish_authentication(lws * wsi, messagereader reader) +{ + auto const credentials = reader.read_str(); + + webfuse::authenticator auth(authenticator); + auto const result = auth.authenticate(credentials); + if (result) + { + is_authenticated = true; + } + else + { + shutdown_requested = true; + lws_callback_on_writable(wsi); + } +} + } \ No newline at end of file diff --git a/src/webfuse/ws/server_handler.hpp b/src/webfuse/ws/server_handler.hpp index d8a3249..025a6ed 100644 --- a/src/webfuse/ws/server_handler.hpp +++ b/src/webfuse/ws/server_handler.hpp @@ -25,7 +25,7 @@ public: int on_established(lws* wsi); void on_receive(lws * wsi, void* in, int len); - void on_writable(); + int on_writable(); void on_closed(lws * wsi); std::future perform(messagewriter writer); @@ -35,9 +35,11 @@ private: int authenticate_via_header(lws * wsi); std::string get_auth_token(lws * wsi) const; uint32_t next_id(); + void finish_authentication(lws * wsi, messagereader reader); struct lws * connection; uint32_t id; + bool shutdown_requested; std::atomic is_authenticated; std::string authenticator; diff --git a/test-src/integration/webfuse/test/filesystem_mock.hpp b/test-src/integration/webfuse/test/filesystem_mock.hpp index 844160f..6e583ab 100644 --- a/test-src/integration/webfuse/test/filesystem_mock.hpp +++ b/test-src/integration/webfuse/test/filesystem_mock.hpp @@ -40,6 +40,9 @@ public: MOCK_METHOD(int, rmdir, (std::string const & path)); MOCK_METHOD(int, statfs, (std::string const & path, struct statvfs * statistics)); + + MOCK_METHOD(std::string, get_credentials, ()); + }; }