Rewrite and fix CynaraAdmin::SetPolicies
[platform/core/security/security-manager.git] / src / common / cynara.cpp
index 4d4ec75..d1bbef0 100644 (file)
@@ -22,6 +22,7 @@
  */
 
 #include <cstring>
+#include <unordered_set>
 #include "cynara.h"
 
 #include <dpl/log/log.h>
@@ -113,7 +114,7 @@ CynaraAdmin::BucketsMap CynaraAdmin::Buckets =
 
 
 CynaraAdminPolicy::CynaraAdminPolicy(const std::string &client, const std::string &user,
-        const std::string &privilege, Operation operation,
+        const std::string &privilege, int operation,
         const std::string &bucket)
 {
     this->client = strdup(client.c_str());
@@ -131,7 +132,7 @@ CynaraAdminPolicy::CynaraAdminPolicy(const std::string &client, const std::strin
                 std::string("Error in CynaraAdminPolicy allocation."));
     }
 
-    this->result = static_cast<int>(operation);
+    this->result = operation;
     this->result_extra = nullptr;
 }
 
@@ -175,6 +176,26 @@ CynaraAdminPolicy::CynaraAdminPolicy(CynaraAdminPolicy &&that)
     that.result_extra = nullptr;
 }
 
+CynaraAdminPolicy& CynaraAdminPolicy::operator=(CynaraAdminPolicy &&that)
+{
+    if (this != &that) {
+        bucket = that.bucket;
+        client = that.client;
+        user = that.user;
+        privilege = that.privilege;
+        result_extra = that.result_extra;
+        result = that.result;
+
+        that.bucket = nullptr;
+        that.client = nullptr;
+        that.user = nullptr;
+        that.privilege = nullptr;
+        that.result_extra = nullptr;
+    };
+
+    return *this;
+}
+
 CynaraAdminPolicy::~CynaraAdminPolicy()
 {
     free(this->bucket);
@@ -192,12 +213,20 @@ static bool checkCynaraError(int result, const std::string &msg)
             return true;
         case CYNARA_API_ACCESS_DENIED:
             return false;
+        case CYNARA_API_MAX_PENDING_REQUESTS:
+            ThrowMsg(CynaraException::MaxPendingRequests, msg);
         case CYNARA_API_OUT_OF_MEMORY:
             ThrowMsg(CynaraException::OutOfMemory, msg);
         case CYNARA_API_INVALID_PARAM:
             ThrowMsg(CynaraException::InvalidParam, msg);
         case CYNARA_API_SERVICE_NOT_AVAILABLE:
             ThrowMsg(CynaraException::ServiceNotAvailable, msg);
+        case CYNARA_API_METHOD_NOT_SUPPORTED:
+            ThrowMsg(CynaraException::MethodNotSupported, msg);
+        case CYNARA_API_OPERATION_NOT_ALLOWED:
+            ThrowMsg(CynaraException::OperationNotAllowed, msg);
+        case CYNARA_API_OPERATION_FAILED:
+            ThrowMsg(CynaraException::OperationFailed, msg);
         case CYNARA_API_BUCKET_NOT_FOUND:
             ThrowMsg(CynaraException::BucketNotFound, msg);
         default:
@@ -205,7 +234,11 @@ static bool checkCynaraError(int result, const std::string &msg)
     }
 }
 
+CynaraAdmin::TypeToDescriptionMap CynaraAdmin::TypeToDescription;
+CynaraAdmin::DescriptionToTypeMap CynaraAdmin::DescriptionToType;
+
 CynaraAdmin::CynaraAdmin()
