diff --git a/src/exec/CMakeLists.txt b/src/exec/CMakeLists.txt index f0552041efa9f214eb02a787508ce2fd7c9c1073..ab1747bd2216bbf9c6799a854fd8ccf4720d4730 100644 --- a/src/exec/CMakeLists.txt +++ b/src/exec/CMakeLists.txt @@ -27,6 +27,15 @@ nebula_add_library( query/DataCollectExecutor.cpp query/DataJoinExecutor.cpp admin/SwitchSpaceExecutor.cpp + admin/CreateUserExecutor.cpp + admin/DropUserExecutor.cpp + admin/UpdateUserExecutor.cpp + admin/GrantRoleExecutor.cpp + admin/RevokeRoleExecutor.cpp + admin/ChangePasswordExecutor.cpp + admin/ListUserRolesExecutor.cpp + admin/ListUsersExecutor.cpp + admin/ListRolesExecutor.cpp admin/SubmitJobExecutor.cpp admin/BalanceExecutor.cpp admin/StopBalanceExecutor.cpp diff --git a/src/exec/Executor.cpp b/src/exec/Executor.cpp index 9b42c319b31e4b1fc6fe9d1671eb821211e6918c..1cd63b4bb38d929063d8ba01c8207be170997634 100644 --- a/src/exec/Executor.cpp +++ b/src/exec/Executor.cpp @@ -13,6 +13,15 @@ #include "context/ExecutionContext.h" #include "context/QueryContext.h" #include "exec/ExecutionError.h" +#include "exec/admin/CreateUserExecutor.h" +#include "exec/admin/DropUserExecutor.h" +#include "exec/admin/UpdateUserExecutor.h" +#include "exec/admin/GrantRoleExecutor.h" +#include "exec/admin/RevokeRoleExecutor.h" +#include "exec/admin/ChangePasswordExecutor.h" +#include "exec/admin/ListUserRolesExecutor.h" +#include "exec/admin/ListUsersExecutor.h" +#include "exec/admin/ListRolesExecutor.h" #include "exec/admin/BalanceLeadersExecutor.h" #include "exec/admin/BalanceExecutor.h" #include "exec/admin/StopBalanceExecutor.h" @@ -408,6 +417,69 @@ Executor *Executor::makeExecutor(const PlanNode *node, exec->dependsOn(input); break; } + case PlanNode::Kind::kCreateUser: { + auto createUser = asNode<CreateUser>(node); + auto input = makeExecutor(createUser->dep(), qctx, visited); + exec = new CreateUserExecutor(createUser, qctx); + exec->dependsOn(input); + break; + } + case PlanNode::Kind::kDropUser: { + auto dropUser = asNode<DropUser>(node); + auto input = makeExecutor(dropUser->dep(), qctx, visited); + exec = new DropUserExecutor(dropUser, qctx); + exec->dependsOn(input); + break; + } + case PlanNode::Kind::kUpdateUser: { + auto updateUser = asNode<UpdateUser>(node); + auto input = makeExecutor(updateUser->dep(), qctx, visited); + exec = new UpdateUserExecutor(updateUser, qctx); + exec->dependsOn(input); + break; + } + case PlanNode::Kind::kGrantRole: { + auto grantRole = asNode<GrantRole>(node); + auto input = makeExecutor(grantRole->dep(), qctx, visited); + exec = new GrantRoleExecutor(grantRole, qctx); + exec->dependsOn(input); + break; + } + case PlanNode::Kind::kRevokeRole: { + auto revokeRole = asNode<RevokeRole>(node); + auto input = makeExecutor(revokeRole->dep(), qctx, visited); + exec = new RevokeRoleExecutor(revokeRole, qctx); + exec->dependsOn(input); + break; + } + case PlanNode::Kind::kChangePassword: { + auto changePassword = asNode<ChangePassword>(node); + auto input = makeExecutor(changePassword->dep(), qctx, visited); + exec = new ChangePasswordExecutor(changePassword, qctx); + exec->dependsOn(input); + break; + } + case PlanNode::Kind::kListUserRoles: { + auto listUserRoles = asNode<ListUserRoles>(node); + auto input = makeExecutor(listUserRoles->dep(), qctx, visited); + exec = new ListUserRolesExecutor(listUserRoles, qctx); + exec->dependsOn(input); + break; + } + case PlanNode::Kind::kListUsers: { + auto listUsers = asNode<ListUsers>(node); + auto input = makeExecutor(listUsers->dep(), qctx, visited); + exec = new ListUsersExecutor(listUsers, qctx); + exec->dependsOn(input); + break; + } + case PlanNode::Kind::kListRoles: { + auto listRoles = asNode<ListRoles>(node); + auto input = makeExecutor(listRoles->dep(), qctx, visited); + exec = new ListRolesExecutor(listRoles, qctx); + exec->dependsOn(input); + break; + } case PlanNode::Kind::kBalanceLeaders: { auto balanceLeaders = asNode<BalanceLeaders>(node); auto dep = makeExecutor(balanceLeaders->dep(), qctx, visited); diff --git a/src/exec/admin/ChangePasswordExecutor.cpp b/src/exec/admin/ChangePasswordExecutor.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8ae013a67e016ff7cff7b3215903a0f4e791e789 --- /dev/null +++ b/src/exec/admin/ChangePasswordExecutor.cpp @@ -0,0 +1,35 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#include "exec/admin/ChangePasswordExecutor.h" +#include "planner/Admin.h" +#include "context/QueryContext.h" + +namespace nebula { +namespace graph { + +folly::Future<Status> ChangePasswordExecutor::execute() { + SCOPED_TIMER(&execTime_); + return changePassword(); +} + +folly::Future<Status> ChangePasswordExecutor::changePassword() { + auto *cpNode = asNode<ChangePassword>(node()); + return qctx()->getMetaClient()->changePassword( + *cpNode->username(), *cpNode->newPassword(), *cpNode->password()) + .via(runner()) + .then([this](StatusOr<bool> &&resp) { + SCOPED_TIMER(&execTime_); + NG_RETURN_IF_ERROR(resp); + if (!resp.value()) { + return Status::Error("Change password failed!"); + } + return Status::OK(); + }); +} + +} // namespace graph +} // namespace nebula diff --git a/src/exec/admin/ChangePasswordExecutor.h b/src/exec/admin/ChangePasswordExecutor.h new file mode 100644 index 0000000000000000000000000000000000000000..cb3922963aab1bbd9fa270ee3507e28147995b87 --- /dev/null +++ b/src/exec/admin/ChangePasswordExecutor.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#ifndef EXEC_ADMIN_CHANGEPASSWORDEXECUTOR_H_ +#define EXEC_ADMIN_CHANGEPASSWORDEXECUTOR_H_ + +#include "exec/Executor.h" + +namespace nebula { +namespace graph { + +class ChangePasswordExecutor final : public Executor { +public: + ChangePasswordExecutor(const PlanNode *node, QueryContext *ectx) + : Executor("ChangePasswordExecutor", node, ectx) {} + + folly::Future<Status> execute() override; + +private: + folly::Future<Status> changePassword(); +}; + +} // namespace graph +} // namespace nebula + +#endif // EXEC_ADMIN_CHANGEPASSWORDEXECUTOR_H_ diff --git a/src/exec/admin/CreateUserExecutor.cpp b/src/exec/admin/CreateUserExecutor.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8b7502e347c322fe6ed568ff7e987ac987c562bf --- /dev/null +++ b/src/exec/admin/CreateUserExecutor.cpp @@ -0,0 +1,35 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#include "exec/admin/CreateUserExecutor.h" +#include "planner/Admin.h" +#include "context/QueryContext.h" + +namespace nebula { +namespace graph { + +folly::Future<Status> CreateUserExecutor::execute() { + SCOPED_TIMER(&execTime_); + return createUser(); +} + +folly::Future<Status> CreateUserExecutor::createUser() { + auto *cuNode = asNode<CreateUser>(node()); + return qctx()->getMetaClient()->createUser( + *cuNode->username(), *cuNode->password(), cuNode->ifNotExist()) + .via(runner()) + .then([this](StatusOr<bool> resp) { + SCOPED_TIMER(&execTime_); + NG_RETURN_IF_ERROR(resp); + if (!resp.value()) { + return Status::Error("Create User failed!"); + } + return Status::OK(); + }); +} + +} // namespace graph +} // namespace nebula diff --git a/src/exec/admin/CreateUserExecutor.h b/src/exec/admin/CreateUserExecutor.h new file mode 100644 index 0000000000000000000000000000000000000000..9dffa7d94759c712f18f1a2fcae8481db601c1db --- /dev/null +++ b/src/exec/admin/CreateUserExecutor.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#ifndef EXEC_ADMIN_CREATEUSEREXECUTOR_H_ +#define EXEC_ADMIN_CREATEUSEREXECUTOR_H_ + +#include "exec/Executor.h" + +namespace nebula { +namespace graph { + +class CreateUserExecutor final : public Executor { +public: + CreateUserExecutor(const PlanNode *node, QueryContext *ectx) + : Executor("CreateUserExecutor", node, ectx) {} + + folly::Future<Status> execute() override; + +private: + folly::Future<Status> createUser(); +}; + +} // namespace graph +} // namespace nebula + +#endif // EXEC_ADMIN_CREATEUSEREXECUTOR_H_ diff --git a/src/exec/admin/DropUserExecutor.cpp b/src/exec/admin/DropUserExecutor.cpp new file mode 100644 index 0000000000000000000000000000000000000000..04602fb953c55c09845598dd218314249541a547 --- /dev/null +++ b/src/exec/admin/DropUserExecutor.cpp @@ -0,0 +1,34 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#include "exec/admin/DropUserExecutor.h" +#include "planner/Admin.h" +#include "context/QueryContext.h" + +namespace nebula { +namespace graph { + +folly::Future<Status> DropUserExecutor::execute() { + SCOPED_TIMER(&execTime_); + return dropUser(); +} + +folly::Future<Status> DropUserExecutor::dropUser() { + auto *duNode = asNode<DropUser>(node()); + return qctx()->getMetaClient()->dropUser(*duNode->username(), duNode->ifExist()) + .via(runner()) + .then([this](StatusOr<bool> resp) { + SCOPED_TIMER(&execTime_); + NG_RETURN_IF_ERROR(resp); + if (!resp.value()) { + return Status::Error("Drop user failed!"); + } + return Status::OK(); + }); +} + +} // namespace graph +} // namespace nebula diff --git a/src/exec/admin/DropUserExecutor.h b/src/exec/admin/DropUserExecutor.h new file mode 100644 index 0000000000000000000000000000000000000000..5151480118804d5f1065a0f8ee3537587d7abc11 --- /dev/null +++ b/src/exec/admin/DropUserExecutor.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#ifndef EXEC_ADMIN_DROPUSEREXECUTOR_H_ +#define EXEC_ADMIN_DROPUSEREXECUTOR_H_ + +#include "exec/Executor.h" + +namespace nebula { +namespace graph { + +class DropUserExecutor final : public Executor { +public: + DropUserExecutor(const PlanNode *node, QueryContext *ectx) + : Executor("DropUserExecutor", node, ectx) {} + + folly::Future<Status> execute() override; + +private: + folly::Future<Status> dropUser(); +}; + +} // namespace graph +} // namespace nebula + +#endif // EXEC_ADMIN_DROPUSEREXECUTOR_H_ diff --git a/src/exec/admin/GrantRoleExecutor.cpp b/src/exec/admin/GrantRoleExecutor.cpp new file mode 100644 index 0000000000000000000000000000000000000000..103fea4054964af9dc6d17675b9ac6aabcc54411 --- /dev/null +++ b/src/exec/admin/GrantRoleExecutor.cpp @@ -0,0 +1,45 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#include "exec/admin/GrantRoleExecutor.h" +#include "planner/Admin.h" +#include "context/QueryContext.h" + +namespace nebula { +namespace graph { + +folly::Future<Status> GrantRoleExecutor::execute() { + SCOPED_TIMER(&execTime_); + return grantRole(); +} + +folly::Future<Status> GrantRoleExecutor::grantRole() { + SCOPED_TIMER(&execTime_); + auto *grNode = asNode<GrantRole>(node()); + const auto *spaceName = grNode->spaceName(); + auto spaceIdResult = qctx()->getMetaClient()->getSpaceIdByNameFromCache(*spaceName); + if (!spaceIdResult.ok()) { + return std::move(spaceIdResult).status(); + } + auto spaceId = spaceIdResult.value(); + meta::cpp2::RoleItem item; + item.set_space_id(spaceId); // TODO(shylock) pass space name directly + item.set_user_id(*grNode->username()); + item.set_role_type(grNode->role()); + return qctx()->getMetaClient()->grantToUser(std::move(item)) + .via(runner()) + .then([this](StatusOr<bool> resp) { + SCOPED_TIMER(&execTime_); + NG_RETURN_IF_ERROR(resp); + if (!resp.value()) { + return Status::Error("Grant role failed!"); + } + return Status::OK(); + }); +} + +} // namespace graph +} // namespace nebula diff --git a/src/exec/admin/GrantRoleExecutor.h b/src/exec/admin/GrantRoleExecutor.h new file mode 100644 index 0000000000000000000000000000000000000000..d9bd01b7eba03e581501cffa812401aef7854da8 --- /dev/null +++ b/src/exec/admin/GrantRoleExecutor.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#ifndef EXEC_ADMIN_GRANTROLEEXECUTOR_H_ +#define EXEC_ADMIN_GRANTROLEEXECUTOR_H_ + +#include "exec/Executor.h" + +namespace nebula { +namespace graph { + +class GrantRoleExecutor final : public Executor { +public: + GrantRoleExecutor(const PlanNode *node, QueryContext *ectx) + : Executor("GrantRoleExecutor", node, ectx) {} + + folly::Future<Status> execute() override; + +private: + folly::Future<Status> grantRole(); +}; + +} // namespace graph +} // namespace nebula + +#endif // EXEC_ADMIN_GRANTROLEEXECUTOR_H_ diff --git a/src/exec/admin/ListRolesExecutor.cpp b/src/exec/admin/ListRolesExecutor.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9d813b89616c623ab75d84e9bfda1dc85e24ad95 --- /dev/null +++ b/src/exec/admin/ListRolesExecutor.cpp @@ -0,0 +1,42 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#include "exec/admin/ListRolesExecutor.h" +#include "planner/Admin.h" +#include "context/QueryContext.h" + +namespace nebula { +namespace graph { + +folly::Future<Status> ListRolesExecutor::execute() { + SCOPED_TIMER(&execTime_); + return listRoles(); +} + +folly::Future<Status> ListRolesExecutor::listRoles() { + auto *lrNode = asNode<ListRoles>(node()); + return qctx()->getMetaClient()->listRoles(lrNode->space()) + .via(runner()) + .then([this](StatusOr<std::vector<meta::cpp2::RoleItem>> &&resp) { + SCOPED_TIMER(&execTime_); + if (!resp.ok()) { + return std::move(resp).status(); + } + nebula::DataSet v({"Account", "Role Type"}); + auto items = std::move(resp).value(); + for (const auto &item : items) { + v.emplace_back(nebula::Row( + { + item.get_user_id(), + meta::cpp2::_RoleType_VALUES_TO_NAMES.at(item.get_role_type()) + })); + } + return finish(std::move(v)); + }); +} + +} // namespace graph +} // namespace nebula diff --git a/src/exec/admin/ListRolesExecutor.h b/src/exec/admin/ListRolesExecutor.h new file mode 100644 index 0000000000000000000000000000000000000000..bff550cb0fdf84d42652bbd13d97d497d25a53f6 --- /dev/null +++ b/src/exec/admin/ListRolesExecutor.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#ifndef EXEC_ADMIN_LISTROLESEXECUTOR_H_ +#define EXEC_ADMIN_LISTROLESEXECUTOR_H_ + +#include "exec/Executor.h" + +namespace nebula { +namespace graph { + +class ListRolesExecutor final : public Executor { +public: + ListRolesExecutor(const PlanNode *node, QueryContext *ectx) + : Executor("ListRolesExecutor", node, ectx) {} + + folly::Future<Status> execute() override; + +private: + folly::Future<Status> listRoles(); +}; + +} // namespace graph +} // namespace nebula + +#endif // EXEC_ADMIN_LISTROLESEXECUTOR_H_ diff --git a/src/exec/admin/ListUserRolesExecutor.cpp b/src/exec/admin/ListUserRolesExecutor.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6772e9fafbe7da487abcf5edc28e36039842b677 --- /dev/null +++ b/src/exec/admin/ListUserRolesExecutor.cpp @@ -0,0 +1,42 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#include "exec/admin/ListUserRolesExecutor.h" +#include "planner/Admin.h" +#include "context/QueryContext.h" + +namespace nebula { +namespace graph { + +folly::Future<Status> ListUserRolesExecutor::execute() { + SCOPED_TIMER(&execTime_); + return listUserRoles(); +} + +folly::Future<Status> ListUserRolesExecutor::listUserRoles() { + auto *lurNode = asNode<ListUserRoles>(node()); + return qctx()->getMetaClient()->getUserRoles(*lurNode->username()) + .via(runner()) + .then([this](StatusOr<std::vector<meta::cpp2::RoleItem>> &&resp) { + SCOPED_TIMER(&execTime_); + if (!resp.ok()) { + return std::move(resp).status(); + } + nebula::DataSet v({"Account", "Role Type"}); + auto items = std::move(resp).value(); + for (const auto &item : items) { + v.emplace_back(nebula::Row( + { + item.get_user_id(), + meta::cpp2::_RoleType_VALUES_TO_NAMES.at(item.get_role_type()) + })); + } + return finish(std::move(v)); + }); +} + +} // namespace graph +} // namespace nebula diff --git a/src/exec/admin/ListUserRolesExecutor.h b/src/exec/admin/ListUserRolesExecutor.h new file mode 100644 index 0000000000000000000000000000000000000000..513361dc886cc54b491950f15a41a71fc6a8d4da --- /dev/null +++ b/src/exec/admin/ListUserRolesExecutor.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#ifndef EXEC_ADMIN_LISTUSERROLESEXECUTOR_H_ +#define EXEC_ADMIN_LISTUSERROLESEXECUTOR_H_ + +#include "exec/Executor.h" + +namespace nebula { +namespace graph { + +class ListUserRolesExecutor final : public Executor { +public: + ListUserRolesExecutor(const PlanNode *node, QueryContext *ectx) + : Executor("ListUserRolesExecutor", node, ectx) {} + + folly::Future<Status> execute() override; + +private: + folly::Future<Status> listUserRoles(); +}; + +} // namespace graph +} // namespace nebula + +#endif // EXEC_ADMIN_LISTUSERROLESEXECUTOR_H_ diff --git a/src/exec/admin/ListUsersExecutor.cpp b/src/exec/admin/ListUsersExecutor.cpp new file mode 100644 index 0000000000000000000000000000000000000000..18fca65a0c2be804e0e3d49457e041c02e03e41c --- /dev/null +++ b/src/exec/admin/ListUsersExecutor.cpp @@ -0,0 +1,40 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#include "exec/admin/ListUsersExecutor.h" +#include "planner/Admin.h" +#include "context/QueryContext.h" + +namespace nebula { +namespace graph { + +folly::Future<Status> ListUsersExecutor::execute() { + SCOPED_TIMER(&execTime_); + return listUsers(); +} + +folly::Future<Status> ListUsersExecutor::listUsers() { + return qctx()->getMetaClient()->listUsers() + .via(runner()) + .then([this](StatusOr<std::unordered_map<std::string, std::string>> &&resp) { + SCOPED_TIMER(&execTime_); + if (!resp.ok()) { + return std::move(resp).status(); + } + nebula::DataSet v({"Account"}); + auto items = std::move(resp).value(); + for (const auto &item : items) { + v.emplace_back(nebula::Row( + { + std::move(item).first, + })); + } + return finish(std::move(v)); + }); +} + +} // namespace graph +} // namespace nebula diff --git a/src/exec/admin/ListUsersExecutor.h b/src/exec/admin/ListUsersExecutor.h new file mode 100644 index 0000000000000000000000000000000000000000..8f22667f6ec9a34391e3558b5837e40b1bd546fc --- /dev/null +++ b/src/exec/admin/ListUsersExecutor.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#ifndef EXEC_ADMIN_LISTUSERSEXECUTOR_H_ +#define EXEC_ADMIN_LISTUSERSEXECUTOR_H_ + +#include "exec/Executor.h" + +namespace nebula { +namespace graph { + +class ListUsersExecutor final : public Executor { +public: + ListUsersExecutor(const PlanNode *node, QueryContext *ectx) + : Executor("ListUsersExecutor", node, ectx) {} + + folly::Future<Status> execute() override; + +private: + folly::Future<Status> listUsers(); +}; + +} // namespace graph +} // namespace nebula + +#endif // EXEC_ADMIN_LISTUSERSEXECUTOR_H_ diff --git a/src/exec/admin/RevokeRoleExecutor.cpp b/src/exec/admin/RevokeRoleExecutor.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fef2d12cfcd2d2fa90f6ba7d9221e2c428b4d654 --- /dev/null +++ b/src/exec/admin/RevokeRoleExecutor.cpp @@ -0,0 +1,44 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#include "exec/admin/RevokeRoleExecutor.h" +#include "planner/Admin.h" +#include "context/QueryContext.h" + +namespace nebula { +namespace graph { + +folly::Future<Status> RevokeRoleExecutor::execute() { + SCOPED_TIMER(&execTime_); + return revokeRole(); +} + +folly::Future<Status> RevokeRoleExecutor::revokeRole() { + auto *rrNode = asNode<RevokeRole>(node()); + const auto *spaceName = rrNode->spaceName(); + auto spaceIdResult = qctx()->getMetaClient()->getSpaceIdByNameFromCache(*spaceName); + if (!spaceIdResult.ok()) { + return std::move(spaceIdResult).status(); + } + auto spaceId = spaceIdResult.value(); + meta::cpp2::RoleItem item; + item.set_space_id(spaceId); + item.set_user_id(*rrNode->username()); + item.set_role_type(rrNode->role()); + return qctx()->getMetaClient()->revokeFromUser(std::move(item)) + .via(runner()) + .then([this](StatusOr<bool> resp) { + SCOPED_TIMER(&execTime_); + NG_RETURN_IF_ERROR(resp); + if (!resp.value()) { + return Status::Error("Revoke role failed!"); + } + return Status::OK(); + }); +} + +} // namespace graph +} // namespace nebula diff --git a/src/exec/admin/RevokeRoleExecutor.h b/src/exec/admin/RevokeRoleExecutor.h new file mode 100644 index 0000000000000000000000000000000000000000..3413e9f82ca2897e74a0cb4f705bec224a2a1ae4 --- /dev/null +++ b/src/exec/admin/RevokeRoleExecutor.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#ifndef EXEC_ADMIN_REVOKEROLEEXECUTOR_H_ +#define EXEC_ADMIN_REVOKEROLEEXECUTOR_H_ + +#include "exec/Executor.h" + +namespace nebula { +namespace graph { + +class RevokeRoleExecutor final : public Executor { +public: + RevokeRoleExecutor(const PlanNode *node, QueryContext *ectx) + : Executor("RevokeRoleExecutor", node, ectx) {} + + folly::Future<Status> execute() override; + +private: + folly::Future<Status> revokeRole(); +}; + +} // namespace graph +} // namespace nebula + +#endif // EXEC_ADMIN_REVOKEROLEEXECUTOR_H_ diff --git a/src/exec/admin/UpdateUserExecutor.cpp b/src/exec/admin/UpdateUserExecutor.cpp new file mode 100644 index 0000000000000000000000000000000000000000..224b304a19f3b384486bdfb72ac72aab999e77b7 --- /dev/null +++ b/src/exec/admin/UpdateUserExecutor.cpp @@ -0,0 +1,34 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#include "exec/admin/UpdateUserExecutor.h" +#include "planner/Admin.h" +#include "context/QueryContext.h" + +namespace nebula { +namespace graph { + +folly::Future<Status> UpdateUserExecutor::execute() { + SCOPED_TIMER(&execTime_); + return updateUser(); +} + +folly::Future<Status> UpdateUserExecutor::updateUser() { + auto *uuNode = asNode<UpdateUser>(node()); + return qctx()->getMetaClient()->alterUser(*uuNode->username(), *uuNode->password()) + .via(runner()) + .then([this](StatusOr<bool> resp) { + SCOPED_TIMER(&execTime_); + NG_RETURN_IF_ERROR(resp); + if (!resp.value()) { + return Status::Error("Update user failed!"); + } + return Status::OK(); + }); +} + +} // namespace graph +} // namespace nebula diff --git a/src/exec/admin/UpdateUserExecutor.h b/src/exec/admin/UpdateUserExecutor.h new file mode 100644 index 0000000000000000000000000000000000000000..c282e97965116375c23328be56767f13bb5ea6d5 --- /dev/null +++ b/src/exec/admin/UpdateUserExecutor.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#ifndef EXEC_ADMIN_UPDATEUSEREXECUTOR_H_ +#define EXEC_ADMIN_UPDATEUSEREXECUTOR_H_ + +#include "exec/Executor.h" + +namespace nebula { +namespace graph { + +class UpdateUserExecutor final : public Executor { +public: + UpdateUserExecutor(const PlanNode *node, QueryContext *ectx) + : Executor("UpdateUserExecutor", node, ectx) {} + + folly::Future<Status> execute() override; + +private: + folly::Future<Status> updateUser(); +}; + +} // namespace graph +} // namespace nebula + +#endif // EXEC_ADMIN_UPDATEUSEREXECUTOR_H_ diff --git a/src/mock/MetaCache.cpp b/src/mock/MetaCache.cpp index 0a74d98d100af78fabd94c14185ff2676bef5b92..813f1258524ef98dc25146df34cb3714f9909061 100644 --- a/src/mock/MetaCache.cpp +++ b/src/mock/MetaCache.cpp @@ -41,6 +41,7 @@ Status MetaCache::createSpace(const meta::cpp2::CreateSpaceReq &req, GraphSpaceI << ", replica_factor: " << space.get_properties().get_replica_factor() << ", rvid_size: " << space.get_properties().get_vid_size(); cache_[spaceId] = SpaceInfoCache(); + roles_.emplace(spaceId, UserRoles()); return Status::OK(); } @@ -87,7 +88,11 @@ Status MetaCache::dropSpace(const meta::cpp2::DropSpaceReq &req) { auto id = findIter->second; spaces_.erase(id); cache_.erase(id); +<<<<<<< HEAD + roles_.erase(id); +======= spaceIndex_.erase(spaceName); +>>>>>>> 5ef41cc3161e6403d0ac3ef9144f7e6b5a9a8f87 return Status::OK(); } @@ -293,10 +298,6 @@ Status MetaCache::heartBeat(const meta::cpp2::HBReq& req) { return Status::OK(); } -Status MetaCache::listUsers(const meta::cpp2::ListUsersReq&) { - return Status::OK(); -} - std::vector<meta::cpp2::HostItem> MetaCache::listHosts() { folly::RWSpinLock::WriteHolder holder(lock_); std::vector<meta::cpp2::HostItem> hosts; @@ -326,6 +327,199 @@ std::unordered_map<PartitionID, std::vector<HostAddr>> MetaCache::getParts() { return parts; } +////////////////////////////////////////////// ACL related mock //////////////////////////////////// +meta::cpp2::ExecResp MetaCache::createUser(const meta::cpp2::CreateUserReq& req) { + meta::cpp2::ExecResp resp; + folly::RWSpinLock::WriteHolder wh(userLock_); + const auto user = users_.find(req.get_account()); + if (user != users_.end()) { // already exists + resp.set_code(req.get_if_not_exists() ? + meta::cpp2::ErrorCode::SUCCEEDED : + meta::cpp2::ErrorCode::E_EXISTED); + return resp; + } + + auto result = users_.emplace(req.get_account(), UserInfo{req.get_encoded_pwd()}); + resp.set_code(result.second ? + meta::cpp2::ErrorCode::SUCCEEDED : + meta::cpp2::ErrorCode::E_UNKNOWN); + return resp; +} + +meta::cpp2::ExecResp MetaCache::dropUser(const meta::cpp2::DropUserReq& req) { + meta::cpp2::ExecResp resp; + folly::RWSpinLock::WriteHolder wh(userLock_); + const auto user = users_.find(req.get_account()); + if (user == users_.end()) { // not exists + resp.set_code(req.get_if_exists() ? + meta::cpp2::ErrorCode::SUCCEEDED : + meta::cpp2::ErrorCode::E_NOT_FOUND); + return resp; + } + + auto result = users_.erase(req.get_account()); + resp.set_code(result == 1 ? + meta::cpp2::ErrorCode::SUCCEEDED : + meta::cpp2::ErrorCode::E_UNKNOWN); + return resp; +} + +meta::cpp2::ExecResp MetaCache::alterUser(const meta::cpp2::AlterUserReq& req) { + meta::cpp2::ExecResp resp; + folly::RWSpinLock::WriteHolder wh(userLock_); + auto user = users_.find(req.get_account()); + if (user == users_.end()) { // not exists + resp.set_code(meta::cpp2::ErrorCode::E_NOT_FOUND); + return resp; + } + user->second.password = req.get_encoded_pwd(); + resp.set_code(meta::cpp2::ErrorCode::SUCCEEDED); + return resp; +} + +meta::cpp2::ExecResp MetaCache::grantRole(const meta::cpp2::GrantRoleReq& req) { + meta::cpp2::ExecResp resp; + const auto &item = req.get_role_item(); + { + folly::RWSpinLock::ReadHolder spaceRH(roleLock_); + folly::RWSpinLock::ReadHolder userRH(userLock_); + // find space + auto space = roles_.find(item.get_space_id()); + if (space == roles_.end()) { + resp.set_code(meta::cpp2::ErrorCode::E_NOT_FOUND); + return resp; + } + // find user + auto user = users_.find(item.get_user_id()); + if (user == users_.end()) { + resp.set_code(meta::cpp2::ErrorCode::E_NOT_FOUND); + return resp; + } + } + folly::RWSpinLock::WriteHolder roleWH(roleLock_); + // space + auto space = roles_.find(item.get_space_id()); + // user + auto user = space->second.find(item.get_user_id()); + if (user == space->second.end()) { + space->second.emplace(item.get_user_id(), + std::unordered_set<meta::cpp2::RoleType>{item.get_role_type()}); + } else { + user->second.emplace(item.get_role_type()); + } + resp.set_code(meta::cpp2::ErrorCode::SUCCEEDED); + return resp; +} + +meta::cpp2::ExecResp MetaCache::revokeRole(const meta::cpp2::RevokeRoleReq& req) { + meta::cpp2::ExecResp resp; + const auto &item = req.get_role_item(); + folly::RWSpinLock::WriteHolder rolesWH(roleLock_); + // find space + auto space = roles_.find(item.get_space_id()); + if (space == roles_.end()) { + resp.set_code(meta::cpp2::ErrorCode::E_NOT_FOUND); + return resp; + } + // find user + auto user = space->second.find(item.get_user_id()); + if (user == space->second.end()) { + resp.set_code(meta::cpp2::ErrorCode::E_NOT_FOUND); + return resp; + } + // find role + auto role = user->second.find(item.get_role_type()); + if (role == user->second.end()) { + resp.set_code(meta::cpp2::ErrorCode::E_NOT_FOUND); + return resp; + } + user->second.erase(item.get_role_type()); + resp.set_code(meta::cpp2::ErrorCode::SUCCEEDED); + return resp; +} + +meta::cpp2::ListUsersResp MetaCache::listUsers(const meta::cpp2::ListUsersReq&) { + meta::cpp2::ListUsersResp resp; + folly::RWSpinLock::ReadHolder rh(userLock_); + std::unordered_map<std::string, std::string> users; + for (const auto &user : users_) { + users.emplace(user.first, user.second.password); + } + resp.set_code(meta::cpp2::ErrorCode::SUCCEEDED); + resp.set_users(std::move(users)); + return resp; +} + +meta::cpp2::ListRolesResp MetaCache::listRoles(const meta::cpp2::ListRolesReq& req) { + meta::cpp2::ListRolesResp resp; + folly::RWSpinLock::ReadHolder rh(roleLock_); + std::vector<meta::cpp2::RoleItem> items; + const auto space = roles_.find(req.get_space_id()); + if (space == roles_.end()) { + resp.set_code(meta::cpp2::ErrorCode::E_NOT_FOUND); + return resp; + } + for (const auto &user : space->second) { + for (const auto &role : user.second) { + meta::cpp2::RoleItem item; + item.set_space_id(space->first); + item.set_user_id(user.first); + item.set_role_type(role); + items.emplace_back(std::move(item)); + } + } + resp.set_code(meta::cpp2::ErrorCode::SUCCEEDED); + resp.set_roles(std::move(items)); + return resp; +} + +meta::cpp2::ExecResp MetaCache::changePassword(const meta::cpp2::ChangePasswordReq& req) { + meta::cpp2::ExecResp resp; + folly::RWSpinLock::WriteHolder wh(userLock_); + auto user = users_.find(req.get_account()); + if (user == users_.end()) { + resp.set_code(meta::cpp2::ErrorCode::E_NOT_FOUND); + return resp; + } + if (user->second.password != req.get_old_encoded_pwd()) { + resp.set_code(meta::cpp2::ErrorCode::E_INVALID_PASSWORD); + } + user->second.password = req.get_new_encoded_pwd(); + resp.set_code(meta::cpp2::ErrorCode::SUCCEEDED); + return resp; +} + +meta::cpp2::ListRolesResp MetaCache::getUserRoles(const meta::cpp2::GetUserRolesReq& req) { + meta::cpp2::ListRolesResp resp; + { + folly::RWSpinLock::ReadHolder userRH(userLock_); + // find user + auto user = users_.find(req.get_account()); + if (user == users_.end()) { + resp.set_code(meta::cpp2::ErrorCode::E_NOT_FOUND); + return resp; + } + } + + folly::RWSpinLock::ReadHolder roleRH(roleLock_); + std::vector<meta::cpp2::RoleItem> items; + for (const auto& space : roles_) { + const auto& user = space.second.find(req.get_account()); + if (user != space.second.end()) { + for (const auto & role : user->second) { + meta::cpp2::RoleItem item; + item.set_space_id(space.first); + item.set_user_id(user->first); + item.set_role_type(role); + items.emplace_back(std::move(item)); + } + } + } + resp.set_code(meta::cpp2::ErrorCode::SUCCEEDED); + resp.set_roles(std::move(items)); + return resp; +} + ErrorOr<meta::cpp2::ErrorCode, int64_t> MetaCache::balanceSubmit(std::vector<HostAddr> dels) { folly::RWSpinLock::ReadHolder rh(lock_); for (const auto &job : balanceJobs_) { diff --git a/src/mock/MetaCache.h b/src/mock/MetaCache.h index d109f3d063e29ee7f6b090c9950b49afd91aa0b9..68755ee69dd6b7adb6cbfbf59eaf9d28e6a975ca 100644 --- a/src/mock/MetaCache.h +++ b/src/mock/MetaCache.h @@ -64,12 +64,29 @@ public: Status heartBeat(const meta::cpp2::HBReq &req); - Status listUsers(const meta::cpp2::ListUsersReq& req); - std::vector<meta::cpp2::HostItem> listHosts(); std::unordered_map<PartitionID, std::vector<HostAddr>> getParts(); +////////////////////////////////////////////// ACL related mock //////////////////////////////////// + meta::cpp2::ExecResp createUser(const meta::cpp2::CreateUserReq& req); + + meta::cpp2::ExecResp dropUser(const meta::cpp2::DropUserReq& req); + + meta::cpp2::ExecResp alterUser(const meta::cpp2::AlterUserReq& req); + + meta::cpp2::ExecResp grantRole(const meta::cpp2::GrantRoleReq& req); + + meta::cpp2::ExecResp revokeRole(const meta::cpp2::RevokeRoleReq& req); + + meta::cpp2::ListUsersResp listUsers(const meta::cpp2::ListUsersReq& req); + + meta::cpp2::ListRolesResp listRoles(const meta::cpp2::ListRolesReq& req); + + meta::cpp2::ExecResp changePassword(const meta::cpp2::ChangePasswordReq& req); + + meta::cpp2::ListRolesResp getUserRoles(const meta::cpp2::GetUserRolesReq& req); + ErrorOr<meta::cpp2::ErrorCode, int64_t> balanceSubmit(std::vector<HostAddr> dels); ErrorOr<meta::cpp2::ErrorCode, int64_t> balanceStop(); meta::cpp2::ErrorCode balanceLeaders(); @@ -152,6 +169,21 @@ private: std::unordered_map<std::string, meta::cpp2::Snapshot> snapshots_; mutable folly::RWSpinLock lock_; +///////////////////////////////////////////// ACL cache //////////////////////////////////////////// + struct UserInfo { + std::string password; + // revserved + }; + + // username -> UserInfo + std::unordered_map<std::string, UserInfo> users_; + mutable folly::RWSpinLock userLock_; + // authority + using UserRoles = + std::unordered_map<std::string/*user*/, std::unordered_set<meta::cpp2::RoleType>>; + std::unordered_map<GraphSpaceID, UserRoles> roles_; + mutable folly::RWSpinLock roleLock_; + ////////////////////////////////////////////// Balance ///////////////////////////////////////////// struct BalanceTask { GraphSpaceID space; diff --git a/src/mock/MockMetaServiceHandler.cpp b/src/mock/MockMetaServiceHandler.cpp index 63830a572b532f0cd331cf1425e71f9bf411de52..67d0e6532b2fc11bc3bef9eb7f6f043567c78702 100644 --- a/src/mock/MockMetaServiceHandler.cpp +++ b/src/mock/MockMetaServiceHandler.cpp @@ -466,63 +466,48 @@ MockMetaServiceHandler::future_heartBeat(const meta::cpp2::HBReq& req) { } folly::Future<meta::cpp2::ExecResp> -MockMetaServiceHandler::future_createUser(const meta::cpp2::CreateUserReq&) { - RETURN_SUCCESSED(); +MockMetaServiceHandler::future_createUser(const meta::cpp2::CreateUserReq& req) { + return MetaCache::instance().createUser(req); } folly::Future<meta::cpp2::ExecResp> -MockMetaServiceHandler::future_dropUser(const meta::cpp2::DropUserReq&) { - RETURN_SUCCESSED(); +MockMetaServiceHandler::future_dropUser(const meta::cpp2::DropUserReq& req) { + return MetaCache::instance().dropUser(req); } folly::Future<meta::cpp2::ExecResp> -MockMetaServiceHandler::future_alterUser(const meta::cpp2::AlterUserReq&) { - RETURN_SUCCESSED(); +MockMetaServiceHandler::future_alterUser(const meta::cpp2::AlterUserReq& req) { + return MetaCache::instance().alterUser(req); } folly::Future<meta::cpp2::ExecResp> -MockMetaServiceHandler::future_grantRole(const meta::cpp2::GrantRoleReq&) { - RETURN_SUCCESSED(); +MockMetaServiceHandler::future_grantRole(const meta::cpp2::GrantRoleReq& req) { + return MetaCache::instance().grantRole(req); } folly::Future<meta::cpp2::ExecResp> -MockMetaServiceHandler::future_revokeRole(const meta::cpp2::RevokeRoleReq&) { - RETURN_SUCCESSED(); +MockMetaServiceHandler::future_revokeRole(const meta::cpp2::RevokeRoleReq& req) { + return MetaCache::instance().revokeRole(req); } folly::Future<meta::cpp2::ListUsersResp> -MockMetaServiceHandler::future_listUsers(const meta::cpp2::ListUsersReq&) { - folly::Promise<meta::cpp2::ListUsersResp> promise; - auto future = promise.getFuture(); - meta::cpp2::ListUsersResp resp; - resp.set_code(meta::cpp2::ErrorCode::SUCCEEDED); - promise.setValue(std::move(resp)); - return future; +MockMetaServiceHandler::future_listUsers(const meta::cpp2::ListUsersReq& req) { + return MetaCache::instance().listUsers(req); } folly::Future<meta::cpp2::ListRolesResp> -MockMetaServiceHandler::future_listRoles(const meta::cpp2::ListRolesReq&) { - folly::Promise<meta::cpp2::ListRolesResp> promise; - auto future = promise.getFuture(); - meta::cpp2::ListRolesResp resp; - resp.set_code(meta::cpp2::ErrorCode::SUCCEEDED); - promise.setValue(std::move(resp)); - return future; +MockMetaServiceHandler::future_listRoles(const meta::cpp2::ListRolesReq& req) { + return MetaCache::instance().listRoles(req); } folly::Future<meta::cpp2::ExecResp> -MockMetaServiceHandler::future_changePassword(const meta::cpp2::ChangePasswordReq&) { - RETURN_SUCCESSED(); +MockMetaServiceHandler::future_changePassword(const meta::cpp2::ChangePasswordReq& req) { + return MetaCache::instance().changePassword(req); } folly::Future<meta::cpp2::ListRolesResp> -MockMetaServiceHandler::future_getUserRoles(const meta::cpp2::GetUserRolesReq&) { - folly::Promise<meta::cpp2::ListRolesResp> promise; - auto future = promise.getFuture(); - meta::cpp2::ListRolesResp resp; - resp.set_code(meta::cpp2::ErrorCode::SUCCEEDED); - promise.setValue(std::move(resp)); - return future; +MockMetaServiceHandler::future_getUserRoles(const meta::cpp2::GetUserRolesReq& req) { + return MetaCache::instance().getUserRoles(req); } folly::Future<meta::cpp2::BalanceResp> diff --git a/src/mock/test/ACLTest.cpp b/src/mock/test/ACLTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..839e5a039a3c137c67f2fc4d398c94db4e36bbfa --- /dev/null +++ b/src/mock/test/ACLTest.cpp @@ -0,0 +1,128 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#include "common/base/Status.h" +#include "common/network/NetworkUtils.h" +#include "common/interface/gen-cpp2/common_types.h" +#include "mock/test/TestEnv.h" +#include "mock/test/TestBase.h" +#include <gtest/gtest.h> + +DECLARE_int32(heartbeat_interval_secs); + +namespace nebula { +namespace graph { + +class ACLTest : public TestBase { +public: + void SetUp() override { + TestBase::SetUp(); + client_ = gEnv->getGraphClient(); + ASSERT_NE(nullptr, client_); + }; + + void TearDown() override { + TestBase::TearDown(); + client_.reset(); + }; + +protected: + std::unique_ptr<GraphClient> client_; +}; + +TEST_F(ACLTest, Error) { + { + // alter user not exists + cpp2::ExecutionResponse resp; + std::string query = "ALTER USER not_exists WITH PASSWORD \"not_exists\";"; + client_->execute(query, resp); + ASSERT_ERROR_CODE(resp, cpp2::ErrorCode::E_EXECUTION_ERROR); + } + { + // drop user not exists + cpp2::ExecutionResponse resp; + std::string query = "DROP USER not_exists;"; + client_->execute(query, resp); + ASSERT_ERROR_CODE(resp, cpp2::ErrorCode::E_EXECUTION_ERROR); + } + { + // grant to user not exists + cpp2::ExecutionResponse resp; + std::string query = "GRANT ROLE ADMIN ON not_exists TO not_exists"; + client_->execute(query, resp); + ASSERT_ERROR_CODE(resp, cpp2::ErrorCode::E_EXECUTION_ERROR); + } + { + // revoke from user not exists + cpp2::ExecutionResponse resp; + std::string query = "REVOKE ROLE ADMIN ON not_exists FROM not_exists"; + client_->execute(query, resp); + ASSERT_ERROR_CODE(resp, cpp2::ErrorCode::E_EXECUTION_ERROR); + } +} + +TEST_F(ACLTest, Simple) { + { + // drop user if exists + cpp2::ExecutionResponse resp; + std::string query = "DROP USER IF EXISTS not_exists;"; + client_->execute(query, resp); + ASSERT_ERROR_CODE(resp, cpp2::ErrorCode::SUCCEEDED); + } + { + // create user + cpp2::ExecutionResponse resp; + std::string query = "CREATE USER test1"; + client_->execute(query, resp); + ASSERT_ERROR_CODE(resp, cpp2::ErrorCode::SUCCEEDED); + } + { + // create user with password + cpp2::ExecutionResponse resp; + std::string query = "CREATE USER test2 WITH PASSWORD \"123456\""; + client_->execute(query, resp); + ASSERT_ERROR_CODE(resp, cpp2::ErrorCode::SUCCEEDED); + } + { + // create user exists + cpp2::ExecutionResponse resp; + std::string query = "CREATE USER IF NOT EXISTS test2 WITH PASSWORD \"123456\""; + client_->execute(query, resp); + ASSERT_ERROR_CODE(resp, cpp2::ErrorCode::SUCCEEDED); + } + { + // create one space + cpp2::ExecutionResponse resp; + std::string query = "CREATE SPACE test"; + client_->execute(query, resp); + ASSERT_ERROR_CODE(resp, cpp2::ErrorCode::SUCCEEDED); + sleep(FLAGS_heartbeat_interval_secs + 1); + } + { + // grant + cpp2::ExecutionResponse resp; + std::string query = "GRANT ROLE ADMIN ON test TO test1"; + client_->execute(query, resp); + ASSERT_ERROR_CODE(resp, cpp2::ErrorCode::SUCCEEDED); + } + { + // revoke + cpp2::ExecutionResponse resp; + std::string query = "REVOKE ROLE ADMIN ON test FROM test1"; + client_->execute(query, resp); + ASSERT_ERROR_CODE(resp, cpp2::ErrorCode::SUCCEEDED); + } + { + // drop user + cpp2::ExecutionResponse resp; + std::string query = "DROP USER test2"; + client_->execute(query, resp); + ASSERT_ERROR_CODE(resp, cpp2::ErrorCode::SUCCEEDED); + } +} + +} // namespace graph +} // namespace nebula diff --git a/src/mock/test/CMakeLists.txt b/src/mock/test/CMakeLists.txt index b977ac10f484802262f694d9e41d0d7ae7b331a5..fa48586f6d60e3f41570a0f038749663299f65c1 100644 --- a/src/mock/test/CMakeLists.txt +++ b/src/mock/test/CMakeLists.txt @@ -38,8 +38,6 @@ set(GRAPH_TEST_LIB $<TARGET_OBJECTS:common_storage_thrift_obj> $<TARGET_OBJECTS:common_thrift_obj> $<TARGET_OBJECTS:common_meta_obj> - $<TARGET_OBJECTS:common_ws_obj> - $<TARGET_OBJECTS:common_ws_common_obj> $<TARGET_OBJECTS:common_thread_obj> $<TARGET_OBJECTS:common_time_obj> $<TARGET_OBJECTS:common_fs_obj> @@ -78,6 +76,22 @@ nebula_add_test( SchemaTest.cpp OBJECTS ${GRAPH_TEST_LIB} + LIBRARIES + ${THRIFT_LIBRARIES} + proxygenhttpserver + proxygenlib + wangle + gtest + gtest_main +) + +nebula_add_test( + NAME + acl_test + SOURCES + ACLTest.cpp + OBJECTS + ${GRAPH_TEST_LIB} LIBRARIES ${THRIFT_LIBRARIES} gtest @@ -127,9 +141,9 @@ nebula_add_test( OBJECTS ${GRAPH_TEST_LIB} LIBRARIES + ${THRIFT_LIBRARIES} proxygenhttpserver proxygenlib - ${THRIFT_LIBRARIES} wangle gtest gtest_main diff --git a/src/parser/AdminSentences.h b/src/parser/AdminSentences.h index 268df1c67a971c48eac9d9aca450eed6a62af9d8..030ab6f4e39db1ba03f7ac62b0085833b25db270 100644 --- a/src/parser/AdminSentences.h +++ b/src/parser/AdminSentences.h @@ -84,8 +84,13 @@ public: name_.reset(name); kind_ = Kind::kShowRoles; } + std::string toString() const override; + const std::string *name() const { + return name_.get(); + } + private: std::unique_ptr<std::string> name_; }; diff --git a/src/parser/UserSentences.h b/src/parser/UserSentences.h index 16fe6cf9b76e71460aa60addc1045491642498c7..07e1cfb332302ed188025290bc7677242f9b146e 100644 --- a/src/parser/UserSentences.h +++ b/src/parser/UserSentences.h @@ -8,6 +8,7 @@ #define PARSER_USERSENTENCES_H_ #include "common/base/Base.h" +#include "common/interface/gen-cpp2/meta_types.h" #include "parser/Clauses.h" #include "parser/Sentence.h" @@ -15,18 +16,13 @@ namespace nebula { class RoleTypeClause final { public: - enum RoleType : uint8_t { - GOD, - ADMIN, - DBA, - USER, - GUEST - }; + using RoleType = meta::cpp2::RoleType; explicit RoleTypeClause(RoleType roleType) { roleType_ = roleType; } + RoleType getRoleType() const { return roleType_; } @@ -221,6 +217,10 @@ public: return aclItemClause_.get(); } + AclItemClause* mutableAclItemClause() { + return aclItemClause_.get(); + } + const std::string* getAccount() const { return account_.get(); } diff --git a/src/parser/parser.yy b/src/parser/parser.yy index 1b80a7d541693b690288ba158cfcecf974ebfd00..f72676e571927a24831420bb607066157bbef303 100644 --- a/src/parser/parser.yy +++ b/src/parser/parser.yy @@ -2032,11 +2032,11 @@ change_password_sentence ; role_type_clause - : KW_GOD { $$ = new RoleTypeClause(RoleTypeClause::GOD); } - | KW_ADMIN { $$ = new RoleTypeClause(RoleTypeClause::ADMIN); } - | KW_DBA { $$ = new RoleTypeClause(RoleTypeClause::DBA); } - | KW_USER { $$ = new RoleTypeClause(RoleTypeClause::USER); } - | KW_GUEST { $$ = new RoleTypeClause(RoleTypeClause::GUEST); } + : KW_GOD { $$ = new RoleTypeClause(meta::cpp2::RoleType::GOD); } + | KW_ADMIN { $$ = new RoleTypeClause(meta::cpp2::RoleType::ADMIN); } + | KW_DBA { $$ = new RoleTypeClause(meta::cpp2::RoleType::DBA); } + | KW_USER { $$ = new RoleTypeClause(meta::cpp2::RoleType::USER); } + | KW_GUEST { $$ = new RoleTypeClause(meta::cpp2::RoleType::GUEST); } ; acl_item_clause diff --git a/src/planner/Admin.h b/src/planner/Admin.h index 1dabd9dcc20530a030c8579b13d4f3786b3868ef..798c82fcae84a411ccca87b85430747b3ba50438 100644 --- a/src/planner/Admin.h +++ b/src/planner/Admin.h @@ -20,8 +20,35 @@ namespace nebula { namespace graph { -// TODO: All DDLs, DMLs and DQLs could be used in a single query -// which would make them in a single and big execution plan +// Some template node such as Create template for the node create something(user,tag...) +// Fit the conflict create process +class CreateNode : public SingleDependencyNode { +protected: + CreateNode(ExecutionPlan* plan, Kind kind, PlanNode* input, bool ifNotExist = false) + : SingleDependencyNode(plan, kind, input), ifNotExist_(ifNotExist) {} + +public: + bool ifNotExist() const { + return ifNotExist_; + } + +private: + bool ifNotExist_{false}; +}; + +class DropNode : public SingleDependencyNode { +protected: + DropNode(ExecutionPlan* plan, Kind kind, PlanNode* input, bool ifExist = false) + : SingleDependencyNode(plan, kind, input), ifExist_(ifExist) {} + +public: + bool ifExist() const { + return ifExist_; + } + +private: + bool ifExist_{false}; +}; class ShowHosts final : public SingleDependencyNode { // TODO(shylock) meta/storage/graph enumerate @@ -69,8 +96,8 @@ private: } private: - meta::SpaceDesc props_; - bool ifNotExists_; + meta::SpaceDesc props_; + bool ifNotExists_{false}; }; class DropSpace final : public SingleInputNode { @@ -325,6 +352,329 @@ class Ingest final : public SingleInputNode { public: }; +// User related Node +class CreateUser final : public CreateNode { +public: + static CreateUser* make(ExecutionPlan* plan, + PlanNode* dep, + const std::string* username, + const std::string* password, + bool ifNotExists) { + return new CreateUser(plan, + dep, + username, + password, + ifNotExists); + } + + std::unique_ptr<cpp2::PlanNodeDescription> explain() const override { + LOG(FATAL) << "Unimplemented"; + return nullptr; + } + + const std::string* username() const { + return username_; + } + + const std::string* password() const { + return password_; + } + +private: + CreateUser(ExecutionPlan* plan, + PlanNode* dep, + const std::string* username, + const std::string* password, + bool ifNotExists) + : CreateNode(plan, Kind::kCreateUser, dep, ifNotExists), + username_(username), + password_(password) {} + +private: + const std::string* username_; + const std::string* password_; +}; + +class DropUser final : public DropNode { +public: + static DropUser* make(ExecutionPlan* plan, + PlanNode* dep, + const std::string* username, + bool ifNotExists) { + return new DropUser(plan, + dep, + username, + ifNotExists); + } + + std::unique_ptr<cpp2::PlanNodeDescription> explain() const override { + LOG(FATAL) << "Unimplemented"; + return nullptr; + } + + const std::string* username() const { + return username_; + } + +private: + DropUser(ExecutionPlan* plan, PlanNode* dep, const std::string* username, bool ifNotExists) + : DropNode(plan, Kind::kDropUser, dep, ifNotExists), + username_(username) {} + +private: + const std::string* username_; +}; + +class UpdateUser final : public SingleDependencyNode { +public: + static UpdateUser* make(ExecutionPlan* plan, + PlanNode* dep, + const std::string* username, + const std::string* password) { + return new UpdateUser(plan, + dep, + username, + password); + } + + std::unique_ptr<cpp2::PlanNodeDescription> explain() const override { + LOG(FATAL) << "Unimplemented"; + return nullptr; + } + + const std::string* username() const { + return username_; + } + + const std::string* password() const { + return password_; + } + +private: + UpdateUser(ExecutionPlan* plan, + PlanNode* dep, + const std::string* username, + const std::string* password) + : SingleDependencyNode(plan, Kind::kUpdateUser, dep), + username_(username), + password_(password) {} + +private: + const std::string* username_; + const std::string* password_; +}; + +class GrantRole final : public SingleDependencyNode { +public: + static GrantRole* make(ExecutionPlan* plan, + PlanNode* dep, + const std::string* username, + const std::string* spaceName, + meta::cpp2::RoleType role) { + return new GrantRole(plan, + dep, + username, + spaceName, + role); + } + + std::unique_ptr<cpp2::PlanNodeDescription> explain() const override { + LOG(FATAL) << "Unimplemented"; + return nullptr; + } + + const std::string* username() const { + return username_; + } + + const std::string* spaceName() const { + return spaceName_; + } + + meta::cpp2::RoleType role() const { + return role_; + } + +private: + GrantRole(ExecutionPlan* plan, + PlanNode* dep, + const std::string* username, + const std::string* spaceName, + meta::cpp2::RoleType role) + : SingleDependencyNode(plan, Kind::kGrantRole, dep), + username_(username), + spaceName_(spaceName), + role_(role) {} + +private: + const std::string* username_; + const std::string* spaceName_; + meta::cpp2::RoleType role_; +}; + +class RevokeRole final : public SingleDependencyNode { +public: + static RevokeRole* make(ExecutionPlan* plan, + PlanNode* dep, + const std::string* username, + const std::string* spaceName, + meta::cpp2::RoleType role) { + return new RevokeRole(plan, + dep, + username, + spaceName, + role); + } + + std::unique_ptr<cpp2::PlanNodeDescription> explain() const override { + LOG(FATAL) << "Unimplemented"; + return nullptr; + } + + const std::string* username() const { + return username_; + } + + const std::string* spaceName() const { + return spaceName_; + } + + meta::cpp2::RoleType role() const { + return role_; + } + +private: + RevokeRole(ExecutionPlan* plan, + PlanNode* dep, + const std::string* username, + const std::string* spaceName, + meta::cpp2::RoleType role) + : SingleDependencyNode(plan, Kind::kRevokeRole, dep), + username_(username), + spaceName_(spaceName), + role_(role) {} + +private: + const std::string* username_; + const std::string* spaceName_; + meta::cpp2::RoleType role_; +}; + +class ChangePassword final : public SingleDependencyNode { +public: + static ChangePassword* make(ExecutionPlan* plan, + PlanNode* dep, + const std::string* username, + const std::string* password, + const std::string* newPassword) { + return new ChangePassword(plan, + dep, + username, + password, + newPassword); + } + + std::unique_ptr<cpp2::PlanNodeDescription> explain() const override { + LOG(FATAL) << "Unimplemented"; + return nullptr; + } + + const std::string* username() const { + return username_; + } + + const std::string* password() const { + return password_; + } + + const std::string* newPassword() const { + return newPassword_; + } + +private: + ChangePassword(ExecutionPlan* plan, + PlanNode* dep, + const std::string* username, + const std::string* password, + const std::string* newPassword) + : SingleDependencyNode(plan, Kind::kChangePassword, dep), + username_(username), + password_(password), + newPassword_(newPassword) {} + +private: + const std::string* username_; + const std::string* password_; + const std::string* newPassword_; +}; + + +class ListUserRoles final : public SingleDependencyNode { +public: + static ListUserRoles* make(ExecutionPlan* plan, + PlanNode* dep, + const std::string* username) { + return new ListUserRoles(plan, + dep, + username); + } + + std::unique_ptr<cpp2::PlanNodeDescription> explain() const override { + LOG(FATAL) << "Unimplemented"; + return nullptr; + } + + const std::string* username() const { + return username_; + } + +private: + ListUserRoles(ExecutionPlan* plan, PlanNode* dep, const std::string* username) + : SingleDependencyNode(plan, Kind::kListUserRoles, dep), + username_(username) {} + +private: + const std::string* username_; +}; + +class ListUsers final : public SingleDependencyNode { +public: + static ListUsers* make(ExecutionPlan* plan, PlanNode* dep) { + return new ListUsers(plan, dep); + } + + std::unique_ptr<cpp2::PlanNodeDescription> explain() const override { + LOG(FATAL) << "Unimplemented"; + return nullptr; + } + +private: + explicit ListUsers(ExecutionPlan* plan, PlanNode* dep) + : SingleDependencyNode(plan, Kind::kListUsers, dep) {} +}; + +class ListRoles final : public SingleDependencyNode { +public: + static ListRoles* make(ExecutionPlan* plan, PlanNode* dep, GraphSpaceID space) { + return new ListRoles(plan, dep, space); + } + + std::unique_ptr<cpp2::PlanNodeDescription> explain() const override { + LOG(FATAL) << "Unimplemented"; + return nullptr; + } + + GraphSpaceID space() const { + return space_; + } + +private: + explicit ListRoles(ExecutionPlan* plan, PlanNode* dep, GraphSpaceID space) + : SingleDependencyNode(plan, Kind::kListRoles, dep), space_(space) {} + + GraphSpaceID space_{-1}; +}; + class ShowParts final : public SingleInputNode { public: static ShowParts* make(ExecutionPlan* plan, diff --git a/src/planner/PlanNode.cpp b/src/planner/PlanNode.cpp index a6ee52ba65e3a7b5e514a7e7ff1f83413885efd2..29c9459884e040b8b172c442ac216fd32fe27435 100644 --- a/src/planner/PlanNode.cpp +++ b/src/planner/PlanNode.cpp @@ -81,6 +81,25 @@ const char* PlanNode::toString(PlanNode::Kind kind) { return "InsertEdges"; case Kind::kDataCollect: return "DataCollect"; + // acl + case Kind::kCreateUser: + return "CreateUser"; + case Kind::kDropUser: + return "DropUser"; + case Kind::kUpdateUser: + return "UpdateUser"; + case Kind::kGrantRole: + return "GrantRole"; + case Kind::kRevokeRole: + return "RevokeRole"; + case Kind::kChangePassword: + return "ChangePassword"; + case Kind::kListUserRoles: + return "ListUserRoles"; + case Kind::kListUsers: + return "ListUsers"; + case Kind::kListRoles: + return "ListRoles"; case Kind::kShowCreateSpace: return "ShowCreateSpace"; case Kind::kShowCreateTag: diff --git a/src/planner/PlanNode.h b/src/planner/PlanNode.h index 4db31fb57da3168b7619c971295e8642ff84b32a..26a49012f68eb185d4789670416fa776b4972a63 100644 --- a/src/planner/PlanNode.h +++ b/src/planner/PlanNode.h @@ -72,6 +72,16 @@ public: kSubmitJob, kShowHosts, kDataCollect, + // user related + kCreateUser, + kDropUser, + kUpdateUser, + kGrantRole, + kRevokeRole, + kChangePassword, + kListUserRoles, + kListUsers, + kListRoles, kCreateSnapshot, kDropSnapshot, kShowSnapshots, diff --git a/src/validator/ACLValidator.cpp b/src/validator/ACLValidator.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c5c8812a3a1babc97073380f4d1b4ab340c919b9 --- /dev/null +++ b/src/validator/ACLValidator.cpp @@ -0,0 +1,151 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. +* +* This source code is licensed under Apache 2.0 License, +* attached with Common Clause Condition 1.0, found in the LICENSES directory. +*/ + +#include "common/base/Base.h" +#include "common/clients/meta/MetaClient.h" + +#include "util/SchemaUtil.h" +#include "planner/Admin.h" +#include "validator/ACLValidator.h" + +namespace nebula { +namespace graph { + +static std::size_t kUsernameMaxLength = 16; +static std::size_t kPasswordMaxLength = 24; + +// create user +Status CreateUserValidator::validateImpl() { + const auto *sentence = static_cast<const CreateUserSentence*>(sentence_); + if (sentence->getAccount()->size() > kUsernameMaxLength) { + return Status::Error("Username exceed maximum length %ld characters.", kUsernameMaxLength); + } + if (sentence->getPassword()->size() > kPasswordMaxLength) { + return Status::Error("Password exceed maximum length %ld characters.", kPasswordMaxLength); + } + return Status::OK(); +} + +Status CreateUserValidator::toPlan() { + auto sentence = static_cast<CreateUserSentence*>(sentence_); + return genSingleNodePlan<CreateUser>(sentence->getAccount(), + sentence->getPassword(), + sentence->ifNotExists()); +} + +// drop user +Status DropUserValidator::validateImpl() { + const auto *sentence = static_cast<const DropUserSentence*>(sentence_); + if (sentence->getAccount()->size() > kUsernameMaxLength) { + return Status::Error("Username exceed maximum length %ld characters.", kUsernameMaxLength); + } + return Status::OK(); +} + +Status DropUserValidator::toPlan() { + auto sentence = static_cast<DropUserSentence*>(sentence_); + return genSingleNodePlan<DropUser>(sentence->getAccount(), + sentence->ifExists()); +} + +// update user +Status UpdateUserValidator::validateImpl() { + const auto *sentence = static_cast<const AlterUserSentence*>(sentence_); + if (sentence->getAccount()->size() > kUsernameMaxLength) { + return Status::Error("Username exceed maximum length %ld characters.", kUsernameMaxLength); + } + if (sentence->getPassword()->size() > kPasswordMaxLength) { + return Status::Error("Password exceed maximum length %ld characters.", kPasswordMaxLength); + } + return Status::OK(); +} + +Status UpdateUserValidator::toPlan() { + auto sentence = static_cast<AlterUserSentence*>(sentence_); + return genSingleNodePlan<UpdateUser>(sentence->getAccount(), + sentence->getPassword()); +} + +// show users +Status ShowUsersValidator::validateImpl() { + return Status::OK(); +} + +Status ShowUsersValidator::toPlan() { + return genSingleNodePlan<ListUsers>(); +} + +// change password +Status ChangePasswordValidator::validateImpl() { + const auto *sentence = static_cast<const ChangePasswordSentence*>(sentence_); + if (sentence->getAccount()->size() > kUsernameMaxLength) { + return Status::Error("Username exceed maximum length %ld characters.", kUsernameMaxLength); + } + if (sentence->getOldPwd()->size() > kPasswordMaxLength) { + return Status::Error("Old password exceed maximum length %ld characters.", + kPasswordMaxLength); + } + if (sentence->getNewPwd()->size() > kPasswordMaxLength) { + return Status::Error("New password exceed maximum length %ld characters.", + kPasswordMaxLength); + } + return Status::OK(); +} + +Status ChangePasswordValidator::toPlan() { + auto sentence = static_cast<ChangePasswordSentence*>(sentence_); + return genSingleNodePlan<ChangePassword>(sentence->getAccount(), + sentence->getOldPwd(), + sentence->getNewPwd()); +} + +// grant role +Status GrantRoleValidator::validateImpl() { + const auto *sentence = static_cast<const GrantSentence*>(sentence_); + if (sentence->getAccount()->size() > kUsernameMaxLength) { + return Status::Error("Username exceed maximum length %ld characters.", kUsernameMaxLength); + } + return Status::OK(); +} + +Status GrantRoleValidator::toPlan() { + auto sentence = static_cast<GrantSentence*>(sentence_); + return genSingleNodePlan<GrantRole>(sentence->getAccount(), + sentence->getAclItemClause()->getSpaceName(), + sentence->getAclItemClause()->getRoleType()); +} + +// revoke role +Status RevokeRoleValidator::validateImpl() { + const auto *sentence = static_cast<const RevokeSentence*>(sentence_); + if (sentence->getAccount()->size() > kUsernameMaxLength) { + return Status::Error("Username exceed maximum length %ld characters.", kUsernameMaxLength); + } + return Status::OK(); +} + +Status RevokeRoleValidator::toPlan() { + auto sentence = static_cast<RevokeSentence*>(sentence_); + return genSingleNodePlan<RevokeRole>(sentence->getAccount(), + sentence->getAclItemClause()->getSpaceName(), + sentence->getAclItemClause()->getRoleType()); +} + +// show roles in space +Status ShowRolesInSpaceValidator::validateImpl() { + return Status::OK(); +} + +Status ShowRolesInSpaceValidator::toPlan() { + auto sentence = static_cast<ShowRolesSentence*>(sentence_); + auto spaceIdResult = qctx_->schemaMng()->toGraphSpaceID(*sentence->name()); + NG_RETURN_IF_ERROR(spaceIdResult); + auto spaceId = spaceIdResult.value(); + return genSingleNodePlan<ListRoles>(spaceId); +} + +} // namespace graph +} // namespace nebula diff --git a/src/validator/ACLValidator.h b/src/validator/ACLValidator.h new file mode 100644 index 0000000000000000000000000000000000000000..e9e44def09157d564a86165d45d5f602a163756b --- /dev/null +++ b/src/validator/ACLValidator.h @@ -0,0 +1,118 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#ifndef VALIDATOR_ACLVALIDATOR_H_ +#define VALIDATOR_ACLVALIDATOR_H_ + +#include "common/base/Base.h" +#include "parser/UserSentences.h" +#include "validator/Validator.h" + +namespace nebula { +namespace graph { + +class CreateUserValidator final : public Validator { +public: + CreateUserValidator(Sentence* sentence, QueryContext* context) : Validator(sentence, context) { + setNoSpaceRequired(); + } + +private: + Status validateImpl() override; + + Status toPlan() override; +}; + +class DropUserValidator final : public Validator { +public: + DropUserValidator(Sentence* sentence, QueryContext* context) : Validator(sentence, context) { + setNoSpaceRequired(); + } + +private: + Status validateImpl() override; + + Status toPlan() override; +}; + +class UpdateUserValidator final : public Validator { +public: + UpdateUserValidator(Sentence* sentence, QueryContext* context) : Validator(sentence, context) { + setNoSpaceRequired(); + } + +private: + Status validateImpl() override; + + Status toPlan() override; +}; + +class ShowUsersValidator final : public Validator { +public: + ShowUsersValidator(Sentence* sentence, QueryContext* context) : Validator(sentence, context) { + setNoSpaceRequired(); + } + +private: + Status validateImpl() override; + + Status toPlan() override; +}; + +class ChangePasswordValidator final : public Validator { +public: + ChangePasswordValidator(Sentence* sentence, QueryContext* context) + : Validator(sentence, context) { + setNoSpaceRequired(); + } + +private: + Status validateImpl() override; + + Status toPlan() override; +}; + +class GrantRoleValidator final : public Validator { +public: + GrantRoleValidator(Sentence* sentence, QueryContext* context) : Validator(sentence, context) { + setNoSpaceRequired(); + } + +private: + Status validateImpl() override; + + Status toPlan() override; +}; + +class RevokeRoleValidator final : public Validator { +public: + RevokeRoleValidator(Sentence* sentence, QueryContext* context) : Validator(sentence, context) { + setNoSpaceRequired(); + } + +private: + Status validateImpl() override; + + Status toPlan() override; +}; + +class ShowRolesInSpaceValidator final : public Validator { +public: + ShowRolesInSpaceValidator(Sentence* sentence, QueryContext* context) + : Validator(sentence, context) { + setNoSpaceRequired(); + } + +private: + Status validateImpl() override; + + Status toPlan() override; +}; + +} // namespace graph +} // namespace nebula + +#endif // VALIDATOR_ACLVALIDATOR_H_ diff --git a/src/validator/CMakeLists.txt b/src/validator/CMakeLists.txt index bb6e9544fbf6b6720eafa28c716d666f9a0e2da3..086880a271962e40e42999e6e162ddaf39d32e2e 100644 --- a/src/validator/CMakeLists.txt +++ b/src/validator/CMakeLists.txt @@ -19,6 +19,7 @@ nebula_add_library( AdminJobValidator.cpp MaintainValidator.cpp MutateValidator.cpp + ACLValidator.cpp FetchEdgesValidator.cpp FetchVerticesValidator.cpp LimitValidator.cpp diff --git a/src/validator/Validator.cpp b/src/validator/Validator.cpp index 3ede023c162b5ed8d4c0655d1d4189271aeedd58..f5346911bffd36d9274a6aa183c394fac3ab3dfe 100644 --- a/src/validator/Validator.cpp +++ b/src/validator/Validator.cpp @@ -26,6 +26,7 @@ #include "validator/SequentialValidator.h" #include "validator/SetValidator.h" #include "validator/UseValidator.h" +#include "validator/ACLValidator.h" #include "validator/BalanceValidator.h" #include "validator/AdminJobValidator.h" #include "validator/YieldValidator.h" @@ -105,6 +106,22 @@ std::unique_ptr<Validator> Validator::makeValidator(Sentence* sentence, QueryCon return std::make_unique<InsertVerticesValidator>(sentence, context); case Sentence::Kind::kInsertEdges: return std::make_unique<InsertEdgesValidator>(sentence, context); + case Sentence::Kind::kCreateUser: + return std::make_unique<CreateUserValidator>(sentence, context); + case Sentence::Kind::kDropUser: + return std::make_unique<DropUserValidator>(sentence, context); + case Sentence::Kind::kAlterUser: + return std::make_unique<UpdateUserValidator>(sentence, context); + case Sentence::Kind::kShowUsers: + return std::make_unique<ShowUsersValidator>(sentence, context); + case Sentence::Kind::kChangePassword: + return std::make_unique<ChangePasswordValidator>(sentence, context); + case Sentence::Kind::kGrant: + return std::make_unique<GrantRoleValidator>(sentence, context); + case Sentence::Kind::kRevoke: + return std::make_unique<RevokeRoleValidator>(sentence, context); + case Sentence::Kind::kShowRoles: + return std::make_unique<ShowRolesInSpaceValidator>(sentence, context); case Sentence::Kind::kBalance: return std::make_unique<BalanceValidator>(sentence, context); case Sentence::Kind::kAdminJob: @@ -157,14 +174,6 @@ std::unique_ptr<Validator> Validator::makeValidator(Sentence* sentence, QueryCon case Sentence::Kind::kShowEdgeIndexes: case Sentence::Kind::kRebuildEdgeIndex: case Sentence::Kind::kDropEdgeIndex: - case Sentence::Kind::kShowUsers: - case Sentence::Kind::kCreateUser: - case Sentence::Kind::kDropUser: - case Sentence::Kind::kAlterUser: - case Sentence::Kind::kGrant: - case Sentence::Kind::kRevoke: - case Sentence::Kind::kShowRoles: - case Sentence::Kind::kChangePassword: case Sentence::Kind::kLookup: case Sentence::Kind::kDownload: case Sentence::Kind::kIngest: @@ -188,6 +197,15 @@ Status Validator::appendPlan(PlanNode* node, PlanNode* appended) { case PlanNode::Kind::kAggregate: case PlanNode::Kind::kSelect: case PlanNode::Kind::kLoop: + case PlanNode::Kind::kCreateUser: + case PlanNode::Kind::kDropUser: + case PlanNode::Kind::kUpdateUser: + case PlanNode::Kind::kGrantRole: + case PlanNode::Kind::kRevokeRole: + case PlanNode::Kind::kChangePassword: + case PlanNode::Kind::kListUserRoles: + case PlanNode::Kind::kListUsers: + case PlanNode::Kind::kListRoles: case PlanNode::Kind::kMultiOutputs: case PlanNode::Kind::kSwitchSpace: case PlanNode::Kind::kGetEdges: diff --git a/src/validator/Validator.h b/src/validator/Validator.h index 99cb67edc86bd9d2fc0c6e6161a6807da30b4e0f..a0e65a3d00c3840272f8fbf9294583653f1a546d 100644 --- a/src/validator/Validator.h +++ b/src/validator/Validator.h @@ -144,6 +144,16 @@ protected: static Status appendPlan(PlanNode* plan, PlanNode* appended); + // use for simple Plan only contain one node + template <typename Node, typename... Args> + Status genSingleNodePlan(Args... args) { + auto* plan = qctx_->plan(); + auto *doNode = Node::make(plan, nullptr, std::forward<Args>(args)...); + root_ = doNode; + tail_ = root_; + return Status::OK(); + } + // Check the variable or input property reference // return the input variable StatusOr<std::string> checkRef(const Expression *ref, const Value::Type type) const; diff --git a/src/validator/test/ACLValidatorTest.cpp b/src/validator/test/ACLValidatorTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..338e4d147500502d826903918c108148ec1c2d9c --- /dev/null +++ b/src/validator/test/ACLValidatorTest.cpp @@ -0,0 +1,225 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#include "validator/test/ValidatorTestBase.h" +#include "planner/Admin.h" + +namespace nebula { +namespace graph { + + +class ACLValidatorTest : public ValidatorTestBase { +}; + +TEST_F(ACLValidatorTest, Simple) { + constexpr char user[] = "shylock"; + constexpr char password[] = "123456"; + constexpr char newPassword[] = "654321"; + constexpr char roleTypeName[] = "ADMIN"; + constexpr char space[] = "test_space"; + // create user + { + ASSERT_TRUE(toPlan(folly::stringPrintf("CREATE USER %s", user))); + const ExecutionPlan *plan = qCtx_->plan(); + + std::vector<PlanNode::Kind> expectedTop { + PlanNode::Kind::kCreateUser, + PlanNode::Kind::kStart, + }; + ASSERT_TRUE(verifyPlan(plan->root(), expectedTop)); + + auto root = plan->root(); + ASSERT_EQ(root->kind(), PlanNode::Kind::kCreateUser); + auto createUser = static_cast<const CreateUser*>(root); + ASSERT_FALSE(createUser->ifNotExist()); + ASSERT_EQ(*createUser->username(), user); + ASSERT_EQ(*createUser->password(), ""); + } + { // if not exists + ASSERT_TRUE(toPlan(folly::stringPrintf("CREATE USER IF NOT EXISTS %s", user))); + const ExecutionPlan *plan = qCtx_->plan(); + + std::vector<PlanNode::Kind> expectedTop { + PlanNode::Kind::kCreateUser, + PlanNode::Kind::kStart, + }; + ASSERT_TRUE(verifyPlan(plan->root(), expectedTop)); + + auto root = plan->root(); + ASSERT_EQ(root->kind(), PlanNode::Kind::kCreateUser); + auto createUser = static_cast<const CreateUser*>(root); + ASSERT_TRUE(createUser->ifNotExist()); + ASSERT_EQ(*createUser->username(), user); + ASSERT_EQ(*createUser->password(), ""); + } + { // with password + ASSERT_TRUE(toPlan(folly::stringPrintf("CREATE USER %s WITH PASSWORD \"%s\"", + user, + password))); + const ExecutionPlan *plan = qCtx_->plan(); + + std::vector<PlanNode::Kind> expectedTop { + PlanNode::Kind::kCreateUser, + PlanNode::Kind::kStart, + }; + ASSERT_TRUE(verifyPlan(plan->root(), expectedTop)); + + auto root = plan->root(); + ASSERT_EQ(root->kind(), PlanNode::Kind::kCreateUser); + auto createUser = static_cast<const CreateUser*>(root); + ASSERT_FALSE(createUser->ifNotExist()); + ASSERT_EQ(*createUser->username(), user); + ASSERT_EQ(*createUser->password(), password); + } + + // drop user + { + ASSERT_TRUE(toPlan(folly::stringPrintf("DROP USER %s", + user))); + const ExecutionPlan *plan = qCtx_->plan(); + + std::vector<PlanNode::Kind> expectedTop { + PlanNode::Kind::kDropUser, + PlanNode::Kind::kStart, + }; + ASSERT_TRUE(verifyPlan(plan->root(), expectedTop)); + + auto root = plan->root(); + ASSERT_EQ(root->kind(), PlanNode::Kind::kDropUser); + auto dropUser = static_cast<const DropUser*>(root); + ASSERT_FALSE(dropUser->ifExist()); + ASSERT_EQ(*dropUser->username(), user); + } + { // if exits + ASSERT_TRUE(toPlan(folly::stringPrintf("DROP USER IF EXISTS %s", + user))); + const ExecutionPlan *plan = qCtx_->plan(); + + std::vector<PlanNode::Kind> expectedTop { + PlanNode::Kind::kDropUser, + PlanNode::Kind::kStart, + }; + ASSERT_TRUE(verifyPlan(plan->root(), expectedTop)); + + auto root = plan->root(); + ASSERT_EQ(root->kind(), PlanNode::Kind::kDropUser); + auto dropUser = static_cast<const DropUser*>(root); + ASSERT_TRUE(dropUser->ifExist()); + ASSERT_EQ(*dropUser->username(), user); + } + + // update user + { + ASSERT_TRUE(toPlan(folly::stringPrintf("ALTER USER %s WITH PASSWORD \"%s\"", + user, password))); + const ExecutionPlan *plan = qCtx_->plan(); + + std::vector<PlanNode::Kind> expectedTop { + PlanNode::Kind::kUpdateUser, + PlanNode::Kind::kStart, + }; + ASSERT_TRUE(verifyPlan(plan->root(), expectedTop)); + + auto root = plan->root(); + ASSERT_EQ(root->kind(), PlanNode::Kind::kUpdateUser); + auto updateUser = static_cast<const UpdateUser*>(root); + ASSERT_EQ(*updateUser->username(), user); + ASSERT_EQ(*updateUser->password(), password); + } + + // show users + { + ASSERT_TRUE(toPlan("SHOW USERS")); + const ExecutionPlan *plan = qCtx_->plan(); + + std::vector<PlanNode::Kind> expectedTop { + PlanNode::Kind::kListUsers, + PlanNode::Kind::kStart, + }; + ASSERT_TRUE(verifyPlan(plan->root(), expectedTop)); + } + + // change password + { + ASSERT_TRUE(toPlan(folly::stringPrintf("CHANGE PASSWORD %s FROM \"%s\" TO \"%s\"", + user, password, newPassword))); + const ExecutionPlan *plan = qCtx_->plan(); + + std::vector<PlanNode::Kind> expectedTop { + PlanNode::Kind::kChangePassword, + PlanNode::Kind::kStart, + }; + ASSERT_TRUE(verifyPlan(plan->root(), expectedTop)); + + auto root = plan->root(); + ASSERT_EQ(root->kind(), PlanNode::Kind::kChangePassword); + auto changePassword = static_cast<const ChangePassword*>(root); + ASSERT_EQ(*changePassword->username(), user); + ASSERT_EQ(*changePassword->password(), password); + ASSERT_EQ(*changePassword->newPassword(), newPassword); + } + + // grant role + { + ASSERT_TRUE(toPlan(folly::stringPrintf("GRANT ROLE %s ON %s TO %s", + roleTypeName, space, user))); + const ExecutionPlan *plan = qCtx_->plan(); + + std::vector<PlanNode::Kind> expectedTop { + PlanNode::Kind::kGrantRole, + PlanNode::Kind::kStart, + }; + ASSERT_TRUE(verifyPlan(plan->root(), expectedTop)); + + auto root = plan->root(); + ASSERT_EQ(root->kind(), PlanNode::Kind::kGrantRole); + auto grantRole = static_cast<const GrantRole*>(root); + ASSERT_EQ(*grantRole->username(), user); + ASSERT_EQ(*grantRole->spaceName(), space); + ASSERT_EQ(grantRole->role(), meta::cpp2::RoleType::ADMIN); + } + + // revoke role + { + ASSERT_TRUE(toPlan(folly::stringPrintf("REVOKE ROLE %s ON %s FROM %s", + roleTypeName, space, user))); + const ExecutionPlan *plan = qCtx_->plan(); + + std::vector<PlanNode::Kind> expectedTop { + PlanNode::Kind::kRevokeRole, + PlanNode::Kind::kStart, + }; + ASSERT_TRUE(verifyPlan(plan->root(), expectedTop)); + + auto root = plan->root(); + ASSERT_EQ(root->kind(), PlanNode::Kind::kRevokeRole); + auto revokeRole = static_cast<const RevokeRole*>(root); + ASSERT_EQ(*revokeRole->username(), user); + ASSERT_EQ(*revokeRole->spaceName(), space); + ASSERT_EQ(revokeRole->role(), meta::cpp2::RoleType::ADMIN); + } + + // show roles in space + { + ASSERT_TRUE(toPlan(folly::stringPrintf("SHOW ROLES IN %s", + space))); + const ExecutionPlan *plan = qCtx_->plan(); + + std::vector<PlanNode::Kind> expectedTop { + PlanNode::Kind::kListRoles, + PlanNode::Kind::kStart, + }; + ASSERT_TRUE(verifyPlan(plan->root(), expectedTop)); + + auto root = plan->root(); + ASSERT_EQ(root->kind(), PlanNode::Kind::kListRoles); + auto showRoles = static_cast<const ListRoles*>(root); + ASSERT_EQ(showRoles->space(), 1); + } +} + +} // namespace graph +} // namespace nebula diff --git a/src/validator/test/CMakeLists.txt b/src/validator/test/CMakeLists.txt index 66878617e4c980c203083315a68928c0f134e005..795f3f2c56200674774d3a484719cc49dd49e467 100644 --- a/src/validator/test/CMakeLists.txt +++ b/src/validator/test/CMakeLists.txt @@ -50,15 +50,16 @@ set(VALIDATOR_TEST_LIBS nebula_add_test( NAME validator_test SOURCES + ACLValidatorTest.cpp FetchVerticesTest.cpp FetchEdgesTest.cpp MockSchemaManagerTest.cpp - ValidatorTestBase.cpp QueryValidatorTest.cpp AdminValidatorTest.cpp MaintainValidatorTest.cpp MutateValidatorTest.cpp YieldValidatorTest.cpp + ValidatorTestBase.cpp ExplainValidatorTest.cpp GroupByValidatorTest.cpp OBJECTS ${VALIDATOR_TEST_LIBS} diff --git a/src/validator/test/ValidatorTestBase.h b/src/validator/test/ValidatorTestBase.h index 6df88da348b2a44bb08cbee5d1d80f0bd38c57c1..737324d5f3141440c06071d8d002784de0d31565 100644 --- a/src/validator/test/ValidatorTestBase.h +++ b/src/validator/test/ValidatorTestBase.h @@ -153,6 +153,15 @@ protected: case PlanNode::Kind::kStart: { break; } + case PlanNode::Kind::kCreateUser: + case PlanNode::Kind::kDropUser: + case PlanNode::Kind::kUpdateUser: + case PlanNode::Kind::kGrantRole: + case PlanNode::Kind::kRevokeRole: + case PlanNode::Kind::kChangePassword: + case PlanNode::Kind::kListUserRoles: + case PlanNode::Kind::kListUsers: + case PlanNode::Kind::kListRoles: case PlanNode::Kind::kShowHosts: case PlanNode::Kind::kGetNeighbors: case PlanNode::Kind::kGetVertices: @@ -192,7 +201,7 @@ protected: case PlanNode::Kind::kDeleteEdges: case PlanNode::Kind::kUpdateVertex: case PlanNode::Kind::kUpdateEdge: { - auto* current = static_cast<const SingleInputNode*>(node); + auto* current = static_cast<const SingleDependencyNode*>(node); queue.emplace(current->dep()); break; } diff --git a/tests/admin/test_users.py b/tests/admin/test_users.py new file mode 100644 index 0000000000000000000000000000000000000000..9f251fba20726c8352f03620917cb5cb1fc5722b --- /dev/null +++ b/tests/admin/test_users.py @@ -0,0 +1,199 @@ +# --coding:utf-8-- +# +# Copyright (c) 2020 vesoft inc. All rights reserved. +# +# This source code is licensed under Apache 2.0 License, +# attached with Common Clause Condition 1.0, found in the LICENSES directory. + +import time + +from tests.common.nebula_test_suite import NebulaTestSuite + +class TestUsers(NebulaTestSuite): + @classmethod + def prepare(self): + query = 'CREATE SPACE user_space(partition_num=1, replica_factor=1)' + resp = self.execute(query) + self.check_resp_succeeded(resp) + time.sleep(self.delay) + + @classmethod + def cleanup(self): + query = 'DROP SPACE user_space' + resp = self.execute(query) + self.check_resp_succeeded(resp) + + query = 'DROP USER IF EXISTS user1' + resp = self.execute(query) + self.check_resp_succeeded(resp) + + query = 'DROP USER IF EXISTS user2' + resp = self.execute(query) + self.check_resp_succeeded(resp) + + query = 'DROP USER IF EXISTS user3' + resp = self.execute(query) + self.check_resp_succeeded(resp) + + def test_create_users(self): + query = 'CREATE USER user1 WITH PASSWORD "pwd1"' + resp = self.execute(query) + self.check_resp_succeeded(resp) + + query = 'CREATE USER user2' + resp = self.execute(query) + self.check_resp_succeeded(resp) + + # user exists. + query = 'CREATE USER user1 WITH PASSWORD "pwd1" ' + resp = self.execute(query) + self.check_resp_failed(resp) + + # user exists. but if not exists is true. + query = 'CREATE USER IF NOT EXISTS user1 WITH PASSWORD "pwd1" ' + resp = self.execute(query) + self.check_resp_succeeded(resp) + + query = 'SHOW USERS' + expected_column_names = ['Account'] + expected_result = [['root'], ['user1'], ['user2']] + resp = self.execute_query(query) + self.check_resp_succeeded(resp) + self.check_column_names(resp, expected_column_names) + self.check_out_of_order_result(resp, expected_result) + + def test_alter_users(self): + # not exist user + query = 'ALTER USER user WITH PASSWORD "pwd1"' + resp = self.execute(query) + self.check_resp_failed(resp) + + query = 'ALTER USER user2 WITH PASSWORD "pwd2"' + resp = self.execute(query) + self.check_resp_succeeded(resp) + + def test_drop_users(self): + query = 'DROP USER user' + resp = self.execute(query) + self.check_resp_failed(resp) + + query = 'DROP USER IF EXISTS user' + resp = self.execute(query) + self.check_resp_succeeded(resp) + + query = 'CREATE USER user3' + resp = self.execute(query) + self.check_resp_succeeded(resp) + + query = 'DROP USER user3' + resp = self.execute(query) + self.check_resp_succeeded(resp) + + query = 'SHOW USERS' + expected_column_names = ['Account'] + expected_result = [['root'], ['user1'], ['user2']] + resp = self.execute_query(query) + self.check_resp_succeeded(resp) + self.check_column_names(resp, expected_column_names) + self.check_out_of_order_result(resp, expected_result) + + def test_change_password(self): + # user is not exists. expect fail. + query = 'CHANGE PASSWORD user FROM "pwd" TO "pwd"' + resp = self.execute(query) + self.check_resp_failed(resp) + + # user login type is LDAP, can not change password. expect fail. + query = 'CHANGE PASSWORD user1 FROM "pwd" TO "pwd"' + resp = self.execute(query) + self.check_resp_failed(resp) + + # old password invalid. expect fail. + query = 'CHANGE PASSWORD user2 FROM "pwd" TO "newpwd"' + resp = self.execute(query) + self.check_resp_failed(resp) + + query = 'CHANGE PASSWORD user2 FROM "pwd2" TO "newpwd"' + resp = self.execute(query) + self.check_resp_succeeded(resp) + + def test_grant_revoke(self): + # must set the space if is not god role. expect fail. + query = 'GRANT DBA TO user1' + resp = self.execute(query) + self.check_resp_failed(resp) + + # user not exist. expect fail. + query = 'GRANT DBA ON user_space TO user' + resp = self.execute(query); + self.check_resp_failed(resp) + + # TODO(shylock) permission + # query = 'GRANT GOD ON user_space TO user1' + # resp = self.execute(query) + # self.check_resp_failed(resp) + + # space not exists. expect fail. + query = 'GRANT ROLE DBA ON space TO user2' + resp = self.execute(query) + self.check_resp_failed(resp) + + query = 'SHOW USERS' + expected_column_names = ['Account'] + expected_result = [['root'], ['user1'], ['user2']] + resp = self.execute_query(query) + self.check_resp_succeeded(resp) + self.check_column_names(resp, expected_column_names) + self.check_out_of_order_result(resp, expected_result) + + query = 'GRANT ROLE DBA ON user_space TO user2' + resp = self.execute(query) + self.check_resp_succeeded(resp) + + query = 'CREATE USER user3' + resp = self.execute(query) + self.check_resp_succeeded(resp) + + query = 'GRANT ROLE GUEST ON user_space TO user3' + resp = self.execute(query) + self.check_resp_succeeded(resp) + + # space not exists. expect fail. + query = 'SHOW ROLES IN space' + resp = self.execute(query) + self.check_resp_failed(resp); + + query = 'SHOW ROLES IN user_space' + resp = self.execute_query(query) + expected_column_names = ['Account', 'Role Type'] + expected_result = [['user2', 'DBA'], ['user3', 'GUEST']] + self.check_resp_succeeded(resp) + self.check_column_names(resp, expected_column_names) + self.check_out_of_order_result(resp, expected_result) + + # space not exist, expect fail. + query = 'REVOKE ROLE DBA ON space FROM user2' + resp = self.execute(query) + self.check_resp_failed(resp) + + # user not exist. expect fail. + query = 'REVOKE ROLE DBA ON user_space FROM user' + resp = self.execute(query) + self.check_resp_failed(resp) + + # role invalid. expect fail. + query = 'REVOKE ROLE ADMIN ON user_space FROM user2' + resp = self.execute(query) + self.check_resp_failed(resp) + + query = 'REVOKE ROLE DBA ON user_space FROM user2' + resp = self.execute(query) + self.check_resp_succeeded(resp) + + query = 'SHOW ROLES IN user_space' + expected_column_names = ['Account', 'Role Type'] + expected_result = [['user3', 'GUEST']] + resp = self.execute_query(query) + self.check_resp_succeeded(resp) + self.check_column_names(resp, expected_column_names) + self.check_out_of_order_result(resp, expected_result)