| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171 |
- #include "access.hpp"
- #include "common/time_utils.hpp"
- #include "common/uuid.hpp"
- #include "logging/logger.hpp"
- namespace smartbotic::webserver::auth {
- using namespace common;
- namespace {
- constexpr const char* PROJECTS = "projects";
- }
- InstanceRole instanceRoleFromString(const std::string& role) {
- if (role == "owner") return InstanceRole::Owner;
- if (role == "admin") return InstanceRole::Admin;
- // "user" is what the role was called before there were three of them, and
- // stored accounts still say it.
- return InstanceRole::Member;
- }
- std::string instanceRoleToString(InstanceRole role) {
- switch (role) {
- case InstanceRole::Owner: return "owner";
- case InstanceRole::Admin: return "admin";
- case InstanceRole::Member: return "member";
- }
- return "member";
- }
- ProjectRole projectRoleFromString(const std::string& role) {
- if (role == "admin") return ProjectRole::Admin;
- if (role == "editor") return ProjectRole::Editor;
- if (role == "viewer") return ProjectRole::Viewer;
- return ProjectRole::None;
- }
- std::string projectRoleToString(ProjectRole role) {
- switch (role) {
- case ProjectRole::Admin: return "admin";
- case ProjectRole::Editor: return "editor";
- case ProjectRole::Viewer: return "viewer";
- case ProjectRole::None: return "none";
- }
- return "none";
- }
- AccessControl::AccessControl(storage::StorageClient& storage) : storage_(storage) {}
- std::string AccessControl::projectOf(const nlohmann::json& record) {
- return record.value("projectId", std::string());
- }
- std::unordered_set<std::string> AccessControl::projectsFor(const AuthContext& ctx) {
- std::unordered_set<std::string> out;
- storage::QueryOptions options;
- options.page_size = 1000;
- auto result = storage_.query(PROJECTS, options);
- if (result.failed()) {
- LOG_WARN("Could not read projects for access check: {}", result.error().message());
- return out;
- }
- const bool sees_everything = instanceRoleFromString(ctx.role) != InstanceRole::Member;
- for (const auto& project : result.value().documents) {
- const std::string id = project.value("_id", "");
- if (id.empty()) continue;
- if (sees_everything) {
- out.insert(id);
- continue;
- }
- if (project.value("ownerId", "") == ctx.user_id) {
- out.insert(id);
- continue;
- }
- for (const auto& member : project.value("members", nlohmann::json::array())) {
- if (member.value("userId", "") == ctx.user_id) {
- out.insert(id);
- break;
- }
- }
- }
- return out;
- }
- ProjectRole AccessControl::roleIn(const AuthContext& ctx, const std::string& project_id) {
- if (instanceRoleFromString(ctx.role) != InstanceRole::Member) {
- return ProjectRole::Admin;
- }
- if (project_id.empty()) return ProjectRole::None;
- auto project = storage_.get(PROJECTS, project_id);
- if (project.failed()) return ProjectRole::None;
- // A personal project belongs entirely to its owner - there is no
- // membership row to look up and none can be added.
- if (project.value().value("ownerId", "") == ctx.user_id) return ProjectRole::Admin;
- for (const auto& member : project.value().value("members", nlohmann::json::array())) {
- if (member.value("userId", "") == ctx.user_id) {
- return projectRoleFromString(member.value("role", "viewer"));
- }
- }
- return ProjectRole::None;
- }
- bool AccessControl::allowed(const AuthContext& ctx, const std::string& project_id, Action action) {
- const ProjectRole role = roleIn(ctx, project_id);
- switch (action) {
- case Action::Read:
- return role != ProjectRole::None;
- case Action::Run:
- // A viewer may watch but not start. Running a workflow sends email,
- // writes to collections and spends money at an API - it is not a
- // read however little it changes the workflow itself.
- return role == ProjectRole::Editor || role == ProjectRole::Admin;
- case Action::Write:
- return role == ProjectRole::Editor || role == ProjectRole::Admin;
- case Action::Manage:
- return role == ProjectRole::Admin;
- }
- return false;
- }
- common::Result<std::string> AccessControl::personalProjectFor(const std::string& user_id) {
- storage::QueryOptions options;
- options.page_size = 1000;
- options.filters.push_back({"type", "personal"});
- auto result = storage_.query(PROJECTS, options);
- if (result.failed()) return Error(ErrorCode::DatabaseError, result.error().message());
- for (const auto& project : result.value().documents) {
- if (project.value("ownerId", "") == user_id) {
- return project.value("_id", std::string());
- }
- }
- return Error(ErrorCode::NotFound, "No personal project for user " + user_id);
- }
- common::Result<nlohmann::json> AccessControl::ensurePersonalProject(const std::string& user_id,
- const std::string& username) {
- auto existing = personalProjectFor(user_id);
- if (existing.ok()) {
- auto project = storage_.get(PROJECTS, existing.value());
- if (project.ok()) return project.value();
- }
- const std::string id = "prj_" + UUID::generate();
- nlohmann::json project = {
- {"name", username.empty() ? std::string("Personal") : username + "'s project"},
- {"type", "personal"},
- {"ownerId", user_id},
- {"members", nlohmann::json::array()},
- {"createdAt", TimeUtils::nowMs()},
- };
- auto inserted = storage_.insert(PROJECTS, project, id);
- if (inserted.failed()) {
- return Error(ErrorCode::DatabaseError, inserted.error().message());
- }
- project["_id"] = id;
- LOG_INFO("Created personal project {} for user {}", id, user_id);
- return project;
- }
- } // namespace smartbotic::webserver::auth
|