+    : m_policyDescriptionsInitialized(false)
 {
     checkCynaraError(
         cynara_admin_initialize(&m_CynaraAdmin),
@@ -225,6 +258,11 @@ CynaraAdmin &CynaraAdmin::getInstance()
 
 void CynaraAdmin::SetPolicies(const std::vector<CynaraAdminPolicy> &policies)
 {
+    if (policies.empty()) {
+        LogDebug("no policies to set in Cynara.");
+        return;
+    }
+
     std::vector<const struct cynara_admin_policy *> pp_policies(policies.size() + 1);
 
     LogDebug("Sending " << policies.size() << " policies to Cynara");
@@ -249,54 +287,36 @@ void CynaraAdmin::SetPolicies(const std::vector<CynaraAdminPolicy> &policies)
 void CynaraAdmin::UpdateAppPolicy(
     const std::string &label,
     const std::string &user,
-    const std::vector<std::string> &oldPrivileges,
-    const std::vector<std::string> &newPrivileges)
+    const std::vector<std::string> &privileges)
 {
-    std::vector<CynaraAdminPolicy> policies;
-
-    // Perform sort-merge join on oldPrivileges and newPrivileges.
-    // Assume that they are already sorted and without duplicates.
-    auto oldIter = oldPrivileges.begin();
-    auto newIter = newPrivileges.begin();
+    std::unordered_set<std::string> privilegesSet(privileges.begin(), privileges.end());
 
-    while (oldIter != oldPrivileges.end() && newIter != newPrivileges.end()) {
-        int compare = oldIter->compare(*newIter);
-        if (compare == 0) {
-            LogDebug("(user = " << user << " label = " << label << ") " <<
-                "keeping privilege " << *newIter);
-            ++oldIter;
-            ++newIter;
-            continue;
-        } else if (compare < 0) {
+    std::vector<CynaraAdminPolicy> policies;
+    CynaraAdmin::getInstance().ListPolicies(
+        CynaraAdmin::Buckets.at(Bucket::MANIFESTS),
+        label, user, CYNARA_ADMIN_ANY, policies);
+
+    // Compare previous policies with set of new requested privileges
+    for (auto &policy : policies) {
+        if (privilegesSet.erase(policy.privilege)) {
+            // privilege was found and removed from the set, keeping policy
             LogDebug("(user = " << user << " label = " << label << ") " <<
-                "removing privilege " << *oldIter);
-            policies.push_back(CynaraAdminPolicy(label, user, *oldIter,
-                    CynaraAdminPolicy::Operation::Delete,
-                    Buckets.at(Bucket::MANIFESTS)));
-            ++oldIter;
+                "keeping privilege " << policy.privilege);
         } else {
+            // privilege was not found in the set, deleting policy
+            policy.result = static_cast<int>(CynaraAdminPolicy::Operation::Delete);
             LogDebug("(user = " << user << " label = " << label << ") " <<
-                "adding privilege " << *newIter);
-            policies.push_back(CynaraAdminPolicy(label, user, *newIter,
-                    CynaraAdminPolicy::Operation::Allow,
-                    Buckets.at(Bucket::MANIFESTS)));
-            ++newIter;
+                "removing privilege " << policy.privilege);
         }
     }
 
-    for (; oldIter != oldPrivileges.end(); ++oldIter) {
+    // Add policies for privileges that weren't previously enabled
+    // Those that were previously enabled are now removed from privilegesSet
+    for (const auto &privilege : privilegesSet) {
         LogDebug("(user = " << user << " label = " << label << ") " <<
-            "removing privilege " << *oldIter);
-        policies.push_back(CynaraAdminPolicy(label, user, *oldIter,
-                    CynaraAdminPolicy::Operation::Delete,
-                    Buckets.at(Bucket::MANIFESTS)));
-    }
-
-    for (; newIter != newPrivileges.end(); ++newIter) {
-        LogDebug("(user = " << user << " label = " << label << ") " <<
-            "adding privilege " << *newIter);
-        policies.push_back(CynaraAdminPolicy(label, user, *newIter,
-                    CynaraAdminPolicy::Operation::Allow,
+            "adding privilege " << privilege);
+        policies.push_back(CynaraAdminPolicy(label, user, privilege,
+                    static_cast<int>(CynaraAdminPolicy::Operation::Allow),
                     Buckets.at(Bucket::MANIFESTS)));
     }
 
@@ -337,6 +357,39 @@ void CynaraAdmin::UserInit(uid_t uid, security_manager_user_type userType)
     CynaraAdmin::getInstance().SetPolicies(policies);
 }
 
+void CynaraAdmin::ListUsers(std::vector<uid_t> &listOfUsers)
+{
+    std::vector<CynaraAdminPolicy> tmpListOfUsers;
+    CynaraAdmin::getInstance().ListPolicies(
+        CynaraAdmin::Buckets.at(Bucket::MAIN),
+        CYNARA_ADMIN_WILDCARD,
+        CYNARA_ADMIN_ANY,
+        CYNARA_ADMIN_WILDCARD,
+        tmpListOfUsers);
+
+    for (const auto &tmpUser : tmpListOfUsers) {
+        std::string user = tmpUser.user;
+        if (!user.compare(CYNARA_ADMIN_WILDCARD))
+            continue;
+        try {
+            listOfUsers.push_back(std::stoul(user));
+        } catch (std::invalid_argument &e) {
+            LogError("Invalid UID: " << e.what());
+            continue;
+        };
+    };
+    LogDebug("Found users: " << listOfUsers.size());
+};
+
+void CynaraAdmin::UserRemove(uid_t uid)
+{
+    std::vector<CynaraAdminPolicy> policies;
+    std::string user = std::to_string(static_cast<unsigned int>(uid));
+
+    EmptyBucket(Buckets.at(Bucket::PRIVACY_MANAGER),true,
+            CYNARA_ADMIN_ANY, user, CYNARA_ADMIN_ANY);
+}
+
 void CynaraAdmin::ListPolicies(
     const std::string &bucketName,
     const std::string &appId,
@@ -371,16 +424,142 @@ void CynaraAdmin::EmptyBucket(const std::string &bucketName, bool recursive, con
             client + ", " + user + ", " + privilege);
 }
 
+void CynaraAdmin::FetchCynaraPolicyDescriptions(bool forceRefresh)
+{
+    struct cynara_admin_policy_descr **descriptions = nullptr;
+
+    if (!forceRefresh && m_policyDescriptionsInitialized)
+        return;
+
+    // fetch
+    checkCynaraError(
+        cynara_admin_list_policies_descriptions(m_CynaraAdmin, &descriptions),
+        "Error while getting list of policies descriptions from Cynara.");
+
+    if (descriptions[0] == nullptr) {
+        LogError("Fetching policies levels descriptions from Cynara returned empty list. "
+                "There should be at least 2 entries - Allow and Deny");
+        return;
+    }
+
+    // reset the state
+    m_policyDescriptionsInitialized = false;
+    DescriptionToType.clear();
+    TypeToDescription.clear();
+
+    // extract strings
+    for (int i = 0; descriptions[i] != nullptr; i++) {
+        std::string descriptionName(descriptions[i]->name);
+
+        DescriptionToType[descriptionName] = descriptions[i]->result;
+        TypeToDescription[descriptions[i]->result] = std::move(descriptionName);
+
+        free(descriptions[i]->name);
+        free(descriptions[i]);
+    }
+
+    free(descriptions);
+
+    m_policyDescriptionsInitialized = true;
+}
+
+void CynaraAdmin::ListPoliciesDescriptions(std::vector<std::string> &policiesDescriptions)
+{
+    FetchCynaraPolicyDescriptions(false);
+
+    for (const auto &it : TypeToDescription)
+        policiesDescriptions.push_back(it.second);
+}
+
+std::string CynaraAdmin::convertToPolicyDescription(const int policyType, bool forceRefresh)
+{
+    FetchCynaraPolicyDescriptions(forceRefresh);
+
+    return TypeToDescription.at(policyType);
+}
+
+int CynaraAdmin::convertToPolicyType(const std::string &policy, bool forceRefresh)
+{
+    FetchCynaraPolicyDescriptions(forceRefresh);
+
+    return DescriptionToType.at(policy);
+}
+void CynaraAdmin::Check(const std::string &label, const std::string &user, const std::string &privilege,
+    const std::string &bucket, int &result, std::string &resultExtra, const bool recursive)
+{
+    char *resultExtraCstr = nullptr;
+
+    checkCynaraError(
+        cynara_admin_check(m_CynaraAdmin, bucket.c_str(), recursive, label.c_str(),
+            user.c_str(), privilege.c_str(), &result, &resultExtraCstr),
+        "Error while asking cynara admin API for permission for app label: " + label + ", user: "
+            + user + " privilege: " + privilege + " bucket: " + bucket);
+
+    if (resultExtraCstr == nullptr)
+        resultExtra = "";
+    else {
+        resultExtra = std::string(resultExtraCstr);
+        free(resultExtraCstr);
+    }
+}
+
+int CynaraAdmin::GetPrivilegeManagerCurrLevel(const std::string &label, const std::string &user,
+        const std::string &privilege)
+{
+    int result;
+    std::string resultExtra;
+
+    Check(label, user, privilege, Buckets.at(Bucket::PRIVACY_MANAGER), result, resultExtra, true);
+
+    return result;
+}
+
+int CynaraAdmin::GetPrivilegeManagerMaxLevel(const std::string &label, const std::string &user,
+        const std::string &privilege)
+{
+    int result;
+    std::string resultExtra;
+
+    Check(label, user, privilege, Buckets.at(Bucket::MAIN), result, resultExtra, true);
+
+    return result;
+}
+
 Cynara::Cynara()
 {
+    int ret;
+
+    ret = eventfd(0, 0);
+    if (ret == -1) {
+        LogError("Error while creating eventfd: " << strerror(errno));
+        ThrowMsg(CynaraException::UnknownError, "Error while creating eventfd");
+    }
+
+    // Poll the eventfd for reading
+    pollFds[0].fd = ret;
+    pollFds[0].events = POLLIN;
+
+    // Temporary, will be replaced by cynara fd when available
+    pollFds[1].fd = pollFds[0].fd;
+    pollFds[1].events = 0;
+
     checkCynaraError(
-        cynara_initialize(&m_Cynara, nullptr),
+        cynara_async_initialize(&cynara, nullptr, &Cynara::statusCallback, &(pollFds[1])),
         "Cannot connect to Cynara policy interface.");
+
+    thread = std::thread(&Cynara::run, this);
 }
 
 Cynara::~Cynara()
 {
-    cynara_finish(m_Cynara);
+    LogDebug("Sending terminate event to Cynara thread");
+    terminate.store(true);
+    threadNotifyPut();
+    thread.join();
+
+    // Critical section
+    std::lock_guard<std::mutex> guard(mutex);
+    cynara_async_finish(cynara);
 }
 
 Cynara &Cynara::getInstance()
@@ -389,13 +568,150 @@ Cynara &Cynara::getInstance()
     return cynara;
 }
 
+void Cynara::threadNotifyPut()
+{
+    int ret = eventfd_write(pollFds[0].fd, 1);
+    if (ret == -1)
+        LogError("Unexpected error while writing to eventfd: " << strerror(errno));
+}
+
+void Cynara::threadNotifyGet()
+{
+    eventfd_t value;
+    int ret = eventfd_read(pollFds[0].fd, &value);
+    if (ret == -1)
+        LogError("Unexpected error while reading from eventfd: " << strerror(errno));
+}
+
+void Cynara::statusCallback(int oldFd, int newFd, cynara_async_status status,
+    void *ptr)
+{
+    auto cynaraFd = static_cast<struct pollfd *>(ptr);
+
+    LogDebug("Cynara status callback. " <<
+        "Status = " << status << ", oldFd = " << oldFd << ", newFd = " << newFd);
+
+    if (newFd == -1) {
+        cynaraFd->events = 0;
+    } else {
+        cynaraFd->fd = newFd;
+
+        switch (status) {
+        case CYNARA_STATUS_FOR_READ:
+            cynaraFd->events = POLLIN;
+            break;
+
+        case CYNARA_STATUS_FOR_RW:
+            cynaraFd->events = POLLIN | POLLOUT;
+            break;
+        }
+    }
+}
+
+void Cynara::responseCallback(cynara_check_id checkId,
+    cynara_async_call_cause cause, int response, void *ptr)
+{
+    LogDebug("Response for received for Cynara check id: " << checkId);
+
+    auto promise = static_cast<std::promise<bool>*>(ptr);
+
+    switch (cause) {
+    case CYNARA_CALL_CAUSE_ANSWER:
+        LogDebug("Cynara cause: ANSWER: " << response);
+        promise->set_value(response);
+        break;
+
+    case CYNARA_CALL_CAUSE_CANCEL:
+        LogDebug("Cynara cause: CANCEL");
+        promise->set_value(CYNARA_API_ACCESS_DENIED);
+        break;
+
+    case CYNARA_CALL_CAUSE_FINISH:
+        LogDebug("Cynara cause: FINISH");
+        promise->set_value(CYNARA_API_ACCESS_DENIED);
+        break;
+
+    case CYNARA_CALL_CAUSE_SERVICE_NOT_AVAILABLE:
+        LogError("Cynara cause: SERVICE_NOT_AVAILABLE");
+
+        try {
+            ThrowMsg(CynaraException::ServiceNotAvailable,
+                "Cynara service not available");
+        } catch (...) {
+            promise->set_exception(std::current_exception());
+        }
+        break;
+    }
+}
+
+void Cynara::run()
+{
+    LogInfo("Cynara thread started");
+    while (true) {
+        int ret = poll(pollFds, 2, -1);
+        if (ret == -1) {
+            if (errno != EINTR)
+                LogError("Unexpected error returned by poll: " << strerror(errno));
+            continue;
+        }
+
+        // Check eventfd for termination signal
+        if (pollFds[0].revents) {
+            threadNotifyGet();
+            if (terminate.load()) {
+                LogInfo("Cynara thread terminated");
+                return;
+            }
+        }
+
+        // Check if Cynara fd is ready for processing
+        try {
+            if (pollFds[1].revents) {
+                // Critical section
+                std::lock_guard<std::mutex> guard(mutex);
+
+                checkCynaraError(cynara_async_process(cynara),
+                    "Unexpected error returned by cynara_async_process");
+            }
+        } catch (const CynaraException::Base &e) {
+            LogError("Error while processing Cynara events: " << e.DumpToString());
+        }
+    }
+}
+
 bool Cynara::check(const std::string &label, const std::string &privilege,
         const std::string &user, const std::string &session)
 {
-    return checkCynaraError(
-        cynara_check(m_Cynara,
-            label.c_str(), session.c_str(), user.c_str(), privilege.c_str()),
-        "Cannot check permission with Cynara.");
+    LogDebug("check: client = " << label << ", user = " << user <<
+        ", privilege = " << privilege << ", session = " << session);
+
+    std::promise<bool> promise;
+    auto future = promise.get_future();
+
+    // Critical section
+    {
+        std::lock_guard<std::mutex> guard(mutex);
+
+        int ret = cynara_async_check_cache(cynara,
+            label.c_str(), session.c_str(), user.c_str(), privilege.c_str());
+
+        if (ret != CYNARA_API_CACHE_MISS)
+            return checkCynaraError(ret, "Error while checking Cynara cache");
+
+        LogDebug("Cynara cache miss");
+
+        cynara_check_id check_id;
+        checkCynaraError(
+            cynara_async_create_request(cynara,
+                label.c_str(), session.c_str(), user.c_str(), privilege.c_str(),
+                &check_id, &Cynara::responseCallback, &promise),
+            "Cannot check permission with Cynara.");
+
+        threadNotifyPut();
+        LogDebug("Waiting for response to Cynara query id " << check_id);
+    }
+
+    return future.get();
 }
 
 } // namespace SecurityManager