diff --git a/src/graph/PermissionManager.h b/src/graph/PermissionManager.h new file mode 100644 index 00000000000..5fd18579572 --- /dev/null +++ b/src/graph/PermissionManager.h @@ -0,0 +1,47 @@ +/* Copyright (c) 2019 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 GRAPH_PERMISSIONMANAGER_H +#define GRAPH_PERMISSIONMANAGER_H + +// Operation and permission define: +// Operation | GOD | ADMIN | USER | GUEST +// ---------------- | ------------- | ------------- | ------------- | ------------- +// kGo | Y | Y | Y | Y +// kSet | Y | Y | Y | Y +// kPipe | Y | Y | Y | Y +// kUse | Y | Y | Y | Y +// kMatch | Y | Y | Y | Y +// kAssignment | Y | Y | Y | Y +// kCreateTag | Y | Y | | +// kAlterTag | Y | Y | | +// kCreateEdge | Y | Y | | +// kAlterEdge | Y | Y | | +// kDescribeTag | Y | Y | Y | Y +// kDescribeEdge | Y | Y | Y | Y +// kRemoveTag | Y | Y | | +// kRemoveEdge | Y | Y | | +// kInsertVertex | Y | Y | Y | +// kInsertEdge | Y | Y | Y | +// kShow | Y | Y | Y | Y +// kDeleteVertex | Y | Y | Y | +// kDeleteEdge | Y | Y | Y | +// kFind | Y | Y | Y | Y +// kAddHosts | Y | | | +// kRemoveHosts | Y | | | +// kCreateSpace | Y | | | +// kDropSpace | Y | Y | | +// kYield | Y | Y | Y | Y +// kCreateUser | Y | | | +// kDropUser | Y | | | +// kAlterUser | Y | Y | Y | Y +// kGrant | Y | Y | | +// kRevoke | Y | Y | | +// kChangePassword | Y | Y | Y | Y + + +#endif // GRAPH_PERMISSIONMANAGER_H diff --git a/src/interface/meta.thrift b/src/interface/meta.thrift index b1991a78d59..44ec672372e 100644 --- a/src/interface/meta.thrift +++ b/src/interface/meta.thrift @@ -45,6 +45,13 @@ enum AlterSchemaOp { UNKNOWN = 0x04, } (cpp.enum_strict) +/** +** GOD is A global senior administrator.like root of Linux systems. +** ADMIN is an administrator for a given Graph Space. +** USER is a normal user for a given Graph Space. A User can access (read and write) the data in the Graph Space. +** GUEST is a read-only role for a given Graph Space. A Guest cannot modify the data in the Graph Space. +** Refer to header file src/graph/PermissionManager.h for details. +**/ enum RoleType { GOD = 0x01, @@ -104,16 +111,23 @@ struct EdgeItem { struct UserItem { - 1: string account, - 2: string first_name, - 3: string last_name, - 4: bool is_lock, + 1: string account; + // Disable user if lock status is true. + 2: bool is_lock, + // The number of queries an account can issue per hour + 3: i32 max_queries_per_hour, + // The number of updates an account can issue per hour + 4: i32 max_updates_per_hour, + // The number of times an account can connect to the server per hour + 5: i32 max_connections_per_hour, + // The number of simultaneous connections to the server by an account + 6: i32 max_user_connections, } struct RoleItem { - 1: string account, - 2: string space, - 3: RoleType role_type, + 1: common.UserID user_id, + 2: common.GraphSpaceID space_id, + 3: RoleType role_type, } struct ExecResp { @@ -368,11 +382,11 @@ struct ListUsersResp { 1: ErrorCode code, // Valid if ret equals E_LEADER_CHANGED. 2: common.HostAddr leader, - 3: list users, + 3: map(cpp.template = "std::unordered_map") users, } struct ListRolesReq { - 1: string space, + 1: common.GraphSpaceID space_id, } struct ListRolesResp { diff --git a/src/meta/MetaServiceUtils.cpp b/src/meta/MetaServiceUtils.cpp index 2ee2ca6a3a9..4ef095dd8ab 100644 --- a/src/meta/MetaServiceUtils.cpp +++ b/src/meta/MetaServiceUtils.cpp @@ -342,15 +342,22 @@ folly::StringPiece MetaServiceUtils::userItemVal(folly::StringPiece rawVal) { std::string MetaServiceUtils::replaceUserVal(const cpp2::UserItem& user, folly::StringPiece val) { cpp2:: UserItem oldUser; apache::thrift::CompactSerializer::deserialize(userItemVal(val), oldUser); - if (user.__isset.first_name) { - oldUser.set_first_name(user.get_first_name()); - } - if (user.__isset.last_name) { - oldUser.set_last_name(user.get_last_name()); - } if (user.__isset.is_lock) { oldUser.set_is_lock(user.get_is_lock()); } + if (user.__isset.max_queries_per_hour) { + oldUser.set_max_queries_per_hour(user.get_max_queries_per_hour()); + } + if (user.__isset.max_updates_per_hour) { + oldUser.set_max_updates_per_hour(user.get_max_updates_per_hour()); + } + if (user.__isset.max_connections_per_hour) { + oldUser.set_max_connections_per_hour(user.get_max_connections_per_hour()); + } + if (user.__isset.max_user_connections) { + oldUser.set_max_user_connections(user.get_max_user_connections()); + } + std::string newVal, userVal; apache::thrift::CompactSerializer::serialize(oldUser, &userVal); auto len = sizeof(int32_t) + *reinterpret_cast(val.begin()); @@ -402,11 +409,16 @@ std::string MetaServiceUtils::roleSpacePrefix(GraphSpaceID spaceId) { return key; } -UserID MetaServiceUtils::parseUserId(folly::StringPiece val) { +UserID MetaServiceUtils::parseRoleUserId(folly::StringPiece val) { return *reinterpret_cast(val.begin() + kRolesTable.size() + sizeof(GraphSpaceID)); } +UserID MetaServiceUtils::parseUserId(folly::StringPiece val) { + return *reinterpret_cast(val.begin() + + kUsersTable.size()); +} + } // namespace meta } // namespace nebula diff --git a/src/meta/MetaServiceUtils.h b/src/meta/MetaServiceUtils.h index f044e23c94e..477af151261 100644 --- a/src/meta/MetaServiceUtils.h +++ b/src/meta/MetaServiceUtils.h @@ -109,6 +109,8 @@ class MetaServiceUtils final { static std::string roleSpacePrefix(GraphSpaceID spaceId); + static UserID parseRoleUserId(folly::StringPiece val); + static UserID parseUserId(folly::StringPiece val); }; diff --git a/src/meta/processors/BaseProcessor.h b/src/meta/processors/BaseProcessor.h index aafabb8bcb3..c1e2530d040 100644 --- a/src/meta/processors/BaseProcessor.h +++ b/src/meta/processors/BaseProcessor.h @@ -48,6 +48,13 @@ GENERATE_LOCK(user); return; \ } +#define CHECK_USER_ID_AND_RETURN(userID) \ + if (userExist(userID) == Status::UserNotFound()) { \ + resp_.set_code(cpp2::ErrorCode::E_NOT_FOUND); \ + onFinished(); \ + return; \ + } + /** * Check segemnt is consist of numbers and letters and should not empty. * */ @@ -185,6 +192,11 @@ class BaseProcessor { * */ Status spaceExist(GraphSpaceID spaceId); + /** + * Check userId exist or not. + **/ + Status userExist(UserID userId); + /** * Check host has been registered or not. * */ diff --git a/src/meta/processors/BaseProcessor.inl b/src/meta/processors/BaseProcessor.inl index 341531798a5..fbbc95f6e87 100644 --- a/src/meta/processors/BaseProcessor.inl +++ b/src/meta/processors/BaseProcessor.inl @@ -162,6 +162,18 @@ Status BaseProcessor::spaceExist(GraphSpaceID spaceId) { return Status::SpaceNotFound(); } +template +Status BaseProcessor::userExist(UserID spaceId) { + folly::SharedMutex::ReadHolder rHolder(LockUtils::userLock()); + auto userKey = MetaServiceUtils::userKey(spaceId); + std::string val; + auto ret = kvstore_->get(kDefaultSpaceId_, kDefaultPartId_, userKey, &val); + if (ret == kvstore::ResultCode::SUCCEEDED) { + return Status::OK(); + } + return Status::UserNotFound(); +} + template Status BaseProcessor::hostExist(const std::string& hostKey) { std::string val; diff --git a/src/meta/processors/usersMan/AuthenticationProcessor.cpp b/src/meta/processors/usersMan/AuthenticationProcessor.cpp index 06b9ab92d2a..9a40a451457 100644 --- a/src/meta/processors/usersMan/AuthenticationProcessor.cpp +++ b/src/meta/processors/usersMan/AuthenticationProcessor.cpp @@ -89,7 +89,7 @@ void DropUserProcessor::process(const cpp2::DropUserReq& req) { if (userRet == kvstore::ResultCode::SUCCEEDED) { while (iter->valid()) { auto key = iter->key(); - auto userId = MetaServiceUtils::parseUserId(key); + auto userId = MetaServiceUtils::parseRoleUserId(key); if (userId == ret.value()) { keys.emplace_back(std::move(key)); } @@ -105,22 +105,12 @@ void DropUserProcessor::process(const cpp2::DropUserReq& req) { void GrantProcessor::process(const cpp2::GrantRoleReq& req) { - folly::SharedMutex::WriteHolder wHolder(LockUtils::userLock()); const auto& roleItem = req.get_role_item(); - auto spaceRet = getSpaceId(roleItem.get_space()); - if (!spaceRet.ok()) { - resp_.set_code(to(spaceRet.status())); - onFinished(); - return;; - } - auto userRet = getUserId(roleItem.get_account()); - if (!userRet.ok()) { - resp_.set_code(to(userRet.status())); - onFinished(); - return; - } + CHECK_SPACE_ID_AND_RETURN(roleItem.get_space_id()); + CHECK_USER_ID_AND_RETURN(roleItem.get_user_id()); + folly::SharedMutex::WriteHolder wHolder(LockUtils::userLock()); std::vector data; - data.emplace_back(MetaServiceUtils::roleKey(spaceRet.value(), userRet.value()), + data.emplace_back(MetaServiceUtils::roleKey(roleItem.get_space_id(), roleItem.get_user_id()), MetaServiceUtils::roleVal(roleItem.get_role_type())); resp_.set_code(cpp2::ErrorCode::SUCCEEDED); doPut(std::move(data)); @@ -128,22 +118,12 @@ void GrantProcessor::process(const cpp2::GrantRoleReq& req) { void RevokeProcessor::process(const cpp2::RevokeRoleReq& req) { - folly::SharedMutex::WriteHolder wHolder(LockUtils::userLock()); const auto& roleItem = req.get_role_item(); - auto spaceRet = getSpaceId(roleItem.get_space()); - if (!spaceRet.ok()) { - resp_.set_code(to(spaceRet.status())); - onFinished(); - return;; - } - auto userRet = getUserId(roleItem.get_account()); - if (!userRet.ok()) { - resp_.set_code(to(userRet.status())); - onFinished(); - return; - } - auto roleKey = MetaServiceUtils::roleKey(spaceRet.value(), userRet.value()); - resp_.set_id(to(userRet.value(), EntryType::USER)); + CHECK_SPACE_ID_AND_RETURN(roleItem.get_space_id()); + CHECK_USER_ID_AND_RETURN(roleItem.get_user_id()); + folly::SharedMutex::WriteHolder wHolder(LockUtils::userLock()); + auto roleKey = MetaServiceUtils::roleKey(roleItem.get_space_id(), roleItem.get_user_id()); + resp_.set_id(to(roleItem.get_user_id(), EntryType::USER)); resp_.set_code(cpp2::ErrorCode::SUCCEEDED); doRemove(std::move(roleKey)); } @@ -222,7 +202,8 @@ void ListUsersProcessor::process(const cpp2::ListUsersReq& req) { decltype(resp_.users) users; while (iter->valid()) { cpp2::UserItem user = MetaServiceUtils::parseUserItem(iter->val()); - users.emplace_back(std::move(user)); + auto userId = MetaServiceUtils::parseUserId(iter->key()); + users.emplace(userId, std::move(user)); iter->next(); } resp_.set_users(users); @@ -252,18 +233,14 @@ void CheckPasswordProcessor::process(const cpp2::CheckPasswordReq& req) { void ListRolesProcessor::process(const cpp2::ListRolesReq& req) { + auto spaceId = req.get_space_id(); + CHECK_SPACE_ID_AND_RETURN(spaceId); folly::SharedMutex::ReadHolder rHolder(LockUtils::userLock()); - auto spaceRet = getSpaceId(req.get_space()); - if (!spaceRet.ok()) { - resp_.set_code(cpp2::ErrorCode::E_NOT_FOUND); - onFinished(); - return;; - } - auto prefix = MetaServiceUtils::roleSpacePrefix(spaceRet.value()); + auto prefix = MetaServiceUtils::roleSpacePrefix(spaceId); std::unique_ptr iter; auto ret = kvstore_->prefix(kDefaultSpaceId_, kDefaultPartId_, prefix, &iter); if (ret != kvstore::ResultCode::SUCCEEDED) { - LOG(ERROR) << "Can't find any roles by space " << req.get_space(); + LOG(ERROR) << "Can't find any roles by space id " << spaceId; resp_.set_code(cpp2::ErrorCode::E_NOT_FOUND); onFinished(); return; @@ -271,7 +248,7 @@ void ListRolesProcessor::process(const cpp2::ListRolesReq& req) { decltype(resp_.roles) roles; while (iter->valid()) { - auto userId = MetaServiceUtils::parseUserId(iter->key()); + auto userId = MetaServiceUtils::parseRoleUserId(iter->key()); auto val = iter->val(); auto account = getUserAccount(userId); if (!account.ok()) { @@ -281,7 +258,7 @@ void ListRolesProcessor::process(const cpp2::ListRolesReq& req) { return; } cpp2::RoleItem role(apache::thrift::FragileConstructor::FRAGILE, - account.value(), ""/*space name can be ignore at here*/, + userId, spaceId, *reinterpret_cast(val.begin())); roles.emplace_back(std::move(role)); iter->next(); diff --git a/src/meta/test/AuthProcessorTest.cpp b/src/meta/test/AuthProcessorTest.cpp index 69e41d14c57..cdef386da70 100644 --- a/src/meta/test/AuthProcessorTest.cpp +++ b/src/meta/test/AuthProcessorTest.cpp @@ -23,9 +23,8 @@ TEST(AuthProcessorTest, CreateUserTest) { std::unique_ptr kv(TestUtils::initKV(rootPath.path())); // Simple test auto code = TestUtils::createUser(kv.get(), false, "user1", "pwd", - "first name", "last name" , - false); - ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + false, 0, 0, 0, 0); + ASSERT_TRUE(code.ok()); /** * missing_ok should be turn on when "IF NOT EXISTS" is set. @@ -35,11 +34,13 @@ TEST(AuthProcessorTest, CreateUserTest) { * the result will be EXISTED if the user exists. **/ - code = TestUtils::createUser(kv.get(), false, "user1", "pwd", "", "" , false); - ASSERT_EQ(cpp2::ErrorCode::E_EXISTED, code); + code = TestUtils::createUser(kv.get(), false, "user1", "pwd", + false, 0, 0, 0, 0); + ASSERT_FALSE(code.ok()); - code = TestUtils::createUser(kv.get(), true, "user1", "pwd", "", "" , false); - ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + code = TestUtils::createUser(kv.get(), true, "user1", "pwd", + false, 0, 0, 0, 0); + ASSERT_TRUE(code.ok()); } @@ -49,16 +50,15 @@ TEST(AuthProcessorTest, AlterUserTest) { // Setup { auto code = TestUtils::createUser(kv.get(), false, "user1", "pwd", - "first name", "last name" , - false); - ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + false, 1, 2, 3, 4); + ASSERT_TRUE(code.ok()); } // Alter a few attributes { cpp2::AlterUserReq req; decltype(req.user_item) newUser; newUser.set_account("user1"); - newUser.set_first_name("user name"); + newUser.set_is_lock(true); req.set_user_item(std::move(newUser)); auto* processor = AlterUserProcessor::instance(kv.get()); auto f = processor->getFuture(); @@ -83,8 +83,10 @@ TEST(AuthProcessorTest, AlterUserTest) { cpp2::AlterUserReq req; decltype(req.user_item) newUser; newUser.set_account("user1"); - newUser.set_first_name("new first name"); - newUser.set_last_name(""); + newUser.set_max_queries_per_hour(10); + newUser.set_max_updates_per_hour(20); + newUser.set_max_connections_per_hour(30); + newUser.set_max_user_connections(40); newUser.set_is_lock(false); req.set_user_item(std::move(newUser)); auto* processor = AlterUserProcessor::instance(kv.get()); @@ -102,8 +104,10 @@ TEST(AuthProcessorTest, AlterUserTest) { auto resp = std::move(f).get(); decltype(resp.user_item) user; user.set_account("user1"); - user.set_first_name("new first name"); - user.set_last_name(""); + user.set_max_queries_per_hour(10); + user.set_max_updates_per_hour(20); + user.set_max_connections_per_hour(30); + user.set_max_user_connections(40); user.set_is_lock(false); ASSERT_EQ(user, resp.get_user_item()); } @@ -115,9 +119,8 @@ TEST(AuthProcessorTest, DropUserTest) { // Setup { auto code = TestUtils::createUser(kv.get(), false, "user1", "pwd", - "first name", "last name" , - false); - ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + false, 1, 2, 3, 4); + ASSERT_TRUE(code.ok()); } // Simple drop. { @@ -162,9 +165,8 @@ TEST(AuthProcessorTest, PasswordTest) { // Setup { auto code = TestUtils::createUser(kv.get(), false, "user1", "pwd", - "first name", "last name" , - false); - ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + false, 4, 3, 2, 1); + ASSERT_TRUE(code.ok()); } // verify password. { @@ -221,17 +223,15 @@ TEST(AuthProcessorTest, PasswordTest) { TEST(AuthProcessorTest, GrantRevokeTest) { fs::TempDir rootPath("/tmp/GrantRevokeTest.XXXXXX"); std::unique_ptr kv(TestUtils::initKV(rootPath.path())); - // Setup - { - auto code = TestUtils::createUser(kv.get(), false, "user1", "pwd", - "first name", "last name", - false); - ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); - } + auto ret = TestUtils::createUser(kv.get(), false, "user1", "pwd", + false, 1, 2, 3, 4); + ASSERT_TRUE(ret.ok()); + auto userId = ret.value(); + // grant test : space does not exist { cpp2::GrantRoleReq req; - decltype(req.role_item) role(FRAGILE, "user1", "space1", cpp2::RoleType::USER); + decltype(req.role_item) role(FRAGILE, userId, 100, cpp2::RoleType::USER); req.set_role_item(std::move(role)); auto* processor = GrantProcessor::instance(kv.get()); auto f = processor->getFuture(); @@ -243,7 +243,7 @@ TEST(AuthProcessorTest, GrantRevokeTest) { // revoke test : space does not exist { cpp2::RevokeRoleReq req; - decltype(req.role_item) role(FRAGILE, "user1", "space1", cpp2::RoleType::USER); + decltype(req.role_item) role(FRAGILE, userId, 100, cpp2::RoleType::USER); req.set_role_item(std::move(role)); auto* processor = RevokeProcessor::instance(kv.get()); auto f = processor->getFuture(); @@ -252,6 +252,7 @@ TEST(AuthProcessorTest, GrantRevokeTest) { ASSERT_EQ(cpp2::ErrorCode::E_NOT_FOUND, resp.get_code()); } // setup space + GraphSpaceID spaceId; { TestUtils::createSomeHosts(kv.get()); cpp2::CreateSpaceReq req(FRAGILE, @@ -261,11 +262,12 @@ TEST(AuthProcessorTest, GrantRevokeTest) { processor->process(req); auto resp = std::move(f).get(); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, resp.code); + spaceId = resp.get_id().get_space_id(); } // grant test { cpp2::GrantRoleReq req; - decltype(req.role_item) role(FRAGILE, "user1", "test_space", cpp2::RoleType::USER); + decltype(req.role_item) role(FRAGILE, userId, spaceId, cpp2::RoleType::USER); req.set_role_item(std::move(role)); auto* processor = GrantProcessor::instance(kv.get()); auto f = processor->getFuture(); @@ -275,19 +277,19 @@ TEST(AuthProcessorTest, GrantRevokeTest) { } // List acl by space name. { - cpp2::ListRolesReq req(FRAGILE, "test_space"); + cpp2::ListRolesReq req(FRAGILE, spaceId); auto* processor = ListRolesProcessor::instance(kv.get()); auto f = processor->getFuture(); processor->process(req); auto resp = std::move(f).get(); decltype(resp.roles) rolesList; - rolesList.emplace_back(FRAGILE, "user1", "", cpp2::RoleType::USER); + rolesList.emplace_back(FRAGILE, userId, spaceId, cpp2::RoleType::USER); ASSERT_EQ(rolesList, resp.get_roles()); } // revoke test { cpp2::RevokeRoleReq req; - decltype(req.role_item) role(FRAGILE, "user1", "test_space", cpp2::RoleType::USER); + decltype(req.role_item) role(FRAGILE, userId, spaceId, cpp2::RoleType::USER); req.set_role_item(std::move(role)); auto* processor = RevokeProcessor::instance(kv.get()); auto f = processor->getFuture(); @@ -295,11 +297,23 @@ TEST(AuthProcessorTest, GrantRevokeTest) { auto resp = std::move(f).get(); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, resp.get_code()); std::unique_ptr iter; - std::string prefix = "__acl__"; + std::string prefix = "__roles__"; auto code = kv.get()->prefix(0, 0, prefix, &iter); ASSERT_EQ(kvstore::ResultCode::SUCCEEDED, code); ASSERT_FALSE(iter->valid()); } + // List Users + { + cpp2::ListUsersReq req; + auto* processor = ListUsersProcessor::instance(kv.get()); + auto f = processor->getFuture(); + processor->process(req); + auto resp = std::move(f).get(); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, resp.get_code()); + decltype(resp.users) users; + users.emplace(userId, cpp2::UserItem(FRAGILE, "user1", false, 1, 2, 3, 4)); + ASSERT_EQ(users, resp.get_users()); + } } } // namespace meta } // namespace nebula diff --git a/src/meta/test/TestUtils.h b/src/meta/test/TestUtils.h index 33b6b6f50ec..b1633fde6c5 100644 --- a/src/meta/test/TestUtils.h +++ b/src/meta/test/TestUtils.h @@ -179,27 +179,35 @@ class TestUtils { return sc; } - static cpp2::ErrorCode createUser(kvstore::KVStore* kv, + static StatusOr createUser(kvstore::KVStore* kv, bool missingOk, folly::StringPiece account, folly::StringPiece password, - folly::StringPiece first, - folly::StringPiece last, - bool isLock) { + bool isLock, + int32_t maxQueries, + int32_t maxUpdates, + int32_t maxConnections, + int32_t maxConnectors) { cpp2::CreateUserReq req; req.set_missing_ok(missingOk); req.set_encoded_pwd(password.str()); decltype(req.user) user(FRAGILE, account.str(), - first.str(), - last.str(), - isLock); + isLock, + maxQueries, + maxUpdates, + maxConnections, + maxConnectors); req.set_user(std::move(user)); auto* processor = CreateUserProcessor::instance(kv); auto f = processor->getFuture(); processor->process(req); auto resp = std::move(f).get(); - return resp.get_code(); + if (resp.get_code() == cpp2::ErrorCode::SUCCEEDED) { + return resp.get_id().get_user_id(); + } else { + return Status::Error("Create user fail"); + } } };