diff --git a/src/graph/executor/CMakeLists.txt b/src/graph/executor/CMakeLists.txt index b7270c16ff9..5bb8064ce3c 100644 --- a/src/graph/executor/CMakeLists.txt +++ b/src/graph/executor/CMakeLists.txt @@ -51,6 +51,7 @@ nebula_add_library( admin/ChangePasswordExecutor.cpp admin/ListUserRolesExecutor.cpp admin/ListUsersExecutor.cpp + admin/DescribeUserExecutor.cpp admin/ListRolesExecutor.cpp admin/SubmitJobExecutor.cpp admin/ShowHostsExecutor.cpp diff --git a/src/graph/executor/Executor.cpp b/src/graph/executor/Executor.cpp index 27a6985e620..447e25acb31 100644 --- a/src/graph/executor/Executor.cpp +++ b/src/graph/executor/Executor.cpp @@ -20,6 +20,7 @@ #include "graph/executor/admin/CharsetExecutor.h" #include "graph/executor/admin/ConfigExecutor.h" #include "graph/executor/admin/CreateUserExecutor.h" +#include "graph/executor/admin/DescribeUserExecutor.h" #include "graph/executor/admin/DownloadExecutor.h" #include "graph/executor/admin/DropUserExecutor.h" #include "graph/executor/admin/GrantRoleExecutor.h" @@ -385,6 +386,9 @@ Executor *Executor::makeExecutor(QueryContext *qctx, const PlanNode *node) { case PlanNode::Kind::kListRoles: { return pool->add(new ListRolesExecutor(node, qctx)); } + case PlanNode::Kind::kDescribeUser: { + return pool->add(new DescribeUserExecutor(node, qctx)); + } case PlanNode::Kind::kShowConfigs: { return pool->add(new ShowConfigsExecutor(node, qctx)); } diff --git a/src/graph/executor/admin/DescribeUserExecutor.cpp b/src/graph/executor/admin/DescribeUserExecutor.cpp new file mode 100644 index 00000000000..9b2d893b7f8 --- /dev/null +++ b/src/graph/executor/admin/DescribeUserExecutor.cpp @@ -0,0 +1,57 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License. + */ + +#include "graph/executor/admin/DescribeUserExecutor.h" + +#include + +#include "graph/context/QueryContext.h" +#include "graph/planner/plan/Admin.h" +#include "interface/gen-cpp2/meta_types.h" + +namespace nebula { +namespace graph { + +folly::Future DescribeUserExecutor::execute() { + SCOPED_TIMER(&execTime_); + return describeUser(); +} + +folly::Future DescribeUserExecutor::describeUser() { + auto* duNode = asNode(node()); + return qctx() + ->getMetaClient() + ->getUserRoles(*duNode->username()) + .via(runner()) + .thenValue([this](StatusOr>&& resp) { + SCOPED_TIMER(&execTime_); + if (!resp.ok()) { + return std::move(resp).status(); + } + + DataSet v({"role", "space"}); + auto roleItemList = std::move(resp).value(); + for (auto& item : roleItemList) { + if (item.get_space_id() == 0) { + v.emplace_back( + nebula::Row({apache::thrift::util::enumNameSafe(item.get_role_type()), ""})); + } else { + auto spaceNameResult = qctx_->schemaMng()->toGraphSpaceName(item.get_space_id()); + if (spaceNameResult.ok()) { + v.emplace_back(nebula::Row({apache::thrift::util::enumNameSafe(item.get_role_type()), + spaceNameResult.value()})); + } else { + LOG(ERROR) << " Space name of " << item.get_space_id() << " no found"; + return Status::Error("Space not found"); + } + } + } + return finish( + ResultBuilder().value(Value(std::move(v))).iter(Iterator::Kind::kSequential).build()); + }); +} + +} // namespace graph +} // namespace nebula diff --git a/src/graph/executor/admin/DescribeUserExecutor.h b/src/graph/executor/admin/DescribeUserExecutor.h new file mode 100644 index 00000000000..629e1cb7854 --- /dev/null +++ b/src/graph/executor/admin/DescribeUserExecutor.h @@ -0,0 +1,28 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License. + */ + +#ifndef GRAPH_EXECUTOR_ADMIN_DESCRIBEUSEREXECUTOR_H_ +#define GRAPH_EXECUTOR_ADMIN_DESCRIBEUSEREXECUTOR_H_ + +#include "graph/executor/Executor.h" + +namespace nebula { +namespace graph { + +class DescribeUserExecutor final : public Executor { + public: + DescribeUserExecutor(const PlanNode *node, QueryContext *qctx) + : Executor("DescribeUsersExecutor", node, qctx) {} + + folly::Future execute() override; + + private: + folly::Future describeUser(); +}; + +} // namespace graph +} // namespace nebula + +#endif // GRAPH_EXECUTOR_ADMIN_LISTUSERSEXECUTOR_H_ diff --git a/src/graph/planner/plan/Admin.cpp b/src/graph/planner/plan/Admin.cpp index 7361c531163..e48793939fb 100644 --- a/src/graph/planner/plan/Admin.cpp +++ b/src/graph/planner/plan/Admin.cpp @@ -136,6 +136,12 @@ std::unique_ptr ChangePassword::explain() const { return desc; } +std::unique_ptr DescribeUser::explain() const { + auto desc = SingleDependencyNode::explain(); + addDescription("username", *username_, desc.get()); + return desc; +} + std::unique_ptr ListUserRoles::explain() const { auto desc = SingleDependencyNode::explain(); addDescription("username", *username_, desc.get()); diff --git a/src/graph/planner/plan/Admin.h b/src/graph/planner/plan/Admin.h index 998fdea1fea..1f5b4c24215 100644 --- a/src/graph/planner/plan/Admin.h +++ b/src/graph/planner/plan/Admin.h @@ -653,6 +653,23 @@ class ListUsers final : public SingleDependencyNode { : SingleDependencyNode(qctx, Kind::kListUsers, dep) {} }; +class DescribeUser final : public SingleDependencyNode { + public: + static DescribeUser* make(QueryContext* qctx, PlanNode* dep, const std::string* username) { + return qctx->objPool()->add(new DescribeUser(qctx, dep, username)); + } + + std::unique_ptr explain() const override; + + const std::string* username() const { return username_; } + + private: + explicit DescribeUser(QueryContext* qctx, PlanNode* dep, const std::string* username) + : SingleDependencyNode(qctx, Kind::kDescribeUser, dep), username_(username) {} + + const std::string* username_; +}; + class ListRoles final : public SingleDependencyNode { public: static ListRoles* make(QueryContext* qctx, PlanNode* dep, GraphSpaceID space) { diff --git a/src/graph/planner/plan/PlanNode.cpp b/src/graph/planner/plan/PlanNode.cpp index 388c08afab5..b1a43a2836c 100644 --- a/src/graph/planner/plan/PlanNode.cpp +++ b/src/graph/planner/plan/PlanNode.cpp @@ -148,6 +148,8 @@ const char* PlanNode::toString(PlanNode::Kind kind) { return "ListUserRoles"; case Kind::kListUsers: return "ListUsers"; + case Kind::kDescribeUser: + return "DescribeUser"; case Kind::kListRoles: return "ListRoles"; case Kind::kShowCreateSpace: diff --git a/src/graph/planner/plan/PlanNode.h b/src/graph/planner/plan/PlanNode.h index a5fdc3b5918..053272ce01b 100644 --- a/src/graph/planner/plan/PlanNode.h +++ b/src/graph/planner/plan/PlanNode.h @@ -121,6 +121,7 @@ class PlanNode { kListUserRoles, kListUsers, kListRoles, + kDescribeUser, // Snapshot kCreateSnapshot, diff --git a/src/graph/service/PermissionCheck.cpp b/src/graph/service/PermissionCheck.cpp index c0752634f3e..ccc3ebdcf36 100644 --- a/src/graph/service/PermissionCheck.cpp +++ b/src/graph/service/PermissionCheck.cpp @@ -177,6 +177,7 @@ Status PermissionCheck::permissionCheck(ClientSession *session, case Sentence::Kind::kShowRoles: { return PermissionManager::canReadSpace(session, targetSpace); } + case Sentence::Kind::kDescribeUser: case Sentence::Kind::kShowUsers: case Sentence::Kind::kShowSnapshots: case Sentence::Kind::kShowTSClients: diff --git a/src/graph/service/PermissionManager.cpp b/src/graph/service/PermissionManager.cpp index a0ea92b5229..80ce3153a53 100644 --- a/src/graph/service/PermissionManager.cpp +++ b/src/graph/service/PermissionManager.cpp @@ -113,6 +113,25 @@ Status PermissionManager::canWriteUser(ClientSession *session) { } } +// static +Status PermissionManager::canReadUser(ClientSession *session, const std::string &targetUser) { + if (!FLAGS_enable_authorize) { + return Status::OK(); + } + if (FLAGS_auth_type == "cloud") { + return Status::PermissionError("Cloud authenticate user can't read user."); + } + if (session->isGod()) { + return Status::OK(); + } + + if (session->user() == targetUser) { + return Status::OK(); + } + + return Status::PermissionError("No permission to read user `%s'.", targetUser.c_str()); +} + Status PermissionManager::canWriteRole(ClientSession *session, meta::cpp2::RoleType targetRole, GraphSpaceID spaceId, diff --git a/src/graph/service/PermissionManager.h b/src/graph/service/PermissionManager.h index 335e8d7fdaf..655b8dd420f 100644 --- a/src/graph/service/PermissionManager.h +++ b/src/graph/service/PermissionManager.h @@ -24,6 +24,7 @@ class PermissionManager final { static Status canWriteSpace(ClientSession *session); static Status canWriteSchema(ClientSession *session, ValidateContext *vctx); static Status canWriteUser(ClientSession *session); + static Status canReadUser(ClientSession *session, const std::string &targetUser); static Status canWriteRole(ClientSession *session, meta::cpp2::RoleType targetRole, GraphSpaceID spaceId, diff --git a/src/graph/validator/ACLValidator.cpp b/src/graph/validator/ACLValidator.cpp index 9fd45da9523..3f570f2e67a 100644 --- a/src/graph/validator/ACLValidator.cpp +++ b/src/graph/validator/ACLValidator.cpp @@ -131,6 +131,31 @@ Status RevokeRoleValidator::toPlan() { sentence->getAclItemClause()->getRoleType()); } +// describe user +Status DescribeUserValidator::validateImpl() { + auto sentence = static_cast(sentence_); + if (sentence->account()->size() > kUsernameMaxLength) { + return Status::SemanticError("Username exceed maximum length %ld characters.", + kUsernameMaxLength); + } + if (!inputs_.empty()) { + return Status::SemanticError("Show queries sentence do not support input"); + } + outputs_.emplace_back("role", Value::Type::STRING); + outputs_.emplace_back("space", Value::Type::STRING); + return Status::OK(); +} + +Status DescribeUserValidator::checkPermission() { + auto sentence = static_cast(sentence_); + return PermissionManager::canReadUser(qctx_->rctx()->session(), *sentence->account()); +} + +Status DescribeUserValidator::toPlan() { + auto sentence = static_cast(sentence_); + return genSingleNodePlan(sentence->account()); +} + // show roles in space Status ShowRolesInSpaceValidator::validateImpl() { auto sentence = static_cast(sentence_); diff --git a/src/graph/validator/ACLValidator.h b/src/graph/validator/ACLValidator.h index bf88439383c..616d6bc4eed 100644 --- a/src/graph/validator/ACLValidator.h +++ b/src/graph/validator/ACLValidator.h @@ -97,6 +97,20 @@ class RevokeRoleValidator final : public Validator { Status toPlan() override; }; +class DescribeUserValidator final : public Validator { + public: + DescribeUserValidator(Sentence* sentence, QueryContext* context) : Validator(sentence, context) { + setNoSpaceRequired(); + } + + private: + Status validateImpl() override; + + Status checkPermission() override; + + Status toPlan() override; +}; + class ShowRolesInSpaceValidator final : public Validator { public: ShowRolesInSpaceValidator(Sentence* sentence, QueryContext* context) diff --git a/src/graph/validator/Validator.cpp b/src/graph/validator/Validator.cpp index f7e2cb79816..687d4731492 100644 --- a/src/graph/validator/Validator.cpp +++ b/src/graph/validator/Validator.cpp @@ -131,6 +131,8 @@ std::unique_ptr Validator::makeValidator(Sentence* sentence, QueryCon return std::make_unique(sentence, context); case Sentence::Kind::kShowRoles: return std::make_unique(sentence, context); + case Sentence::Kind::kDescribeUser: + return std::make_unique(sentence, context); case Sentence::Kind::kAdminJob: case Sentence::Kind::kAdminShowJobs: return std::make_unique(sentence, context); diff --git a/src/parser/AdminSentences.cpp b/src/parser/AdminSentences.cpp index 54f4f29dea2..4e3cfafc281 100644 --- a/src/parser/AdminSentences.cpp +++ b/src/parser/AdminSentences.cpp @@ -27,6 +27,10 @@ std::string ShowPartsSentence::toString() const { return std::string("SHOW PARTS std::string ShowUsersSentence::toString() const { return std::string("SHOW USERS"); } +std::string DescribeUserSentence::toString() const { + return folly::stringPrintf("DESCRIBE USER %s", account_.get()->c_str()); +} + std::string ShowRolesSentence::toString() const { return folly::stringPrintf("SHOW ROLES IN %s", name_.get()->c_str()); } diff --git a/src/parser/AdminSentences.h b/src/parser/AdminSentences.h index f0f0af9500f..c08c738d39f 100644 --- a/src/parser/AdminSentences.h +++ b/src/parser/AdminSentences.h @@ -80,6 +80,21 @@ class ShowUsersSentence : public Sentence { std::string toString() const override; }; +class DescribeUserSentence : public Sentence { + public: + explicit DescribeUserSentence(std::string* account) { + account_.reset(account); + kind_ = Kind::kDescribeUser; + } + + std::string toString() const override; + + const std::string* account() const { return account_.get(); } + + private: + std::unique_ptr account_; +}; + class ShowRolesSentence : public Sentence { public: explicit ShowRolesSentence(std::string* name) { diff --git a/src/parser/Sentence.h b/src/parser/Sentence.h index ade75177b17..e943f8ed24a 100644 --- a/src/parser/Sentence.h +++ b/src/parser/Sentence.h @@ -79,6 +79,7 @@ class Sentence { kShowStats, kShowTSClients, kShowFTIndexes, + kDescribeUser, kDeleteVertices, kDeleteTags, kDeleteEdges, diff --git a/src/parser/parser.yy b/src/parser/parser.yy index a30907dd26e..3a5aab1f84b 100644 --- a/src/parser/parser.yy +++ b/src/parser/parser.yy @@ -358,7 +358,7 @@ static constexpr size_t kCommentLengthLimit = 256; %type add_listener_sentence remove_listener_sentence list_listener_sentence %type admin_job_sentence -%type create_user_sentence alter_user_sentence drop_user_sentence change_password_sentence +%type create_user_sentence alter_user_sentence drop_user_sentence change_password_sentence describe_user_sentence %type show_queries_sentence kill_query_sentence %type show_sentence @@ -2436,6 +2436,15 @@ column_property } ; +describe_user_sentence + : KW_DESCRIBE KW_USER name_label { + $$ = new DescribeUserSentence($3); + } + | KW_DESC KW_USER name_label { + $$ = new DescribeUserSentence($3); + } + ; + describe_tag_sentence : KW_DESCRIBE KW_TAG name_label { $$ = new DescribeTagSentence($3); @@ -2685,6 +2694,7 @@ traverse_sentence | delete_edge_sentence { $$ = $1; } | show_queries_sentence { $$ = $1; } | kill_query_sentence { $$ = $1; } + | describe_user_sentence { $$ = $1; } ; piped_sentence diff --git a/tests/tck/conftest.py b/tests/tck/conftest.py index 5ddcaddfcd4..637e1e1db32 100644 --- a/tests/tck/conftest.py +++ b/tests/tck/conftest.py @@ -213,6 +213,12 @@ def executing_query(query, graph_spaces, session, request): ngql = combine_query(query) exec_query(request, ngql, session, graph_spaces) +@when(parse("executing query with user {username} with password {password}:\n{query}")) +def executing_query(username, password, conn_pool_to_first_graph_service, query, graph_spaces, request): + sess = conn_pool_to_first_graph_service.get_session(username, password) + ngql = combine_query(query) + exec_query(request, ngql, sess, graph_spaces) + sess.release() @when(parse("profiling query:\n{query}")) def profiling_query(query, graph_spaces, session, request): diff --git a/tests/tck/features/user/User.feature b/tests/tck/features/user/User.feature index 5be998fb107..e66bb795bfa 100644 --- a/tests/tck/features/user/User.feature +++ b/tests/tck/features/user/User.feature @@ -280,3 +280,89 @@ Feature: User & privilege Test DROP SPACE user_tmp_space_3; """ Then the execution should be successful + + Scenario: Describe User + When executing query: + """ + CREATE SPACE IF NOT EXISTS user_tmp_space_4(partition_num=1, replica_factor=1, vid_type=FIXED_STRING(8)) + """ + Then the execution should be successful + And wait 10 seconds + When executing query: + """ + CREATE USER IF NOT EXISTS user1 WITH PASSWORD "pwd1" + """ + Then the execution should be successful + When executing query: + """ + GRANT ROLE ADMIN ON user_tmp_space_4 TO user1 + """ + Then the execution should be successful + When executing query: + """ + CREATE USER IF NOT EXISTS user2 WITH PASSWORD "pwd2" + """ + Then the execution should be successful + When executing query: + """ + GRANT ROLE ADMIN ON user_tmp_space_4 TO user2 + """ + Then the execution should be successful + When executing query: + """ + SHOW USERS + """ + Then the result should contain: + | Account | + | "root" | + | "user1" | + | "user2" | + When executing query: + """ + DESC USER root + """ + Then the result should be, in any order, with relax comparison: + | role | space | + | "GOD" | "" | + When executing query: + """ + DESC USER user1 + """ + Then the result should be, in any order, with relax comparison: + | role | space | + | "ADMIN" | "user_tmp_space_4" | + When executing query: + """ + DESC USER user1 | YIELD $-.space as sp + """ + Then the result should be, in any order, with relax comparison: + | sp | + | "user_tmp_space_4" | + When executing query: + """ + DESC USER user_not_exist + """ + Then the result should be, in any order, with relax comparison: + | role | space | + When executing query with user user1 with password pwd1: + """ + DESC USER user1 + """ + Then the result should be, in any order, with relax comparison: + | role | space | + | "ADMIN" | "user_tmp_space_4" | + When executing query with user user1 with password pwd1: + """ + DESC USER user2 + """ + Then a PermissionError should be raised at runtime: + When executing query: + """ + GRANT ROLE GUEST ON user_tmp_space_4 TO user1 + """ + Then the execution should be successful + When executing query with user user1 with password pwd1: + """ + DESC USER root + """ + Then a PermissionError should be raised at runtime: