diff --git a/proto/CMakeLists.txt b/proto/CMakeLists.txt index 9747da0..6c746f1 100644 --- a/proto/CMakeLists.txt +++ b/proto/CMakeLists.txt @@ -1,9 +1,4 @@ # ruek/api/v1/**/*.proto -cmake_path(SET authz_proto ${CMAKE_CURRENT_SOURCE_DIR}/ruek/api/v1/authz.proto) -cmake_path(SET authz_grpcxx_header ${CMAKE_CURRENT_BINARY_DIR}/ruek/api/v1/authz.grpcxx.pb.h) -cmake_path(SET authz_header ${CMAKE_CURRENT_BINARY_DIR}/ruek/api/v1/authz.pb.h) -cmake_path(SET authz_source ${CMAKE_CURRENT_BINARY_DIR}/ruek/api/v1/authz.pb.cc) - cmake_path(SET principals_proto ${CMAKE_CURRENT_SOURCE_DIR}/ruek/api/v1/principals.proto) cmake_path(SET principals_grpcxx_header ${CMAKE_CURRENT_BINARY_DIR}/ruek/api/v1/principals.grpcxx.pb.h) cmake_path(SET principals_header ${CMAKE_CURRENT_BINARY_DIR}/ruek/api/v1/principals.pb.h) @@ -14,30 +9,19 @@ cmake_path(SET relations_grpcxx_header ${CMAKE_CURRENT_BINARY_DIR}/ruek/api/v1/r cmake_path(SET relations_header ${CMAKE_CURRENT_BINARY_DIR}/ruek/api/v1/relations.pb.h) cmake_path(SET relations_source ${CMAKE_CURRENT_BINARY_DIR}/ruek/api/v1/relations.pb.cc) -cmake_path(SET entities_proto ${CMAKE_CURRENT_SOURCE_DIR}/ruek/api/v1/entities.proto) -cmake_path(SET entities_grpcxx_header ${CMAKE_CURRENT_BINARY_DIR}/ruek/api/v1/entities.grpcxx.pb.h) -cmake_path(SET entities_header ${CMAKE_CURRENT_BINARY_DIR}/ruek/api/v1/entities.pb.h) -cmake_path(SET entities_source ${CMAKE_CURRENT_BINARY_DIR}/ruek/api/v1/entities.pb.cc) - set(protos - ${authz_proto} ${principals_proto} ${relations_proto} - ${entities_proto} ) set(headers - ${authz_header} ${authz_grpcxx_header} ${principals_header} ${principals_grpcxx_header} ${relations_header} ${relations_grpcxx_header} - ${entities_header} ${entities_grpcxx_header} ) set(sources - ${authz_source} ${principals_source} ${relations_source} - ${entities_source} ) add_custom_command( diff --git a/proto/ruek/api/v1/authz.proto b/proto/ruek/api/v1/authz.proto deleted file mode 100644 index 0f09717..0000000 --- a/proto/ruek/api/v1/authz.proto +++ /dev/null @@ -1,41 +0,0 @@ -syntax = "proto3"; - -package ruek.api.v1; - -import "google/protobuf/struct.proto"; - -service Authz { - rpc Check(AuthzCheckRequest) returns (AuthzCheckResponse); - rpc Grant(AuthzGrantRequest) returns (AuthzGrantResponse); - rpc Revoke(AuthzRevokeRequest) returns (AuthzRevokeResponse); -} - -message AuthzCheckRequest { - string principal_id = 1; - string entity_id = 3; - string entity_type = 2; -} - -message AuthzCheckResponse { - bool ok = 1; - - optional google.protobuf.Struct attrs = 2; -} - -message AuthzGrantRequest { - string principal_id = 1; - string entity_id = 3; - string entity_type = 2; - - optional google.protobuf.Struct attrs = 4; -} - -message AuthzGrantResponse {} - -message AuthzRevokeRequest { - string principal_id = 1; - string entity_id = 3; - string entity_type = 2; -} - -message AuthzRevokeResponse {} diff --git a/proto/ruek/api/v1/entities.proto b/proto/ruek/api/v1/entities.proto deleted file mode 100644 index 1ecdf8a..0000000 --- a/proto/ruek/api/v1/entities.proto +++ /dev/null @@ -1,51 +0,0 @@ -syntax = "proto3"; - -package ruek.api.v1; - -import "google/protobuf/struct.proto"; - -service Entities { - rpc List(EntitiesListRequest) returns (EntitiesListResponse); - rpc ListPrincipals(EntitiesListPrincipalsRequest) returns (EntitiesListPrincipalsResponse); -} - -message EntitiesEntity { - string id = 1; - string type = 2; - - optional google.protobuf.Struct attrs = 3; -} - -message EntitiesPrincipal { - string id = 1; - - optional google.protobuf.Struct attrs = 2; -} - -message EntitiesListRequest { - string principal_id = 1; - string entity_type = 2; - - optional uint32 pagination_limit = 3; - optional string pagination_token = 4; -} - -message EntitiesListResponse { - repeated EntitiesEntity entities = 1; - - optional string pagination_token = 2; -} - -message EntitiesListPrincipalsRequest { - string entity_id = 2; - string entity_type = 1; - - optional uint32 pagination_limit = 3; - optional string pagination_token = 4; -} - -message EntitiesListPrincipalsResponse { - repeated EntitiesPrincipal principals = 1; - - optional string pagination_token = 2; -} diff --git a/src/main.cpp b/src/main.cpp index 36f9bf6..3ae4dc7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -45,12 +45,6 @@ int main(int argc, char *argv[]) { grpcxx::server server; - svc::Authz a; - server.add(a.service()); - - svc::Entities e; - server.add(e.service()); - svc::Principals p; server.add(p.service()); diff --git a/src/svc/CMakeLists.txt b/src/svc/CMakeLists.txt index c242c5a..75a4851 100644 --- a/src/svc/CMakeLists.txt +++ b/src/svc/CMakeLists.txt @@ -1,15 +1,11 @@ add_library(svc) target_sources(svc PRIVATE - authz.cpp - entities.cpp principals.cpp relations.cpp PUBLIC FILE_SET headers TYPE HEADERS FILES - authz.h - entities.h principals.h relations.h svc.h @@ -42,8 +38,6 @@ if (RUEK_BUILD_TESTING) add_executable(svc_tests) target_sources(svc_tests PRIVATE - authz_test.cpp - entities_test.cpp principals_test.cpp relations_test.cpp ) diff --git a/src/svc/authz.cpp b/src/svc/authz.cpp deleted file mode 100644 index 9841a91..0000000 --- a/src/svc/authz.cpp +++ /dev/null @@ -1,120 +0,0 @@ -#include "authz.h" - -#include -#include - -#include "err/errors.h" - -#include "common.h" - -namespace svc { -namespace authz { -template <> -rpcCheck::result_type Impl::call( - grpcxx::context &ctx, const rpcCheck::request_type &req) { - auto r = db::Tuple::lookup( - ctx.meta(common::space_id_v), {req.principal_id()}, {req.entity_type(), req.entity_id()}); - return {grpcxx::status::code_t::ok, map(r)}; -} - -template <> -rpcGrant::result_type Impl::call( - grpcxx::context &ctx, const rpcGrant::request_type &req) { - // Upsert if exists - if (auto r = db::Tuple::lookup( - ctx.meta(common::space_id_v), - {req.principal_id()}, - {req.entity_type(), req.entity_id()}); - r) { - if (req.has_attrs()) { - std::string attrs; - google::protobuf::util::MessageToJsonString(req.attrs(), &attrs); - - r->attrs(std::move(attrs)); - r->store(); - } - - return {grpcxx::status::code_t::ok, map(r.value())}; - } - - auto r = map(ctx, req); - r.store(); - - return {grpcxx::status::code_t::ok, map(r)}; -} - -template <> -rpcRevoke::result_type Impl::call( - grpcxx::context &ctx, const rpcRevoke::request_type &req) { - if (auto r = db::Tuple::lookup( - ctx.meta(common::space_id_v), - {req.principal_id()}, - {req.entity_type(), req.entity_id()}); - r) { - db::Tuple::discard(r->id()); - } - - return {grpcxx::status::code_t::ok, rpcRevoke::response_type()}; -} - -google::rpc::Status Impl::exception() noexcept { - google::rpc::Status status; - status.set_code(google::rpc::UNKNOWN); - - try { - std::rethrow_exception(std::current_exception()); - } catch (const err::DbTupleInvalidData &e) { - status.set_code(google::rpc::INVALID_ARGUMENT); - status.set_message(std::string(e.str())); - } catch (const err::DbTupleInvalidKey &e) { - status.set_code(google::rpc::INVALID_ARGUMENT); - status.set_message(std::string(e.str())); - } catch (const err::DbRevisionMismatch &e) { - status.set_code(google::rpc::INTERNAL); - status.set_message(std::string(e.str())); - } catch (const std::exception &e) { - status.set_code(google::rpc::INTERNAL); - status.set_message(e.what()); - } - - return status; -} - -db::Tuple Impl::map(const grpcxx::context &ctx, const rpcGrant::request_type &from) const noexcept { - db::Tuple to({ - .lPrincipalId = from.principal_id(), - .rEntityId = from.entity_id(), - .rEntityType = from.entity_type(), - .spaceId = std::string(ctx.meta(common::space_id_v)), - }); - - if (from.has_attrs()) { - std::string attrs; - google::protobuf::util::MessageToJsonString(from.attrs(), &attrs); - - to.attrs(std::move(attrs)); - } - - return to; -} - -rpcCheck::response_type Impl::map(const std::optional &from) const noexcept { - rpcCheck::response_type to; - if (!from) { - to.set_ok(false); - return to; - } - - to.set_ok(true); - if (from->attrs()) { - google::protobuf::util::JsonStringToMessage(*from->attrs(), to.mutable_attrs()); - } - - return to; -} - -rpcGrant::response_type Impl::map(const db::Tuple &from) const noexcept { - return {}; -} -} // namespace authz -} // namespace svc diff --git a/src/svc/authz.h b/src/svc/authz.h deleted file mode 100644 index fd53d90..0000000 --- a/src/svc/authz.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once -#include - -#include "db/tuples.h" -#include "ruek/api/v1/authz.grpcxx.pb.h" - -namespace svc { -namespace authz { -using namespace ruek::api::v1::Authz; - -class Impl { -public: - using service_type = Service; - - template - typename T::result_type call(grpcxx::context &, const typename T::request_type &) { - return {grpcxx::status::code_t::unimplemented, std::nullopt}; - } - - google::rpc::Status exception() noexcept; - -private: - rpcCheck::response_type map(const std::optional &from) const noexcept; - - db::Tuple map(const grpcxx::context &ctx, const rpcGrant::request_type &from) const noexcept; - rpcGrant::response_type map(const db::Tuple &from) const noexcept; -}; - -template <> -rpcCheck::result_type Impl::call(grpcxx::context &ctx, const rpcCheck::request_type &req); - -template <> -rpcGrant::result_type Impl::call(grpcxx::context &ctx, const rpcGrant::request_type &req); - -template <> -rpcRevoke::result_type Impl::call( - grpcxx::context &ctx, const rpcRevoke::request_type &req); -} // namespace authz -} // namespace svc diff --git a/src/svc/authz_test.cpp b/src/svc/authz_test.cpp deleted file mode 100644 index 0a2fd76..0000000 --- a/src/svc/authz_test.cpp +++ /dev/null @@ -1,354 +0,0 @@ -#include -#include -#include - -#include "db/testing.h" - -#include "common.h" -#include "svc.h" - -using namespace ruek::api::v1::Authz; - -class svc_AuthzTest : public testing::Test { -protected: - static void SetUpTestSuite() { - db::testing::setup(); - - // Clear data - db::pg::exec("truncate table principals cascade;"); - db::pg::exec("truncate table tuples;"); - } - - static void TearDownTestSuite() { db::testing::teardown(); } -}; - -TEST_F(svc_AuthzTest, Check) { - grpcxx::context ctx; - svc::Authz svc; - - db::Principal principal({ - .id = "id:svc_AuthzTest.Check", - }); - ASSERT_NO_THROW(principal.store()); - - // Success: check - { - db::Tuple tuple({ - .lPrincipalId = principal.id(), - .rEntityId = "Check", - .rEntityType = "svc_AuthzTest", - .spaceId = principal.spaceId(), - }); - ASSERT_NO_THROW(tuple.store()); - - rpcCheck::request_type request; - request.set_principal_id(*tuple.lPrincipalId()); - request.set_entity_type(tuple.rEntityType()); - request.set_entity_id(tuple.rEntityId()); - - rpcCheck::result_type result; - EXPECT_NO_THROW(result = svc.call(ctx, request)); - EXPECT_EQ(grpcxx::status::code_t::ok, result.status.code()); - ASSERT_TRUE(result.response); - - EXPECT_TRUE(result.response->ok()); - EXPECT_FALSE(result.response->has_attrs()); - } - - // Success: check with `attrs` - { - db::Tuple tuple({ - .attrs = R"({"foo":"bar"})", - .lPrincipalId = principal.id(), - .rEntityId = "Check-with_attrs", - .rEntityType = "svc_AuthzTest", - .spaceId = principal.spaceId(), - }); - ASSERT_NO_THROW(tuple.store()); - - rpcCheck::request_type request; - request.set_principal_id(*tuple.lPrincipalId()); - request.set_entity_type(tuple.rEntityType()); - request.set_entity_id(tuple.rEntityId()); - - rpcCheck::result_type result; - EXPECT_NO_THROW(result = svc.call(ctx, request)); - EXPECT_EQ(grpcxx::status::code_t::ok, result.status.code()); - ASSERT_TRUE(result.response); - - EXPECT_TRUE(result.response->ok()); - - std::string responseAttrs; - google::protobuf::util::MessageToJsonString(result.response->attrs(), &responseAttrs); - EXPECT_EQ(tuple.attrs(), responseAttrs); - } - - // Success: check with space-id - { - db::Principal principal({ - .id = "id:svc_AuthzTest.Check", - .spaceId = "space_id:svc_AuthzTest.Check", - }); - ASSERT_NO_THROW(principal.store()); - - db::Tuple tuple({ - .lPrincipalId = principal.id(), - .rEntityId = "Check-with_space_id", - .rEntityType = "svc_AuthzTest", - .spaceId = principal.spaceId(), - }); - ASSERT_NO_THROW(tuple.store()); - - grpcxx::detail::request r(1); - r.header(std::string(svc::common::space_id_v), std::string(principal.spaceId())); - - grpcxx::context ctx(r); - - rpcCheck::request_type request; - request.set_principal_id(*tuple.lPrincipalId()); - request.set_entity_type(tuple.rEntityType()); - request.set_entity_id(tuple.rEntityId()); - - rpcCheck::result_type result; - EXPECT_NO_THROW(result = svc.call(ctx, request)); - EXPECT_EQ(grpcxx::status::code_t::ok, result.status.code()); - ASSERT_TRUE(result.response); - - EXPECT_TRUE(result.response->ok()); - EXPECT_FALSE(result.response->has_attrs()); - } - - // Success: check !ok - { - rpcCheck::request_type request; - request.set_principal_id("non-existent"); - request.set_entity_type("svc_AuthzTest"); - request.set_entity_id("Check-non_existent"); - - rpcCheck::result_type result; - EXPECT_NO_THROW(result = svc.call(ctx, request)); - EXPECT_EQ(grpcxx::status::code_t::ok, result.status.code()); - ASSERT_TRUE(result.response); - - EXPECT_FALSE(result.response->ok()); - EXPECT_FALSE(result.response->has_attrs()); - } - - // Success: check !ok with space-id - { - db::Tuple tuple({ - .lPrincipalId = principal.id(), - .rEntityId = "Check-space_id_mismatch", - .rEntityType = "svc_AuthzTest", - .spaceId = principal.spaceId(), - }); - ASSERT_NO_THROW(tuple.store()); - - grpcxx::detail::request r(1); - r.header(std::string(svc::common::space_id_v), "invalid"); - - grpcxx::context ctx(r); - - rpcCheck::request_type request; - request.set_principal_id(*tuple.lPrincipalId()); - request.set_entity_type(tuple.rEntityType()); - request.set_entity_id(tuple.rEntityId()); - - rpcCheck::result_type result; - EXPECT_NO_THROW(result = svc.call(ctx, request)); - EXPECT_EQ(grpcxx::status::code_t::ok, result.status.code()); - ASSERT_TRUE(result.response); - - EXPECT_FALSE(result.response->ok()); - EXPECT_FALSE(result.response->has_attrs()); - } -} - -TEST_F(svc_AuthzTest, Grant) { - grpcxx::context ctx; - svc::Authz svc; - - db::Principal principal({ - .id = "id:svc_AuthzTest.Grant", - }); - ASSERT_NO_THROW(principal.store()); - - // Success: grant - { - rpcGrant::request_type request; - request.set_principal_id(principal.id()); - request.set_entity_type("svc_AuthzTest"); - request.set_entity_id("Grant"); - - rpcGrant::result_type result; - EXPECT_NO_THROW(result = svc.call(ctx, request)); - - EXPECT_EQ(grpcxx::status::code_t::ok, result.status.code()); - ASSERT_TRUE(result.response); - } - - // Success: grant with space-id - { - db::Principal principal({ - .id = "id:svc_AuthzTest.Grant-with_space_id", - .spaceId = "space_id:svc_AuthzTest.Grant-with_space_id", - }); - ASSERT_NO_THROW(principal.store()); - - grpcxx::detail::request r(1); - r.header(std::string(svc::common::space_id_v), std::string(principal.spaceId())); - - grpcxx::context ctx(r); - - rpcGrant::request_type request; - request.set_principal_id(principal.id()); - request.set_entity_type("svc_AuthzTest"); - request.set_entity_id("Grant-with_space_id"); - - rpcGrant::result_type result; - EXPECT_NO_THROW(result = svc.call(ctx, request)); - - EXPECT_EQ(grpcxx::status::code_t::ok, result.status.code()); - ASSERT_TRUE(result.response); - } - - // Success: upsert - { - db::Tuple tuple({ - .lPrincipalId = principal.id(), - .rEntityId = "Grant-upsert", - .rEntityType = "svc_AuthzTest", - .spaceId = principal.spaceId(), - }); - ASSERT_NO_THROW(tuple.store()); - - rpcGrant::request_type request; - request.set_principal_id(*tuple.lPrincipalId()); - request.set_entity_type(tuple.rEntityType()); - request.set_entity_id(tuple.rEntityId()); - - const std::string attrs(R"({"foo":"bar"})"); - google::protobuf::util::JsonStringToMessage(attrs, request.mutable_attrs()); - - rpcGrant::result_type result; - EXPECT_NO_THROW(result = svc.call(ctx, request)); - - EXPECT_EQ(grpcxx::status::code_t::ok, result.status.code()); - ASSERT_TRUE(result.response); - - auto actual = db::Tuple::lookup( - tuple.spaceId(), {*tuple.lPrincipalId()}, {tuple.rEntityType(), tuple.rEntityId()}); - EXPECT_EQ(tuple.rev() + 1, actual->rev()); - EXPECT_EQ(R"({"foo": "bar"})", actual->attrs()); - } - - // Error: invalid data - { - rpcGrant::request_type request; - request.set_principal_id(principal.id()); - - rpcGrant::result_type result; - EXPECT_NO_THROW(result = svc.call(ctx, request)); - - EXPECT_EQ(grpcxx::status::code_t::invalid_argument, result.status.code()); - ASSERT_FALSE(result.response); - } - - // Error: invalid `principal_id` - { - rpcGrant::request_type request; - request.set_principal_id("invalid"); - request.set_entity_type("svc_AuthzTest"); - request.set_entity_id("Grant-invalid_principal_id"); - - rpcGrant::result_type result; - EXPECT_NO_THROW(result = svc.call(ctx, request)); - - EXPECT_EQ(grpcxx::status::code_t::invalid_argument, result.status.code()); - ASSERT_FALSE(result.response); - } - - // Error: invalid space-id - { - grpcxx::detail::request r(1); - r.header(std::string(svc::common::space_id_v), "invalid"); - - grpcxx::context ctx(r); - - rpcGrant::request_type request; - request.set_principal_id(principal.id()); - request.set_entity_type("svc_AuthzTest"); - request.set_entity_id("Grant-invalid_space_id"); - - rpcGrant::result_type result; - EXPECT_NO_THROW(result = svc.call(ctx, request)); - - EXPECT_EQ(grpcxx::status::code_t::invalid_argument, result.status.code()); - ASSERT_FALSE(result.response); - } -} - -TEST_F(svc_AuthzTest, Revoke) { - grpcxx::context ctx; - svc::Authz svc; - - db::Principal principal({ - .id = "id:svc_AuthzTest.Revoke", - }); - ASSERT_NO_THROW(principal.store()); - - // Success: revoke - { - db::Tuple tuple({ - .lPrincipalId = principal.id(), - .rEntityId = "Revoke", - .rEntityType = "svc_AuthzTest", - .spaceId = principal.spaceId(), - }); - ASSERT_NO_THROW(tuple.store()); - - rpcRevoke::request_type request; - request.set_principal_id(*tuple.lPrincipalId()); - request.set_entity_type(tuple.rEntityType()); - request.set_entity_id(tuple.rEntityId()); - - rpcRevoke::result_type result; - EXPECT_NO_THROW(result = svc.call(ctx, request)); - - EXPECT_EQ(grpcxx::status::code_t::ok, result.status.code()); - ASSERT_TRUE(result.response); - - EXPECT_FALSE(db::Tuple::lookup( - tuple.spaceId(), {*tuple.lPrincipalId()}, {tuple.rEntityType(), tuple.rEntityId()})); - } - - // Success: invalid space-id - { - db::Tuple tuple({ - .lPrincipalId = principal.id(), - .rEntityId = "Revoke-invalid_space_id", - .rEntityType = "svc_AuthzTest", - .spaceId = principal.spaceId(), - }); - ASSERT_NO_THROW(tuple.store()); - - grpcxx::detail::request r(1); - r.header(std::string(svc::common::space_id_v), "invalid"); - - grpcxx::context ctx(r); - - rpcRevoke::request_type request; - request.set_principal_id(*tuple.lPrincipalId()); - request.set_entity_type(tuple.rEntityType()); - request.set_entity_id(tuple.rEntityId()); - - rpcRevoke::result_type result; - EXPECT_NO_THROW(result = svc.call(ctx, request)); - - EXPECT_EQ(grpcxx::status::code_t::ok, result.status.code()); - ASSERT_TRUE(result.response); - - EXPECT_TRUE(db::Tuple::lookup( - tuple.spaceId(), {*tuple.lPrincipalId()}, {tuple.rEntityType(), tuple.rEntityId()})); - } -} diff --git a/src/svc/entities.cpp b/src/svc/entities.cpp deleted file mode 100644 index da160cc..0000000 --- a/src/svc/entities.cpp +++ /dev/null @@ -1,173 +0,0 @@ -#include "entities.h" - -#include -#include - -#include "encoding/b32.h" -#include "ruek/detail/pagination.pb.h" - -#include "common.h" - -namespace svc { -namespace entities { -template <> -rpcList::result_type Impl::call(grpcxx::context &ctx, const rpcList::request_type &req) { - std::string lastId; - if (req.has_pagination_token()) { - ruek::detail::PaginationToken pbToken; - if (pbToken.ParseFromString(encoding::b32::decode(req.pagination_token()))) { - lastId = pbToken.last_id(); - } - } - - auto limit = common::pagination_limit_v; - if (req.pagination_limit() > 0 && req.pagination_limit() < 30) { - limit = req.pagination_limit(); - } - - db::Tuples results; - results.reserve(limit); - - while (results.size() < limit) { - auto tuples = db::ListTuplesRight( - ctx.meta(common::space_id_v), {req.principal_id()}, {}, lastId, limit); - - if (tuples.empty()) { - break; - } - - lastId = tuples.back().rEntityId(); - for (auto &t : tuples) { - if (t.rEntityType() != req.entity_type()) { - continue; - } - - results.push_back(std::move(t)); - } - - if (tuples.size() < limit) { - break; - } - } - - auto response = map(results); - if (results.size() == limit) { - ruek::detail::PaginationToken pbToken; - pbToken.set_last_id(results.back().rEntityId()); - - auto strToken = encoding::b32::encode(pbToken.SerializeAsString()); - response.set_pagination_token(strToken); - } - - return {grpcxx::status::code_t::ok, response}; -} - -template <> -rpcListPrincipals::result_type Impl::call( - grpcxx::context &ctx, const rpcListPrincipals::request_type &req) { - std::string lastId; - if (req.has_pagination_token()) { - ruek::detail::PaginationToken pbToken; - if (pbToken.ParseFromString(encoding::b32::decode(req.pagination_token()))) { - lastId = pbToken.last_id(); - } - } - - auto limit = common::pagination_limit_v; - if (req.pagination_limit() > 0 && req.pagination_limit() < 30) { - limit = req.pagination_limit(); - } - - db::Tuples results; - results.reserve(limit); - - while (results.size() < limit) { - auto tuples = db::ListTuplesLeft( - ctx.meta(common::space_id_v), {req.entity_type(), req.entity_id()}, {}, lastId, limit); - - if (tuples.empty()) { - break; - } - - lastId = tuples.back().lEntityId(); - for (auto &t : tuples) { - if (!t.lPrincipalId()) { - continue; - } - - results.push_back(std::move(t)); - } - - if (tuples.size() < limit) { - break; - } - } - - auto response = map(results); - - if (results.size() == limit) { - ruek::detail::PaginationToken pbToken; - pbToken.set_last_id(*results.back().lPrincipalId()); - - auto strToken = encoding::b32::encode(pbToken.SerializeAsString()); - response.set_pagination_token(strToken); - } - - return {grpcxx::status::code_t::ok, response}; -} - -google::rpc::Status Impl::exception() noexcept { - google::rpc::Status status; - status.set_code(google::rpc::UNKNOWN); - - return status; -} - -template <> rpcList::response_type Impl::map(const db::Tuples &from) const noexcept { - rpcList::response_type to; - - auto *arr = to.mutable_entities(); - arr->Reserve(from.size()); - for (const auto &t : from) { - arr->Add(map(t)); - } - - return to; -} - -template <> rpcListPrincipals::response_type Impl::map(const db::Tuples &from) const noexcept { - rpcListPrincipals::response_type to; - - auto *arr = to.mutable_principals(); - arr->Reserve(from.size()); - for (const auto &t : from) { - arr->Add(map(t)); - } - - return to; -} - -template <> ruek::api::v1::EntitiesEntity Impl::map(const db::Tuple &from) const noexcept { - ruek::api::v1::EntitiesEntity to; - to.set_id(from.rEntityId()); - to.set_type(from.rEntityType()); - - if (from.attrs()) { - google::protobuf::util::JsonStringToMessage(*from.attrs(), to.mutable_attrs()); - } - - return to; -} - -template <> ruek::api::v1::EntitiesPrincipal Impl::map(const db::Tuple &from) const noexcept { - ruek::api::v1::EntitiesPrincipal to; - to.set_id(*from.lPrincipalId()); - - if (from.attrs()) { - google::protobuf::util::JsonStringToMessage(*from.attrs(), to.mutable_attrs()); - } - - return to; -} -} // namespace entities -} // namespace svc diff --git a/src/svc/entities.h b/src/svc/entities.h deleted file mode 100644 index a118bed..0000000 --- a/src/svc/entities.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include - -#include "db/tuples.h" -#include "ruek/api/v1/entities.grpcxx.pb.h" - -namespace svc { -namespace entities { -using namespace ruek::api::v1::Entities; - -class Impl { -public: - using service_type = Service; - - template - typename T::result_type call(grpcxx::context &, const typename T::request_type &) { - return {grpcxx::status::code_t::unimplemented, std::nullopt}; - } - - google::rpc::Status exception() noexcept; - -private: - template T map(const F &) const noexcept; -}; - -template <> -rpcList::result_type Impl::call(grpcxx::context &ctx, const rpcList::request_type &req); - -template <> -rpcListPrincipals::result_type Impl::call( - grpcxx::context &ctx, const rpcListPrincipals::request_type &req); - -template <> rpcList::response_type Impl::map(const db::Tuples &from) const noexcept; -template <> rpcListPrincipals::response_type Impl::map(const db::Tuples &from) const noexcept; - -template <> ruek::api::v1::EntitiesEntity Impl::map(const db::Tuple &from) const noexcept; -template <> ruek::api::v1::EntitiesPrincipal Impl::map(const db::Tuple &from) const noexcept; -} // namespace entities -} // namespace svc diff --git a/src/svc/entities_test.cpp b/src/svc/entities_test.cpp deleted file mode 100644 index afacf05..0000000 --- a/src/svc/entities_test.cpp +++ /dev/null @@ -1,315 +0,0 @@ -#include -#include -#include - -#include "db/testing.h" - -#include "common.h" -#include "svc.h" - -using namespace ruek::api::v1::Entities; - -class svc_EntitiesTest : public testing::Test { -protected: - static void SetUpTestSuite() { - db::testing::setup(); - - // Clear data - db::pg::exec("truncate table principals cascade;"); - db::pg::exec("truncate table tuples;"); - } - - static void TearDownTestSuite() { db::testing::teardown(); } -}; - -TEST_F(svc_EntitiesTest, List) { - grpcxx::context ctx; - svc::Entities svc; - - db::Principal principal({.id = "id:svc_EntitiesTest.List"}); - ASSERT_NO_THROW(principal.store()); - - // Success: list - { - db::Tuple tuple({ - .attrs = R"({"foo":"bar"})", - .lPrincipalId = principal.id(), - .rEntityId = "List", - .rEntityType = "svc_EntitiesTest", - .spaceId = principal.spaceId(), - }); - ASSERT_NO_THROW(tuple.store()); - - rpcList::request_type request; - request.set_principal_id(principal.id()); - request.set_entity_type(tuple.rEntityType()); - - rpcList::result_type result; - EXPECT_NO_THROW(result = svc.call(ctx, request)); - EXPECT_EQ(grpcxx::status::code_t::ok, result.status.code()); - ASSERT_TRUE(result.response); - EXPECT_FALSE(result.response->has_pagination_token()); - - auto &actual = result.response->entities(); - ASSERT_EQ(1, actual.size()); - EXPECT_EQ(tuple.rEntityId(), actual[0].id()); - EXPECT_EQ(tuple.rEntityType(), actual[0].type()); - - std::string responseAttrs; - google::protobuf::util::MessageToJsonString(actual[0].attrs(), &responseAttrs); - EXPECT_EQ(tuple.attrs(), responseAttrs); - } - - // Success: list with space-id - { - db::Principal principal({ - .id = "id:svc_EntitiesTest.List-with_space_id", - .spaceId = "space_id:svc_EntitiesTest.List-with_space_id", - }); - ASSERT_NO_THROW(principal.store()); - - db::Tuple tuple({ - .lPrincipalId = principal.id(), - .rEntityId = "List-with_space_id", - .rEntityType = "svc_EntitiesTest", - .spaceId = principal.spaceId(), - }); - ASSERT_NO_THROW(tuple.store()); - - grpcxx::detail::request r(1); - r.header(std::string(svc::common::space_id_v), std::string(principal.spaceId())); - - grpcxx::context ctx(r); - - rpcList::request_type request; - request.set_principal_id(principal.id()); - request.set_entity_type(tuple.rEntityType()); - - rpcList::result_type result; - EXPECT_NO_THROW(result = svc.call(ctx, request)); - EXPECT_EQ(grpcxx::status::code_t::ok, result.status.code()); - ASSERT_TRUE(result.response); - EXPECT_FALSE(result.response->has_pagination_token()); - - auto &actual = result.response->entities(); - ASSERT_EQ(1, actual.size()); - EXPECT_EQ(tuple.rEntityId(), actual[0].id()); - EXPECT_EQ(tuple.rEntityType(), actual[0].type()); - } - - // Success: list with pagination - { - db::Tuples tuples({ - {{ - .lPrincipalId = principal.id(), - .rEntityId = "List-with_pagination[0]", - .rEntityType = "svc_EntitiesTest", - .spaceId = principal.spaceId(), - }}, - {{ - .lPrincipalId = principal.id(), - .rEntityId = "List-with_pagination[1]", - .rEntityType = "svc_EntitiesTest", - .spaceId = principal.spaceId(), - }}, - }); - - for (auto &t : tuples) { - ASSERT_NO_THROW(t.store()); - } - - rpcList::request_type request; - request.set_principal_id(principal.id()); - request.set_entity_type(tuples[0].rEntityType()); - request.set_pagination_limit(1); - - rpcList::result_type result; - - // Page 1 - { - EXPECT_NO_THROW(result = svc.call(ctx, request)); - EXPECT_EQ(grpcxx::status::code_t::ok, result.status.code()); - ASSERT_TRUE(result.response); - - EXPECT_TRUE(result.response->has_pagination_token()); - EXPECT_EQ( - "18bkoqbjegmneqbkd1fn0ob7d5n62t39dtn5mcat", result.response->pagination_token()); - - auto &actual = result.response->entities(); - ASSERT_EQ(1, actual.size()); - EXPECT_EQ(tuples[1].rEntityId(), actual[0].id()); - EXPECT_EQ(tuples[1].rEntityType(), actual[0].type()); - EXPECT_FALSE(actual[0].has_attrs()); - } - - // Use pagination token to get the next page of results - request.set_pagination_token(result.response->pagination_token()); - - // Page 2 - { - EXPECT_NO_THROW(result = svc.call(ctx, request)); - EXPECT_EQ(grpcxx::status::code_t::ok, result.status.code()); - ASSERT_TRUE(result.response); - - EXPECT_TRUE(result.response->has_pagination_token()); - EXPECT_EQ( - "18bkoqbjegmneqbkd1fn0ob7d5n62t39dtn5mc2t", result.response->pagination_token()); - - auto &actual = result.response->entities(); - ASSERT_EQ(1, actual.size()); - EXPECT_EQ(tuples[0].rEntityId(), actual[0].id()); - EXPECT_EQ(tuples[0].rEntityType(), actual[0].type()); - EXPECT_FALSE(actual[0].has_attrs()); - } - } -} - -TEST_F(svc_EntitiesTest, ListPrincipals) { - grpcxx::context ctx; - svc::Entities svc; - - // Success: list - { - db::Principal principal({.id = "id:svc_EntitiesTest.ListPrincipals"}); - ASSERT_NO_THROW(principal.store()); - - db::Tuple tuple({ - .attrs = R"({"foo":"bar"})", - .lPrincipalId = principal.id(), - .rEntityId = "ListPrincipals", - .rEntityType = "svc_EntitiesTest", - .spaceId = principal.spaceId(), - }); - ASSERT_NO_THROW(tuple.store()); - - rpcListPrincipals::request_type request; - request.set_entity_id(tuple.rEntityId()); - request.set_entity_type(tuple.rEntityType()); - - rpcListPrincipals::result_type result; - EXPECT_NO_THROW(result = svc.call(ctx, request)); - EXPECT_EQ(grpcxx::status::code_t::ok, result.status.code()); - ASSERT_TRUE(result.response); - EXPECT_FALSE(result.response->has_pagination_token()); - - auto &actual = result.response->principals(); - ASSERT_EQ(1, actual.size()); - EXPECT_EQ(principal.id(), actual[0].id()); - - std::string responseAttrs; - google::protobuf::util::MessageToJsonString(actual[0].attrs(), &responseAttrs); - EXPECT_EQ(tuple.attrs(), responseAttrs); - } - - // Success: list (space-id mismatch) - { - db::Principal principal({ - .id = "id:svc_EntitiesTest.ListPrincipals-space_id_mismatch", - .spaceId = "space_id:svc_EntitiesTest.ListPrincipals-space_id_mismatch", - }); - ASSERT_NO_THROW(principal.store()); - - db::Tuple tuple({ - .lPrincipalId = principal.id(), - .rEntityId = "ListPrincipals-space_id_mismatch", - .rEntityType = "svc_EntitiesTest", - .spaceId = principal.spaceId(), - }); - ASSERT_NO_THROW(tuple.store()); - - grpcxx::detail::request r(1); - r.header(std::string(svc::common::space_id_v), "invalid"); - - grpcxx::context ctx(r); - - rpcListPrincipals::request_type request; - request.set_entity_id(tuple.rEntityId()); - request.set_entity_type(tuple.rEntityType()); - - rpcListPrincipals::result_type result; - EXPECT_NO_THROW(result = svc.call(ctx, request)); - EXPECT_EQ(grpcxx::status::code_t::ok, result.status.code()); - ASSERT_TRUE(result.response); - EXPECT_FALSE(result.response->has_pagination_token()); - - auto &actual = result.response->principals(); - ASSERT_EQ(0, actual.size()); - } - - // Success: list with pagination - { - db::Principals principals({ - {{.id = "id:svc_EntitiesTest.ListPrincipals-with_pagination[0]"}}, - {{.id = "id:svc_EntitiesTest.ListPrincipals-with_pagination[1]"}}, - }); - - for (auto &p : principals) { - ASSERT_NO_THROW(p.store()); - } - - db::Tuples tuples({ - {{ - .lPrincipalId = principals[0].id(), - .rEntityId = "ListPrincipals-with_pagination", - .rEntityType = "svc_EntitiesTest", - .spaceId = principals[0].spaceId(), - }}, - {{ - .lPrincipalId = principals[1].id(), - .rEntityId = "ListPrincipals-with_pagination", - .rEntityType = "svc_EntitiesTest", - .spaceId = principals[1].spaceId(), - }}, - }); - - for (auto &r : tuples) { - ASSERT_NO_THROW(r.store()); - } - - rpcListPrincipals::request_type request; - request.set_entity_id(tuples[0].rEntityId()); - request.set_entity_type(tuples[0].rEntityType()); - request.set_pagination_limit(1); - - rpcListPrincipals::result_type result; - - // Page 1 - { - EXPECT_NO_THROW(result = svc.call(ctx, request)); - EXPECT_EQ(grpcxx::status::code_t::ok, result.status.code()); - ASSERT_TRUE(result.response); - - EXPECT_TRUE(result.response->has_pagination_token()); - EXPECT_EQ( - "18qmip1qedr66nq5dpq6it39clpl8pbjegn4oqbjeh874qbecdkn0obcecmneqbkd1fn0ob7d5n62t39dt" - "n5mcat", - result.response->pagination_token()); - - auto &actual = result.response->principals(); - ASSERT_EQ(1, actual.size()); - EXPECT_EQ(principals[1].id(), actual[0].id()); - EXPECT_FALSE(actual[0].has_attrs()); - } - - // Use pagination token to get the next page of results - request.set_pagination_token(result.response->pagination_token()); - - // Page 2 - { - EXPECT_NO_THROW(result = svc.call(ctx, request)); - EXPECT_EQ(grpcxx::status::code_t::ok, result.status.code()); - ASSERT_TRUE(result.response); - - EXPECT_TRUE(result.response->has_pagination_token()); - EXPECT_EQ( - "18qmip1qedr66nq5dpq6it39clpl8pbjegn4oqbjeh874qbecdkn0obcecmneqbkd1fn0ob7d5n62t39dt" - "n5mc2t", - result.response->pagination_token()); - - auto &actual = result.response->principals(); - ASSERT_EQ(1, actual.size()); - EXPECT_EQ(principals[0].id(), actual[0].id()); - EXPECT_FALSE(actual[0].has_attrs()); - } - } -} diff --git a/src/svc/svc.h b/src/svc/svc.h index 5bf7b7b..b39e0f2 100644 --- a/src/svc/svc.h +++ b/src/svc/svc.h @@ -1,14 +1,10 @@ #pragma once -#include "authz.h" -#include "entities.h" #include "principals.h" #include "relations.h" #include "wrapper.h" namespace svc { -using Authz = Wrapper; -using Entities = Wrapper; using Principals = Wrapper; using Relations = Wrapper; } // namespace svc