diff --git a/Viewer/ecflowUI/src/VTriggerAttr.cpp b/Viewer/ecflowUI/src/VTriggerAttr.cpp index 6ff9b63ba..6975486cd 100644 --- a/Viewer/ecflowUI/src/VTriggerAttr.cpp +++ b/Viewer/ecflowUI/src/VTriggerAttr.cpp @@ -152,7 +152,7 @@ namespace detail { template void render_expression(Expression* e, const Node& node, STREAM& stream) { - auto ctx = ecf::Context::make_for(PrintStyle::DEFS); + auto ctx = ecf::FormatContext::make_for(PrintStyle::DEFS); ecf::Indent l1(ctx); diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index 1c6ba88b3..5795cd4e9 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -57,7 +57,6 @@ set(srcs # Base -- Headers base/src/ecflow/base/AbstractClientEnv.hpp base/src/ecflow/base/AbstractServer.hpp - base/src/ecflow/base/Algorithms.hpp base/src/ecflow/base/Authentication.hpp base/src/ecflow/base/AuthenticationDetails.hpp base/src/ecflow/base/Authorisation.hpp @@ -341,6 +340,7 @@ set(srcs node/src/ecflow/node/ClientSuiteMgr.hpp node/src/ecflow/node/ClientSuites.hpp node/src/ecflow/node/CmdContext.hpp + node/src/ecflow/node/AuthorisationContext.hpp node/src/ecflow/node/Defs.hpp node/src/ecflow/node/DefsDelta.hpp node/src/ecflow/node/DefsTreeVisitor.hpp @@ -367,11 +367,11 @@ set(srcs node/src/ecflow/node/NodeAlgorithms.hpp node/src/ecflow/node/NodeContainer.hpp node/src/ecflow/node/NodeFwd.hpp + node/src/ecflow/node/NodePathAlgorithms.hpp node/src/ecflow/node/NodeState.hpp node/src/ecflow/node/NodeStats.hpp node/src/ecflow/node/NodeTreeVisitor.hpp node/src/ecflow/node/Operations.hpp - node/src/ecflow/node/Permissions.hpp node/src/ecflow/node/ResolveExternsVisitor.hpp node/src/ecflow/node/ServerState.hpp node/src/ecflow/node/Signal.hpp @@ -418,6 +418,10 @@ set(srcs node/src/ecflow/node/parser/VariableParser.hpp node/src/ecflow/node/parser/VerifyParser.hpp node/src/ecflow/node/parser/ZombieAttrParser.hpp + node/src/ecflow/node/permissions/ActivePermissions.hpp + node/src/ecflow/node/permissions/Allowed.hpp + node/src/ecflow/node/permissions/Permission.hpp + node/src/ecflow/node/permissions/Permissions.hpp # Node -- Sources node/src/ecflow/node/Alias.cpp node/src/ecflow/node/Attr.cpp @@ -426,6 +430,7 @@ set(srcs node/src/ecflow/node/ClientSuiteMgr.cpp node/src/ecflow/node/ClientSuites.cpp node/src/ecflow/node/CmdContext.cpp + node/src/ecflow/node/AuthorisationContext.cpp node/src/ecflow/node/Defs.cpp node/src/ecflow/node/DefsDelta.cpp node/src/ecflow/node/EcfFile.cpp @@ -457,7 +462,6 @@ set(srcs node/src/ecflow/node/NodeStats.cpp node/src/ecflow/node/NodeTime.cpp node/src/ecflow/node/NodeTreeVisitor.cpp - node/src/ecflow/node/Permissions.cpp node/src/ecflow/node/ResolveExternsVisitor.cpp node/src/ecflow/node/ServerState.cpp node/src/ecflow/node/Signal.cpp @@ -499,6 +503,10 @@ set(srcs node/src/ecflow/node/parser/VariableParser.cpp node/src/ecflow/node/parser/VerifyParser.cpp node/src/ecflow/node/parser/ZombieAttrParser.cpp + node/src/ecflow/node/permissions/ActivePermissions.cpp + node/src/ecflow/node/permissions/Allowed.cpp + node/src/ecflow/node/permissions/Permission.cpp + node/src/ecflow/node/permissions/Permissions.cpp # Server -- Headers server/src/ecflow/server/AuthenticationService.hpp diff --git a/libs/base/CMakeLists.txt b/libs/base/CMakeLists.txt index 25bf14ad0..f48198565 100644 --- a/libs/base/CMakeLists.txt +++ b/libs/base/CMakeLists.txt @@ -25,7 +25,6 @@ set(test_srcs test/TestInLimitParsing.cpp test/TestLogCmd.cpp test/TestMeterCmd.cpp - test/TestPermissions.cpp test/TestProgramOptions.cpp test/TestQueryCmd.cpp test/TestQueueCmd.cpp @@ -61,30 +60,6 @@ target_clangformat(u_base CONDITION ENABLE_TESTS ) -set(test_srcs - # Sources - test/TestAlgorithms.cpp -) - -ecbuild_add_test( - TARGET - u_base_algorithms - LABELS - unit nightly - SOURCES - ${test_srcs} - LIBS - ecflow_all - test_scaffold - test_harness.base - Threads::Threads - $<$:OpenSSL::SSL> -) -target_clangformat(u_base_algorithms - CONDITION ENABLE_TESTS -) - - # The following is not technically a test (as it makes no checks), # but a tool to measure the time it takes to generate a job file if (ENABLE_ALL_TESTS) diff --git a/libs/base/src/ecflow/base/AuthenticationDetails.hpp b/libs/base/src/ecflow/base/AuthenticationDetails.hpp index d367e94c7..e2e4a38a0 100644 --- a/libs/base/src/ecflow/base/AuthenticationDetails.hpp +++ b/libs/base/src/ecflow/base/AuthenticationDetails.hpp @@ -72,7 +72,8 @@ inline authentication_t verify_user_authentication_rules(const AbstractServer& s return authentication_t::success("Authentication (user command) successful"); } - return authentication_t::failure("Authentication (user command) failed, due to: Incorrect credentials detected"); + return authentication_t::failure("Authentication (user command) failed, due to: Incorrect credentials for (" + + command.identity().username().value() + " / )"); } template diff --git a/libs/base/src/ecflow/base/AuthorisationDetails.hpp b/libs/base/src/ecflow/base/AuthorisationDetails.hpp index 514244686..187488b5f 100644 --- a/libs/base/src/ecflow/base/AuthorisationDetails.hpp +++ b/libs/base/src/ecflow/base/AuthorisationDetails.hpp @@ -11,9 +11,6 @@ #ifndef ecflow_base_AuthorisationDetails_HPP #define ecflow_base_AuthorisationDetails_HPP -#include -#include - #include "ecflow/base/AbstractServer.hpp" #include "ecflow/base/Authorisation.hpp" #include "ecflow/base/cts/ClientToServerCmd.hpp" @@ -52,6 +49,7 @@ #include "ecflow/base/cts/user/ServerVersionCmd.hpp" #include "ecflow/base/cts/user/ShowCmd.hpp" #include "ecflow/base/cts/user/ZombieCmd.hpp" +#include "ecflow/node/Defs.hpp" #include "ecflow/server/BaseServer.hpp" namespace ecf { @@ -79,8 +77,6 @@ std::vector get_affected_paths(const COMMAND& command) { template authorisation_t allows_as_per_read_write_rules(const COMMAND& command, AbstractServer& server) { - auto base = dynamic_cast(&server); - static_assert(std::is_base_of_v || std::is_base_of_v, "The command must be either a TaskCmd or a UserCmd"); @@ -91,8 +87,9 @@ authorisation_t allows_as_per_read_write_rules(const COMMAND& command, AbstractS std::vector paths = get_affected_paths(command); - const std::string required_permission = command.isWrite() ? "write" : "read"; - if (base->authorisation().allows(command.identity(), *base, paths, required_permission)) { + auto required = Authoriser::required(command); + ServiceAuthorisationContext ctx{command.identity(), *server.defs(), server.authorisation()}; + if (ctx.allows(paths, required)) { return authorisation_t::success("Authorisation (user) granted"); } @@ -114,6 +111,8 @@ struct Authoriser } static void paths(const InitCmd& command, std::vector& paths) { /* Nothing to do... */ } + + static Allowed required(const InitCmd&) { return Allowed::WRITE; } }; template <> @@ -124,6 +123,8 @@ struct Authoriser } static void paths(const CompleteCmd& command, std::vector& paths) { /* Nothing to do... */ } + + static Allowed required(const CompleteCmd&) { return Allowed::WRITE; } }; template <> @@ -134,6 +135,8 @@ struct Authoriser } static void paths(const AbortCmd& command, std::vector& paths) { /* Nothing to do... */ } + + static Allowed required(const AbortCmd&) { return Allowed::WRITE; } }; template <> @@ -144,6 +147,8 @@ struct Authoriser } static void paths(const LabelCmd& command, std::vector& paths) { /* Nothing to do... */ } + + static Allowed required(const LabelCmd&) { return Allowed::WRITE; } }; template <> @@ -154,6 +159,8 @@ struct Authoriser } static void paths(const MeterCmd& command, std::vector& paths) { /* Nothing to do... */ } + + static Allowed required(const MeterCmd&) { return Allowed::WRITE; } }; template <> @@ -164,6 +171,8 @@ struct Authoriser } static void paths(const EventCmd& command, std::vector& paths) { /* Nothing to do... */ } + + static Allowed required(const EventCmd&) { return Allowed::WRITE; } }; template <> @@ -174,6 +183,8 @@ struct Authoriser } static void paths(const QueueCmd& command, std::vector& paths) { /* Nothing to do... */ } + + static Allowed required(const QueueCmd&) { return Allowed::WRITE; } }; template <> @@ -184,6 +195,8 @@ struct Authoriser } static void paths(const CtsWaitCmd& command, std::vector& paths) { /* Nothing to do... */ } + + static Allowed required(const CtsWaitCmd&) { return Allowed::WRITE; } }; // User commands @@ -218,6 +231,8 @@ struct Authoriser } static void paths(const AlterCmd& command, std::vector& paths) { select_all_paths(command, paths); } + + static Allowed required(const AlterCmd&) { return Allowed::WRITE; } }; template <> @@ -228,6 +243,8 @@ struct Authoriser } static void paths(const BeginCmd& command, std::vector& paths) { select_root_path(command, paths); } + + static Allowed required(const BeginCmd&) { return Allowed::WRITE; } }; template <> @@ -238,6 +255,8 @@ struct Authoriser } static void paths(const CFileCmd& command, std::vector& paths) { select_node_path(command, paths); } + + static Allowed required(const CFileCmd&) { return Allowed::READ; } }; template <> @@ -248,6 +267,8 @@ struct Authoriser } static void paths(const CheckPtCmd& command, std::vector& paths) { select_root_path(command, paths); } + + static Allowed required(const CheckPtCmd&) { return Allowed::WRITE; } }; template <> @@ -260,6 +281,11 @@ struct Authoriser static void paths(const ClientHandleCmd& command, std::vector& paths) { select_root_path(command, paths); } + + static Allowed required(const ClientHandleCmd&) { + // Todo[MB]: Check the correct choice for Suite handle commands + return Allowed::READ; + } }; template <> @@ -270,6 +296,8 @@ struct Authoriser } static void paths(const CSyncCmd& command, std::vector& paths) { select_root_path(command, paths); } + + static Allowed required(const CSyncCmd&) { return Allowed::READ; } }; template <> @@ -280,6 +308,12 @@ struct Authoriser } static void paths(const CtsCmd& command, std::vector& paths) { select_root_path(command, paths); } + + static Allowed required(const CtsCmd& cmd) { + // Todo[MB]: Check the correct choice for "multi-Action" commands, as it really depends on the value of the + // action + return cmd.isWrite() ? Allowed::WRITE : Allowed::READ; + } }; template <> @@ -297,6 +331,12 @@ struct Authoriser paths.push_back(path); } } + + static Allowed required(const CtsNodeCmd& cmd) { + // Todo[MB]: Check the correct choice for "multi-Action" commands, as it really depends on the value of the + // action + return cmd.isWrite() ? Allowed::WRITE : Allowed::READ; + } }; template <> @@ -314,6 +354,8 @@ struct Authoriser paths.push_back(affected[0]); } } + + static Allowed required(const DeleteCmd&) { return Allowed::WRITE; } }; template <> @@ -326,6 +368,12 @@ struct Authoriser static void paths(const EditScriptCmd& command, std::vector& paths) { select_node_path(command, paths); } + + static Allowed required(const EditScriptCmd& cmd) { + // Todo[MB]: Check the correct choice for "multi-Action" commands, as it really depends on the value of the + // action + return cmd.isWrite() ? Allowed::WRITE : Allowed::READ; + } }; template <> @@ -336,6 +384,8 @@ struct Authoriser } static void paths(const ForceCmd& command, std::vector& paths) { select_all_paths(command, paths); } + + static Allowed required(const ForceCmd&) { return Allowed::WRITE; } }; template <> @@ -346,6 +396,8 @@ struct Authoriser } static void paths(const FreeDepCmd& command, std::vector& paths) { select_root_path(command, paths); } + + static Allowed required(const FreeDepCmd&) { return Allowed::WRITE; } }; template <> @@ -356,6 +408,8 @@ struct Authoriser } static void paths(const LoadDefsCmd& command, std::vector& paths) { select_root_path(command, paths); } + + static Allowed required(const LoadDefsCmd&) { return Allowed::WRITE; } }; template <> @@ -366,6 +420,12 @@ struct Authoriser } static void paths(const LogCmd& command, std::vector& paths) { select_root_path(command, paths); } + + static Allowed required(const LogCmd& cmd) { + // Todo[MB]: Check the correct choice for "multi-Action" commands, as it really depends on the value of the + // action + return cmd.isWrite() ? Allowed::WRITE : Allowed::READ; + } }; template <> @@ -378,6 +438,11 @@ struct Authoriser static void paths(const LogMessageCmd& command, std::vector& paths) { select_root_path(command, paths); } + + static Allowed required(const LogMessageCmd& cmd) { + // Todo[MB]: Check the correct choice for "Logging a Message" command + return Allowed::READ; + } }; template <> @@ -388,6 +453,8 @@ struct Authoriser } static void paths(const MoveCmd& command, std::vector& paths) { paths.push_back(command.src_path()); } + + static Allowed required(const MoveCmd&) { return Allowed::WRITE; } }; template <> @@ -400,6 +467,8 @@ struct Authoriser static void paths(const OrderNodeCmd& command, std::vector& paths) { select_node_path(command, paths); } + + static Allowed required(const OrderNodeCmd&) { return Allowed::WRITE; } }; template <> @@ -410,6 +479,12 @@ struct Authoriser } static void paths(const PathsCmd& command, std::vector& paths) { select_all_paths(command, paths); } + + static Allowed required(const PathsCmd& cmd) { + // Todo[MB]: Check the correct choice for "multi-Action" commands, as it really depends on the value of the + // action + return cmd.isWrite() ? Allowed::WRITE : Allowed::READ; + } }; template <> @@ -420,6 +495,8 @@ struct Authoriser } static void paths(const PlugCmd& command, std::vector& paths) { paths.push_back(command.source()); } + + static Allowed required(const PlugCmd&) { return Allowed::WRITE; } }; template <> @@ -432,6 +509,8 @@ struct Authoriser static void paths(const QueryCmd& command, std::vector& paths) { paths.push_back(command.path_to_attribute()); } + + static Allowed required(const QueryCmd&) { return Allowed::READ; } }; template <> @@ -444,6 +523,8 @@ struct Authoriser static void paths(const ReplaceNodeCmd& command, std::vector& paths) { select_node_path(command, paths); } + + static Allowed required(const ReplaceNodeCmd&) { return Allowed::WRITE; } }; template <> @@ -456,6 +537,8 @@ struct Authoriser static void paths(const RequeueNodeCmd& command, std::vector& paths) { select_all_paths(command, paths); } + + static Allowed required(const RequeueNodeCmd&) { return Allowed::WRITE | Allowed::EXECUTE; } }; template <> @@ -466,6 +549,8 @@ struct Authoriser } static void paths(const RunNodeCmd& command, std::vector& paths) { select_all_paths(command, paths); } + + static Allowed required(const RunNodeCmd&) { return Allowed::WRITE | Allowed::EXECUTE; } }; template <> @@ -478,6 +563,8 @@ struct Authoriser static void paths(const ServerVersionCmd& command, std::vector& paths) { select_root_path(command, paths); } + + static Allowed required(const ServerVersionCmd&) { return Allowed::READ; } }; template <> @@ -488,6 +575,8 @@ struct Authoriser } static void paths(const ShowCmd& command, std::vector& paths) { select_root_path(command, paths); } + + static Allowed required(const ShowCmd&) { return Allowed::READ; } }; template <> @@ -498,6 +587,11 @@ struct Authoriser } static void paths(const ZombieCmd& command, std::vector& paths) { select_all_paths(command, paths); } + + static Allowed required(const ZombieCmd&) { + // Todo[MB]: Check the correct choice for Zombie related commands + return Allowed::READ; + } }; template @@ -578,6 +672,11 @@ struct Authoriser Apply_t::to(cmd, [&](auto&& c) { accumulate_paths(c, paths); }); } } + + static Allowed required(const GroupCTSCmd& command) { + // Todo[MB]: Confirm that groups are always allowed, since each individual commands will be checked anyway + return Allowed::READ | Allowed::WRITE | Allowed::EXECUTE | Allowed::OWNER; + } }; } // namespace implementation diff --git a/libs/base/src/ecflow/base/HttpClient.cpp b/libs/base/src/ecflow/base/HttpClient.cpp index 9b77824d6..30801314c 100644 --- a/libs/base/src/ecflow/base/HttpClient.cpp +++ b/libs/base/src/ecflow/base/HttpClient.cpp @@ -75,7 +75,7 @@ bool HttpClient::handle_server_response(ServerReply& server_reply, bool debug) c if (debug) { std::cout << " Client::handle_server_response" << std::endl; } - server_reply.set_host_port(host_, port_); // client context, needed by some commands, ie. SServerLoadCmd + server_reply.set_host_port(host_, port_); // client context, needed by some commands, i.e., SServerLoadCmd if (status_ == ecf::http::Status::OK) { return inbound_response_.handle_server_response(server_reply, outbound_request_.get_cmd(), debug); diff --git a/libs/base/src/ecflow/base/Identification.cpp b/libs/base/src/ecflow/base/Identification.cpp index ca6735a2c..13145aed3 100644 --- a/libs/base/src/ecflow/base/Identification.cpp +++ b/libs/base/src/ecflow/base/Identification.cpp @@ -20,7 +20,9 @@ Identity identify(const Cmd_ptr& cmd) { if (user_cmd->is_custom_user()) { return ecf::Identity::make_custom_user(user_cmd->user(), user_cmd->passwd()); } - return ecf::Identity::make_user(user_cmd->user(), user_cmd->passwd()); + else { + return ecf::Identity::make_user(user_cmd->user(), user_cmd->passwd()); + } } if (auto task_cmd = dynamic_cast(cmd.get()); task_cmd != nullptr) { return ecf::Identity::make_task( diff --git a/libs/base/src/ecflow/base/cts/user/CSyncCmd.cpp b/libs/base/src/ecflow/base/cts/user/CSyncCmd.cpp index e2eda858d..d18520b7f 100644 --- a/libs/base/src/ecflow/base/cts/user/CSyncCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/CSyncCmd.cpp @@ -151,21 +151,23 @@ STC_Cmd_ptr CSyncCmd::doHandleRequest(AbstractServer* as) const { } case CSyncCmd::SYNC: { as->update_stats().sync_++; - return PreAllocatedReply::sync_cmd(client_handle_, client_state_change_no_, client_modify_change_no_, as); + return PreAllocatedReply::sync_cmd( + client_handle_, client_state_change_no_, client_modify_change_no_, this->identity(), as); } case CSyncCmd::SYNC_FULL: { as->update_stats().sync_full_++; - return PreAllocatedReply::sync_full_cmd(client_handle_, as); + return PreAllocatedReply::sync_full_cmd(client_handle_, this->identity(), as); } case CSyncCmd::SYNC_CLOCK: { as->update_stats().sync_clock_++; return PreAllocatedReply::sync_clock_cmd( - client_handle_, client_state_change_no_, client_modify_change_no_, as); + client_handle_, client_state_change_no_, client_modify_change_no_, this->identity(), as); } } // should never get here: - return PreAllocatedReply::sync_cmd(client_handle_, client_state_change_no_, client_modify_change_no_, as); + return PreAllocatedReply::sync_cmd( + client_handle_, client_state_change_no_, client_modify_change_no_, this->identity(), as); } void CSyncCmd::addOption(boost::program_options::options_description& desc) const { diff --git a/libs/base/src/ecflow/base/cts/user/CtsNodeCmd.cpp b/libs/base/src/ecflow/base/cts/user/CtsNodeCmd.cpp index c4e0dd597..a64e6b808 100644 --- a/libs/base/src/ecflow/base/cts/user/CtsNodeCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/CtsNodeCmd.cpp @@ -197,11 +197,12 @@ STC_Cmd_ptr CtsNodeCmd::doHandleRequest(AbstractServer* as) const { as->update_stats().get_defs_++; if (absNodePath_.empty()) { // with migrate we need to get edit history. - return PreAllocatedReply::defs_cmd(as, (api_ == MIGRATE)); // if true, save edit history + return PreAllocatedReply::defs_cmd( + this->identity(), as, (api_ == MIGRATE)); // if true, save edit history } - // however request for a particular node, thats not there, treated as an error + // however request for a particular node, that does not exist, treated as an error node_ptr theNodeToReturn = find_node(defs, absNodePath_); - return PreAllocatedReply::node_cmd(as, theNodeToReturn); + return PreAllocatedReply::node_cmd(this->identity(), as, theNodeToReturn); } case CtsNodeCmd::CHECK_JOB_GEN_ONLY: { diff --git a/libs/base/src/ecflow/base/cts/user/ForceCmd.cpp b/libs/base/src/ecflow/base/cts/user/ForceCmd.cpp index cba96f4a3..f5c239992 100644 --- a/libs/base/src/ecflow/base/cts/user/ForceCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/ForceCmd.cpp @@ -153,7 +153,8 @@ STC_Cmd_ptr ForceCmd::doHandleRequest(AbstractServer* as) const { } if (recursive_) { - node->set_state_hierarchically(new_state, true /* force */); + ServiceAuthorisationContext ctx{this->identity(), *defs, as->authorisation()}; + node->set_state_hierarchically(new_state, ctx, true /* force */); } else { node->set_state(new_state, true /* force */); diff --git a/libs/base/src/ecflow/base/cts/user/RequeueNodeCmd.cpp b/libs/base/src/ecflow/base/cts/user/RequeueNodeCmd.cpp index ba40b3e1f..d7b0dc7c1 100644 --- a/libs/base/src/ecflow/base/cts/user/RequeueNodeCmd.cpp +++ b/libs/base/src/ecflow/base/cts/user/RequeueNodeCmd.cpp @@ -106,12 +106,15 @@ STC_Cmd_ptr RequeueNodeCmd::doHandleRequest(AbstractServer* as) const { SuiteChangedPtr changed(theNodeToRequeue.get()); + // Setup authorisation callback + ServiceAuthorisationContext authorisation(this->identity(), *as); + if (option_ == RequeueNodeCmd::ABORT) { // ONLY Re-queue the aborted tasks auto tasks = ecf::get_all_tasks(*theNodeToRequeue); for (auto& task : tasks) { if (task->state() == NState::ABORTED) { - task->requeue(args); + task->requeue(args, authorisation); task->set_most_significant_state_up_node_tree(); // Must in loop and not outside ECFLOW-428 } } @@ -141,7 +144,7 @@ STC_Cmd_ptr RequeueNodeCmd::doHandleRequest(AbstractServer* as) const { // Therefore *any* *MANUAL* re-queue afterward will NOT reset the next valid time slot. // To overcome this manual re-queue will always clear NO_REQUE_IF_SINGLE_TIME_DEP and hence reset next valid // time slot - theNodeToRequeue->requeue(args); + theNodeToRequeue->requeue(args, authorisation); theNodeToRequeue->set_most_significant_state_up_node_tree(); // Call handleStateChange on parent, to avoid requeue same node again. @@ -159,7 +162,7 @@ STC_Cmd_ptr RequeueNodeCmd::doHandleRequest(AbstractServer* as) const { // The GUI: that calls this command should call a separate request // the returns the active/submitted tasks first. This can then be // presented to the user, who can elect to kill them if required. - theNodeToRequeue->requeue(args); + theNodeToRequeue->requeue(args, authorisation); theNodeToRequeue->set_most_significant_state_up_node_tree(); // Call handleStateChange on parent, to avoid requeue same node again. diff --git a/libs/base/src/ecflow/base/stc/DefsCache.cpp b/libs/base/src/ecflow/base/stc/DefsCache.cpp index f5b30b77a..8aa5b3adb 100644 --- a/libs/base/src/ecflow/base/stc/DefsCache.cpp +++ b/libs/base/src/ecflow/base/stc/DefsCache.cpp @@ -10,6 +10,8 @@ #include "ecflow/base/stc/DefsCache.hpp" +#include + #include "ecflow/core/Ecf.hpp" #include "ecflow/core/Log.hpp" #include "ecflow/node/Defs.hpp" @@ -27,25 +29,21 @@ std::string DefsCache::full_server_defs_as_string_ = ""; unsigned int DefsCache::state_change_no_ = 0; unsigned int DefsCache::modify_change_no_ = 0; +ecf::Identity DefsCache::identity_ = ecf::Identity::make_none(); -void DefsCache::update_cache_if_state_changed(Defs* defs) { - // See if there was a state change *OR* if cache is empty +void DefsCache::update_cache_if_state_changed(Defs* defs, const ecf::AuthorisationContext& authorisation) { if (state_change_no_ != Ecf::state_change_no() || modify_change_no_ != Ecf::modify_change_no() || full_server_defs_as_string_.empty()) { - update_cache(defs); + update_cache(defs, authorisation); } -#ifdef DEBUG_SERVER_SYNC - else { - cout << ": *cache* up to date"; - } -#endif } -void DefsCache::update_cache(Defs* defs) { +void DefsCache::update_cache(Defs* defs, const ecf::AuthorisationContext& authorisation) { #ifdef DEBUG_SERVER_SYNC cout << ": *updating* cache"; #endif - defs->write_to_string(full_server_defs_as_string_, PrintStyle::NET); // update cache + auto ctx = ecf::FormatContext::make_for(PrintStyle::NET, &authorisation); + defs->write_to_string(full_server_defs_as_string_, ctx); // update cache state_change_no_ = Ecf::state_change_no(); modify_change_no_ = Ecf::modify_change_no(); } diff --git a/libs/base/src/ecflow/base/stc/DefsCache.hpp b/libs/base/src/ecflow/base/stc/DefsCache.hpp index 15d55eda4..5ec023009 100644 --- a/libs/base/src/ecflow/base/stc/DefsCache.hpp +++ b/libs/base/src/ecflow/base/stc/DefsCache.hpp @@ -11,6 +11,9 @@ #ifndef ecflow_base_cts_DefsCache_HPP #define ecflow_base_cts_DefsCache_HPP +#include + +#include "ecflow/node/AuthorisationContext.hpp" #include "ecflow/node/NodeFwd.hpp" //================================================================================ @@ -46,8 +49,8 @@ class DefsCache { DefsCache() = delete; // Server side - static void update_cache_if_state_changed(Defs* defs); - static void update_cache(Defs* defs); + static void update_cache_if_state_changed(Defs* defs, const ecf::AuthorisationContext& authorisation); + static void update_cache(Defs* defs, const ecf::AuthorisationContext& authorisation); // Client side static defs_ptr restore_defs_from_string(const std::string&); @@ -57,6 +60,7 @@ class DefsCache { friend class SSyncCmd; friend class DefsCmd; + static ecf::Identity identity_; static std::string full_server_defs_as_string_; static unsigned int state_change_no_; // detect state change in defs across clients static unsigned int modify_change_no_; // detect state change in defs across clients diff --git a/libs/base/src/ecflow/base/stc/DefsCmd.cpp b/libs/base/src/ecflow/base/stc/DefsCmd.cpp index 85a18b81b..fae421822 100644 --- a/libs/base/src/ecflow/base/stc/DefsCmd.cpp +++ b/libs/base/src/ecflow/base/stc/DefsCmd.cpp @@ -22,11 +22,11 @@ //===================================================================================== // The defs command returns the full definition back to the client -DefsCmd::DefsCmd(AbstractServer* as, bool save_edit_history) { - init(as, save_edit_history); // save edit history +DefsCmd::DefsCmd(const ecf::Identity& identity, AbstractServer* as, bool save_edit_history) { + init(identity, as, save_edit_history); // save edit history } -void DefsCmd::init(AbstractServer* as, bool save_edit_history) { +void DefsCmd::init(const ecf::Identity& identity, AbstractServer* as, bool save_edit_history) { /// Return the current value of the state change no. So the that /// the next call to get the SSYncCmd , we need only return what's changed Defs* server_defs = as->defs().get(); @@ -36,7 +36,8 @@ void DefsCmd::init(AbstractServer* as, bool save_edit_history) { // The CACHE is only updated if state/modify numbers change, hence does not take into account suite CLOCK // However DefsCmd should always return the most up to date server contents. - DefsCache::update_cache(server_defs); + ecf::ServiceAuthorisationContext authorisation{identity, *server_defs, as->authorisation()}; + DefsCache::update_cache(server_defs, authorisation); } bool DefsCmd::equals(ServerToClientCmd* rhs) const { diff --git a/libs/base/src/ecflow/base/stc/DefsCmd.hpp b/libs/base/src/ecflow/base/stc/DefsCmd.hpp index 3193d0751..749ce3c0f 100644 --- a/libs/base/src/ecflow/base/stc/DefsCmd.hpp +++ b/libs/base/src/ecflow/base/stc/DefsCmd.hpp @@ -22,10 +22,10 @@ class AbstractServer; //================================================================================ class DefsCmd final : public ServerToClientCmd { public: - explicit DefsCmd(AbstractServer* as, bool save_edit_history = false); + explicit DefsCmd(const ecf::Identity& identity, AbstractServer* as, bool save_edit_history = false); DefsCmd() = default; - void init(AbstractServer* as, bool save_edit_history); + void init(const ecf::Identity& identity, AbstractServer* as, bool save_edit_history); bool handle_server_response(ServerReply&, Cmd_ptr cts_cmd, bool debug) const override; std::string print() const override; diff --git a/libs/base/src/ecflow/base/stc/PreAllocatedReply.cpp b/libs/base/src/ecflow/base/stc/PreAllocatedReply.cpp index c1df4ddca..d7ba634c9 100644 --- a/libs/base/src/ecflow/base/stc/PreAllocatedReply.cpp +++ b/libs/base/src/ecflow/base/stc/PreAllocatedReply.cpp @@ -70,15 +70,15 @@ STC_Cmd_ptr PreAllocatedReply::block_client_zombie_cmd(ecf::Child::ZombieType zt return block_client_zombie_cmd_; } -STC_Cmd_ptr PreAllocatedReply::defs_cmd(AbstractServer* as, bool save_edit_history) { +STC_Cmd_ptr PreAllocatedReply::defs_cmd(const ecf::Identity& identity, AbstractServer* as, bool save_edit_history) { auto* cmd = dynamic_cast(defs_cmd_.get()); - cmd->init(as, save_edit_history); + cmd->init(identity, as, save_edit_history); return defs_cmd_; } -STC_Cmd_ptr PreAllocatedReply::node_cmd(AbstractServer* as, node_ptr node) { +STC_Cmd_ptr PreAllocatedReply::node_cmd(const ecf::Identity& identity, AbstractServer* as, node_ptr node) { auto* cmd = dynamic_cast(node_cmd_.get()); - cmd->init(as, node); + cmd->init(identity, as, node); return node_cmd_; } @@ -148,6 +148,7 @@ STC_Cmd_ptr PreAllocatedReply::news_cmd(unsigned int client_handle, STC_Cmd_ptr PreAllocatedReply::sync_cmd(unsigned int client_handle, unsigned int client_state_change_no, unsigned int client_modify_change_no, + const ecf::Identity& identity, AbstractServer* as) { auto* cmd = dynamic_cast(sync_cmd_.get()); cmd->init(client_handle, @@ -155,6 +156,7 @@ STC_Cmd_ptr PreAllocatedReply::sync_cmd(unsigned int client_handle, client_modify_change_no, false /*full sync*/, false /*sync suite clock*/, + identity, as); return sync_cmd_; } @@ -162,6 +164,7 @@ STC_Cmd_ptr PreAllocatedReply::sync_cmd(unsigned int client_handle, STC_Cmd_ptr PreAllocatedReply::sync_clock_cmd(unsigned int client_handle, unsigned int client_state_change_no, unsigned int client_modify_change_no, + const ecf::Identity& identity, AbstractServer* as) { auto* cmd = dynamic_cast(sync_cmd_.get()); cmd->init(client_handle, @@ -169,13 +172,15 @@ STC_Cmd_ptr PreAllocatedReply::sync_clock_cmd(unsigned int client_handle, client_modify_change_no, false /*full sync*/, true /*sync suite clock*/, + identity, as); return sync_cmd_; } -STC_Cmd_ptr PreAllocatedReply::sync_full_cmd(unsigned int client_handle, AbstractServer* as) { +STC_Cmd_ptr +PreAllocatedReply::sync_full_cmd(unsigned int client_handle, const ecf::Identity& identity, AbstractServer* as) { auto* cmd = dynamic_cast(sync_cmd_.get()); // can reuse the same command - cmd->init(client_handle, 0, 0, true /*full sync*/, false /*sync suite clock*/, as); + cmd->init(client_handle, 0, 0, true /*full sync*/, false /*sync suite clock*/, identity, as); return sync_cmd_; } diff --git a/libs/base/src/ecflow/base/stc/PreAllocatedReply.hpp b/libs/base/src/ecflow/base/stc/PreAllocatedReply.hpp index c22af03c5..34d1d0b0a 100644 --- a/libs/base/src/ecflow/base/stc/PreAllocatedReply.hpp +++ b/libs/base/src/ecflow/base/stc/PreAllocatedReply.hpp @@ -15,6 +15,7 @@ #include "ecflow/base/Cmd.hpp" #include "ecflow/core/Child.hpp" +#include "ecflow/core/Identity.hpp" #include "ecflow/node/NodeFwd.hpp" class AbstractServer; @@ -34,8 +35,8 @@ class PreAllocatedReply { static STC_Cmd_ptr block_client_zombie_cmd(ecf::Child::ZombieType zt); static STC_Cmd_ptr delete_all_cmd(); - static STC_Cmd_ptr defs_cmd(AbstractServer*, bool save_edit_history); - static STC_Cmd_ptr node_cmd(AbstractServer*, node_ptr); + static STC_Cmd_ptr defs_cmd(const ecf::Identity& identity, AbstractServer*, bool save_edit_history); + static STC_Cmd_ptr node_cmd(const ecf::Identity& identity, AbstractServer*, node_ptr); static STC_Cmd_ptr stats_cmd(AbstractServer*); static STC_Cmd_ptr suites_cmd(AbstractServer*); static STC_Cmd_ptr zombie_get_cmd(AbstractServer*); @@ -52,12 +53,14 @@ class PreAllocatedReply { static STC_Cmd_ptr sync_cmd(unsigned int client_handle, unsigned int client_state_change_no, unsigned int client_modify_change_no, + const ecf::Identity& identity, AbstractServer* as); static STC_Cmd_ptr sync_clock_cmd(unsigned int client_handle, unsigned int client_state_change_no, unsigned int client_modify_change_no, + const ecf::Identity& identity, AbstractServer* as); - static STC_Cmd_ptr sync_full_cmd(unsigned int client_handle, AbstractServer* as); + static STC_Cmd_ptr sync_full_cmd(unsigned int client_handle, const ecf::Identity& identity, AbstractServer* as); private: static STC_Cmd_ptr stc_cmd_; diff --git a/libs/base/src/ecflow/base/stc/SNewsCmd.cpp b/libs/base/src/ecflow/base/stc/SNewsCmd.cpp index 0f19a07a2..3b11eab1b 100644 --- a/libs/base/src/ecflow/base/stc/SNewsCmd.cpp +++ b/libs/base/src/ecflow/base/stc/SNewsCmd.cpp @@ -36,6 +36,7 @@ void SNewsCmd::init(unsigned int client_handle, // a reference to a set of suite unsigned int client_modify_change_no, AbstractServer* as) { news_ = ServerReply::NO_NEWS; + /// This method assumes that all users see the same content !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // ===================================================================================== // The code to determine changes here must also relate to SSyncCmd diff --git a/libs/base/src/ecflow/base/stc/SNodeCmd.cpp b/libs/base/src/ecflow/base/stc/SNodeCmd.cpp index 1bea6a7c4..f14e1579c 100644 --- a/libs/base/src/ecflow/base/stc/SNodeCmd.cpp +++ b/libs/base/src/ecflow/base/stc/SNodeCmd.cpp @@ -23,14 +23,17 @@ // This command returns the requested node back to the client // Note: In the case where defs has not been loaded, it can be NULL -SNodeCmd::SNodeCmd(AbstractServer* as, node_ptr node) { - init(as, node); +SNodeCmd::SNodeCmd(const ecf::Identity& identity, AbstractServer* as, node_ptr node) { + init(identity, as, node); } -void SNodeCmd::init(AbstractServer* as, node_ptr node) { +void SNodeCmd::init(const ecf::Identity& identity, AbstractServer* as, node_ptr node) { the_node_str_.clear(); if (node.get()) { - the_node_str_ = ecf::as_string(node, PrintStyle::NET); + ecf::ServiceAuthorisationContext authorisation{identity, *as->defs(), as->authorisation()}; + + auto format_ctx = ecf::FormatContext::make_for(PrintStyle::NET, &authorisation); + the_node_str_ = ecf::as_string(node, format_ctx); } } diff --git a/libs/base/src/ecflow/base/stc/SNodeCmd.hpp b/libs/base/src/ecflow/base/stc/SNodeCmd.hpp index eecb75c05..94b1e62df 100644 --- a/libs/base/src/ecflow/base/stc/SNodeCmd.hpp +++ b/libs/base/src/ecflow/base/stc/SNodeCmd.hpp @@ -12,6 +12,8 @@ #define ecflow_base_stc_SNodeCmd_HPP #include "ecflow/base/stc/ServerToClientCmd.hpp" +#include "ecflow/core/Identity.hpp" + class AbstractServer; //================================================================================ @@ -20,10 +22,10 @@ class AbstractServer; //================================================================================ class SNodeCmd final : public ServerToClientCmd { public: - SNodeCmd(AbstractServer* as, node_ptr node); + SNodeCmd(const ecf::Identity& identity, AbstractServer* as, node_ptr node); SNodeCmd() = default; - void init(AbstractServer* as, node_ptr node); + void init(const ecf::Identity& identity, AbstractServer* as, node_ptr node); bool handle_server_response(ServerReply&, Cmd_ptr cts_cmd, bool debug) const override; std::string print() const override; diff --git a/libs/base/src/ecflow/base/stc/SSyncCmd.cpp b/libs/base/src/ecflow/base/stc/SSyncCmd.cpp index 4301744b6..6eaf0479d 100644 --- a/libs/base/src/ecflow/base/stc/SSyncCmd.cpp +++ b/libs/base/src/ecflow/base/stc/SSyncCmd.cpp @@ -15,6 +15,7 @@ #include "ecflow/base/AbstractServer.hpp" #include "ecflow/core/Ecf.hpp" #include "ecflow/node/Defs.hpp" +#include "ecflow/node/formatter/DefsWriter.hpp" using namespace ecf; @@ -25,9 +26,10 @@ using namespace ecf; SSyncCmd::SSyncCmd(unsigned int client_handle, unsigned int client_state_change_no, unsigned int client_modify_change_no, + ecf::Identity identity, AbstractServer* as) : incremental_changes_(client_state_change_no) { - init(client_handle, client_state_change_no, client_modify_change_no, false, false, as); + init(client_handle, client_state_change_no, client_modify_change_no, false, false, identity, as); } void SSyncCmd::reset_data_members(unsigned int client_state_change_no, bool sync_suite_clock) { @@ -43,6 +45,7 @@ void SSyncCmd::init(unsigned int client_handle, // a reference to a set of suite unsigned int client_modify_change_no, bool do_full_sync, bool sync_suite_clock, + ecf::Identity identity, AbstractServer* as) { // ******************************************************** // This is called in the server @@ -61,7 +64,7 @@ void SSyncCmd::init(unsigned int client_handle, // a reference to a set of suite #ifdef DEBUG_SERVER_SYNC cout << ": *Flag do_full_sync set* "; #endif - full_sync(client_handle, as); + full_sync(client_handle, identity, as); return; } @@ -79,7 +82,7 @@ void SSyncCmd::init(unsigned int client_handle, // a reference to a set of suite #ifdef DEBUG_SERVER_SYNC cout << ": client modify no > server modify no: Server died/restored? "; #endif - full_sync(client_handle, as); + full_sync(client_handle, identity, as); return; } @@ -87,7 +90,7 @@ void SSyncCmd::init(unsigned int client_handle, // a reference to a set of suite #ifdef DEBUG_SERVER_SYNC cout << ": *Large* scale changes: modify numbers not in sync "; #endif - full_sync(client_handle, as); + full_sync(client_handle, identity, as); return; } @@ -98,12 +101,15 @@ void SSyncCmd::init(unsigned int client_handle, // a reference to a set of suite // return; // } + // Setup authorisation callback + ServiceAuthorisationContext authorisation{identity, *as->defs(), as->authorisation()}; + // small scale changes. Collate changes over *defs* and all suites. // Suite stores the maximum state change, over *all* its children, this is used by client handle mechanism // and here to avoid traversing down the hierarchy. // ******** We must trap all child changes under the suite. See class SuiteChanged // ******** otherwise some attribute sync's will be missed - as->defs()->collateChanges(client_handle, incremental_changes_); + as->defs()->collateChanges(client_handle, incremental_changes_, authorisation); incremental_changes_.set_server_state_change_no(Ecf::state_change_no()); incremental_changes_.set_server_modify_change_no(Ecf::modify_change_no()); #ifdef DEBUG_SERVER_SYNC @@ -150,7 +156,7 @@ void SSyncCmd::init(unsigned int client_handle, // a reference to a set of suite #ifdef DEBUG_SERVER_SYNC cout << ": client no > server no: Server died/restored?"; #endif - full_sync(client_handle, as); + full_sync(client_handle, identity, as); return; } @@ -158,7 +164,7 @@ void SSyncCmd::init(unsigned int client_handle, // a reference to a set of suite #ifdef DEBUG_SERVER_SYNC cout << ": *Large* scale changes : modify numbers not in sync"; #endif - full_sync(client_handle, as); + full_sync(client_handle, identity, as); return; } @@ -167,12 +173,15 @@ void SSyncCmd::init(unsigned int client_handle, // a reference to a set of suite #ifdef DEBUG_SERVER_SYNC cout << ": *Large* scale changes: added/removed suites to handle"; #endif - full_sync(client_handle, as); + full_sync(client_handle, identity, as); return; } + // Setup authorisation callback + ServiceAuthorisationContext authorisation{identity, *as->defs(), as->authorisation()}; + // small scale changes - as->defs()->collateChanges(client_handle, incremental_changes_); + as->defs()->collateChanges(client_handle, incremental_changes_, authorisation); incremental_changes_.set_server_state_change_no(max_client_handle_state_change_no); incremental_changes_.set_server_modify_change_no(max_client_handle_modify_change_no); #ifdef DEBUG_SERVER_SYNC @@ -185,7 +194,7 @@ void SSyncCmd::init(unsigned int client_handle, // a reference to a set of suite #endif } -void SSyncCmd::full_sync(unsigned int client_handle, AbstractServer* as) { +void SSyncCmd::full_sync(unsigned int client_handle, Identity identity, AbstractServer* as) { Defs* server_defs = as->defs().get(); if (0 == client_handle) { @@ -193,7 +202,8 @@ void SSyncCmd::full_sync(unsigned int client_handle, AbstractServer* as) { server_defs->set_state_change_no(Ecf::state_change_no()); server_defs->set_modify_change_no(Ecf::modify_change_no()); - DefsCache::update_cache_if_state_changed(server_defs); + ServiceAuthorisationContext authorisation{identity, *server_defs, as->authorisation()}; + DefsCache::update_cache_if_state_changed(server_defs, authorisation); full_defs_ = true; #ifdef DEBUG_SERVER_SYNC cout << ": *no handle* returning FULL defs(*cached* string, size(" @@ -221,10 +231,11 @@ void SSyncCmd::full_sync(unsigned int client_handle, AbstractServer* as) { // **** --> The defs serialisation will setup the suite defs pointers. <--- // **** An alternative would be to clone the entire suites, since this can have // **** hundreds of tasks. It would be very expensive. - // **** This means that server_defs_ will fail invarint_checking before serialisation + // **** This means that server_defs_ will fail invariant_checking before serialisation defs_ptr the_server_defs = server_defs->client_suite_mgr().create_defs(client_handle, as->defs()); if (the_server_defs.get() == server_defs) { - DefsCache::update_cache_if_state_changed(server_defs); + ServiceAuthorisationContext authorisation{identity, *server_defs, as->authorisation()}; + DefsCache::update_cache_if_state_changed(server_defs, authorisation); full_defs_ = true; #ifdef DEBUG_SERVER_SYNC cout << ": The handle has *ALL* the suites: return the FULL defs(*cached* string, size(" @@ -232,7 +243,9 @@ void SSyncCmd::full_sync(unsigned int client_handle, AbstractServer* as) { #endif } else { - the_server_defs->write_to_string(server_defs_, PrintStyle::NET); + ServiceAuthorisationContext authorisation{identity, *server_defs, as->authorisation()}; + auto format_context = FormatContext::make_for(PrintStyle::NET, &authorisation); + the_server_defs->write_to_string(server_defs_, format_context); } #ifdef DEBUG_SERVER_SYNC diff --git a/libs/base/src/ecflow/base/stc/SSyncCmd.hpp b/libs/base/src/ecflow/base/stc/SSyncCmd.hpp index 78de726d0..ae7e54576 100644 --- a/libs/base/src/ecflow/base/stc/SSyncCmd.hpp +++ b/libs/base/src/ecflow/base/stc/SSyncCmd.hpp @@ -13,6 +13,7 @@ #include "ecflow/base/stc/DefsCache.hpp" #include "ecflow/base/stc/ServerToClientCmd.hpp" +#include "ecflow/core/Identity.hpp" #include "ecflow/node/DefsDelta.hpp" class AbstractServer; @@ -40,6 +41,7 @@ class SSyncCmd final : public ServerToClientCmd { SSyncCmd(unsigned int client_handle, // a reference to a set of suites used by client unsigned int client_state_change_no, unsigned int client_modify_change_no, + ecf::Identity identity, AbstractServer* as); SSyncCmd() @@ -64,13 +66,14 @@ class SSyncCmd final : public ServerToClientCmd { unsigned int client_modify_change_no, bool full_sync, bool sync_suite_clock, + ecf::Identity identity, AbstractServer* as); /// For use when doing a full sync void init(unsigned int client_handle, AbstractServer* as); void reset_data_members(unsigned int client_state_change_no, bool sync_suite_clock); - void full_sync(unsigned int client_handle, AbstractServer* as); + void full_sync(unsigned int client_handle, ecf::Identity identity, AbstractServer* as); void cleanup() override; /// run in the server, after command sent to client private: diff --git a/libs/base/test/TestPermissions.cpp b/libs/base/test/TestPermissions.cpp deleted file mode 100644 index 7681af8d6..000000000 --- a/libs/base/test/TestPermissions.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2009- ECMWF. - * - * This software is licensed under the terms of the Apache Licence version 2.0 - * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. - * In applying this licence, ECMWF does not waive the privileges and immunities - * granted to it by virtue of its status as an intergovernmental organisation - * nor does it submit to any jurisdiction. - */ - -#include - -#include "ecflow/server/AuthorisationService.hpp" -#include "ecflow/test/scaffold/Naming.hpp" -#include "ecflow/test/scaffold/Provisioning.hpp" - -BOOST_AUTO_TEST_SUITE(U_Base) - -BOOST_AUTO_TEST_SUITE(T_Perms) - -BOOST_AUTO_TEST_CASE(test_file_automatic_name_exists) { - ECF_NAME_THIS_TEST(); - - using namespace ecf::test::scaffold; - - std::vector paths; - for (int i = 0; i < 10; i++) { - WithTestFile file; - - auto path = fs::absolute(file.path()); - paths.push_back(path); - - BOOST_CHECK(fs::exists(path)); - } - for (const auto& path : paths) { - BOOST_CHECK(!fs::exists(path)); - } -} - -BOOST_AUTO_TEST_CASE(test_file_automatic_prefix_name_exists) { - ECF_NAME_THIS_TEST(); - - using namespace ecf::test::scaffold; - - std::vector paths; - for (int i = 0; i < 10; i++) { - auto prefix = "test_file_" + std::to_string(i); - WithTestFile file{AutomaticTestFile{prefix}}; - - auto path = file.path(); - paths.push_back(path); - - BOOST_CHECK(fs::exists(path)); - } - for (const auto& path : paths) { - BOOST_CHECK(!fs::exists(path)); - } -} - -BOOST_AUTO_TEST_SUITE_END() - -BOOST_AUTO_TEST_SUITE_END() diff --git a/libs/base/test/TestRequest.cpp b/libs/base/test/TestRequest.cpp index 99fb966a4..cc9ca12d4 100644 --- a/libs/base/test/TestRequest.cpp +++ b/libs/base/test/TestRequest.cpp @@ -331,10 +331,10 @@ populateCmdVec(std::vector& cmd_vec, std::vector& stc_cmd_ stc_cmd_vec.push_back(STC_Cmd_ptr(new BlockClientZombieCmd(ecf::Child::ECF))); stc_cmd_vec.push_back(STC_Cmd_ptr(new SStringCmd("Dummy contents"))); stc_cmd_vec.push_back(STC_Cmd_ptr(new SServerLoadCmd("/path/to/log_file"))); - stc_cmd_vec.push_back(STC_Cmd_ptr(new SSyncCmd(0, 0, 0, mock_server))); + stc_cmd_vec.push_back(STC_Cmd_ptr(new SSyncCmd(0, 0, 0, Identity::make_none(), mock_server))); stc_cmd_vec.push_back(STC_Cmd_ptr(new SNewsCmd(0, 0, 0, mock_server))); - stc_cmd_vec.push_back(STC_Cmd_ptr(new DefsCmd(mock_server))); - stc_cmd_vec.push_back(STC_Cmd_ptr(new SNodeCmd(mock_server, node_ptr()))); + stc_cmd_vec.push_back(STC_Cmd_ptr(new DefsCmd(Identity::make_none(), mock_server))); + stc_cmd_vec.push_back(STC_Cmd_ptr(new SNodeCmd(Identity::make_none(), mock_server, node_ptr()))); std::shared_ptr theSTCGroupCmd = std::make_shared(); theSTCGroupCmd->addChild(STC_Cmd_ptr(new ErrorCmd())); @@ -344,8 +344,8 @@ populateCmdVec(std::vector& cmd_vec, std::vector& stc_cmd_ theSTCGroupCmd->addChild(STC_Cmd_ptr(new BlockClientZombieCmd(ecf::Child::ECF))); theSTCGroupCmd->addChild(STC_Cmd_ptr(new SStringCmd())); theSTCGroupCmd->addChild(STC_Cmd_ptr(new SServerLoadCmd())); - theSTCGroupCmd->addChild(STC_Cmd_ptr(new DefsCmd(mock_server))); - theSTCGroupCmd->addChild(STC_Cmd_ptr(new SNodeCmd(mock_server, node_ptr()))); + theSTCGroupCmd->addChild(STC_Cmd_ptr(new DefsCmd(Identity::make_none(), mock_server))); + theSTCGroupCmd->addChild(STC_Cmd_ptr(new SNodeCmd(Identity::make_none(), mock_server, node_ptr()))); stc_cmd_vec.push_back(theSTCGroupCmd); } diff --git a/libs/base/test/TestSSyncCmd.cpp b/libs/base/test/TestSSyncCmd.cpp index 5f96243d8..c86f7662c 100644 --- a/libs/base/test/TestSSyncCmd.cpp +++ b/libs/base/test/TestSSyncCmd.cpp @@ -92,7 +92,7 @@ static void test_sync_scaffold(defs_change_cmd the_defs_change_command, MockServer mock_server(server_defs); unsigned int client_handle = 0; SNewsCmd news_cmd(client_handle, client_state_change_no, client_modify_change_no, &mock_server); - SSyncCmd cmd(client_handle, client_state_change_no, client_modify_change_no, &mock_server); + SSyncCmd cmd(client_handle, client_state_change_no, client_modify_change_no, Identity::make_none(), &mock_server); std::string error_msg; BOOST_REQUIRE_MESSAGE(server_defs->checkInvariants(error_msg), diff --git a/libs/base/test/TestSSyncCmdOrder.cpp b/libs/base/test/TestSSyncCmdOrder.cpp index fc8f39204..4802bafad 100644 --- a/libs/base/test/TestSSyncCmdOrder.cpp +++ b/libs/base/test/TestSSyncCmdOrder.cpp @@ -99,7 +99,7 @@ static void test_sync_scaffold(defs_change_cmd the_defs_change_command, } MockServer mock_server(server_defs); - SSyncCmd cmd(client_handle, client_state_change_no, client_modify_change_no, &mock_server); + SSyncCmd cmd(client_handle, client_state_change_no, client_modify_change_no, Identity::make_none(), &mock_server); std::string error_msg; BOOST_REQUIRE_MESSAGE(mock_server.defs()->checkInvariants(error_msg), error_msg); BOOST_CHECK_MESSAGE(cmd.do_sync(server_reply), "Expected server to change"); diff --git a/libs/base/test/TestSSyncCmd_CH1.cpp b/libs/base/test/TestSSyncCmd_CH1.cpp index fc6048820..86a027612 100644 --- a/libs/base/test/TestSSyncCmd_CH1.cpp +++ b/libs/base/test/TestSSyncCmd_CH1.cpp @@ -148,7 +148,7 @@ void test_sync_scaffold(defs_change_cmd the_defs_change_command, const std::stri MockServer mock_server(server_defs); SNewsCmd news_cmd(client_handle, client_state_change_no, client_modify_change_no, &mock_server); - SSyncCmd cmd(client_handle, client_state_change_no, client_modify_change_no, &mock_server); + SSyncCmd cmd(client_handle, client_state_change_no, client_modify_change_no, Identity::make_none(), &mock_server); if (expected_change) { BOOST_CHECK_MESSAGE(news_cmd.get_news(), test_name << " : get_news : Expected server to change"); @@ -183,6 +183,7 @@ void test_sync_scaffold(defs_change_cmd the_defs_change_command, const std::stri /* server side */ SSyncCmd cmd1(client_handle, server_reply.client_defs()->state_change_no(), server_reply.client_defs()->modify_change_no(), + Identity::make_none(), &mock_server); Ecf::set_server(false); /* client side */ BOOST_CHECK_MESSAGE(!news_cmd1.get_news(), @@ -659,6 +660,7 @@ BOOST_AUTO_TEST_CASE(test_ssync_full_sync_using_handle) { /* server side */ SSyncCmd cmd(client_handle, server_reply.client_defs()->state_change_no(), server_reply.client_defs()->modify_change_no(), + Identity::make_none(), &mock_server); Ecf::set_server(false); @@ -697,6 +699,7 @@ BOOST_AUTO_TEST_CASE(test_ssync_full_sync_using_handle) { /* server side */ SSyncCmd cmd1(client_handle, server_reply.client_defs()->state_change_no(), server_reply.client_defs()->modify_change_no(), + Identity::make_none(), &mock_server); Ecf::set_server(false); /* client side */ BOOST_CHECK_MESSAGE(!news_cmd1.get_news(), "Expected no changes to client, we should be in sync"); diff --git a/libs/core/src/ecflow/core/Environment.hpp b/libs/core/src/ecflow/core/Environment.hpp index 63e982dca..43f957ce0 100644 --- a/libs/core/src/ecflow/core/Environment.hpp +++ b/libs/core/src/ecflow/core/Environment.hpp @@ -53,6 +53,7 @@ constexpr const char* ECF_EXTN = "ECF_EXTN"; constexpr const char* ECF_LOG = "ECF_LOG"; constexpr const char* ECF_PASSWD = "ECF_PASSWD"; constexpr const char* ECF_CUSTOM_PASSWD = "ECF_CUSTOM_PASSWD"; +constexpr const char* ECF_PERMISSIONS = "ECF_PERMISSIONS"; constexpr const char* ECF_SSL = "ECF_SSL"; constexpr const char* ECF_USER = "ECF_USER"; diff --git a/libs/core/src/ecflow/core/Identity.hpp b/libs/core/src/ecflow/core/Identity.hpp index 861a0c4c4..56da1a724 100644 --- a/libs/core/src/ecflow/core/Identity.hpp +++ b/libs/core/src/ecflow/core/Identity.hpp @@ -16,14 +16,42 @@ namespace ecf { +class Username { +public: + explicit Username(std::string username) + : v_{std::move(username)} {}; + + const std::string& value() const { return v_; } + + friend bool operator==(const Username& lhs, const Username& rhs) { return lhs.v_ == rhs.v_; } + friend bool operator!=(const Username& lhs, const Username& rhs) { return !(lhs == rhs); } + +private: + std::string v_; +}; + +class Password { +public: + explicit Password(std::string pass) + : v_{std::move(pass)} {}; + + const std::string& value() const { return v_; } + + friend bool operator==(const Password& lhs, const Password& rhs) { return lhs.v_ == rhs.v_; } + friend bool operator!=(const Password& lhs, const Password& rhs) { return !(lhs == rhs); } + +private: + std::string v_; +}; + class AbstractIdentity { public: virtual ~AbstractIdentity() = default; virtual std::unique_ptr clone() const = 0; - virtual std::string username() const = 0; - virtual std::string password() const = 0; + virtual Username username() const = 0; + virtual Password password() const = 0; virtual std::string as_string() const = 0; }; @@ -39,8 +67,8 @@ class WrappingIdentity : public AbstractIdentity { return std::make_unique(std::move(clone)); } - [[nodiscard]] std::string username() const override { return id_.username(); } - [[nodiscard]] std::string password() const override { return id_.password(); } + [[nodiscard]] Username username() const override { return id_.username(); } + [[nodiscard]] Password password() const override { return id_.password(); } [[nodiscard]] std::string as_string() const override { return id_.as_string(); } @@ -48,62 +76,103 @@ class WrappingIdentity : public AbstractIdentity { T id_; }; +/// +/// @brief None represents... +/// class None { public: - [[nodiscard]] std::string username() const { return ""; } - [[nodiscard]] std::string password() const { return ""; } + [[nodiscard]] const Username& username() const { return empty_username; } + [[nodiscard]] const Password& password() const { return empty_password; } [[nodiscard]] std::string as_string() const { return "None"; } + +private: + inline static Username empty_username{""}; + inline static Password empty_password{""}; }; +/// +/// @brief UserX represents a user with a username and password, extracted by the client from the environment +/// (i.e. the user is extracted from the $USER, and the password is found on the client side $ECF_PASSWD file). +/// +/// The user and password are provided by the client in the inbound request, and the authentication is performed by the +/// ecFlow server itself. This is done by checking the user's credentials against the server side $ECF_PASSWD file. +/// class UserX { public: explicit UserX(std::string username, std::string password) : username_(std::move(username)), password_(std::move(password)) {} - [[nodiscard]] std::string username() const { return username_; } - [[nodiscard]] std::string password() const { return password_; } + [[nodiscard]] const Username& username() const { return username_; } + [[nodiscard]] const Password& password() const { return password_; } - [[nodiscard]] std::string as_string() const { return "{UserX: " + username_ + "}"; } + [[nodiscard]] std::string as_string() const { return "{UserX: " + username_.value() + "}"; } private: - std::string username_; - std::string password_; + Username username_; + Password password_; }; +/// +/// @brief UserX represents a user with a username and password, provided explicitly to the client +/// (i.e. the user is either defined by $ECF_USER or provided by the --user option, and the password is either found on +/// the client side $ECF_CUSTOM_PASSWD file or provided by the --password option). +/// +/// The user and password are provided by the client in the inbound request, and the authentication is performed by the +/// ecFlow server itself. This is done by checking the user's credentials against the server side $ECF_CUSTOM_PASSWD +/// file. +/// class CustomUserX { public: explicit CustomUserX(std::string username, std::string password) : username_(std::move(username)), password_(std::move(password)) {} - [[nodiscard]] std::string username() const { return username_; } - [[nodiscard]] std::string password() const { return password_; } + [[nodiscard]] const Username& username() const { return username_; } + [[nodiscard]] const Password& password() const { return password_; } - [[nodiscard]] std::string as_string() const { return "{UserX: " + username_ + ":}"; } + [[nodiscard]] std::string as_string() const { return "{CustomUserX: " + username_.value() + "}"; } private: - std::string username_; - std::string password_; + Username username_; + Password password_; }; +/// +/// @brief SecureUserX represents a user for which the credentials where successfully verified. +/// +/// This kind of user is used, when using HTTPS, to encode the fact that external Authentication has been +/// performed. +/// +/// This kind of user has a name, but it does not have an associated password (i.e. any eventual authentication +/// token was provided to and used by the external Authentication mechanism, but not passed on to the ecFlow server). +/// +/// Since no password is available, the only Authentication mechanism that is applicable is external Authentication, +/// meaning that ecFlow server will not apply any Authentication related to PasswordFile. +/// class SecureUserX { public: explicit SecureUserX(std::string username) - : username_(std::move(username)), - password_{} {} + : username_(std::move(username)) {} - [[nodiscard]] std::string username() const { return username_; } - [[nodiscard]] std::string password() const { return password_; } + [[nodiscard]] const Username& username() const { return username_; } + [[nodiscard]] const Password& password() const { return empty; } - [[nodiscard]] std::string as_string() const { return "{SecuredUserX: " + username_ + ":}"; } + [[nodiscard]] std::string as_string() const { return "{SecuredUserX: " + username_.value() + "}"; } private: - std::string username_; - std::string password_; + Username username_; + inline static Password empty{""}; }; +/// +/// @brief TaskX represents a task with a pid, password, and try number, extracted by the client from the environment +/// (respectively, $ECF_RID, $ECF_PASS, and $ECF_TRYNO). +/// +/// In fact, this object contains "meta" information about the Task and does not identify any actual user. +/// At the moment, the ecFlow server does not have information about the 'user' that issues the "Task Requests". +/// class TaskX { public: explicit TaskX(std::string pid, std::string pass, std::string tryno) @@ -111,14 +180,16 @@ class TaskX { pass_(std::move(pass)), tryno_(std::move(tryno)) {} - [[nodiscard]] std::string username() const { return pid_; } - [[nodiscard]] std::string password() const { return pass_; } + [[nodiscard]] Username username() const { return pid_; } + [[nodiscard]] Password password() const { return pass_; } - [[nodiscard]] std::string as_string() const { return "{TaskX: " + pid_ + ":" + pass_ + ":" + tryno_ + "}"; } + [[nodiscard]] std::string as_string() const { + return "{TaskX: " + pid_.value() + ":" + pass_.value() + ":" + tryno_ + "}"; + } private: - std::string pid_; - std::string pass_; + Username pid_; + Password pass_; std::string tryno_; }; @@ -175,9 +246,13 @@ class Identity { : handle_{std::move(other.handle_)} {} ~Identity() = default; - Identity& operator=(Identity other) { - using std::swap; - std::swap(handle_, other.handle_); + Identity& operator=(const Identity& other) { + handle_ = other.handle_->clone(); + return *this; + } + + Identity& operator=(Identity&& other) noexcept { + handle_ = std::move(other.handle_); return *this; } @@ -189,8 +264,8 @@ class Identity { [[nodiscard]] bool is_custom() const { return is_a>(*handle_); } [[nodiscard]] bool is_secure() const { return is_a>(*handle_); } - [[nodiscard]] std::string username() const { return handle_->username(); } - [[nodiscard]] std::string password() const { return handle_->password(); } + [[nodiscard]] Username username() const { return handle_->username(); } + [[nodiscard]] Password password() const { return handle_->password(); } [[nodiscard]] std::string as_string() const { return handle_->as_string(); } diff --git a/libs/node/CMakeLists.txt b/libs/node/CMakeLists.txt index f5b313d89..64f364df8 100644 --- a/libs/node/CMakeLists.txt +++ b/libs/node/CMakeLists.txt @@ -40,9 +40,11 @@ set(test_srcs test/TestMovePeer.cpp test/TestNodeAlgorithms.cpp test/TestNodeBeginRequeue.cpp + test/TestNodePathAlgorithms.cpp test/TestNodeState.cpp test/TestNode_main.cpp # test entry point test/TestOrder.cpp + test/TestPermissions.cpp test/TestPersistence.cpp test/TestPreProcessing.cpp test/TestRepeatWithTimeDependencies.cpp diff --git a/libs/node/src/ecflow/node/Alias.cpp b/libs/node/src/ecflow/node/Alias.cpp index 632318a5f..2aed11cba 100644 --- a/libs/node/src/ecflow/node/Alias.cpp +++ b/libs/node/src/ecflow/node/Alias.cpp @@ -65,8 +65,8 @@ void Alias::begin() { Submittable::begin(); } -void Alias::requeue(Requeue_args& args) { - Submittable::requeue(args); +void Alias::requeue(Requeue_args& args, const ecf::AuthorisationContext& authorisation) { + Submittable::requeue(args, authorisation); } const std::string& Alias::debugType() const { @@ -106,7 +106,12 @@ const std::string& Alias::script_extension() const { return File::USR_EXTN(); } -void Alias::collateChanges(DefsDelta& changes) const { +void Alias::collateChanges(DefsDelta& changes, const ecf::AuthorisationContext& ctx) const { + + if (!ctx.allows(this->absNodePath(), ecf::Allowed::READ)) { + return; + } + /// All changes to Alias should be on ONE compound_memento_ptr compound_memento_ptr comp; Submittable::incremental_changes(changes, comp); diff --git a/libs/node/src/ecflow/node/Alias.hpp b/libs/node/src/ecflow/node/Alias.hpp index d864f482e..f5152bb5a 100644 --- a/libs/node/src/ecflow/node/Alias.hpp +++ b/libs/node/src/ecflow/node/Alias.hpp @@ -32,7 +32,8 @@ class Alias final : public Submittable { /// the output. The try number is used in SMSJOB/SMSJOBOUT to preserve the output when /// there are multiple runs. re-queue/begin() resets the try Number void begin() override; - void requeue(Requeue_args&) override; + using Node::requeue; + void requeue(Requeue_args& args, const ecf::AuthorisationContext& authorisation) override; Suite* suite() const override { return parent()->suite(); } Defs* defs() const override { @@ -49,7 +50,7 @@ class Alias final : public Submittable { const std::string& script_extension() const override; - void collateChanges(DefsDelta&) const override; + void collateChanges(DefsDelta& changes, const ecf::AuthorisationContext& ctx) const override; void set_memento(const SubmittableMemento* m, std::vector& aspects, bool f) { Submittable::set_memento(m, aspects, f); } diff --git a/libs/node/src/ecflow/node/AuthorisationContext.cpp b/libs/node/src/ecflow/node/AuthorisationContext.cpp new file mode 100644 index 000000000..abab40149 --- /dev/null +++ b/libs/node/src/ecflow/node/AuthorisationContext.cpp @@ -0,0 +1,30 @@ +/* + * Copyright 2009- ECMWF. + * + * This software is licensed under the terms of the Apache Licence version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#include "ecflow/node/AuthorisationContext.hpp" + +#include "ecflow/node/Node.hpp" + +namespace ecf { + +bool ServiceAuthorisationContext::allows(const path_t& path, Allowed required) const { + return service_.allows(identity_, defs_, path, required); +} + +bool ServiceAuthorisationContext::allows(const paths_t& paths, Allowed required) const { + for (const auto& path : paths) { + if (!allows(path, required)) { + return false; + } + } + return true; +} + +} // namespace ecf diff --git a/libs/node/src/ecflow/node/AuthorisationContext.hpp b/libs/node/src/ecflow/node/AuthorisationContext.hpp new file mode 100644 index 000000000..40cc494a5 --- /dev/null +++ b/libs/node/src/ecflow/node/AuthorisationContext.hpp @@ -0,0 +1,64 @@ +/* + * Copyright 2009- ECMWF. + * + * This software is licensed under the terms of the Apache Licence version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#ifndef ecflow_node_Ctx_HPP +#define ecflow_node_Ctx_HPP + +#include "ecflow/base/AbstractServer.hpp" +#include "ecflow/node/NodeFwd.hpp" +#include "ecflow/server/AuthorisationService.hpp" + +namespace ecf { + +class AuthorisationContext { +public: + using path_t = std::string; + using paths_t = std::vector; + + [[nodiscard]] virtual bool allows(const path_t& path, Allowed required) const = 0; + [[nodiscard]] virtual bool allows(const paths_t& paths, Allowed required) const = 0; +}; + +class UnrestrictedAuthorisationContext : public AuthorisationContext { +public: + using path_t = std::string; + using paths_t = std::vector; + + [[nodiscard]] bool allows(const path_t& path, Allowed required) const override { return true; } + [[nodiscard]] bool allows(const paths_t& paths, Allowed required) const override { return true; } +}; + +class ServiceAuthorisationContext : public AuthorisationContext { +public: + using path_t = std::string; + using paths_t = std::vector; + + ServiceAuthorisationContext(const Identity& identity, const AbstractServer& server) + : identity_{identity}, + defs_{*server.defs()}, + service_{server.authorisation()} {} + + ServiceAuthorisationContext(const Identity& identity, const Defs& defs, const AuthorisationService& service) + : identity_{identity}, + defs_{defs}, + service_{service} {} + + [[nodiscard]] bool allows(const path_t& path, Allowed required) const override; + [[nodiscard]] bool allows(const paths_t& paths, Allowed required) const override; + +private: + const Identity& identity_; + const Defs& defs_; + const AuthorisationService& service_; +}; + +} // namespace ecf + +#endif /* ecflow_node_Ctx_HPP */ diff --git a/libs/node/src/ecflow/node/ClientSuiteMgr.cpp b/libs/node/src/ecflow/node/ClientSuiteMgr.cpp index 77ff8e8b2..c25117efc 100644 --- a/libs/node/src/ecflow/node/ClientSuiteMgr.cpp +++ b/libs/node/src/ecflow/node/ClientSuiteMgr.cpp @@ -175,12 +175,14 @@ bool ClientSuiteMgr::handle_changed(unsigned int client_handle) { return false; } -void ClientSuiteMgr::collateChanges(unsigned int client_handle, DefsDelta& changes) const { +void ClientSuiteMgr::collateChanges(unsigned int client_handle, + DefsDelta& changes, + const ecf::AuthorisationContext& ctx) const { // collate changes over the suites that match the client handle size_t client_suites_size = clientSuites_.size(); for (size_t i = 0; i < client_suites_size; i++) { if (clientSuites_[i].handle() == client_handle) { - clientSuites_[i].collateChanges(changes); + clientSuites_[i].collateChanges(changes, ctx); return; } } diff --git a/libs/node/src/ecflow/node/ClientSuiteMgr.hpp b/libs/node/src/ecflow/node/ClientSuiteMgr.hpp index dd3dc7c1b..23c9f437d 100644 --- a/libs/node/src/ecflow/node/ClientSuiteMgr.hpp +++ b/libs/node/src/ecflow/node/ClientSuiteMgr.hpp @@ -71,7 +71,7 @@ class ClientSuiteMgr { /// the whole defs is returned. Both integers are returned back to the client /// so that, the client then sends the integers back to server, so we can determine /// what's changed. - void collateChanges(unsigned int client_handle, DefsDelta&) const; + void collateChanges(unsigned int client_handle, DefsDelta& changes, const ecf::AuthorisationContext& ctx) const; // Only return the defs state and suites that the client has registered in the client handle // *HOWEVER* if the client has registered all the suites, just return the server defs diff --git a/libs/node/src/ecflow/node/ClientSuites.cpp b/libs/node/src/ecflow/node/ClientSuites.cpp index 55843cad8..a86ff7c22 100644 --- a/libs/node/src/ecflow/node/ClientSuites.cpp +++ b/libs/node/src/ecflow/node/ClientSuites.cpp @@ -128,11 +128,11 @@ void ClientSuites::suite_deleted_in_defs(suite_ptr suite) { } } -void ClientSuites::collateChanges(DefsDelta& changes) const { +void ClientSuites::collateChanges(DefsDelta& changes, const ecf::AuthorisationContext& ctx) const { for (const HSuite& s : suites_) { suite_ptr suite = s.weak_suite_ptr_.lock(); if (suite.get()) { - suite->collateChanges(changes); + suite->collateChanges(changes, ctx); } } } diff --git a/libs/node/src/ecflow/node/ClientSuites.hpp b/libs/node/src/ecflow/node/ClientSuites.hpp index 0d19a7bfc..e5464fbd7 100644 --- a/libs/node/src/ecflow/node/ClientSuites.hpp +++ b/libs/node/src/ecflow/node/ClientSuites.hpp @@ -36,6 +36,7 @@ #include #include +#include "ecflow/node/AuthorisationContext.hpp" #include "ecflow/node/NodeFwd.hpp" namespace ecf { @@ -98,7 +99,7 @@ class ClientSuites { void suite_deleted_in_defs(suite_ptr); /// Collate the incremental changes, made to my suites - void collateChanges(DefsDelta& changes) const; + void collateChanges(DefsDelta& changes, const ecf::AuthorisationContext& ctx) const; // Only return the defs state and suites that the client has registered in this suite // *HOWEVER* if the client has registered all the suites, just return the server defs diff --git a/libs/node/src/ecflow/node/Defs.cpp b/libs/node/src/ecflow/node/Defs.cpp index bcdf747bf..245c60b03 100644 --- a/libs/node/src/ecflow/node/Defs.cpp +++ b/libs/node/src/ecflow/node/Defs.cpp @@ -611,6 +611,10 @@ void Defs::reset_begin() { } void Defs::requeue() { + requeue(ecf::UnrestrictedAuthorisationContext{}); +} + +void Defs::requeue(const ecf::AuthorisationContext& authorization) { bool edit_history_set = flag().is_set(ecf::Flag::MESSAGE); flag_.reset(); if (edit_history_set) { @@ -620,7 +624,7 @@ void Defs::requeue() { Node::Requeue_args args; size_t theSuiteVecSize = suiteVec_.size(); for (size_t s = 0; s < theSuiteVecSize; s++) { - suiteVec_[s]->requeue(args); + suiteVec_[s]->requeue(args, authorization); } set_most_significant_state(); @@ -1285,6 +1289,11 @@ void Defs::cereal_restore_from_checkpt(const std::string& the_fileName) { } void Defs::write_to_string(std::string& os, PrintStyle::Type_t p_style) const { + auto ctx = ecf::FormatContext::make_for(p_style); + write_to_string(os, ctx); +} + +void Defs::write_to_string(std::string& os, ecf::FormatContext ctx) const { // Set up the output, pre-allocating space based on previous runs (if available) os.clear(); if (print_cache_ > 0) { @@ -1294,7 +1303,6 @@ void Defs::write_to_string(std::string& os, PrintStyle::Type_t p_style) const { os.reserve(4096); } - auto ctx = ecf::Context::make_for(p_style); ecf::write_t(os, *this, ctx); // Store the size of the output, for future use @@ -1606,9 +1614,9 @@ std::string Defs::toString() const { } // Memento functions -void Defs::collateChanges(unsigned int client_handle, DefsDelta& incremental_changes) const { +void Defs::collateChanges(unsigned int client_handle, DefsDelta& changes, const ecf::AuthorisationContext& ctx) const { // Collate any small scale changes to the defs - collate_defs_changes_only(incremental_changes); + collate_defs_changes_only(changes); if (0 == client_handle) { // small scale changes. Collate changes over all suites. @@ -1620,7 +1628,7 @@ void Defs::collateChanges(unsigned int client_handle, DefsDelta& incremental_cha // *IF* node/attribute change no > client_state_change_no // *THEN* // Create a memento, and store in incremental_changes_ - s->collateChanges(incremental_changes); + s->collateChanges(changes, ctx); } } else { @@ -1630,7 +1638,7 @@ void Defs::collateChanges(unsigned int client_handle, DefsDelta& incremental_cha // *IF* node/attribute change no > client_state_change_no // *THEN* // Create a memento, and store in incremental_changes_ - client_suite_mgr_.collateChanges(client_handle, incremental_changes); + client_suite_mgr_.collateChanges(client_handle, changes, ctx); } } diff --git a/libs/node/src/ecflow/node/Defs.hpp b/libs/node/src/ecflow/node/Defs.hpp index 3fcbb3359..0a49c0b92 100644 --- a/libs/node/src/ecflow/node/Defs.hpp +++ b/libs/node/src/ecflow/node/Defs.hpp @@ -32,6 +32,7 @@ #include "ecflow/core/PrintStyle.hpp" #include "ecflow/node/Aspect.hpp" #include "ecflow/node/Attr.hpp" +#include "ecflow/node/AuthorisationContext.hpp" #include "ecflow/node/ClientSuiteMgr.hpp" #include "ecflow/node/Flag.hpp" #include "ecflow/node/NodeFwd.hpp" @@ -41,9 +42,11 @@ namespace cereal { class access; } + namespace ecf { class NodeTreeVisitor; class CalendarUpdateParams; +struct FormatContext; } // namespace ecf class Defs { @@ -184,6 +187,7 @@ class Defs { /// Will requeue all suites. Current used in test only void requeue(); + void requeue(const ecf::AuthorisationContext& authorisation); /// returns true if defs has cron,time,day,date or today time dependencies bool hasTimeDependencies() const; @@ -309,6 +313,7 @@ class Defs { * @param st the print style to use for writing the defs */ void write_to_string(std::string& os, PrintStyle::Type_t st = PrintStyle::MIGRATE) const; + void write_to_string(std::string& os, ecf::FormatContext ctx) const; /** * @brief Write the defs to a file at the given path. @@ -354,7 +359,7 @@ class Defs { constexpr static size_t max_edit_history_size_per_node() { return 10; } /// Memento functions: - void collateChanges(unsigned int client_handle, DefsDelta&) const; + void collateChanges(unsigned int client_handle, DefsDelta& changes, const ecf::AuthorisationContext& ctx) const; void set_memento(const StateMemento*, std::vector& aspects, bool f); void set_memento(const ServerStateMemento*, std::vector& aspects, bool f); void set_memento(const ServerVariableMemento*, std::vector& aspects, bool f); diff --git a/libs/node/src/ecflow/node/ExprParser.cpp b/libs/node/src/ecflow/node/ExprParser.cpp index e015a5093..f7dba5cff 100644 --- a/libs/node/src/ecflow/node/ExprParser.cpp +++ b/libs/node/src/ecflow/node/ExprParser.cpp @@ -485,7 +485,7 @@ bool ExprParser::doParse(std::string& errorMsg) { } // The evaluation function for the AST -void do_print(const tree_iter_t& i, const std::map& rule_names, ecf::Context& ctx) { +void do_print(const tree_iter_t& i, const std::map& rule_names, ecf::FormatContext& ctx) { ecf::Indent l1(ctx); auto iter = rule_names.find(i->value.id()); if (iter != rule_names.end()) { @@ -505,7 +505,7 @@ void do_print(const tree_iter_t& i, const std::map& rule } void do_print(const tree_iter_t& i, const std::map& rule_names) { - ecf::Context ctx = ecf::Context::make_for(PrintStyle::DEFS); + auto ctx = ecf::FormatContext::make_for(PrintStyle::DEFS); do_print(i, rule_names, ctx); } diff --git a/libs/node/src/ecflow/node/Family.cpp b/libs/node/src/ecflow/node/Family.cpp index 9bd64ee6a..db7b19e99 100644 --- a/libs/node/src/ecflow/node/Family.cpp +++ b/libs/node/src/ecflow/node/Family.cpp @@ -85,8 +85,8 @@ bool Family::resolveDependencies(JobsParam& jobsParam) { return NodeContainer::resolveDependencies(jobsParam); } -void Family::requeue(Requeue_args& args) { - NodeContainer::requeue(args); +void Family::requeue(Requeue_args& args, const ecf::AuthorisationContext& authorisation) { + NodeContainer::requeue(args, authorisation); update_generated_variables(); } @@ -105,13 +105,18 @@ const std::string& Family::debugType() const { return ecf::string_constants::family; } -void Family::collateChanges(DefsDelta& changes) const { +void Family::collateChanges(DefsDelta& changes, const ecf::AuthorisationContext& ctx) const { + + if (!ctx.allows(this->absNodePath(), ecf::Allowed::READ)) { + return; + } + /// All changes to family should be on ONE compound_memento_ptr compound_memento_ptr compound; NodeContainer::incremental_changes(changes, compound); // Traversal - NodeContainer::collateChanges(changes); + NodeContainer::collateChanges(changes, ctx); } // generated variables -------------------------------------------------------------------------- diff --git a/libs/node/src/ecflow/node/Family.hpp b/libs/node/src/ecflow/node/Family.hpp index 9d83827c6..b0ee92ba7 100644 --- a/libs/node/src/ecflow/node/Family.hpp +++ b/libs/node/src/ecflow/node/Family.hpp @@ -44,7 +44,8 @@ class Family final : public NodeContainer { void begin() override; bool resolveDependencies(JobsParam&) override; // overriden to speicy family for job profiler - void requeue(Requeue_args&) override; + using Node::requeue; + void requeue(Requeue_args& args, const ecf::AuthorisationContext& authorisation) override; void accept(ecf::NodeTreeVisitor&) override; void acceptVisitTraversor(ecf::NodeTreeVisitor& v) override; void update_generated_variables() const override; @@ -56,7 +57,7 @@ class Family final : public NodeContainer { bool operator==(const Family& rhs) const; - void collateChanges(DefsDelta&) const override; + void collateChanges(DefsDelta& changes, const ecf::AuthorisationContext& ctx) const override; void set_memento(const OrderMemento* m, std::vector& aspects, bool f) { NodeContainer::set_memento(m, aspects, f); } diff --git a/libs/node/src/ecflow/node/Node.cpp b/libs/node/src/ecflow/node/Node.cpp index 2f44de292..d2050110c 100644 --- a/libs/node/src/ecflow/node/Node.cpp +++ b/libs/node/src/ecflow/node/Node.cpp @@ -345,11 +345,20 @@ void Node::begin() { } void Node::requeue(Requeue_args& args) { + requeue(args, ecf::UnrestrictedAuthorisationContext{}); +} + +void Node::requeue(Requeue_args& args, const ecf::AuthorisationContext& authorisation) { #ifdef DEBUG_REQUEUE LOG(Log::DBG, " Node::requeue() " << absNodePath() << " resetRepeats = " << args.resetRepeats_); #endif // Note: we don't reset verify attributes as they record state stat's + if (!authorisation.allows(this->absNodePath(), Allowed::EXECUTE)) { + // User not authorised to requeue this node + return; + } + if (!mirrors_.empty()) { // In case mirror attributes are available, the node state becomes UNKNOWN setStateOnly(NState::State::UNKNOWN, true, ecf::string_constants::empty, false); diff --git a/libs/node/src/ecflow/node/Node.hpp b/libs/node/src/ecflow/node/Node.hpp index dd55721d7..7f9e2a30e 100644 --- a/libs/node/src/ecflow/node/Node.hpp +++ b/libs/node/src/ecflow/node/Node.hpp @@ -40,6 +40,7 @@ #include "ecflow/core/NOrder.hpp" #include "ecflow/node/Aspect.hpp" #include "ecflow/node/Attr.hpp" +#include "ecflow/node/AuthorisationContext.hpp" #include "ecflow/node/AvisoAttr.hpp" #include "ecflow/node/Expression.hpp" #include "ecflow/node/Flag.hpp" @@ -47,7 +48,7 @@ #include "ecflow/node/InLimitMgr.hpp" #include "ecflow/node/MirrorAttr.hpp" #include "ecflow/node/NodeFwd.hpp" -#include "ecflow/node/Permissions.hpp" +#include "ecflow/node/permissions/Permissions.hpp" namespace ecf { class Simulator; @@ -210,7 +211,8 @@ class Node : public std::enable_shared_from_this { const bool reset_relative_duration_{true}; const bool log_state_changes_{true}; }; - virtual void requeue(Requeue_args&); + virtual void requeue(Requeue_args& args); + virtual void requeue(Requeue_args& args, const ecf::AuthorisationContext& authorisation); // force queued allows a job to re-run preserving job output. // However, other nodes may reference this node's events/meters/late in trigger expression, @@ -330,6 +332,12 @@ class Node : public std::enable_shared_from_this { /// ** meaningless, since it will always be the computed state. void set_state(NState::State s, bool force = false, const std::string& additional_info_to_log = ""); virtual void set_state_hierarchically(NState::State s, bool force) { set_state(s, force); } + virtual void set_state_hierarchically(NState::State s, const ecf::AuthorisationContext& ctx, bool force) { + if (!ctx.allows(this->absNodePath(), ecf::Allowed::WRITE)) { + return; + } + set_state(s, force); + } /// Set state only, has no side effects void setStateOnly(NState::State s, @@ -337,6 +345,12 @@ class Node : public std::enable_shared_from_this { const std::string& additional_info_to_log = "", bool log_state_changes = true); virtual void setStateOnlyHierarchically(NState::State s, bool force = false) { setStateOnly(s, force); } + virtual void setStateOnlyHierarchically(NState::State s, const ecf::AuthorisationContext& ctx, bool force = false) { + if (!ctx.allows(this->absNodePath(), ecf::Allowed::WRITE)) { + return; + } + setStateOnly(s, force); + } /// This returns the time of state change: (relative to real time when the suite calendar was begun) /// The returned time is *real time/computer UTC time* and *not* suite real time. @@ -618,7 +632,7 @@ class Node : public std::enable_shared_from_this { // mementos functions: /// Collect all the state changes, so that only small subset is returned to client - virtual void collateChanges(DefsDelta&) const = 0; + virtual void collateChanges(DefsDelta& changes, const ecf::AuthorisationContext& ctx) const = 0; void incremental_changes(DefsDelta&, compound_memento_ptr& comp) const; void set_memento(const NodeStateMemento*, std::vector& aspects, bool f); diff --git a/libs/node/src/ecflow/node/NodeContainer.cpp b/libs/node/src/ecflow/node/NodeContainer.cpp index 5aba57720..831be27bb 100644 --- a/libs/node/src/ecflow/node/NodeContainer.cpp +++ b/libs/node/src/ecflow/node/NodeContainer.cpp @@ -126,11 +126,11 @@ void NodeContainer::handle_migration(const ecf::Calendar& c) { } } -void NodeContainer::requeue(Requeue_args& args) { +void NodeContainer::requeue(Requeue_args& args, const ecf::AuthorisationContext& authorisation) { // LOG(Log::DBG," " << debugType() << "::requeue() " << absNodePath() << " resetRepeats = " << resetRepeats); restore_on_begin_or_requeue(); - Node::requeue(args); + Node::requeue(args, authorisation); // For negative numbers, do nothing, i.e. do not clear if (args.clear_suspended_in_child_nodes_ >= 0) { @@ -151,7 +151,7 @@ void NodeContainer::requeue(Requeue_args& args) { log_state_changes_descendents); for (const auto& n : nodes_) { - n->requeue(largs); + n->requeue(largs, authorisation); } handle_defstatus_propagation(); @@ -309,7 +309,7 @@ void NodeContainer::set_memento(const ChildrenMemento* memento, } } -void NodeContainer::collateChanges(DefsDelta& changes) const { +void NodeContainer::collateChanges(DefsDelta& changes, const ecf::AuthorisationContext& ctx) const { /// Theres no point in traversing children if we have added/removed children /// since ChildrenMemento will copy all children. if (add_remove_state_change_no_ > changes.client_state_change_no()) { @@ -318,7 +318,7 @@ void NodeContainer::collateChanges(DefsDelta& changes) const { // Traversal to children for (const auto& n : nodes_) { - n->collateChanges(changes); + n->collateChanges(changes, ctx); } } @@ -1064,6 +1064,17 @@ void NodeContainer::setStateOnlyHierarchically(NState::State s, bool force) { } } +void NodeContainer::setStateOnlyHierarchically(NState::State s, const AuthorisationContext& ctx, bool force) { + if (!ctx.allows(this->absNodePath(), ecf::Allowed::WRITE)) { + return; + } + + setStateOnly(s, force); + for (const auto& n : nodes_) { + n->setStateOnlyHierarchically(s, ctx, force); + } +} + void NodeContainer::set_state_hierarchically(NState::State s, bool force) { setStateOnlyHierarchically(s, force); if (force) { @@ -1073,6 +1084,19 @@ void NodeContainer::set_state_hierarchically(NState::State s, bool force) { handleStateChange(); // non-hierarchical } +void NodeContainer::set_state_hierarchically(NState::State s, const AuthorisationContext& ctx, bool force) { + if (!ctx.allows(this->absNodePath(), ecf::Allowed::WRITE)) { + return; + } + + setStateOnlyHierarchically(s, ctx, force); + if (force) { + // *force* is only set via ForceCmd. + update_limits(); // hierarchical + } + handleStateChange(); // non-hierarchical +} + void NodeContainer::update_limits() { /// Only tasks can affect the limits, hence no point calling locally for (const auto& n : nodes_) { diff --git a/libs/node/src/ecflow/node/NodeContainer.hpp b/libs/node/src/ecflow/node/NodeContainer.hpp index 77ba8eee8..77adadf87 100644 --- a/libs/node/src/ecflow/node/NodeContainer.hpp +++ b/libs/node/src/ecflow/node/NodeContainer.hpp @@ -35,7 +35,7 @@ class NodeContainer : public Node { void acceptVisitTraversor(ecf::NodeTreeVisitor& v) override; void reset() override; void begin() override; - void requeue(Requeue_args&) override; + void requeue(Requeue_args& args, const ecf::AuthorisationContext& authorisation) override; void requeue_time_attrs() override; void handle_migration(const ecf::Calendar&) override; void reset_late_event_meters() override; @@ -43,7 +43,7 @@ class NodeContainer : public Node { void kill(const std::string& zombie_pid = "") override; void status() override; bool top_down_why(std::vector& theReasonWhy, bool html_tags = false) const override; - void collateChanges(DefsDelta&) const override; + void collateChanges(DefsDelta& changes, const ecf::AuthorisationContext& ctx) const override; void set_memento(const OrderMemento*, std::vector& aspects, bool f); void set_memento(const ChildrenMemento*, std::vector& aspects, bool f); void order(Node* immediateChild, NOrder::Order) override; @@ -107,7 +107,9 @@ class NodeContainer : public Node { void setRepeatToLastValueHierarchically() override; void setStateOnlyHierarchically(NState::State s, bool force = false) override; + void setStateOnlyHierarchically(NState::State s, const ecf::AuthorisationContext& ctx, bool force = false) override; void set_state_hierarchically(NState::State s, bool force) override; + void set_state_hierarchically(NState::State s, const ecf::AuthorisationContext& ctx, bool force) override; void update_limits() override; void sort_attributes(ecf::Attr::Type attr, bool recursive = true, diff --git a/libs/base/src/ecflow/base/Algorithms.hpp b/libs/node/src/ecflow/node/NodePathAlgorithms.hpp similarity index 92% rename from libs/base/src/ecflow/base/Algorithms.hpp rename to libs/node/src/ecflow/node/NodePathAlgorithms.hpp index 960233a18..893fdd193 100644 --- a/libs/base/src/ecflow/base/Algorithms.hpp +++ b/libs/node/src/ecflow/node/NodePathAlgorithms.hpp @@ -8,12 +8,11 @@ * nor does it submit to any jurisdiction. */ -#ifndef ecflow_base_Algorithms_hpp -#define ecflow_base_Algorithms_hpp +#ifndef ecflow_node_NodePathAlgorithms_hpp +#define ecflow_node_NodePathAlgorithms_hpp #include -#include "ecflow/base/AbstractServer.hpp" #include "ecflow/core/Result.hpp" #include "ecflow/node/Defs.hpp" @@ -22,7 +21,7 @@ namespace ecf { /// /// Represents a path in the server's hierarchy /// -/// A path object is always represents a valid path (even if it does not exist). +/// A path object always represents a valid path (even if it does not exist). /// /// A path with 0 tokens represents the root of the hierarchy (represented by a slash, "/"). /// A path with N tokens represents a path from the root to a node. @@ -104,4 +103,4 @@ void visit(const Defs& defs, const Path& path, PREDICATE& predicate) { } // namespace ecf -#endif // ecflow_base_Algorithms_hpp +#endif // ecflow_node_NodePathAlgorithms_hpp diff --git a/libs/node/src/ecflow/node/Permissions.cpp b/libs/node/src/ecflow/node/Permissions.cpp deleted file mode 100644 index 8b4a6a6cd..000000000 --- a/libs/node/src/ecflow/node/Permissions.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2009- ECMWF. - * - * This software is licensed under the terms of the Apache Licence version 2.0 - * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. - * In applying this licence, ECMWF does not waive the privileges and immunities - * granted to it by virtue of its status as an intergovernmental organisation - * nor does it submit to any jurisdiction. - */ - -#include "ecflow/node/Permissions.hpp" - -#include "ecflow/attribute/Variable.hpp" -#include "ecflow/core/Str.hpp" - -namespace ecf { - -Permissions Permissions::make_from_variable(const std::string& value) { - std::vector allowed; - ecf::algorithm::split_at(allowed, value, ","); - return Permissions(std::move(allowed)); -} - -Permissions Permissions::find_in(const std::vector& variables) { - if (auto found = std::find_if( - std::begin(variables), std::end(variables), [](auto&& var) { return var.name() == "PERMISSIONS"; }); - found != std::end(variables)) { - auto var_value = found->theValue(); - return ecf::Permissions::make_from_variable(var_value); - } - else { - return ecf::Permissions::make_empty(); - } -} - -} // namespace ecf diff --git a/libs/node/src/ecflow/node/Permissions.hpp b/libs/node/src/ecflow/node/Permissions.hpp deleted file mode 100644 index b60609f40..000000000 --- a/libs/node/src/ecflow/node/Permissions.hpp +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2009- ECMWF. - * - * This software is licensed under the terms of the Apache Licence version 2.0 - * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. - * In applying this licence, ECMWF does not waive the privileges and immunities - * granted to it by virtue of its status as an intergovernmental organisation - * nor does it submit to any jurisdiction. - */ - -#ifndef ecflow_node_Permissions_HPP -#define ecflow_node_Permissions_HPP - -#include -#include -#include - -class Node; -class Variable; - -namespace ecf { - -class Permissions { -public: - static Permissions make_empty() { return Permissions(); } - static Permissions make_from_variable(const std::string& var_value); - - static Permissions find_in(const std::vector& variables); - - [[nodiscard]] bool is_empty() const { return allowed_.empty(); } - - [[nodiscard]] bool allows(const std::string& user) const { - auto found = - std::find_if(std::begin(allowed_), std::end(allowed_), [&user](auto&& current) { return user == current; }); - return found != std::end(allowed_); - } - -private: - Permissions() - : allowed_{} {} - explicit Permissions(std::vector allowed) - : allowed_{std::move(allowed)} {} - - std::vector allowed_; -}; - -} // namespace ecf - -#endif /* ecflow_node_Permissions_HPP */ diff --git a/libs/node/src/ecflow/node/ServerState.cpp b/libs/node/src/ecflow/node/ServerState.cpp index eeee1b2ce..6348e0be1 100644 --- a/libs/node/src/ecflow/node/ServerState.cpp +++ b/libs/node/src/ecflow/node/ServerState.cpp @@ -137,6 +137,20 @@ bool ServerState::compare(const ServerState& rhs) const { return true; } +Permissions ServerState::permissions() const { + // Find the permissions in the user variables first, as these override the server variables + if (auto permissions = Permissions::find_in(user_variables_); !permissions.is_empty()) { + return permissions; + } + + // Then, find the permissions in the server variables + if (auto permissions = Permissions::find_in(server_variables_); !permissions.is_empty()) { + return permissions; + } + + return Permissions::make_empty(); +} + void ServerState::sort_variables() { variable_state_change_no_ = Ecf::incr_state_change_no(); @@ -157,11 +171,9 @@ void ServerState::add_or_update_server_variable(const std::string& name, const s for (auto& s : server_variables_) { if (s.name() == name) { s.set_value(value); - // std::cout << " Server Variables: Updating " << name << " " << value << "\n"; return; } } - // std::cout << " Server Variables: Adding " << name << " " << value << "\n"; server_variables_.emplace_back(name, value); } diff --git a/libs/node/src/ecflow/node/ServerState.hpp b/libs/node/src/ecflow/node/ServerState.hpp index b43d3e72a..ed517137c 100644 --- a/libs/node/src/ecflow/node/ServerState.hpp +++ b/libs/node/src/ecflow/node/ServerState.hpp @@ -12,11 +12,12 @@ #define ecflow_node_ServerState_HPP #include +#include #include "ecflow/attribute/Variable.hpp" #include "ecflow/core/SState.hpp" #include "ecflow/node/NodeFwd.hpp" -#include "ecflow/node/Permissions.hpp" +#include "ecflow/node/permissions/Permissions.hpp" /// This class stores the server state, so that it is accessible by the node tree /// @@ -49,7 +50,18 @@ class ServerState { /// This does compare server variables. Used in testing bool compare(const ServerState& rhs) const; - ecf::Permissions permissions() const { return ecf::Permissions::find_in(user_variables_); } + /** + * Returns the permissions, as defined at server level. + * + * The permissions can be either defined in the server configuration file (server variables), or + * explicitly set by the user (user variables). The permissions set by the user override the + * ones defined in the server configuration file. + * + * If permissions are not defined, an empty Permissions object is returned. + * + * @return the server level permissions + */ + ecf::Permissions permissions() const; void sort_variables(); diff --git a/libs/node/src/ecflow/node/Submittable.cpp b/libs/node/src/ecflow/node/Submittable.cpp index ced0cff7a..36065897a 100644 --- a/libs/node/src/ecflow/node/Submittable.cpp +++ b/libs/node/src/ecflow/node/Submittable.cpp @@ -161,12 +161,12 @@ void Submittable::begin() { #endif } -void Submittable::requeue(Requeue_args& args) { +void Submittable::requeue(Requeue_args& args, const ecf::AuthorisationContext& authorisation) { /// It is *very* important that we reset the passwords. This allows us to detect zombies. tryNo_ = 0; // reset try number clear(); // jobs password, process_id, aborted_reason - Node::requeue(args); + Node::requeue(args, authorisation); update_generated_variables(); #ifdef DEBUG_STATE_CHANGE_NO diff --git a/libs/node/src/ecflow/node/Submittable.hpp b/libs/node/src/ecflow/node/Submittable.hpp index acb7ccfd9..bf0c3bea2 100644 --- a/libs/node/src/ecflow/node/Submittable.hpp +++ b/libs/node/src/ecflow/node/Submittable.hpp @@ -58,7 +58,7 @@ class Submittable : public Node { /// there are multiple runs. re-queue/begin() resets the try Number void reset() override; void begin() override; - void requeue(Requeue_args&) override; + void requeue(Requeue_args& args, const ecf::AuthorisationContext& authorisation) override; bool run(JobsParam& jobsParam, bool force) override; void kill(const std::string& zombie_pid = "") override; void status() override; diff --git a/libs/node/src/ecflow/node/Suite.cpp b/libs/node/src/ecflow/node/Suite.cpp index ff70d0173..e1beffca9 100644 --- a/libs/node/src/ecflow/node/Suite.cpp +++ b/libs/node/src/ecflow/node/Suite.cpp @@ -149,7 +149,7 @@ void Suite::begin() { } } -void Suite::requeue(Requeue_args& args) { +void Suite::requeue(Requeue_args& args, const ecf::AuthorisationContext& authorisation) { if (false == begun_) { throw std::runtime_error(MESSAGE("Suite::requeue: The suite " << name() << " must be 'begun' first\n")); } @@ -165,7 +165,7 @@ void Suite::requeue(Requeue_args& args) { requeue_calendar(); - NodeContainer::requeue(args); + NodeContainer::requeue(args, authorisation); update_generated_variables(); } @@ -562,9 +562,13 @@ bool Suite::checkInvariants(std::string& errorMsg) const { return NodeContainer::checkInvariants(errorMsg); } -void Suite::collateChanges(DefsDelta& changes) const { +void Suite::collateChanges(DefsDelta& changes, const ecf::AuthorisationContext& ctx) const { /// The suite hold the max state change no, for all its children and attributes + if (!ctx.allows(this->absNodePath(), ecf::Allowed::READ)) { + return; + } + // Optimising updates: // Problem: // User has requested 1 second updated in the viewer. We used add SuiteCalendarMemento @@ -636,7 +640,7 @@ void Suite::collateChanges(DefsDelta& changes) const { // Traversal, we have finished with this node: // Traverse children : *SEPARATE* compound_memento_ptr created on demand - NodeContainer::collateChanges(changes); + NodeContainer::collateChanges(changes, ctx); /// *ONLY* create SuiteCalendarMemento, if something changed in the suite. /// *OR* if it has been specifically requested. see ECFLOW-631 diff --git a/libs/node/src/ecflow/node/Suite.hpp b/libs/node/src/ecflow/node/Suite.hpp index aa53bc707..06711c343 100644 --- a/libs/node/src/ecflow/node/Suite.hpp +++ b/libs/node/src/ecflow/node/Suite.hpp @@ -13,6 +13,7 @@ #include "ecflow/attribute/ClockAttr.hpp" // IWYU pragma: keep #include "ecflow/core/Calendar.hpp" +#include "ecflow/node/Node.hpp" #include "ecflow/node/NodeContainer.hpp" class SuiteGenVariables; @@ -53,7 +54,8 @@ class Suite final : public NodeContainer { void reset() override; void handle_migration(const ecf::Calendar&) override; void begin() override; - void requeue(Requeue_args& args) override; + using Node::requeue; + void requeue(Requeue_args& args, const ecf::AuthorisationContext& authorisation) override; bool begun() const { return begun_; } void reset_begin(); void update_generated_variables() const override; @@ -87,7 +89,7 @@ class Suite final : public NodeContainer { bool checkInvariants(std::string& errorMsg) const override; // Memento functions - void collateChanges(DefsDelta&) const override; + void collateChanges(DefsDelta& changes, const ecf::AuthorisationContext& ctx) const override; void set_memento(const SuiteClockMemento*, std::vector& aspects, bool); void set_memento(const SuiteBeginDeltaMemento*, std::vector& aspects, bool); void set_memento(const SuiteCalendarMemento*, std::vector& aspects, bool); diff --git a/libs/node/src/ecflow/node/Task.cpp b/libs/node/src/ecflow/node/Task.cpp index 4ffd48894..961ed190f 100644 --- a/libs/node/src/ecflow/node/Task.cpp +++ b/libs/node/src/ecflow/node/Task.cpp @@ -320,14 +320,14 @@ void Task::begin() { #endif } -void Task::requeue(Requeue_args& args) { +void Task::requeue(Requeue_args& args, const ecf::AuthorisationContext& authorisation) { if (aliases_.empty()) { if (alias_no_ != 0) { reset_alias_number(); } } - Submittable::requeue(args); + Submittable::requeue(args, authorisation); #ifdef DEBUG_STATE_CHANGE_NO std::cout << "Task::requeue\n"; @@ -729,7 +729,12 @@ const std::string& Task::script_extension() const { return ecf::File::ECF_EXTN(); // ".ecf" } -void Task::collateChanges(DefsDelta& changes) const { +void Task::collateChanges(DefsDelta& changes, const ecf::AuthorisationContext& ctx) const { + + if (!ctx.allows(this->absNodePath(), ecf::Allowed::READ)) { + return; + } + // std::cout << "Task::collateChanges " << debugNodePath() // << " changes.client_state_change_no() = " << changes.client_state_change_no() // << " add_remove_state_change_no_ = " << add_remove_state_change_no_ @@ -777,7 +782,7 @@ void Task::collateChanges(DefsDelta& changes) const { // Traversal to children for (auto& alias : aliases_) { - alias->collateChanges(changes); + alias->collateChanges(changes, ctx); } } diff --git a/libs/node/src/ecflow/node/Task.hpp b/libs/node/src/ecflow/node/Task.hpp index 9986cf73f..ed8d54571 100644 --- a/libs/node/src/ecflow/node/Task.hpp +++ b/libs/node/src/ecflow/node/Task.hpp @@ -68,7 +68,8 @@ class Task final : public Submittable { /// there are multiple runs. re-queue/begin() resets the try Number void reset() override; void begin() override; - void requeue(Requeue_args&) override; + using Node::requeue; + void requeue(Requeue_args& args, const ecf::AuthorisationContext& authorisation) override; Suite* suite() const override { return parent()->suite(); } Defs* defs() const override { @@ -96,7 +97,7 @@ class Task final : public Submittable { bool checkInvariants(std::string& errorMsg) const override; - void collateChanges(DefsDelta&) const override; + void collateChanges(DefsDelta& changes, const ecf::AuthorisationContext& ctx) const override; void set_memento(const OrderMemento* m, std::vector& aspects, bool); void set_memento(const AliasChildrenMemento* m, std::vector& aspects, bool); void set_memento(const AliasNumberMemento* m, std::vector& aspects, bool); diff --git a/libs/node/src/ecflow/node/formatter/DefsWriter.hpp b/libs/node/src/ecflow/node/formatter/DefsWriter.hpp index 19772e3d0..eb8763502 100644 --- a/libs/node/src/ecflow/node/formatter/DefsWriter.hpp +++ b/libs/node/src/ecflow/node/formatter/DefsWriter.hpp @@ -11,6 +11,10 @@ #ifndef ecflow_node_formatter_DefsWriter_HPP #define ecflow_node_formatter_DefsWriter_HPP +#include + +#include + #include "ecflow/attribute/AutoArchiveAttr.hpp" #include "ecflow/attribute/AutoCancelAttr.hpp" #include "ecflow/attribute/LateAttr.hpp" @@ -38,7 +42,7 @@ struct Style Style(PrintStyle::Type_t s) : selected_(s) {} - PrintStyle::Type_t selected() { return selected_; } + PrintStyle::Type_t selected() const { return selected_; } template bool is_one_of() const { @@ -66,30 +70,33 @@ struct Format uint32_t indentation_spaces() const { return indentation_width * indentation_level; } }; -struct Context +struct FormatContext { Style style; Format format; + const AuthorisationContext* authorisation; - static Context make_for(PrintStyle::Type_t style) { + static FormatContext make_for(PrintStyle::Type_t style) { return make_for(style, nullptr); } + + static FormatContext make_for(PrintStyle::Type_t style, const AuthorisationContext* authorisation) { switch (style) { case PrintStyle::DEFS: - return Context{Style{PrintStyle::DEFS}, Format{true, 2, 0}}; + return FormatContext{Style{PrintStyle::DEFS}, Format{true, 2, 0}, authorisation}; case PrintStyle::STATE: - return Context{Style{PrintStyle::STATE}, Format{false, 0, 0}}; + return FormatContext{Style{PrintStyle::STATE}, Format{false, 0, 0}, authorisation}; case PrintStyle::NET: - return Context{Style{PrintStyle::NET}, Format{false, 0, 0}}; + return FormatContext{Style{PrintStyle::NET}, Format{false, 0, 0}, authorisation}; case PrintStyle::MIGRATE: - return Context{Style{PrintStyle::MIGRATE}, Format{false, 0, 0}}; + return FormatContext{Style{PrintStyle::MIGRATE}, Format{false, 0, 0}, authorisation}; default: - return Context{Style{PrintStyle::NOTHING}, Format{true, 2, 0}}; + return FormatContext{Style{PrintStyle::NOTHING}, Format{true, 2, 0}, authorisation}; } } }; struct Indent { - Indent(Context& ctx) + Indent(FormatContext& ctx) : ctx_(ctx) { ctx_.format.increase_indentation(); } @@ -107,7 +114,7 @@ struct Indent } private: - Context& ctx_; + FormatContext& ctx_; }; /* ************************************************************************** */ @@ -128,7 +135,7 @@ struct Writer namespace detail { template -bool write_ast_derived_type(Stream& output, const Ast* root, Context& ctx) { +bool write_ast_derived_type(Stream& output, const Ast* root, FormatContext& ctx) { if (auto x = dynamic_cast(root); x) { Writer::write(output, *x, ctx); return true; @@ -137,12 +144,12 @@ bool write_ast_derived_type(Stream& output, const Ast* root, Context& ctx) { } template -void write_ast_derived_types(Stream& output, const Ast* root, Context& ctx) { +void write_ast_derived_types(Stream& output, const Ast* root, FormatContext& ctx) { (write_ast_derived_type(output, root, ctx) || ...); } template -void write_ast_type(Stream& output, const Ast* root, Context& ctx) { +void write_ast_type(Stream& output, const Ast* root, FormatContext& ctx) { write_ast_derived_types struct Writer { - static void write(Stream& output, const Ast& item, Context& ctx) { detail::write_ast_type(output, &item, ctx); } + static void write(Stream& output, const Ast& item, FormatContext& ctx) { + detail::write_ast_type(output, &item, ctx); + } }; template struct Writer { - static void write(Stream& output, const AstTop& item, Context& ctx) { + static void write(Stream& output, const AstTop& item, FormatContext& ctx) { // taken from AstTop::print(std::string& os) const Indent l1(ctx); @@ -197,7 +206,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const AstRoot& item, Context& ctx) { + static void write(Stream& output, const AstRoot& item, FormatContext& ctx) { // taken from AstRoot::print(std::string& os) const if (const auto* left = item.left(); left) { @@ -213,7 +222,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const AstNot& item, Context& ctx) { + static void write(Stream& output, const AstNot& item, FormatContext& ctx) { // taken from AstNot::print(std::string& os) const Indent l1(ctx); @@ -240,7 +249,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const AstPlus& item, Context& ctx) { + static void write(Stream& output, const AstPlus& item, FormatContext& ctx) { // taken from AstPlus::print(std::string& os) const Indent l1(ctx); @@ -270,7 +279,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const AstMinus& item, Context& ctx) { + static void write(Stream& output, const AstMinus& item, FormatContext& ctx) { // taken from AstMinus::print(std::string& os) const Indent l1(ctx); @@ -300,7 +309,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const AstDivide& item, Context& ctx) { + static void write(Stream& output, const AstDivide& item, FormatContext& ctx) { // taken from AstDivide::print(std::string& os) const Indent l1(ctx); @@ -330,7 +339,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const AstMultiply& item, Context& ctx) { + static void write(Stream& output, const AstMultiply& item, FormatContext& ctx) { // taken from AstMultiply::print(std::string& os) const Indent l1(ctx); @@ -360,7 +369,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const AstModulo& item, Context& ctx) { + static void write(Stream& output, const AstModulo& item, FormatContext& ctx) { // taken from AstModulo::print(std::string& os) const Indent l1(ctx); @@ -390,7 +399,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const AstAnd& item, Context& ctx) { + static void write(Stream& output, const AstAnd& item, FormatContext& ctx) { // taken from AstAnd::print(std::string& os) const Indent l1(ctx); @@ -420,7 +429,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const AstOr& item, Context& ctx) { + static void write(Stream& output, const AstOr& item, FormatContext& ctx) { // taken from AstOr::print(std::string& os) const Indent l1(ctx); @@ -450,7 +459,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const AstEqual& item, Context& ctx) { + static void write(Stream& output, const AstEqual& item, FormatContext& ctx) { // taken from AstEqual::print(std::string& os) const Indent l1(ctx); @@ -480,7 +489,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const AstNotEqual& item, Context& ctx) { + static void write(Stream& output, const AstNotEqual& item, FormatContext& ctx) { // taken from AstNotEqual::print(std::string& os) const Indent l1(ctx); @@ -510,7 +519,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const AstLessEqual& item, Context& ctx) { + static void write(Stream& output, const AstLessEqual& item, FormatContext& ctx) { // taken from AstLessEqual::print(std::string& os) const Indent l1(ctx); @@ -541,7 +550,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const AstGreaterEqual& item, Context& ctx) { + static void write(Stream& output, const AstGreaterEqual& item, FormatContext& ctx) { // taken from AstGreaterEqual::print(std::string& os) const Indent l1(ctx); @@ -571,7 +580,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const AstGreaterThan& item, Context& ctx) { + static void write(Stream& output, const AstGreaterThan& item, FormatContext& ctx) { // taken from AstGreaterThan::print(std::string& os) const Indent l1(ctx); @@ -601,7 +610,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const AstLessThan& item, Context& ctx) { + static void write(Stream& output, const AstLessThan& item, FormatContext& ctx) { // taken from AstLessThan::print(std::string& os) const Indent l1(ctx); @@ -631,7 +640,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const AstFunction& item, Context& ctx) { + static void write(Stream& output, const AstFunction& item, FormatContext& ctx) { // taken from AstFunction::print(std::string& os) const Indent l1(ctx); @@ -662,7 +671,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const AstInteger& item, Context& ctx) { + static void write(Stream& output, const AstInteger& item, FormatContext& ctx) { // taken from AstInteger::print(std::string& os) const Indent l1(ctx); @@ -683,7 +692,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const AstInstant& item, Context& ctx) { + static void write(Stream& output, const AstInstant& item, FormatContext& ctx) { // taken from AstInstant::print(std::string& os) const Indent l1(ctx); @@ -704,7 +713,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const AstNodeState& item, Context& ctx) { + static void write(Stream& output, const AstNodeState& item, FormatContext& ctx) { // taken from AstNodeState::print(std::string& os) const Indent l1(ctx); @@ -728,7 +737,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const AstEventState& item, Context& ctx) { + static void write(Stream& output, const AstEventState& item, FormatContext& ctx) { // taken from AstEventState::print(std::string& os) const Indent l1(ctx); @@ -749,7 +758,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const AstNode& item, Context& ctx) { + static void write(Stream& output, const AstNode& item, FormatContext& ctx) { // taken from AstNode::print(std::string& os) const Indent l1(ctx); @@ -786,7 +795,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const AstFlag& item, Context& ctx) { + static void write(Stream& output, const AstFlag& item, FormatContext& ctx) { // taken from AstFlag::print(std::string& os) const Indent l1(ctx); @@ -821,7 +830,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const AstVariable& item, Context& ctx) { + static void write(Stream& output, const AstVariable& item, FormatContext& ctx) { // taken from AstVariable::print(std::string& os) const Indent l1(ctx); @@ -859,7 +868,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const AstParentVariable& item, Context& ctx) { + static void write(Stream& output, const AstParentVariable& item, FormatContext& ctx) { // taken from AstParentVariable::print(std::string& os) const Indent l1(ctx); @@ -893,8 +902,11 @@ struct Writer template struct Writer { - static void - write(Stream& output, const PartExpression& item, Context& ctx, const std::string& expression_type, bool is_free) { + static void write(Stream& output, + const PartExpression& item, + FormatContext& ctx, + const std::string& expression_type, + bool is_free) { // taken from PartExpression::print(std::string& os, const std::string& exprType, bool isFree) const Indent l1(ctx); @@ -908,7 +920,7 @@ struct Writer static void writeln(Stream& output, const PartExpression& item, - Context& ctx, + FormatContext& ctx, const std::string& expression_type, bool is_free) { output << expression_type; @@ -941,7 +953,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const Expression& item, Context& ctx, const std::string& expression_type) { + static void write(Stream& output, const Expression& item, FormatContext& ctx, const std::string& expression_type) { // taken from Expression::print(std::string& os, const std::string& exprType) const for (const auto& part : item.expr()) { @@ -957,7 +969,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const AutoArchiveAttr& item, Context& ctx) { + static void write(Stream& output, const AutoArchiveAttr& item, FormatContext& ctx) { // taken from AutoArchiveAttr::print(std::string& os) const Indent l1(ctx); @@ -975,7 +987,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const AutoCancelAttr& item, Context& ctx) { + static void write(Stream& output, const AutoCancelAttr& item, FormatContext& ctx) { // taken from AutoCancelAttr::print(std::string& os) const Indent l1(ctx); @@ -992,7 +1004,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const AutoRestoreAttr& item, Context& ctx) { + static void write(Stream& output, const AutoRestoreAttr& item, FormatContext& ctx) { // taken from AutoRestoreAttr::print(std::string& os) const Indent l1(ctx); @@ -1009,7 +1021,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const AvisoAttr& item, Context& ctx) { + static void write(Stream& output, const AvisoAttr& item, FormatContext& ctx) { // taken from AvisoAttr::print(std::string& os) const Indent l1(ctx); @@ -1027,7 +1039,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const ClockAttr& item, Context& ctx) { + static void write(Stream& output, const ClockAttr& item, FormatContext& ctx) { // taken from ClockAttr::print(std::string& os) const Indent l1(ctx); @@ -1044,7 +1056,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const CronAttr& item, Context& ctx) { + static void write(Stream& output, const CronAttr& item, FormatContext& ctx) { // taken from CronAttr::print(std::string& os) const Indent l1(ctx); @@ -1056,7 +1068,7 @@ struct Writer output << "\n"; } - static void writeln(Stream& output, const CronAttr& item, Context& ctx) { + static void writeln(Stream& output, const CronAttr& item, FormatContext& ctx) { item.write(output.buf); if (ctx.style.is_not_one_of()) { item.time_series().write_state(output.buf, item.isSetFree()); @@ -1067,7 +1079,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const DateAttr& item, Context& ctx) { + static void write(Stream& output, const DateAttr& item, FormatContext& ctx) { // taken from DayAttr::print(std::string& os) const Indent l1(ctx); @@ -1079,7 +1091,7 @@ struct Writer output << "\n"; } - static void writeln(Stream& output, const DateAttr& item, Context& ctx) { + static void writeln(Stream& output, const DateAttr& item, FormatContext& ctx) { item.write(output.buf); if (ctx.style.is_not_one_of()) { if (item.isSetFree()) { @@ -1092,7 +1104,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const DayAttr& item, Context& ctx) { + static void write(Stream& output, const DayAttr& item, FormatContext& ctx) { // taken from DayAttr::print(std::string& os) const Indent l1(ctx); @@ -1104,7 +1116,7 @@ struct Writer output << "\n"; } - static void writeln(Stream& output, const DayAttr& item, Context& ctx) { + static void writeln(Stream& output, const DayAttr& item, FormatContext& ctx) { item.write(output.buf); if (ctx.style.is_not_one_of()) { bool added_hash = false; @@ -1134,7 +1146,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const DState::State& item, Context& ctx) { + static void write(Stream& output, const DState::State& item, FormatContext& ctx) { if (item != DState::default_state()) { Indent l1(ctx); @@ -1156,7 +1168,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const Event& item, Context& ctx) { + static void write(Stream& output, const Event& item, FormatContext& ctx) { // taken from Event::print(std::string& os) const Indent l1(ctx); @@ -1168,7 +1180,7 @@ struct Writer output << "\n"; } - static void writeln(Stream& output, const Event& item, Context& ctx) { + static void writeln(Stream& output, const Event& item, FormatContext& ctx) { item.write(output.buf); if (ctx.style.is_not_one_of()) { if (item.initial_value() != item.value()) { // initial value and value differ @@ -1187,7 +1199,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const GenericAttr& item, Context& ctx) { + static void write(Stream& output, const GenericAttr& item, FormatContext& ctx) { // taken from GenericAttr::print(std::string& os) const Indent l1(ctx); @@ -1204,7 +1216,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const InLimit& item, Context& ctx) { + static void write(Stream& output, const InLimit& item, FormatContext& ctx) { // taken from InLimit::print(std::string& os) const Indent l1(ctx); @@ -1216,7 +1228,7 @@ struct Writer output << "\n"; } - static void writeln(Stream& output, const InLimit& item, const Context& ctx) { + static void writeln(Stream& output, const InLimit& item, const FormatContext& ctx) { item.write(output.buf); if (ctx.style.is_not_one_of()) { @@ -1242,7 +1254,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const Label& item, Context& ctx) { + static void write(Stream& output, const Label& item, FormatContext& ctx) { // taken from Meter::print(std::string& os) const Indent l1(ctx); @@ -1254,7 +1266,7 @@ struct Writer output << "\n"; } - static void writeln(Stream& output, const Label& item, const Context& ctx) { + static void writeln(Stream& output, const Label& item, const FormatContext& ctx) { item.write(output.buf); if (ctx.style.is_not_one_of()) { if (!item.new_value().empty()) { @@ -1278,7 +1290,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const LateAttr& item, Context& ctx) { + static void write(Stream& output, const LateAttr& item, FormatContext& ctx) { // taken from LateAttr::print(std::string& os) const Indent l1(ctx); @@ -1290,7 +1302,7 @@ struct Writer output << "\n"; } - static void writeln(Stream& output, const LateAttr& item, const Context& ctx) { + static void writeln(Stream& output, const LateAttr& item, const FormatContext& ctx) { item.write(output.buf); if (ctx.style.is_not_one_of()) { if (item.isLate()) { @@ -1303,7 +1315,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const Limit& item, Context& ctx) { + static void write(Stream& output, const Limit& item, FormatContext& ctx) { // taken from Limit::print(std::string& os) const Indent l1(ctx); @@ -1314,7 +1326,7 @@ struct Writer output << "\n"; } - static void writeln(Stream& output, const Limit& item, const Context& ctx) { + static void writeln(Stream& output, const Limit& item, const FormatContext& ctx) { item.write(output.buf); if (ctx.style.is_not_one_of()) { @@ -1333,7 +1345,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const Meter& item, Context& ctx) { + static void write(Stream& output, const Meter& item, FormatContext& ctx) { // taken from Meter::print(std::string& os) const Indent l1(ctx); @@ -1345,7 +1357,7 @@ struct Writer output << "\n"; } - static void writeln(Stream& output, const Meter& item, const Context& ctx) { + static void writeln(Stream& output, const Meter& item, const FormatContext& ctx) { item.write(output.buf); if (ctx.style.is_not_one_of()) { if (item.value() != item.min()) { @@ -1359,7 +1371,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const MirrorAttr& item, Context& ctx) { + static void write(Stream& output, const MirrorAttr& item, FormatContext& ctx) { // taken from MirrorAttr::print(std::string& os) const Indent l1(ctx); @@ -1377,7 +1389,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const QueueAttr& item, Context& ctx) { + static void write(Stream& output, const QueueAttr& item, FormatContext& ctx) { // taken from QueueAttr::print(std::string& os) const Indent l1(ctx); @@ -1389,7 +1401,7 @@ struct Writer output << "\n"; } - static void writeln(Stream& output, const QueueAttr& item, const Context& ctx) { + static void writeln(Stream& output, const QueueAttr& item, const FormatContext& ctx) { item.write(output.buf); if (ctx.style.is_not_one_of()) { output << " # "; @@ -1405,7 +1417,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const RepeatInteger& item, Context& ctx) { + static void write(Stream& output, const RepeatInteger& item, FormatContext& ctx) { // taken from RepeatInteger::write(std::string& os) const Indent l1(ctx); @@ -1417,7 +1429,7 @@ struct Writer output << "\n"; } - static void writeln(Stream& output, const RepeatInteger& item, const Context& ctx) { + static void writeln(Stream& output, const RepeatInteger& item, const FormatContext& ctx) { output << "repeat integer "; output << item.name(); output << " "; @@ -1439,7 +1451,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const RepeatDate& item, Context& ctx) { + static void write(Stream& output, const RepeatDate& item, FormatContext& ctx) { // taken from RepeatDate::write(std::string& os) const Indent l1(ctx); @@ -1451,7 +1463,7 @@ struct Writer output << "\n"; } - static void writeln(Stream& output, const RepeatDate& item, const Context& ctx) { + static void writeln(Stream& output, const RepeatDate& item, const FormatContext& ctx) { output << "repeat date "; output << item.name(); output << " "; @@ -1471,7 +1483,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const RepeatDateList& item, Context& ctx) { + static void write(Stream& output, const RepeatDateList& item, FormatContext& ctx) { // taken from RepeatDateList::write(std::string& os) const Indent l1(ctx); @@ -1482,7 +1494,7 @@ struct Writer output << "\n"; } - static void writeln(Stream& output, const RepeatDateList& item, const Context& ctx) { + static void writeln(Stream& output, const RepeatDateList& item, const FormatContext& ctx) { output << "repeat datelist "; output << item.name(); for (auto date : item.values()) { @@ -1500,7 +1512,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const RepeatDateTimeList& item, Context& ctx) { + static void write(Stream& output, const RepeatDateTimeList& item, FormatContext& ctx) { Indent l1(ctx); output << l1; @@ -1509,7 +1521,7 @@ struct Writer output << "\n"; } - static void writeln(Stream& output, const RepeatDateTimeList& item, const Context& ctx) { + static void writeln(Stream& output, const RepeatDateTimeList& item, const FormatContext& ctx) { output << "repeat datetimelist "; output << item.name(); for (const auto& instant : item.values()) { @@ -1527,7 +1539,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const RepeatDateTime& item, Context& ctx) { + static void write(Stream& output, const RepeatDateTime& item, FormatContext& ctx) { // taken from RepeatDateTime::write(std::string& os) const Indent l1(ctx); @@ -1539,7 +1551,7 @@ struct Writer output << "\n"; } - static void writeln(Stream& output, const RepeatDateTime& item, Context& ctx) { + static void writeln(Stream& output, const RepeatDateTime& item, FormatContext& ctx) { output << "repeat datetime "; output << item.name(); output << " "; @@ -1561,7 +1573,7 @@ template struct Writer { /* RepeatEnumerated */ - static void write(Stream& output, const RepeatEnumerated& item, Context& ctx) { + static void write(Stream& output, const RepeatEnumerated& item, FormatContext& ctx) { // taken from RepeatEnumerated::write(std::string& os) const Indent l1(ctx); @@ -1573,7 +1585,7 @@ struct Writer output << "\n"; } - static void writeln(Stream& output, const RepeatEnumerated& item, const Context& ctx) { + static void writeln(Stream& output, const RepeatEnumerated& item, const FormatContext& ctx) { output << "repeat enumerated "; output << item.name(); for (const auto& value : item.values()) { @@ -1592,7 +1604,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const RepeatString& item, Context& ctx) { + static void write(Stream& output, const RepeatString& item, FormatContext& ctx) { // taken from RepeatString::write(std::string& os) const Indent l1(ctx); @@ -1604,7 +1616,7 @@ struct Writer output << "\n"; } - static void writeln(Stream& output, const RepeatString& item, const Context& ctx) { + static void writeln(Stream& output, const RepeatString& item, const FormatContext& ctx) { output << "repeat string "; output << item.name(); for (const std::string& s : item.values()) { @@ -1623,7 +1635,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const RepeatDay& item, Context& ctx) { + static void write(Stream& output, const RepeatDay& item, FormatContext& ctx) { // taken from RepeatDay::write(std::string& os) const Indent l1(ctx); @@ -1644,7 +1656,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const RepeatBase& item, Context& ctx) { + static void write(Stream& output, const RepeatBase& item, FormatContext& ctx) { // taken from Repeat::print(std::string& os) const if (auto r = dynamic_cast(&item)) { @@ -1680,7 +1692,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const Repeat& item, Context& ctx) { + static void write(Stream& output, const Repeat& item, FormatContext& ctx) { // taken from Repeat::print(std::string& os) const const RepeatBase* base = item.repeatBase(); @@ -1693,7 +1705,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const TimeAttr& item, Context& ctx) { + static void write(Stream& output, const TimeAttr& item, FormatContext& ctx) { // taken from TimeAttr::print(std::string& os) const Indent l1(ctx); @@ -1704,7 +1716,7 @@ struct Writer output << "\n"; } - static void writeln(Stream& output, const TimeAttr& item, const Context& ctx) { + static void writeln(Stream& output, const TimeAttr& item, const FormatContext& ctx) { item.write(output.buf); if (ctx.style.is_not_one_of()) { item.time_series().write_state(output.buf, item.isSetFree()); @@ -1715,7 +1727,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const TodayAttr& item, Context& ctx) { + static void write(Stream& output, const TodayAttr& item, FormatContext& ctx) { // taken from TodayAttr::print(std::string& os) const Indent l1(ctx); @@ -1727,7 +1739,7 @@ struct Writer output << "\n"; } - static void writeln(Stream& output, const TodayAttr& item, const Context& ctx) { + static void writeln(Stream& output, const TodayAttr& item, const FormatContext& ctx) { item.write(output.buf); if (ctx.style.is_not_one_of()) { item.time_series().write_state(output.buf, item.isSetFree()); @@ -1749,7 +1761,7 @@ struct Writer static void write(Stream& output, const Variable& item, - Context& ctx, + FormatContext& ctx, const std::string& prefix = empty, const std::string& suffix = empty) { // taken from Variable::print(std::string& os) const @@ -1777,7 +1789,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const VerifyAttr& item, Context& ctx) { + static void write(Stream& output, const VerifyAttr& item, FormatContext& ctx) { // taken from VerifyAttr::print(std::string& os) const Indent l1(ctx); @@ -1789,7 +1801,7 @@ struct Writer output << "\n"; } - static void writeln(Stream& output, const VerifyAttr& item, const Context& ctx) { + static void writeln(Stream& output, const VerifyAttr& item, const FormatContext& ctx) { output << item.toString(); if (ctx.style.is_not_one_of()) { output << " # "; @@ -1801,7 +1813,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const ZombieAttr& item, Context& ctx) { + static void write(Stream& output, const ZombieAttr& item, FormatContext& ctx) { // taken from ZombieAttr::print(std::string& os) const Indent l1(ctx); @@ -1823,9 +1835,23 @@ struct Writer template struct Writer { - static void write(Stream& output, const Alias& item, Context& ctx) { + static void write(Stream& output, const Alias& item, FormatContext& ctx) { // taken from Alias::print(std::string& os) const + if (auto* defs = item.defs(); defs) { + // An Alias can be created in isolation, e.g. by using the Python API. + // When a node is not included in a Defs, it does not make sense to perform Permission checks. + + // Check if allowed accress to this Alias + if (const auto* service = ctx.authorisation; service) { + auto allowed = service->allows(item.absNodePath(), Allowed::READ); + if (!allowed) { + // User is not allowed to read this Alias, so we skip writing it + return; + } + } + } + Indent l1(ctx); // Write the alias header @@ -1848,9 +1874,23 @@ struct Writer template struct Writer { - static void write(Stream& output, const Family& item, Context& ctx) { + static void write(Stream& output, const Family& item, FormatContext& ctx) { // taken from Family::print(std::string& os) const + if (auto* defs = item.defs(); defs) { + // A Family can be created in isolation, e.g. by using the Python API. + // When a node is not included in a Defs, it does not make sense to perform Permission checks. + + // Check if allowed accress to this Family + if (const auto* service = ctx.authorisation; service) { + auto allowed = service->allows(item.absNodePath(), Allowed::READ); + if (!allowed) { + // User is not allowed to read this Family, so we skip writing it + return; + } + } + } + Indent l1(ctx); // Write the family header @@ -1880,9 +1920,23 @@ struct Writer template struct Writer { - static void write(Stream& output, const Task& item, Context& ctx) { + static void write(Stream& output, const Task& item, FormatContext& ctx) { // taken from Task::print(std::string& os) const + if (auto* defs = item.defs(); defs) { + // A Task can be created in isolation, e.g. by using the Python API. + // When a node is not included in a Defs, it does not make sense to perform Permission checks. + + // Check if allowed accress to this Task + if (const auto* service = ctx.authorisation; service) { + auto allowed = service->allows(item.absNodePath(), Allowed::READ); + if (!allowed) { + // User is not allowed to read this Task, so we skip writing it + return; + } + } + } + Indent l1(ctx); // Write the task header @@ -1919,7 +1973,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const Node* item, Context& ctx) { + static void write(Stream& output, const Node* item, FormatContext& ctx) { // Note: here we must use dynamic_cast to determine the type of the node // This is because we are using a templated Writer, and we need to call the correct @@ -1946,7 +2000,7 @@ struct Writer template struct Writer { - static void write(Stream& output, Node* item, Context& ctx) { + static void write(Stream& output, Node* item, FormatContext& ctx) { return Writer::write(output, item, ctx); } }; @@ -1954,7 +2008,7 @@ struct Writer template struct Writer, Stream> { - static void write(Stream& output, const std::shared_ptr& item, Context& ctx) { + static void write(Stream& output, const std::shared_ptr& item, FormatContext& ctx) { return Writer::write(output, item.get(), ctx); } }; @@ -1962,7 +2016,7 @@ struct Writer, Stream> template struct Writer { - static void write(Stream& output, const Node& item, Context& ctx) { + static void write(Stream& output, const Node& item, FormatContext& ctx) { // taken from Node::print(std::string& os) const Writer::write(output, item.defStatus(), ctx); @@ -2097,7 +2151,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const NodeContainer& item, Context& ctx) { + static void write(Stream& output, const NodeContainer& item, FormatContext& ctx) { // taken from NodeContainer::print(std::string& os) const for (const auto& node : item.children()) { @@ -2128,9 +2182,23 @@ struct Writer template struct Writer { - static void write(Stream& output, const Suite& item, Context& ctx) { + static void write(Stream& output, const Suite& item, FormatContext& ctx) { // taken from Suite::print(std::string& os) const + if (auto* defs = item.defs(); defs) { + // A Suite can be created in isolation, e.g. by using the Python API. + // When a node is not included in a Defs, it does not make sense to perform Permission checks. + + // Check if allowed accress to this Suite + if (const auto* service = ctx.authorisation; service) { + auto allowed = service->allows(item.absNodePath(), Allowed::READ); + if (!allowed) { + // User is not allowed to read this Suite, so we skip writing it + return; + } + } + } + // Write the suite header output << "suite "; output << item.name(); @@ -2180,7 +2248,7 @@ struct Writer template struct Writer { - static void write(Stream& output, const Defs& item, Context& ctx) { + static void write(Stream& output, const Defs& item, FormatContext& ctx) { // taken from Defs::print(std::string& os) const output << "#"; @@ -2219,7 +2287,7 @@ struct Writer } private: - static void write_state(Stream& output, const Defs& item, Context& ctx) { + static void write_state(Stream& output, const Defs& item, FormatContext& ctx) { // taken from Defs::write_state(std::string& os) const // *IMPORTANT* we *CANT* use ';' character, since is used in the parser, when we have @@ -2317,22 +2385,48 @@ struct Writer /* *** Write entry point **************************************************** */ /* ************************************************************************** */ +/// +/// @brief Write an item to a buffer, using the specified context +/// +/// @tparam T the type of item to write +/// @param buffer the buffer to write to +/// @param item the item to write +/// @param ctx the context for writing +/// template -inline void write_t(std::string& buffer, const T& item, Context& ctx) { +inline void write_t(std::string& buffer, const T& item, FormatContext& ctx) { buffer.reserve(1024 * 4); // Should be using a sensible default size for the buffer stringstreambuf output{buffer}; implementation::Writer::write(output, item, ctx); } +/// +/// @brief Write an item to a stream, using the specified context +/// +/// @tparam Stream @tparam Stream the type of the output stream +/// @tparam T the type of item to write +/// @param buffer the buffer to write to +/// @param item the item to write +/// @param ctx the context for writing +/// template -inline void write_t(Stream& output, const T& item, Context& ctx) { +inline void write_t(Stream& output, const T& item, FormatContext& ctx) { std::string buffer; write_t(buffer, item, ctx); output << buffer; } +/// +/// @brief Convert an item to a string, using the specified context +/// +/// @tparam T the type of item to write +/// @param buffer the buffer to write to +/// @param item the item to write +/// @param ctx the context for writing +/// @return the item as a string +/// template -inline std::string as_string(const T& item, Context& ctx) { +inline std::string as_string(const T& item, FormatContext& ctx) { std::string buffer; write_t(buffer, item, ctx); return buffer; @@ -2342,21 +2436,46 @@ inline std::string as_string(const T& item, Context& ctx) { /* *** Write entry point (with PrintStyle) ********************************** */ /* ************************************************************************** */ +/// +/// @brief Write an expression to a buffer, using the specified PrintStyle and type of the expression +/// +/// @param buffer the buffer to write to +/// @param item the expression to write +/// @param style the print style to use +/// @param type the type of expression (either "trigger" or "complete") +/// inline void write_t(std::string& buffer, const Expression& item, PrintStyle::Type_t style, const std::string& type) { buffer.reserve(1024 * 4); // Should be using a sensible default size for the buffer stringstreambuf output{buffer}; - auto ctx = Context::make_for(style); + auto ctx = FormatContext::make_for(style); implementation::Writer::write(output, item, ctx, type); } +/// +/// @brief Write an item to a buffer, using the specified PrintStyle +/// +/// @tparam T the type of item to write +/// @param buffer the buffer to write to +/// @param item the item to write +/// @param style the print style to use +/// template inline void write_t(std::string& buffer, const T& item, PrintStyle::Type_t style) { buffer.reserve(1024 * 4); // Should be using a sensible default size for the buffer stringstreambuf output{buffer}; - auto ctx = Context::make_for(style); + auto ctx = FormatContext::make_for(style); implementation::Writer::write(output, item, ctx); } +/// +/// @brief Write an item to an output stream, using the specified PrintStyle +/// +/// @tparam Stream the type of the output stream +/// @tparam T the type of item to write +/// @param output the output stream +/// @param item the item to write +/// @param style the print style to use +/// template inline void write_t(Stream& output, const T& item, PrintStyle::Type_t style) { std::string buffer; @@ -2364,6 +2483,14 @@ inline void write_t(Stream& output, const T& item, PrintStyle::Type_t style) { output << buffer; } +/// +/// @brief Convert an item to a string, using the specified PrintStyle +/// +/// @tparam T the type of item to convert +/// @param item the item to convert +/// @param style the print style to use +/// @return the item as a string +/// template inline std::string as_string(const T& item, PrintStyle::Type_t style) { std::string buffer; diff --git a/libs/node/src/ecflow/node/permissions/ActivePermissions.cpp b/libs/node/src/ecflow/node/permissions/ActivePermissions.cpp new file mode 100644 index 000000000..749433d55 --- /dev/null +++ b/libs/node/src/ecflow/node/permissions/ActivePermissions.cpp @@ -0,0 +1,79 @@ +/* + * Copyright 2009- ECMWF. + * + * This software is licensed under the terms of the Apache Licence version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#include "ecflow/node/permissions/ActivePermissions.hpp" + +#include "ecflow/core/Overload.hpp" +#include "ecflow/node/Defs.hpp" +#include "ecflow/node/NodePathAlgorithms.hpp" + +namespace ecf { + +void ActivePermissions::bootstrap(const Permissions& p) { + permissions_ = p; +} + +void ActivePermissions::combine_supersede(const Permissions& p) { + if (!is_none()) { + permissions_ = Permissions::combine_supersede(permissions_, p); + } +} + +void ActivePermissions::combine_override(const Permissions& p) { + if (!is_none()) { + permissions_ = Permissions::combine_override(permissions_, p); + } +} + +ActivePermissions permissions_at(const Identity& identity, const Defs& defs, const std::string& path) { + ActivePermissions active; + + struct Visitor + { + Visitor(ActivePermissions& collected) + : collected_{collected} {} + + void handle(const Defs& defs) { + auto p = defs.server_state().permissions(); + + // At server level, we only care about the server permissions + collected_.bootstrap(p); + } + void handle(const Node& n) { + auto p = n.permissions(); + + if (auto s = dynamic_cast(&n); s) { + // At node level, if the node is a Suite we bootstrap the node permissions + collected_.combine_supersede(p); + } + else { + // ... otherwise, we combine the node permissions + // -- in practice, this combination only restricts node permissions; + // for example, a user can't be allowed to read/write/execute a + // specific node if he can't do it at a higher node level + collected_.combine_override(p); + } + } + + void not_found() { /* do nothing */ } + + private: + ActivePermissions& collected_; + }; + + auto p = Path::make(path).value(); + auto v = Visitor{active}; + + ecf::visit(defs, p, v); + + return active; +} + +} // namespace ecf diff --git a/libs/node/src/ecflow/node/permissions/ActivePermissions.hpp b/libs/node/src/ecflow/node/permissions/ActivePermissions.hpp new file mode 100644 index 000000000..dc271143b --- /dev/null +++ b/libs/node/src/ecflow/node/permissions/ActivePermissions.hpp @@ -0,0 +1,119 @@ +/* + * Copyright 2009- ECMWF. + * + * This software is licensed under the terms of the Apache Licence version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#ifndef ecflow_node_permissions_ActivePermissions_HPP +#define ecflow_node_permissions_ActivePermissions_HPP + +#include +#include +#include +#include +#include +#include + +#include "ecflow/core/WhiteListFile.hpp" +#include "ecflow/node/permissions/Permissions.hpp" + +class Defs; + +namespace ecf { + +/** + * \brief Represents the active permissions, encapsulating the logic for calculating the user permissions as they + * are defined in the server's hierarchy. + */ +class ActivePermissions { +public: + static ActivePermissions make_empty() { return ActivePermissions(); } + + [[nodiscard]] bool is_none() const { return permissions_.is_empty(); } + + [[nodiscard]] bool allows(const Username& username, Allowed permission) const { + return is_none() || permissions_.allows(username, permission); + // The above means that when there are no active rules, everything is allowed! + // Only when there are active rules, the user permissions are effectively checked. + // + // This design choice is considered dangerous, as a misconfigured server becomes essentially open! + // But this is backward-compatible! + } + + /** + * \brief Bootstrap the active permissions with the given permissions. + * This is typically used to set the initial permissions at server level and then begin the permission + * selection algorithm. + * + * @param p the permissions to start selected algorithm + */ + void bootstrap(const Permissions& p); + + /** + * \brief Combine the current active permissions with the new permissions using the _supersede_ logic. + * + * The _supersede_ logic is applied at root node level (i.e. Suite), and means that the new permissions + * replace active permissions. This is needed to accommodate the user permission changes done at suite level, + * and allow new users being added where before there were no permissions defined. + * + * All sticky permissions are preserved. + * + * @param p the permissions to consider + */ + void combine_supersede(const Permissions& p); + + /** + * \brief Combine the current active permissions with the new permissions using the _override_ logic. + * + * The _override_ logic is applied at non-root node level (i.e. Family, Task, etc.), and means that the new + * permissions are combined with active permissions applying a restrictive logic. + * + * All sticky permissions are preserved. + * + * @param p + */ + void combine_override(const Permissions& p); + + friend std::ostream& operator<<(std::ostream& os, const ActivePermissions& p) { + using namespace std::string_literals; + os << "ActivePermissions: "s << p.permissions_; + return os; + } + +private: + Permissions permissions_ = Permissions::make_empty(); +}; + +/// +/// @brief This is a type used to indicate that permissions are unrestricted +/// +struct UnrestrictedRules +{ +}; + +/// +/// @brief This is a type used to indicate that permissions are derived from the Defs nodes (as per ECF_PERMISSIONS) +/// +struct NodeRules +{ +}; + +/// +/// @bried This is a type used to indicate that permissions are derived from a white list file +/// +struct WhiteListRules +{ + WhiteListRules(const WhiteListFile& file) + : file_(file) {} + const WhiteListFile& file_; +}; + +ActivePermissions permissions_at(const Identity& identity, const Defs& defs, const std::string& path); + +} // namespace ecf + +#endif /* ecflow_node_permissions_ActivePermissions_HPP */ diff --git a/libs/node/src/ecflow/node/permissions/Allowed.cpp b/libs/node/src/ecflow/node/permissions/Allowed.cpp new file mode 100644 index 000000000..89912210f --- /dev/null +++ b/libs/node/src/ecflow/node/permissions/Allowed.cpp @@ -0,0 +1,69 @@ +/* + * Copyright 2009- ECMWF. + * + * This software is licensed under the terms of the Apache Licence version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#include "ecflow/node/permissions/Allowed.hpp" + +namespace ecf { + +std::string allowed_to_string(Allowed allowed) { + std::string s; + if ((allowed & Allowed::READ) != Allowed::NONE) { + s += "r"; + } + if ((allowed & Allowed::WRITE) != Allowed::NONE) { + s += "w"; + } + if ((allowed & Allowed::EXECUTE) != Allowed::NONE) { + s += "x"; + } + if ((allowed & Allowed::OWNER) != Allowed::NONE) { + s += "o"; + } + if ((allowed & Allowed::STICKY) != Allowed::NONE) { + s += "s"; + } + return s; +} + +Allowed allowed_from_string(const std::string& s) { + Allowed allowed = Allowed::NONE; + for (char c : s) { + switch (c) { + case ' ': + break; // Ignore spaces + case 'r': + case 'R': + allowed |= Allowed::READ; + break; + case 'w': + case 'W': + allowed |= Allowed::WRITE; + break; + case 'x': + case 'X': + allowed |= Allowed::EXECUTE; + break; + case 'o': + case 'O': + allowed |= Allowed::OWNER; + break; + case 's': + case 'S': + allowed |= Allowed::STICKY; + break; + default: + throw InvalidPermissionValue("Invalid permission character: " + std::string(1, c) + + ". Expected one of: [r, w, x, o, s] (regardless of case)"); + } + } + return allowed; +} + +} // namespace ecf diff --git a/libs/node/src/ecflow/node/permissions/Allowed.hpp b/libs/node/src/ecflow/node/permissions/Allowed.hpp new file mode 100644 index 000000000..9b403f524 --- /dev/null +++ b/libs/node/src/ecflow/node/permissions/Allowed.hpp @@ -0,0 +1,108 @@ +/* + * Copyright 2009- ECMWF. + * + * This software is licensed under the terms of the Apache Licence version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#ifndef ecflow_node_permissions_Allowed_HPP +#define ecflow_node_permissions_Allowed_HPP + +#include +#include +#include + +namespace ecf { + +/** + * \brief Exception thrown when an invalid permission value is encountered. + * + * This exception is used to indicate that a string representation of permissions + * could not be parsed correctly (e.g. contained invalid characters). + */ +struct InvalidPermissionValue : public std::runtime_error +{ + explicit InvalidPermissionValue(const std::string& msg) + : std::runtime_error(msg) {} +}; + +/** + * \brief Represents the permissions a user has when performing actions on a Node and its attributes. + * + * The type is designed to be used in a bitwise manner, allowing for easily combining and checking of permissions. + */ +enum class Allowed : std::uint8_t { + NONE = 0, + READ = 1 << 0, // Can perform read commands on nodes and their attributes + WRITE = 1 << 1, // Can perform write commands on nodes and their attributes + EXECUTE = 1 << 2, // Can perform execute commands on nodes (e.g. run a task) + OWNER = 1 << 3, // Can to load new suites. + STICKY = 1 << 4, // This is a special permission and means the permission is cannot be restricted/removed in lower + // hierarchy levels, i.e. if a user has sticky permissions, it can't be overridden + // by a new permission defined at a lower level. This is useful for defining + // server level permissions that should not be overridden by node level permissions. +}; + +inline Allowed operator|(Allowed lhs, Allowed rhs) { + using allowed_t = std::underlying_type::type; + + return Allowed{static_cast(static_cast(lhs) | static_cast(rhs))}; +} + +inline Allowed operator&(Allowed lhs, Allowed rhs) { + using allowed_t = std::underlying_type::type; + + return Allowed{static_cast(static_cast(lhs) & static_cast(rhs))}; +} + +inline Allowed& operator|=(Allowed& lhs, Allowed rhs) { + lhs = lhs | rhs; + return lhs; +} + +inline Allowed& operator&=(Allowed& lhs, Allowed rhs) { + lhs = lhs & rhs; + return lhs; +} + +/** + * \brief Checks if lhs contains all permissions of rhs. + * + * @param lhs the permissions to check against + * @param rhs the permissions to check for + * @return true if lhs contains all permissions of rhs, false otherwise + */ +inline bool contains(Allowed lhs, Allowed rhs) { + return (lhs & rhs) == rhs; +} + +/** + * \brief Converts Allowed enum to a string representation. + * + * The string representation is a concatenation of permission characters: + * 'r' for READ, 'w' for WRITE, 'x' for EXECUTE, 'o' for OWNER, and 's' for STICKY. + * + * @param allowed the enum value to convert + * @return the string representation of the allowed permissions (e.g. "rwxos") + */ +std::string allowed_to_string(Allowed allowed); + +/** + * \brief Converts a string representation of permissions to an Allowed enum. + * + * The string should contain characters representing permissions: + * 'r' for READ, 'w' for WRITE, 'x' for EXECUTE, 'o' for OWNER, and 's' for STICKY. + * Repeated characters are allowed and considered only once, and spaces are ignored. + * + * @param s the string representation of permissions (e.g. "rwxos") + * @return the Allowed enum value corresponding to the string + * @throws InvalidPermissionValue if the string cannot be parsed correctly + */ +Allowed allowed_from_string(const std::string& s); + +} // namespace ecf + +#endif /* ecflow_node_permissions_Allowed_HPP */ diff --git a/libs/node/src/ecflow/node/permissions/Permission.cpp b/libs/node/src/ecflow/node/permissions/Permission.cpp new file mode 100644 index 000000000..103bdec8a --- /dev/null +++ b/libs/node/src/ecflow/node/permissions/Permission.cpp @@ -0,0 +1,11 @@ +/* + * Copyright 2009- ECMWF. + * + * This software is licensed under the terms of the Apache Licence version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#include "ecflow/node/permissions/Permission.hpp" diff --git a/libs/node/src/ecflow/node/permissions/Permission.hpp b/libs/node/src/ecflow/node/permissions/Permission.hpp new file mode 100644 index 000000000..3ef96ae8f --- /dev/null +++ b/libs/node/src/ecflow/node/permissions/Permission.hpp @@ -0,0 +1,42 @@ +/* + * Copyright 2009- ECMWF. + * + * This software is licensed under the terms of the Apache Licence version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#ifndef ecflow_node_permissions_Permission_HPP +#define ecflow_node_permissions_Permission_HPP + +#include "ecflow/core/Identity.hpp" +#include "ecflow/node/permissions/Allowed.hpp" + +namespace ecf { + +/** + * \brief Represents a permission for a specific user by linking a 'username' with a set of allowed permissions. + */ +class Permission { +public: + Permission(Username user, Allowed allowed) + : username_{std::move(user)}, + allowed_{allowed} {} + + [[nodiscard]] bool allows(const Username& user, Allowed requested) const { + return user == username_ && (contains(allowed_, requested)); + } + + Username username() const { return username_; } + Allowed allowed() const { return allowed_; } + +private: + Username username_; + Allowed allowed_; +}; + +} // namespace ecf + +#endif /* ecflow_node_permissions_Permission_HPP */ diff --git a/libs/node/src/ecflow/node/permissions/Permissions.cpp b/libs/node/src/ecflow/node/permissions/Permissions.cpp new file mode 100644 index 000000000..c026baa1b --- /dev/null +++ b/libs/node/src/ecflow/node/permissions/Permissions.cpp @@ -0,0 +1,147 @@ +/* + * Copyright 2009- ECMWF. + * + * This software is licensed under the terms of the Apache Licence version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#include "ecflow/node/permissions/Permissions.hpp" + +#include "ecflow/attribute/Variable.hpp" +#include "ecflow/core/Environment.hpp" +#include "ecflow/core/Str.hpp" + +namespace ecf { + +Result Permissions::make_from_variable(const std::string& value) { + + if (value.empty()) { + return Result::success(Permissions::make_empty()); + } + + // Expecting a comma-separated list of user/permissions, e.g. "USER1:RWXO,USER2:R" + + std::vector entries; + ecf::algorithm::split_fields_at(entries, value, ","); + + std::vector allowed; + for (auto&& entry : entries) { + std::vector user_permissions; + ecf::algorithm::split_fields_at(user_permissions, entry, ":"); + if (user_permissions.size() != 2) { + return Result::failure("Invalid permission entry: " + entry + + ". Expected format: :"); + } + + const auto& first = user_permissions[0]; + const auto& second = user_permissions[1]; + + if (first.empty()) { + return Result::failure("Invalid permission entry: empty username is not allowed"); + } + if (second.empty()) { + return Result::failure("Invalid permission entry: empty permissions are not allowed"); + } + + auto username = Username{first}; + + try { + auto perms = allowed_from_string(second); + allowed.emplace_back(username, perms); + } + catch (const InvalidPermissionValue& e) { + return Result::failure("Invalid permission value in entry: " + entry + ". " + e.what()); + } + } + + return Result::success(Permissions(std::move(allowed))); +} + +Permissions Permissions::find_in(const std::vector& variables) { + static std::string permissions_var_name = ecf::environment::ECF_PERMISSIONS; + if (auto found = std::find_if( + std::begin(variables), std::end(variables), [](auto&& var) { return var.name() == permissions_var_name; }); + found != std::end(variables)) { + auto var_value = found->theValue(); + if (auto permisions = ecf::Permissions::make_from_variable(var_value); permisions.ok()) { + return permisions.value(); + } + else { + return Permissions::make_empty(); + } + } + else { + return ecf::Permissions::make_empty(); + } +} + +Permissions Permissions::combine_supersede(const Permissions& active, const Permissions& current) { + // `current` is the new permissions + // `previous` is the previous permissions + // + // The superseding rules are to update previous permissions, by keeping all sticky permissions and keeping any + // non-sticky permissions with the new permissions. + // + + std::vector result; + + // First, we keep all sticky permissions from the active permissions + for (auto&& permission : active.allowed_) { + if (contains(permission.allowed(), Allowed::STICKY)) { + result.push_back(permission); + } + } + + // Then, we retain the non-sticky permissions from the current permissions + for (auto&& permission : current.allowed_) { + auto found = std::find_if( + std::begin(result), std::end(result), [&](auto&& p) { return p.username() == permission.username(); }); + + if (found == std::end(result)) { + // If the user is not in listed as sticky, we add the permission + result.push_back(permission); + } + } + + return Permissions{std::move(result)}; +} + +Permissions Permissions::combine_override(const Permissions& active, const Permissions& current) { + // `current` is the new permissions + // `previous` is the previous permissions + // + // The overriding rules are to update previous permissions by restricting them based on the new permissions. + // Sticky permissions are kept as is, while non-sticky permissions are overridden by the current permissions. + // + + if (current.is_empty()) { + // When there are no permissions to override (i.e. the current permissions are empty), the active permissions + // are kept as is. + return active; + } + + std::vector result; + + for (auto&& permission : active.allowed_) { + if (contains(permission.allowed(), Allowed::STICKY)) { + // If the permission is sticky, we keep the permissions as is + result.push_back(permission); + } + else { + if (auto found = std::find_if(std::begin(current.allowed_), + std::end(current.allowed_), + [&](auto&& p) { return p.username() == permission.username(); }); + found != std::end(current.allowed_)) { + // When non-sticky permissions are overridden in current, we combine the permissions + result.push_back(Permission{permission.username(), permission.allowed() & found->allowed()}); + } + } + } + + return Permissions{std::move(result)}; +} + +} // namespace ecf diff --git a/libs/node/src/ecflow/node/permissions/Permissions.hpp b/libs/node/src/ecflow/node/permissions/Permissions.hpp new file mode 100644 index 000000000..118ac72f5 --- /dev/null +++ b/libs/node/src/ecflow/node/permissions/Permissions.hpp @@ -0,0 +1,72 @@ +/* + * Copyright 2009- ECMWF. + * + * This software is licensed under the terms of the Apache Licence version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#ifndef ecflow_node_permissions_Permissions_HPP +#define ecflow_node_permissions_Permissions_HPP + +#include +#include +#include +#include + +#include "ecflow/core/Identity.hpp" +#include "ecflow/core/Result.hpp" +#include "ecflow/node/permissions/Permission.hpp" + +class Variable; + +namespace ecf { + +/** + * \brief Represents a collection of permissions for multiple users. + * + * This class allows checking if a specific user has a certain permission and provides methods to combine + * permissions from different sources. + */ +class Permissions { +public: + static Permissions make_empty() { return Permissions(); } + static Result make_from_variable(const std::string& var_value); + + static Permissions find_in(const std::vector& variables); + + [[nodiscard]] bool is_empty() const { return allowed_.empty(); } + + [[nodiscard]] bool allows(const Username& username, Allowed permission) const { + auto found = std::find_if(std::begin(allowed_), std::end(allowed_), [&](auto&& current) { + return current.allows(username, permission); + }); + return found != std::end(allowed_); + } + + static Permissions combine_supersede(const Permissions& active, const Permissions& current); + static Permissions combine_override(const Permissions& active, const Permissions& current); + + friend std::ostream& operator<<(std::ostream& os, const Permissions& p) { + using namespace std::string_literals; + os << "Permissions: "s; + for (auto&& permission : p.allowed_) { + os << "("s << permission.username().value() << ":"s << allowed_to_string(permission.allowed()) << ") "s; + } + return os; + } + +private: + Permissions() + : allowed_{} {} + explicit Permissions(std::vector allowed) + : allowed_{std::move(allowed)} {} + + std::vector allowed_; +}; + +} // namespace ecf + +#endif /* ecflow_node_permissions_Permissions_HPP */ diff --git a/libs/node/test/TestExprASTRendering.cpp b/libs/node/test/TestExprASTRendering.cpp index 1425d7ff0..fd1c9ef13 100644 --- a/libs/node/test/TestExprASTRendering.cpp +++ b/libs/node/test/TestExprASTRendering.cpp @@ -40,7 +40,7 @@ BOOST_AUTO_TEST_CASE(test_expression_ast_rendering) { t2->add_trigger("t1 eq complete"); { - auto ctx = ecf::Context::make_for(PrintStyle::DEFS); + auto ctx = ecf::FormatContext::make_for(PrintStyle::DEFS); auto ast = t2->triggerAst(); std::string actual; ecf::write_t(actual, *ast, ctx); @@ -63,7 +63,7 @@ BOOST_AUTO_TEST_CASE(test_expression_ast_rendering) { t3->add_trigger("t1:meter > 51"); { - auto ctx = ecf::Context::make_for(PrintStyle::DEFS); + auto ctx = ecf::FormatContext::make_for(PrintStyle::DEFS); auto ast = t3->triggerAst(); std::string actual; ecf::write_t(actual, *ast, ctx); @@ -87,7 +87,7 @@ BOOST_AUTO_TEST_CASE(test_expression_ast_rendering) { t4->add_trigger("t1:meter > 51 AND (t1 eq complete OR t2 eq complete) AND (t3 eq complete)"); { - auto ctx = ecf::Context::make_for(PrintStyle::DEFS); + auto ctx = ecf::FormatContext::make_for(PrintStyle::DEFS); auto ast = t4->triggerAst(); std::string actual; ecf::write_t(actual, *ast, ctx); @@ -124,7 +124,7 @@ BOOST_AUTO_TEST_CASE(test_expression_ast_rendering) { t5->add_trigger("t2 eq complete AND :YMD le t1:YMD"); { - auto ctx = ecf::Context::make_for(PrintStyle::DEFS); + auto ctx = ecf::FormatContext::make_for(PrintStyle::DEFS); auto ast = t5->triggerAst(); std::string actual; ecf::write_t(actual, *ast, ctx); diff --git a/libs/base/test/TestAlgorithms.cpp b/libs/node/test/TestNodePathAlgorithms.cpp similarity index 93% rename from libs/base/test/TestAlgorithms.cpp rename to libs/node/test/TestNodePathAlgorithms.cpp index 886b9658c..60a6354ba 100644 --- a/libs/base/test/TestAlgorithms.cpp +++ b/libs/node/test/TestNodePathAlgorithms.cpp @@ -8,16 +8,13 @@ * nor does it submit to any jurisdiction. */ -#define BOOST_TEST_MODULE Test_Base -#include +#include -#include "MockServer.hpp" -#include "ecflow/base/Algorithms.hpp" #include "ecflow/node/Family.hpp" -#include "ecflow/server/BaseServer.hpp" +#include "ecflow/node/NodePathAlgorithms.hpp" #include "ecflow/test/scaffold/Naming.hpp" -BOOST_AUTO_TEST_SUITE(U_Base) +BOOST_AUTO_TEST_SUITE(U_Node) BOOST_AUTO_TEST_SUITE(T_Path) diff --git a/libs/node/test/TestPermissions.cpp b/libs/node/test/TestPermissions.cpp new file mode 100644 index 000000000..ba3b6aeeb --- /dev/null +++ b/libs/node/test/TestPermissions.cpp @@ -0,0 +1,352 @@ +/* + * Copyright 2009- ECMWF. + * + * This software is licensed under the terms of the Apache Licence version 2.0 + * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. + * In applying this licence, ECMWF does not waive the privileges and immunities + * granted to it by virtue of its status as an intergovernmental organisation + * nor does it submit to any jurisdiction. + */ + +#include +#include + +#include + +#include "ecflow/core/Environment.hpp" +#include "ecflow/core/Filesystem.hpp" +#include "ecflow/node/Defs.hpp" +#include "ecflow/node/Family.hpp" +#include "ecflow/node/Suite.hpp" +#include "ecflow/test/scaffold/Naming.hpp" +#include "ecflow/test/scaffold/Provisioning.hpp" + +BOOST_AUTO_TEST_SUITE(U_Node) + +BOOST_AUTO_TEST_SUITE(T_Permissions) + +BOOST_AUTO_TEST_CASE(test_file_automatic_name_exists) { + ECF_NAME_THIS_TEST(); + + using namespace ecf::test::scaffold; + + std::vector paths; + for (int i = 0; i < 10; i++) { + WithTestFile file; + + auto path = fs::absolute(file.path()); + paths.push_back(path); + + BOOST_CHECK(fs::exists(path)); + } + for (const auto& path : paths) { + BOOST_CHECK(!fs::exists(path)); + } +} + +BOOST_AUTO_TEST_CASE(test_file_automatic_prefix_name_exists) { + ECF_NAME_THIS_TEST(); + + using namespace ecf::test::scaffold; + + std::vector paths; + for (int i = 0; i < 10; i++) { + auto prefix = "test_file_" + std::to_string(i); + WithTestFile file{AutomaticTestFile{prefix}}; + + auto path = file.path(); + paths.push_back(path); + + BOOST_CHECK(fs::exists(path)); + } + for (const auto& path : paths) { + BOOST_CHECK(!fs::exists(path)); + } +} + +BOOST_AUTO_TEST_CASE(can_do_permissions) { + ECF_NAME_THIS_TEST(); + using namespace ecf; + using namespace std::string_literals; + + Defs d; + auto s = d.add_suite("s1"); + s->addVariable(Variable(ecf::environment::ECF_PERMISSIONS, "u1:rwx,u2:rw")); + auto f = s->add_family("f1"); + f->addVariable(Variable(ecf::environment::ECF_PERMISSIONS, "u2:rx,u3:rw")); + auto t = f->add_task("t1"); + + d.server_state().add_or_update_server_variable(ecf::environment::ECF_PERMISSIONS, "a:rwxos"); + + AuthorisationService service = AuthorisationService::load_permissions_from_nodes().value(); + + auto a = Identity::make_secure_user(Username{"a"}.value()); + auto u1 = Identity::make_secure_user(Username{"u1"}.value()); + auto u2 = Identity::make_secure_user(Username{"u2"}.value()); + auto u3 = Identity::make_secure_user(Username{"u3"}.value()); + + { + const auto& path = "/"s; + + BOOST_CHECK(service.allows(a, d, path, Allowed::READ)); + BOOST_CHECK(service.allows(a, d, path, Allowed::WRITE)); + BOOST_CHECK(service.allows(a, d, path, Allowed::EXECUTE)); + BOOST_CHECK(service.allows(a, d, path, Allowed::OWNER)); + BOOST_CHECK(service.allows(a, d, path, Allowed::STICKY)); + + BOOST_CHECK(!service.allows(u1, d, path, Allowed::READ)); // + BOOST_CHECK(!service.allows(u1, d, path, Allowed::WRITE)); // + BOOST_CHECK(!service.allows(u1, d, path, Allowed::EXECUTE)); // + BOOST_CHECK(!service.allows(u1, d, path, Allowed::OWNER)); + BOOST_CHECK(!service.allows(u1, d, path, Allowed::STICKY)); + + BOOST_CHECK(!service.allows(u2, d, path, Allowed::READ)); // + BOOST_CHECK(!service.allows(u2, d, path, Allowed::WRITE)); // + BOOST_CHECK(!service.allows(u2, d, path, Allowed::EXECUTE)); + BOOST_CHECK(!service.allows(u2, d, path, Allowed::OWNER)); + BOOST_CHECK(!service.allows(u2, d, path, Allowed::STICKY)); + + BOOST_CHECK(!service.allows(u3, d, path, Allowed::READ)); + BOOST_CHECK(!service.allows(u3, d, path, Allowed::WRITE)); + BOOST_CHECK(!service.allows(u3, d, path, Allowed::EXECUTE)); + BOOST_CHECK(!service.allows(u3, d, path, Allowed::OWNER)); + BOOST_CHECK(!service.allows(u3, d, path, Allowed::STICKY)); + } + + { + const auto& path = "/s1"s; + + BOOST_CHECK(service.allows(a, d, path, Allowed::READ)); + BOOST_CHECK(service.allows(a, d, path, Allowed::WRITE)); + BOOST_CHECK(service.allows(a, d, path, Allowed::EXECUTE)); + BOOST_CHECK(service.allows(a, d, path, Allowed::OWNER)); + BOOST_CHECK(service.allows(a, d, path, Allowed::STICKY)); + + BOOST_CHECK(service.allows(u1, d, path, Allowed::READ)); // + BOOST_CHECK(service.allows(u1, d, path, Allowed::WRITE)); // + BOOST_CHECK(service.allows(u1, d, path, Allowed::EXECUTE)); // + BOOST_CHECK(!service.allows(u1, d, path, Allowed::OWNER)); + BOOST_CHECK(!service.allows(u1, d, path, Allowed::STICKY)); + + BOOST_CHECK(service.allows(u2, d, path, Allowed::READ)); // + BOOST_CHECK(service.allows(u2, d, path, Allowed::WRITE)); // + BOOST_CHECK(!service.allows(u2, d, path, Allowed::EXECUTE)); + BOOST_CHECK(!service.allows(u2, d, path, Allowed::OWNER)); + BOOST_CHECK(!service.allows(u2, d, path, Allowed::STICKY)); + + BOOST_CHECK(!service.allows(u3, d, path, Allowed::READ)); + BOOST_CHECK(!service.allows(u3, d, path, Allowed::WRITE)); + BOOST_CHECK(!service.allows(u3, d, path, Allowed::EXECUTE)); + BOOST_CHECK(!service.allows(u3, d, path, Allowed::OWNER)); + BOOST_CHECK(!service.allows(u3, d, path, Allowed::STICKY)); + } + + { + const auto& path = "/s1/f1"s; + + BOOST_CHECK(service.allows(a, d, path, Allowed::READ)); + BOOST_CHECK(service.allows(a, d, path, Allowed::WRITE)); + BOOST_CHECK(service.allows(a, d, path, Allowed::EXECUTE)); + BOOST_CHECK(service.allows(a, d, path, Allowed::OWNER)); + BOOST_CHECK(service.allows(a, d, path, Allowed::STICKY)); + + BOOST_CHECK(!service.allows(u1, d, path, Allowed::READ)); + BOOST_CHECK(!service.allows(u1, d, path, Allowed::WRITE)); + BOOST_CHECK(!service.allows(u1, d, path, Allowed::EXECUTE)); + BOOST_CHECK(!service.allows(u1, d, path, Allowed::OWNER)); + BOOST_CHECK(!service.allows(u1, d, path, Allowed::STICKY)); + + BOOST_CHECK(service.allows(u2, d, path, Allowed::READ)); // + BOOST_CHECK(!service.allows(u2, d, path, Allowed::WRITE)); + BOOST_CHECK(!service.allows(u2, d, path, Allowed::EXECUTE)); + BOOST_CHECK(!service.allows(u2, d, path, Allowed::OWNER)); + BOOST_CHECK(!service.allows(u2, d, path, Allowed::STICKY)); + + BOOST_CHECK(!service.allows(u3, d, path, Allowed::READ)); + BOOST_CHECK(!service.allows(u3, d, path, Allowed::WRITE)); + BOOST_CHECK(!service.allows(u3, d, path, Allowed::EXECUTE)); + BOOST_CHECK(!service.allows(u3, d, path, Allowed::OWNER)); + BOOST_CHECK(!service.allows(u3, d, path, Allowed::STICKY)); + } +} + +BOOST_AUTO_TEST_CASE(can_calculate_permission_superseeding_basic_rules) { + ECF_NAME_THIS_TEST(); + using namespace ecf; + + auto a = Username{"a"}; + auto u1 = Username{"u1"}; + auto u2 = Username{"u2"}; + + auto p = Permissions::make_from_variable("a:rws,u1:rw").value(); + BOOST_CHECK(p.allows(a, Allowed::READ)); + BOOST_CHECK(p.allows(a, Allowed::WRITE)); + BOOST_CHECK(!p.allows(a, Allowed::EXECUTE)); + BOOST_CHECK(!p.allows(a, Allowed::OWNER)); + BOOST_CHECK(p.allows(a, Allowed::STICKY)); + BOOST_CHECK(p.allows(u1, Allowed::READ)); + BOOST_CHECK(p.allows(u1, Allowed::WRITE)); + BOOST_CHECK(!p.allows(u1, Allowed::EXECUTE)); + BOOST_CHECK(!p.allows(u1, Allowed::OWNER)); + BOOST_CHECK(!p.allows(u1, Allowed::STICKY)); + + auto q = Permissions::make_from_variable("a:r,u1:rwx,u2:rw").value(); + BOOST_CHECK(q.allows(a, Allowed::READ)); + BOOST_CHECK(!q.allows(a, Allowed::WRITE)); + BOOST_CHECK(!q.allows(a, Allowed::EXECUTE)); + BOOST_CHECK(!q.allows(a, Allowed::OWNER)); + BOOST_CHECK(!q.allows(a, Allowed::STICKY)); + BOOST_CHECK(q.allows(u1, Allowed::READ)); + BOOST_CHECK(q.allows(u1, Allowed::WRITE)); + BOOST_CHECK(q.allows(u1, Allowed::EXECUTE)); + BOOST_CHECK(!q.allows(u1, Allowed::OWNER)); + BOOST_CHECK(!q.allows(u1, Allowed::STICKY)); + BOOST_CHECK(q.allows(u2, Allowed::READ)); + BOOST_CHECK(q.allows(u2, Allowed::WRITE)); + BOOST_CHECK(!q.allows(u2, Allowed::EXECUTE)); + BOOST_CHECK(!q.allows(u2, Allowed::OWNER)); + BOOST_CHECK(!q.allows(u2, Allowed::STICKY)); + + auto r = Permissions::combine_supersede(p, q); + BOOST_CHECK(r.allows(a, Allowed::READ)); + BOOST_CHECK(r.allows(a, Allowed::WRITE)); + BOOST_CHECK(!r.allows(a, Allowed::EXECUTE)); + BOOST_CHECK(!r.allows(a, Allowed::OWNER)); + BOOST_CHECK(r.allows(a, Allowed::STICKY)); + BOOST_CHECK(r.allows(u1, Allowed::READ)); + BOOST_CHECK(r.allows(u1, Allowed::WRITE)); + BOOST_CHECK(r.allows(u1, Allowed::EXECUTE)); + BOOST_CHECK(!r.allows(u1, Allowed::OWNER)); + BOOST_CHECK(!r.allows(u1, Allowed::STICKY)); + BOOST_CHECK(r.allows(u2, Allowed::READ)); + BOOST_CHECK(r.allows(u2, Allowed::WRITE)); + BOOST_CHECK(!r.allows(u2, Allowed::EXECUTE)); + BOOST_CHECK(!r.allows(u2, Allowed::OWNER)); + BOOST_CHECK(!r.allows(u2, Allowed::STICKY)); +} + +BOOST_AUTO_TEST_CASE(can_calculate_permission_overriding_basic_rules) { + ECF_NAME_THIS_TEST(); + using namespace ecf; + + auto u1 = Username{"u1"}; + auto u2 = Username{"u2"}; + + auto p = Permissions::make_from_variable("u1:rwx").value(); + BOOST_CHECK(p.allows(u1, Allowed::READ)); + BOOST_CHECK(p.allows(u1, Allowed::WRITE)); + BOOST_CHECK(p.allows(u1, Allowed::EXECUTE)); + BOOST_CHECK(!p.allows(u1, Allowed::OWNER)); + BOOST_CHECK(!p.allows(u1, Allowed::STICKY)); + + auto q = Permissions::make_from_variable("u1:rw,u2:rwxo").value(); + BOOST_CHECK(q.allows(u1, Allowed::READ)); + BOOST_CHECK(q.allows(u1, Allowed::WRITE)); + BOOST_CHECK(!q.allows(u1, Allowed::EXECUTE)); + BOOST_CHECK(!q.allows(u1, Allowed::OWNER)); + BOOST_CHECK(!q.allows(u1, Allowed::STICKY)); + + auto r = Permissions::combine_override(p, q); + BOOST_CHECK(r.allows(u1, Allowed::READ)); + BOOST_CHECK(r.allows(u1, Allowed::WRITE)); + BOOST_CHECK(!r.allows(u1, Allowed::EXECUTE)); + BOOST_CHECK(!r.allows(u1, Allowed::OWNER)); + BOOST_CHECK(!r.allows(u1, Allowed::STICKY)); + + BOOST_CHECK(!r.allows(u2, Allowed::READ)); + BOOST_CHECK(!r.allows(u2, Allowed::WRITE)); + BOOST_CHECK(!r.allows(u2, Allowed::EXECUTE)); + BOOST_CHECK(!r.allows(u2, Allowed::STICKY)); +} + +BOOST_AUTO_TEST_CASE(can_calculate_permission_overriding_does_not_extend_allowances) { + ECF_NAME_THIS_TEST(); + using namespace ecf; + + auto u1 = Username{"u1"}; + + auto p = Permissions::make_from_variable("u1:rw").value(); + BOOST_CHECK(p.allows(u1, Allowed::READ)); + BOOST_CHECK(p.allows(u1, Allowed::WRITE)); + BOOST_CHECK(!p.allows(u1, Allowed::EXECUTE)); + BOOST_CHECK(!p.allows(u1, Allowed::OWNER)); + BOOST_CHECK(!p.allows(u1, Allowed::STICKY)); + + auto q = Permissions::make_from_variable("u1:rwxo").value(); + BOOST_CHECK(q.allows(u1, Allowed::READ)); + BOOST_CHECK(q.allows(u1, Allowed::WRITE)); + BOOST_CHECK(q.allows(u1, Allowed::EXECUTE)); + BOOST_CHECK(q.allows(u1, Allowed::OWNER)); + BOOST_CHECK(!q.allows(u1, Allowed::STICKY)); + + auto r = Permissions::combine_override(p, q); + BOOST_CHECK(r.allows(u1, Allowed::READ)); + BOOST_CHECK(r.allows(u1, Allowed::WRITE)); + BOOST_CHECK(!r.allows(u1, Allowed::EXECUTE)); + BOOST_CHECK(!r.allows(u1, Allowed::OWNER)); + BOOST_CHECK(!r.allows(u1, Allowed::STICKY)); +} + +BOOST_AUTO_TEST_CASE(can_calculate_permission_overriding_keeps_sticky_allowances) { + ECF_NAME_THIS_TEST(); + using namespace ecf; + + auto u1 = Username{"u1"}; + + auto p = Permissions::make_from_variable("u1:rwxs").value(); + BOOST_CHECK(p.allows(u1, Allowed::READ)); + BOOST_CHECK(p.allows(u1, Allowed::WRITE)); + BOOST_CHECK(p.allows(u1, Allowed::EXECUTE)); + BOOST_CHECK(!p.allows(u1, Allowed::OWNER)); + BOOST_CHECK(p.allows(u1, Allowed::STICKY)); + + auto q = Permissions::make_from_variable("u1:r").value(); + BOOST_CHECK(q.allows(u1, Allowed::READ)); + BOOST_CHECK(!q.allows(u1, Allowed::WRITE)); + BOOST_CHECK(!q.allows(u1, Allowed::EXECUTE)); + BOOST_CHECK(!q.allows(u1, Allowed::OWNER)); + BOOST_CHECK(!q.allows(u1, Allowed::STICKY)); + + auto r = Permissions::combine_override(p, q); + BOOST_CHECK(r.allows(u1, Allowed::READ)); + BOOST_CHECK(r.allows(u1, Allowed::WRITE)); + BOOST_CHECK(r.allows(u1, Allowed::EXECUTE)); + BOOST_CHECK(!r.allows(u1, Allowed::OWNER)); + BOOST_CHECK(r.allows(u1, Allowed::STICKY)); +} + +BOOST_AUTO_TEST_CASE(can_detect_valid_permission_values) { + ECF_NAME_THIS_TEST(); + using namespace ecf; + using namespace std::string_literals; + + std::array perms = {"a:RWXS,b:rwx"s}; + for (const auto& actual : perms) { + BOOST_CHECK_MESSAGE(Permissions::make_from_variable(actual).ok(), + ">" << actual << "< has valid permission format"); + } +} + +BOOST_AUTO_TEST_CASE(can_detect_invalid_permission_values) { + ECF_NAME_THIS_TEST(); + using namespace ecf; + using namespace std::string_literals; + + std::array perms = {":rwxs,:rwx"s, + "a:,b:"s, + "a:rwxs;b:rwx"s, + "a:rwxZ"s, + "a:rwx,b:rwx!"s, + "a:rwx,b:rwx,"s, + ",a:rwx,b:rwx,"s, + "a:rwx,,b:rwx"s, + "a:rwx,x,b:rwx"s}; + for (const auto& actual : perms) { + BOOST_CHECK_MESSAGE(!Permissions::make_from_variable(actual).ok(), + ">" << actual << "< has invalid permission format"); + } +} + +BOOST_AUTO_TEST_SUITE_END() + +BOOST_AUTO_TEST_SUITE_END() diff --git a/libs/pyext/test/py_s_TestClientApi.py b/libs/pyext/test/py_s_TestClientApi.py index 21c6a86a1..e15106d2f 100644 --- a/libs/pyext/test/py_s_TestClientApi.py +++ b/libs/pyext/test/py_s_TestClientApi.py @@ -134,7 +134,13 @@ def test_set_host_port(): def print_test(ci, test_name): - print(test_name, flush=True) + class fg: + NAME = '\033[92m' + TITLE = '\033[93m' + BOLD = '\033[1m' + ENDC = '\033[0m' + + print(f' *** {fg.TITLE}{fg.BOLD}RUNNING TEST:{fg.ENDC} {fg.NAME}{test_name}{fg.ENDC}', flush=True) if ci.is_auto_sync_enabled(): ci.log_msg(test_name + " ============= AUTO SYNC ENABLED ================================ ") else: diff --git a/libs/server/src/ecflow/server/AuthenticationService.cpp b/libs/server/src/ecflow/server/AuthenticationService.cpp index 64ace5824..2fb7cc37c 100644 --- a/libs/server/src/ecflow/server/AuthenticationService.cpp +++ b/libs/server/src/ecflow/server/AuthenticationService.cpp @@ -82,13 +82,17 @@ bool AuthenticationService::is_authentic(const Identity& identity) const { const auto& username = identity.username(); const auto& password = identity.password(); + // The following means that: + // - UserX and SecureUserX are checked against the passwd_file_ + // - CustomUserX are checked against the passwd_custom_file_ + if (!identity.is_custom()) { - if (!passwd_file_.authenticate(username, password)) { + if (!passwd_file_.authenticate(username.value(), password.value())) { return false; } } else { - if (!passwd_custom_file_.authenticate(username, password)) { + if (!passwd_custom_file_.authenticate(username.value(), password.value())) { return false; } } diff --git a/libs/server/src/ecflow/server/AuthorisationService.cpp b/libs/server/src/ecflow/server/AuthorisationService.cpp index 83379c8aa..89bca6fc2 100644 --- a/libs/server/src/ecflow/server/AuthorisationService.cpp +++ b/libs/server/src/ecflow/server/AuthorisationService.cpp @@ -11,46 +11,23 @@ #include "ecflow/server/AuthorisationService.hpp" #include "ecflow/base/AbstractServer.hpp" -#include "ecflow/base/Algorithms.hpp" #include "ecflow/core/Overload.hpp" #include "ecflow/core/WhiteListFile.hpp" +#include "ecflow/node/Defs.hpp" +#include "ecflow/node/permissions/ActivePermissions.hpp" namespace ecf { -/// -/// NodeRules represent permissions derived from the nodes themselves (based on ECF_PERMISSIONS permissions) -/// -struct NodeRules -{ -}; - -/// -/// WhiteListRules represent permissions define by a white list file -/// -struct WhiteListRules -{ - WhiteListRules(const WhiteListFile& file) - : file_(file) {} - const WhiteListFile& file_; -}; - -/// -/// Unrestricted represent no restrictions at all -/// -struct Unrestricted -{ -}; - struct AuthorisationService::Impl { - Impl() - : permissions_(Unrestricted{}) {} + explicit Impl(UnrestrictedRules&& rules) + : rules_(std::move(rules)) {} explicit Impl(NodeRules&& rules) - : permissions_(std::move(rules)) {} + : rules_(std::move(rules)) {} explicit Impl(WhiteListRules&& rules) - : permissions_(std::move(rules)) {} + : rules_(std::move(rules)) {} - std::variant permissions_; + std::variant rules_; }; AuthorisationService::AuthorisationService() = default; @@ -76,23 +53,21 @@ bool AuthorisationService::good() const { return impl_ != nullptr; } -bool AuthorisationService::allows(const Identity& identity, - const AbstractServer& server, - const std::string& permission) const { - return allows(identity, server, paths_t{ROOT}, permission); +bool AuthorisationService::allows(const Identity& identity, const Defs& defs, Allowed required) const { + return allows(identity, defs, paths_t{ROOT}, required); } bool AuthorisationService::allows(const Identity& identity, - const AbstractServer& server, + const Defs& defs, const path_t& path, - const std::string& permission) const { - return allows(identity, server, paths_t{path}, permission); + Allowed required) const { + return allows(identity, defs, paths_t{path}, required); } bool AuthorisationService::allows(const Identity& identity, - const AbstractServer& server, + const Defs& defs, const paths_t& paths, - const std::string& permission) const { + Allowed required) const { if (!good()) { // When no rules are loaded, we allow everything... // Dangerous, but backward compatible! @@ -100,65 +75,37 @@ bool AuthorisationService::allows(const Identity& identity, } bool allowed = false; - std::visit(overload{[&allowed](const Unrestricted&) { - // when no rules are loaded, we allow everything... - // Dangerous, but backward compatible! - allowed = true; - }, - [&allowed, &identity, &paths, &permission](const WhiteListRules& rules) { + std::visit(overload{[&allowed](const UnrestrictedRules&) { allowed = true; }, + [&allowed, &identity, &paths, &required](const WhiteListRules& rules) { // Apply white list rules - if (permission == "read") { - allowed = rules.file_.verify_read_access(identity.username(), paths); + if (contains(required, Allowed::READ)) { + allowed = rules.file_.verify_read_access(identity.username().value(), paths); } - else if (permission == "write") { - allowed = rules.file_.verify_write_access(identity.username(), paths); + else if (contains(required, Allowed::WRITE) || contains(required, Allowed::EXECUTE)) { + allowed = rules.file_.verify_write_access(identity.username().value(), paths); } else { allowed = false; } }, - [&server, &identity, &paths, &allowed](const NodeRules& rules) { + [&allowed, &defs, &identity, &paths, &required](const NodeRules& rules) { for (auto&& path : paths) { - - struct Visitor - { - void handle(const Defs& defs) { - auto p = defs.server_state().permissions(); - permissions = p.is_empty() ? permissions : p; - } - void handle(const Node& s) { - auto p = s.permissions(); - permissions = p.is_empty() ? permissions : p; - } - - void not_found() { permissions = Permissions::make_empty(); } - - Permissions permissions = Permissions::make_empty(); - }; - - auto d = server.defs(); - auto p = Path::make(path).value(); - auto v = Visitor{}; - - ecf::visit(*d, p, v); - - if (v.permissions.is_empty()) { - allowed = true; - } - else if (v.permissions.allows(identity.username())) { - allowed = true; - } - else { - allowed = false; + ActivePermissions active = permissions_at(identity, defs, path); + allowed = active.allows(identity.username(), required); + if (!allowed) { break; } } }}, - impl_->permissions_); + impl_->rules_); return allowed; } +AuthorisationService::result_t AuthorisationService::load_permissions_unrestricted() { + return result_t::success(AuthorisationService(std::make_unique(UnrestrictedRules{}))); +} + AuthorisationService::result_t AuthorisationService::load_permissions_from_nodes() { return result_t::success(AuthorisationService(std::make_unique(NodeRules{}))); } @@ -167,4 +114,13 @@ AuthorisationService::result_t AuthorisationService::load_permissions_from_white return result_t::success(AuthorisationService(std::make_unique(WhiteListRules{whitelist}))); } +void AuthorisationService::init(const Permissions& permissions) { + if (permissions.is_empty()) { + impl_ = std::make_unique(WhiteListRules{WhiteListFile{}}); + } + else { + impl_ = std::make_unique(NodeRules{}); + } +} + } // namespace ecf diff --git a/libs/server/src/ecflow/server/AuthorisationService.hpp b/libs/server/src/ecflow/server/AuthorisationService.hpp index 817e6acf5..00bca4def 100644 --- a/libs/server/src/ecflow/server/AuthorisationService.hpp +++ b/libs/server/src/ecflow/server/AuthorisationService.hpp @@ -18,8 +18,10 @@ #include "ecflow/core/Identity.hpp" #include "ecflow/core/Result.hpp" #include "ecflow/core/WhiteListFile.hpp" +#include "ecflow/node/permissions/ActivePermissions.hpp" class AbstractServer; +class Defs; namespace ecf { @@ -33,9 +35,8 @@ class AuthorisationService { AuthorisationService(); - AuthorisationService(const AuthorisationService& rhs) = delete; - AuthorisationService& operator=(const AuthorisationService& rhs) = delete; - + AuthorisationService(const AuthorisationService& rhs) = delete; + AuthorisationService& operator=(const AuthorisationService& rhs) noexcept = delete; AuthorisationService(AuthorisationService&& rhs) noexcept; AuthorisationService& operator=(AuthorisationService&& rhs) noexcept; @@ -44,11 +45,6 @@ class AuthorisationService { [[nodiscard]] bool good() const; - [[nodiscard]] - bool authenticate(const Identity& identity) const { - return true; - } - /** * Verify if the identity is allowed to perform the action on the give paths. * @@ -58,24 +54,31 @@ class AuthorisationService { * @return true if the identity is allowed to perform the action, false otherwise */ [[nodiscard]] - bool allows(const Identity& identity, const AbstractServer& server, const std::string& permission) const; + bool allows(const Identity& identity, const Defs& defs, Allowed required) const; [[nodiscard]] - bool allows(const Identity& identity, - const AbstractServer& server, - const path_t& path, - const std::string& permission) const; + bool allows(const Identity& identity, const Defs& defs, const path_t& path, Allowed required) const; [[nodiscard]] - bool allows(const Identity& identity, - const AbstractServer& server, - const paths_t& paths, - const std::string& permission) const; + bool allows(const Identity& identity, const Defs& defs, const paths_t& paths, Allowed required) const; + + [[nodiscard]] + static result_t load_permissions_unrestricted(); [[nodiscard]] static result_t load_permissions_from_nodes(); + + [[nodiscard]] static result_t load_permissions_from_whitelist(const WhiteListFile& whitelist); + static AuthorisationService make_unrestricted(); + + static AuthorisationService make_from_nodes(); + + static AuthorisationService make_from_whitelist(const WhiteListFile& whitelist); + + void init(const Permissions& permissions); + private: struct Impl; std::unique_ptr impl_; diff --git a/libs/server/src/ecflow/server/HttpServer.cpp b/libs/server/src/ecflow/server/HttpServer.cpp index 44c6bc654..a7d286420 100644 --- a/libs/server/src/ecflow/server/HttpServer.cpp +++ b/libs/server/src/ecflow/server/HttpServer.cpp @@ -10,10 +10,13 @@ #include "ecflow/server/HttpServer.hpp" +#include + #include "ecflow/base/ClientToServerRequest.hpp" #include "ecflow/base/Identification.hpp" #include "ecflow/base/ServerToClientResponse.hpp" #include "ecflow/base/stc/PreAllocatedReply.hpp" +#include "ecflow/core/Base64.hpp" #include "ecflow/core/Log.hpp" #include "ecflow/core/ecflow_version.h" #include "ecflow/server/ServerEnvironment.hpp" @@ -66,12 +69,19 @@ void handle_request(const boost::beast::http::requestvalue()}; + found_header_username = true; + username = std::string{found->value()}; } } { @@ -88,8 +98,8 @@ void handle_request(const boost::beast::http::requestvalue()}; + found_header_password = true; + password = std::string{found->value()}; } } { @@ -97,16 +107,53 @@ void handle_request(const boost::beast::http::requestvalue()}; + LOG_DEBUG("HttpServer::handle_request", "Found Authorization header"); + + auto space_separator = header.find(' '); + if (space_separator == std::string::npos) { + auto error = std::string{"Incorrect Authorization header, unable to find space separator"}; + LOG_ERROR("HttpServer::handle_request", error); + response = bad_request(error); + return; + } + + auto tag = header.substr(0, space_separator); + auto value = header.substr(space_separator + 1, std::string::npos); + if (tag == "Basic") { + // The Basic tag is processed to extract username and password + found_basic_security = true; + auto decoded = ecf::decode_base64(value); + auto colon_separator = decoded.find(':'); + username = decoded.substr(0, colon_separator); + password = decoded.substr(colon_separator + 1, std::string::npos); + } + else if (tag == "Bearer") { + // The Bearer tag is not handled in ecFlow, and the actual authentication is expected to be + // performed by the reverse proxy. + found_bearer_security = true; + } + else { + // If no Basic or Bearer tag, then ignore the Authorisation header, + // and use the username and password from the inbound_request + } } } - if (identity_secure && identity_headers) { - LOG_DEBUG("HttpServer::handle_request", "Identity extracted from HTTP(s) request header (Authorisation)"); + if (found_bearer_security && found_header_username) { + LOG_DEBUG("HttpServer::handle_request", + "Identity extracted from HTTP(s) request header (Authorisation: Bearer)"); + identity = ecf::Identity::make_secure_user(username); + } + else if (found_basic_security && found_header_username) { + LOG_DEBUG("HttpServer::handle_request", + "Identity extracted from HTTP(s) request header (Authorization: Basic)"); identity = ecf::Identity::make_secure_user(username); } - else if (identity_headers) { - LOG_DEBUG("HttpServer::handle_request", "Identity extracted from HTTP(s) request header (X-Auth-Username)"); + else if (!found_basic_security && found_bearer_security && found_header_username && found_header_password) { + LOG_DEBUG("HttpServer::handle_request", + "Identity extracted from HTTP(s) request header (X-Auth-Username/X-Auth-Password)"); identity = ecf::Identity::make_user(username, password); } else { diff --git a/libs/server/src/ecflow/server/ServerEnvironment.cpp b/libs/server/src/ecflow/server/ServerEnvironment.cpp index f103517fa..a0fe180f3 100644 --- a/libs/server/src/ecflow/server/ServerEnvironment.cpp +++ b/libs/server/src/ecflow/server/ServerEnvironment.cpp @@ -151,7 +151,17 @@ void ServerEnvironment::init(const CommandLine& cl, const std::string& path_to_c ecf_white_list_file_ = host_name_.prefix_host_and_port(port, ecf_white_list_file_); } - authentication_service_.init(host_name_, port); + { + // Init the authentication service + authentication_service_.init(host_name_, port); + } + + { + // Init the authorisation service + if (auto perms = Permissions::make_from_variable(permissions_); perms.ok()) { + authorisation_service_.init(perms.value()); + } + } // Change directory to ECF_HOME and check that it is accessible change_dir_to_ecf_home_and_check_accesibility(); @@ -298,29 +308,41 @@ bool ServerEnvironment::valid(std::string& errorMsg) const { return false; } - if (auto result = AuthorisationService::load_permissions_from_nodes(); result.ok()) { - authorisation_service_ = result.value(); - std::cout << "Loaded server permissions based on Nodes\n"; - } + // + // Authentication Setup + // // Check that Authentication information is valid if (auto status = authentication_service_.valid(serverHost_, the_port(), errorMsg); !status) { return false; } - // *WHEN* the server starts, it is possible that the white list file is empty or simply does not exist, since any - // user is free to access the server. - if (ecf_white_list_file_.empty()) { - return true; - } - if (!fs::exists(ecf_white_list_file_)) { - return true; + // + // Authorisation Setup + // + + // + // Upon server starup, the following Authorisation scenarios are possible: + // 1) a white list file is defined, exists and is valid + // - the Authorisation is based on the white list file + // 2) a white list file is not defined, does not exist or is invalid, and ECF_PERMISSIONS is defined and valid + // - the Authorisation is based on ECF_PERMISSIONS + // 3) a white list file is not defined, does not exist or is invalid, and ECF_PERMISSIONS is not defined or invalid + // - the Authorisation is unrestricted + // + + if (!ecf_white_list_file_.empty() && fs::exists(ecf_white_list_file_)) { + return enable_whitelist_based_permissions(errorMsg); } + else { - /// read in the ecf white list file that specifies valid users and their access rights - /// If the file can't be opened returns false and an error message and false; - /// Automatically add server admin(user) with write access, as this will allow admin reload - return load_whitelist_file(errorMsg); + if (!permissions_.empty()) { + return enable_node_based_permissions(errorMsg); + } + else { + return enable_unrestricted_permissions(errorMsg); + } + } } std::pair ServerEnvironment::hostPort() const { @@ -368,6 +390,8 @@ void ServerEnvironment::variables(std::vectorwhite_list_file_); result.ok()) { + std::cout << "*** Server permissions based on white list file, loaded from '" << ecf_white_list_file_ + << "'\n"; + authorisation_service_ = result.value(); + return true; + } + else { + std::ostringstream ss; + ss << "Unable setup authorisation service based on white file, due to: " << result.reason() << '\n'; + error = ss.str(); + return false; + } + } + else { + std::ostringstream ss; + ss << "Unable to load white file based server permissions, due to: " << error << '\n'; + error = ss.str(); + return false; + } +} + +bool ServerEnvironment::enable_node_based_permissions(std::string& error) const { + if (auto permissions = Permissions::make_from_variable(permissions_); !permissions.ok()) { + std::ostringstream ss; + ss << "Found invalid ECF_PERMISSIONS value, due to: " << permissions.reason() << "\n"; + error = ss.str(); + return false; + } + + if (auto result = AuthorisationService::load_permissions_from_nodes(); result.ok()) { + authorisation_service_ = result.value(); + std::cout << "*** Server permissions based on ECF_PERMISSIONS\n"; + return true; + } + else { + std::ostringstream ss; + ss << "Unable setup authorisation service based on nodes, due to: " << result.reason() << '\n'; + error = ss.str(); + return false; + } +} + +bool ServerEnvironment::enable_unrestricted_permissions(std::string& error) const { + if (auto result = AuthorisationService::load_permissions_unrestricted(); result.ok()) { + authorisation_service_ = result.value(); + std::cout << "*** Server permissions based on unrestricted permissions\n"; + return true; + } + else { + std::ostringstream ss; + ss << "Unable setup unrestricted authorisation service, due to: " << result.reason() << '\n'; + error = ss.str(); + return false; + } +} + +/// +/// @brief Read in the ecf white list file that specifies valid users and their access rights +/// +/// Automatically ensures that the server admin(user) has write access, as this will allow admin reload. +/// +/// @param errorMsg a buffer for the error message +/// @return false, if the file cannot be loaded, with an error message; otherwise, true +/// bool ServerEnvironment::load_whitelist_file(std::string& errorMsg) const { // Only override valid users if we successfully opened and parsed file if (white_list_file_.load(ecf_white_list_file_, errorMsg)) { - std::cout << "*** White list file loaded from '" << ecf_white_list_file_ << "'\n"; if (debug()) { std::cout << white_list_file_.dump_valid_users() << "\n"; } @@ -410,80 +499,11 @@ bool ServerEnvironment::load_whitelist_file(std::string& errorMsg) const { // (Requires terminate, modify white list, restart to fix) // Hence always allow server user write access *IF* required for non-empty file white_list_file_.allow_write_access_for_server_user(); - - if (auto result = AuthorisationService::load_permissions_from_whitelist(this->white_list_file_); result.ok()) { - authorisation_service_ = result.value(); - std::cout << "Loaded server permissions based on Whitelist file\n"; - return true; - } - else { - std::cout << "Unable to load server permissions, due to: " << result.reason() << '\n'; - return false; - } + return true; } return false; } -// bool ServerEnvironment::authenticateReadAccess(const std::string& user, -// bool custom_user, -// const std::string& passwd) const { -// if (!custom_user) { -// if (!passwd_file_.authenticate(user, passwd)) -// return false; -// } -// else { -// if (!passwd_custom_file_.authenticate(user, passwd)) -// return false; -// } -// -// // if *NO* users specified then all users are valid -// return white_list_file_.verify_read_access(user); -// } - -// bool ServerEnvironment::authenticateReadAccess(const std::string& user, -// bool custom_user, -// const std::string& passwd, -// const std::string& path) const { -// if (!custom_user) { -// if (!passwd_file_.authenticate(user, passwd)) -// return false; -// } -// else { -// if (!passwd_custom_file_.authenticate(user, passwd)) -// return false; -// } -// -// return white_list_file_.verify_read_access(user, path); -// } -// -// bool ServerEnvironment::authenticateReadAccess(const std::string& user, -// bool custom_user, -// const std::string& passwd, -// const std::vector& paths) const { -// if (!custom_user) { -// if (!passwd_file_.authenticate(user, passwd)) -// return false; -// } -// else { -// if (!passwd_custom_file_.authenticate(user, passwd)) -// return false; -// } -// -// return white_list_file_.verify_read_access(user, paths); -// } -// -// bool ServerEnvironment::authenticateWriteAccess(const std::string& user) const { -// // if *NO* users specified then all users have write access -// return white_list_file_.verify_write_access(user); -// } -// bool ServerEnvironment::authenticateWriteAccess(const std::string& user, const std::string& path) const { -// return white_list_file_.verify_write_access(user, path); -// } -// bool ServerEnvironment::authenticateWriteAccess(const std::string& user, const std::vector& paths) const -// { -// return white_list_file_.verify_write_access(user, paths); -// } - // ============================================================================================ // Privates: // ============================================================================================ @@ -527,7 +547,10 @@ void ServerEnvironment::read_config_file(std::string& log_file_name, const std:: ("ECF_PASSWD", po::value(&passwd_file)->default_value(std::string{AuthenticationService::default_passwd_file()}), "Path name to passwd file") ("ECF_CUSTOM_PASSWD", po::value(&custom_passwd_file)->default_value(std::string{AuthenticationService::default_custom_passwd_file()}), "Path name to custom passwd file, for user who don't use login name") ("ECF_TASK_THRESHOLD", po::value(&the_task_threshold)->default_value(JobProfiler::task_threshold_default()), "The defaults thresholds when profiling job generation") - ("ECF_PRUNE_NODE_LOG", po::value(&ecf_prune_node_log_)->default_value(30), "Node log, older than 180 days automatically pruned when checkpoint file loaded"); + ("ECF_PRUNE_NODE_LOG", po::value(&ecf_prune_node_log_)->default_value(30), "Node log, older than 180 days automatically pruned when checkpoint file loaded") + (ecf::environment::ECF_PERMISSIONS,po::value(&permissions_)->default_value(""), "") + ; + // clang-format on std::ifstream ifs(path_to_config_file.c_str()); @@ -689,6 +712,8 @@ std::string ServerEnvironment::dump() const { ss << "ECF_SSL = " << ssl_ << "\n"; #endif + ss << ecf::environment::ECF_PERMISSIONS << " = " << permissions_ << "\n"; + ss << white_list_file_.dump_valid_users(); return ss.str(); } @@ -715,6 +740,7 @@ std::vector ServerEnvironment::expected_variables() { expected_variables.emplace_back("ECF_INTERVAL"); expected_variables.emplace_back(ecf::environment::ECF_PASSWD); expected_variables.emplace_back(ecf::environment::ECF_CUSTOM_PASSWD); + expected_variables.emplace_back(ecf::environment::ECF_PERMISSIONS); #ifdef ECF_OPENSSL if (ecf::environment::has(ecf::environment::ECF_SSL)) { expected_variables.emplace_back(ecf::environment::ECF_SSL); diff --git a/libs/server/src/ecflow/server/ServerEnvironment.hpp b/libs/server/src/ecflow/server/ServerEnvironment.hpp index 35fa2b76f..92f560b5b 100644 --- a/libs/server/src/ecflow/server/ServerEnvironment.hpp +++ b/libs/server/src/ecflow/server/ServerEnvironment.hpp @@ -170,26 +170,6 @@ class ServerEnvironment { /// If errors arise the exist user still stay in affect bool reloadWhiteListFile(std::string& errorMsg); - /// There are several kinds of authentification: - /// a/ None - /// b/ List mode. ASCII file based on ECF_LISTS is defined - /// c/ Secure mode. binary file based ECF_PASSWD is defined - /// At the moment we will only implement options a/ and b/ - // - /// Returns true if the given user has access to the server, false otherwise - // bool authenticateReadAccess(const std::string& user, bool custom_user, const std::string& passwd) const; - // bool authenticateReadAccess(const std::string& user, - // bool custom_user, - // const std::string& passwd, - // const std::string& path) const; - // bool authenticateReadAccess(const std::string& user, - // bool custom_user, - // const std::string& passwd, - // const std::vector& paths) const; - // bool authenticateWriteAccess(const std::string& user) const; - // bool authenticateWriteAccess(const std::string& user, const std::string& path) const; - // bool authenticateWriteAccess(const std::string& user, const std::vector& paths) const; - ecf::AuthenticationService& authentication() { return authentication_service_; } const ecf::AuthenticationService& authentication() const { return authentication_service_; } @@ -222,6 +202,10 @@ class ServerEnvironment { void change_dir_to_ecf_home_and_check_accesibility(); + bool enable_whitelist_based_permissions(std::string& error) const; + bool enable_node_based_permissions(std::string& error) const; + bool enable_unrestricted_permissions(std::string& error) const; + bool load_whitelist_file(std::string& err) const; private: @@ -232,6 +216,7 @@ class ServerEnvironment { int checkpt_save_time_alarm_; int submitJobsInterval_; int ecf_prune_node_log_; + std::string permissions_; bool jobGeneration_; // used in debug/test mode only ecf::Protocol protocol_; bool debug_; diff --git a/libs/test/simulator/src/ecflow/simulator/DefsAnalyserVisitor.hpp b/libs/test/simulator/src/ecflow/simulator/DefsAnalyserVisitor.hpp index 4a9dcfed9..97bcb3a5d 100644 --- a/libs/test/simulator/src/ecflow/simulator/DefsAnalyserVisitor.hpp +++ b/libs/test/simulator/src/ecflow/simulator/DefsAnalyserVisitor.hpp @@ -40,7 +40,7 @@ class DefsAnalyserVisitor final : public NodeTreeVisitor { std::set analysedNodes_; // The node we analysed std::string buffer_; ecf::stringstreambuf ss_{buffer_}; - ecf::Context ctx_ = ecf::Context::make_for(PrintStyle::DEFS); + ecf::FormatContext ctx_ = ecf::FormatContext::make_for(PrintStyle::DEFS); }; } // namespace ecf diff --git a/libs/test/simulator/src/ecflow/simulator/FlatAnalyserVisitor.hpp b/libs/test/simulator/src/ecflow/simulator/FlatAnalyserVisitor.hpp index 036314228..d41de82d6 100644 --- a/libs/test/simulator/src/ecflow/simulator/FlatAnalyserVisitor.hpp +++ b/libs/test/simulator/src/ecflow/simulator/FlatAnalyserVisitor.hpp @@ -36,7 +36,7 @@ class FlatAnalyserVisitor final : public NodeTreeVisitor { bool analyse(Node* n); std::string buffer_; ecf::stringstreambuf ss_{buffer_}; - ecf::Context ctx_ = ecf::Context::make_for(PrintStyle::DEFS); + ecf::FormatContext ctx_ = ecf::FormatContext::make_for(PrintStyle::DEFS); }; } // namespace ecf diff --git a/releng/imachination/compose.yaml b/releng/imachination/compose.yaml index 1ee89a172..b2e9e3bfc 100644 --- a/releng/imachination/compose.yaml +++ b/releng/imachination/compose.yaml @@ -15,7 +15,7 @@ services: authotron: hostname: authotron - image: eccr.ecmwf.int/auth-o-tron/auth-o-tron:0.2.5 + image: eccr.ecmwf.int/auth-o-tron/auth-o-tron:0.2.8 ports: - "8080:8080" volumes: diff --git a/releng/imachination/ecflow/deploy/workspace/server_environment.cfg b/releng/imachination/ecflow/deploy/workspace/server_environment.cfg index a183e1475..7725975ee 100644 --- a/releng/imachination/ecflow/deploy/workspace/server_environment.cfg +++ b/releng/imachination/ecflow/deploy/workspace/server_environment.cfg @@ -173,3 +173,16 @@ ECF_CUSTOM_PASSWD = ecf.custom_passwd # * export ECF_TASK_THRESHOLD=4000 # *************************************************************************** ECF_TASK_THRESHOLD = 4000 + +# ************************************************************************** +# * ECF_PERMISSIONS: +# * This defines the default permissions for the server. +# * It is used to set the server-wide permissions and can be overriden by +# * the user. The permissions are defined as follows: +# * - r: read permission +# * - w: write permission +# * - x: execute permission +# * - o: override permission +# * - s: administrator permission (cannot be overridden) +# ************************************************************************** +ECF_PERMISSIONS = admin:rwxo