diff --git a/lib/administration/user/include/irods/user_administration.hpp b/lib/administration/user/include/irods/user_administration.hpp index 28be66d00d..5b2a6325e1 100644 --- a/lib/administration/user/include/irods/user_administration.hpp +++ b/lib/administration/user/include/irods/user_administration.hpp @@ -6,15 +6,18 @@ #undef NAMESPACE_IMPL #undef RxComm #undef rxGeneralAdmin +#undef rxUserAdmin #ifdef IRODS_USER_ADMINISTRATION_ENABLE_SERVER_SIDE_API // NOLINTBEGIN(cppcoreguidelines-macro-usage) # define NAMESPACE_IMPL server # define RxComm RsComm # define rxGeneralAdmin rsGeneralAdmin +# define rxUserAdmin rsUserAdmin // NOLINTEND(cppcoreguidelines-macro-usage) # include "irods/rsGeneralAdmin.hpp" +# include "irods/rsUserAdmin.hpp" struct RsComm; #else @@ -22,9 +25,11 @@ struct RsComm; # define NAMESPACE_IMPL client # define RxComm RcComm # define rxGeneralAdmin rcGeneralAdmin +# define rxUserAdmin rcUserAdmin // NOLINTEND(cppcoreguidelines-macro-usage) # include "irods/generalAdmin.h" +# include "irods/userAdmin.h" struct RcComm; #endif // IRODS_USER_ADMINISTRATION_ENABLE_SERVER_SIDE_API diff --git a/lib/administration/user/src/user_administration.cpp b/lib/administration/user/src/user_administration.cpp index 7a1990cb45..27e8849bf8 100644 --- a/lib/administration/user/src/user_administration.cpp +++ b/lib/administration/user/src/user_administration.cpp @@ -54,6 +54,22 @@ namespace irods::experimental::administration::NAMESPACE_IMPL zone = get_local_zone(_comm); } + const auto current_user{user{_comm.clientUser.userName, _comm.clientUser.rodsZone}}; + const auto user_type{type(_comm, current_user)}; + + if (user_type == irods::experimental::administration::user_type::groupadmin) { + // clang-format off + userAdminInp_t input{.arg0 = "mkuser", + .arg1 = const_cast(name.data()), + .arg3 = const_cast(zone.data())}; + // clang-format on + if (const auto ec{rxUserAdmin(&_comm, &input)}; ec != 0) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) + THROW(ec, fmt::format("Failed to add user [{}].", name)); + } + return; + } + GeneralAdminInput input{}; input.arg0 = "add"; input.arg1 = "user"; @@ -87,6 +103,22 @@ namespace irods::experimental::administration::NAMESPACE_IMPL { const auto zone = get_local_zone(_comm); + const auto current_user{user{_comm.clientUser.userName, _comm.clientUser.rodsZone}}; + const auto user_type{type(_comm, current_user)}; + + if (user_type == irods::experimental::administration::user_type::groupadmin) { + userAdminInp_t input{.arg0 = "mkgroup", + .arg1 = const_cast(_group.name.data()), + .arg2 = "rodsgroup", + .arg3 = const_cast(zone.data())}; + + if (const auto ec{rxUserAdmin(&_comm, &input)}; ec != 0) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) + THROW(ec, fmt::format("Failed to add group [{}].", _group.name)); + } + return; + } + GeneralAdminInput input{}; input.arg0 = "add"; input.arg1 = "user"; @@ -122,6 +154,25 @@ namespace irods::experimental::administration::NAMESPACE_IMPL auto add_user_to_group(RxComm& _comm, const group& _group, const user& _user) -> void { + const auto name{local_unique_name(_comm, _user)}; + const auto current_user{user{_comm.clientUser.userName, _comm.clientUser.rodsZone}}; + const auto user_type{type(_comm, current_user)}; + + if (user_type == irods::experimental::administration::user_type::groupadmin) { + userAdminInp_t input{.arg0 = "modify", + .arg1 = "group", + .arg2 = const_cast(_group.name.data()), + .arg3 = "add", + .arg4 = const_cast(_user.name.data()), + .arg5 = const_cast(_user.zone.data())}; + + if (const auto ec{rxUserAdmin(&_comm, &input)}; ec != 0) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) + THROW(ec, fmt::format("Failed to add user [{}] to group [{}].", name, _group.name)); + } + return; + } + GeneralAdminInput input{}; input.arg0 = "modify"; input.arg1 = "group"; @@ -131,7 +182,6 @@ namespace irods::experimental::administration::NAMESPACE_IMPL input.arg5 = _user.zone.data(); if (const auto ec = rxGeneralAdmin(&_comm, &input); ec != 0) { - const auto name = local_unique_name(_comm, _user); // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) THROW(ec, fmt::format("Failed to add user [{}] to group [{}].", name, _group.name)); } @@ -139,6 +189,25 @@ namespace irods::experimental::administration::NAMESPACE_IMPL auto remove_user_from_group(RxComm& _comm, const group& _group, const user& _user) -> void { + const auto name{local_unique_name(_comm, _user)}; + const auto current_user{user{_comm.clientUser.userName, _comm.clientUser.rodsZone}}; + const auto user_type{type(_comm, current_user)}; + + if (user_type == irods::experimental::administration::user_type::groupadmin) { + userAdminInp_t input{.arg0 = "modify", + .arg1 = "group", + .arg2 = const_cast(_group.name.data()), + .arg3 = "remove", + .arg4 = const_cast(_user.name.data()), + .arg5 = const_cast(_user.zone.data())}; + + if (const auto ec{rxUserAdmin(&_comm, &input)}; ec != 0) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) + THROW(ec, fmt::format("Failed to remove user [{}] from group [{}].", name, _group.name)); + } + return; + } + GeneralAdminInput input{}; input.arg0 = "modify"; input.arg1 = "group"; @@ -148,7 +217,6 @@ namespace irods::experimental::administration::NAMESPACE_IMPL input.arg5 = _user.zone.data(); if (const auto ec = rxGeneralAdmin(&_comm, &input); ec != 0) { - const auto name = local_unique_name(_comm, _user); // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) THROW(ec, fmt::format("Failed to remove user [{}] from group [{}].", name, _group.name)); } diff --git a/unit_tests/src/test_user_administration.cpp b/unit_tests/src/test_user_administration.cpp index 1d4f297940..640825fede 100644 --- a/unit_tests/src/test_user_administration.cpp +++ b/unit_tests/src/test_user_administration.cpp @@ -18,6 +18,12 @@ TEST_CASE("user group administration") irods::experimental::client_connection conn; + // Create a shared, generic test user + adm::user generic_user{"lagging-train"}; + REQUIRE_NOTHROW(adm::client::add_user(conn, generic_user)); + irods::at_scope_exit generic_user_cleanup{ + [&conn, &generic_user] { CHECK_NOTHROW(adm::client::remove_user(conn, generic_user)); }}; + SECTION("local user operations") { adm::user test_user{"unit_test_test_user"}; @@ -223,4 +229,49 @@ TEST_CASE("user group administration") irods::experimental::defer_authentication, env.rodsHost, env.rodsPort, {test_user.name, env.rodsZone}}; CHECK(clientLoginWithPassword(static_cast(user_conn), test_user_prop.value.data()) == 0); } + + SECTION("#7576: ensure no limitations on proxied groupadmin") + { + rodsEnv env{}; + _getRodsEnv(env); + + REQUIRE_NOTHROW( + adm::client::modify_user(conn, generic_user, adm::user_type_property{adm::user_type::groupadmin})); + REQUIRE(adm::client::type(conn, generic_user) == adm::user_type::groupadmin); + + // Connect to iRODS + irods::experimental::client_connection user_conn{env.rodsHost, + env.rodsPort, + {env.rodsUserName, env.rodsZone}, // (proxy) rodsadmin user + {generic_user.name, env.rodsZone}}; // groupadmin user + + // Try to add a group as the proxied groupadmin user. + adm::group test_group{"bad_bad_water_group"}; + REQUIRE_NOTHROW(adm::client::add_group(user_conn, test_group)); + REQUIRE(adm::client::exists(user_conn, test_group)); + irods::at_scope_exit group_cleanup{ + [&conn, &test_group] { CHECK_NOTHROW(adm::client::remove_group(conn, test_group)); }}; + + // Add self to group + REQUIRE_NOTHROW(adm::client::add_user_to_group(user_conn, test_group, generic_user)); + REQUIRE(adm::client::user_is_member_of_group(user_conn, test_group, generic_user)); + irods::at_scope_exit groupadmin_self_add_cleanup{[&user_conn, &test_group, &generic_user] { + CHECK_NOTHROW(adm::client::remove_user_from_group(user_conn, test_group, generic_user)); + }}; + + // Create and remove user using groupadmin + adm::user groupadmin_created_user{"mr_flips"}; + REQUIRE_NOTHROW(adm::client::add_user(user_conn, groupadmin_created_user)); + REQUIRE(adm::client::exists(user_conn, groupadmin_created_user)); + irods::at_scope_exit created_user_cleanup{[&conn, &groupadmin_created_user] { + CHECK_NOTHROW(adm::client::remove_user(conn, groupadmin_created_user)); + }}; + + // Add and remove user from group using groupadmin + REQUIRE_NOTHROW(adm::client::add_user_to_group(user_conn, test_group, groupadmin_created_user)); + REQUIRE(adm::client::user_is_member_of_group(user_conn, test_group, groupadmin_created_user)); + + // Begin cleanup. Previous 'irods::at_scope_exit's will run after the following line + CHECK_NOTHROW(adm::client::remove_user_from_group(user_conn, test_group, groupadmin_created_user)); + } }