Add notification daemon 53/72853/8
authorOskar Świtalski <o.switalski@samsung.com>
Wed, 1 Jun 2016 14:45:36 +0000 (16:45 +0200)
committerOskar Świtalski <o.switalski@samsung.com>
Fri, 10 Jun 2016 15:48:33 +0000 (17:48 +0200)
Change-Id: Icf5dc30074d5c538144da39e0d2b1c9c99bbdef3

19 files changed:
CMakeLists.txt
packaging/askuser-notification.manifest [new file with mode: 0644]
packaging/askuser.spec
src/agent/CMakeLists.txt
src/agent/main/NotificationTalker.cpp [new file with mode: 0644]
src/agent/main/NotificationTalker.h [new file with mode: 0644]
src/agent/notification-daemon/AskUserTalker.cpp [new file with mode: 0644]
src/agent/notification-daemon/AskUserTalker.h [new file with mode: 0644]
src/agent/notification-daemon/CMakeLists.txt [new file with mode: 0644]
src/agent/notification-daemon/GuiRunner.cpp [new file with mode: 0644]
src/agent/notification-daemon/GuiRunner.h [new file with mode: 0644]
src/agent/notification-daemon/main.cpp [new file with mode: 0644]
src/agent/notification-daemon/po/CMakeLists.txt [moved from src/agent/po/CMakeLists.txt with 95% similarity]
src/agent/notification-daemon/po/en.po [new file with mode: 0644]
src/agent/notification-daemon/po/pl.po [new file with mode: 0644]
src/agent/po/en.po [deleted file]
src/agent/po/pl.po [deleted file]
systemd/CMakeLists.txt
systemd/askuser-notification.service [new file with mode: 0644]

index 251d5d8..6b21eba 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2014-2015 Samsung Electronics Co., Ltd All Rights Reserved
+# Copyright (c) 2014-2016 Samsung Electronics Co., Ltd All Rights Reserved
 #
 #    Licensed under the Apache License, Version 2.0 (the "License");
 #    you may not use this file except in compliance with the License.
@@ -14,6 +14,7 @@
 #
 # @file        CMakeLists.txt
 # @author      Adam Malinowski <a.malinowsk2@partner.samsung.com>
+# @author      Oskar Switalski <o.switalski@samsung.com>
 #
 
 ############################# Check minimum CMake version #####################
@@ -72,6 +73,7 @@ SET(TARGET_ASKUSER_COMMON "askuser-common")
 SET(TARGET_PLUGIN_SERVICE "askuser-plugin-service")
 SET(TARGET_PLUGIN_CLIENT "askuser-plugin-client")
 SET(TARGET_CLIENT "askuser-test-client")
+SET(TARGET_ASKUSER_NOTIFICATION "askuser-notification")
 
 ADD_SUBDIRECTORY(src)
 ADD_SUBDIRECTORY(systemd)
diff --git a/packaging/askuser-notification.manifest b/packaging/askuser-notification.manifest
new file mode 100644 (file)
index 0000000..c00c25b
--- /dev/null
@@ -0,0 +1,5 @@
+<manifest>
+        <request>
+                <domain name="_" />
+        </request>
+</manifest>
index 8a0bdef..d62c65d 100644 (file)
@@ -9,13 +9,17 @@ Source1001:    %{name}.manifest
 Source1002:    libaskuser-common.manifest
 Source1003:    askuser-plugins.manifest
 Source1004:    askuser-test.manifest
+Source1005:    askuser-notification.manifest
 BuildRequires: cmake
 BuildRequires: libwayland-egl
 BuildRequires: gettext-tools
 BuildRequires: pkgconfig(cynara-agent)
+BuildRequires: pkgconfig(cynara-creds-socket)
 BuildRequires: pkgconfig(cynara-plugin)
+BuildRequires: pkgconfig(elementary)
 BuildRequires: pkgconfig(libsystemd-daemon)
 BuildRequires: pkgconfig(libsystemd-journal)
+BuildRequires: pkgconfig(security-manager)
 BuildRequires: pkgconfig(security-privilege-manager)
 BuildRequires: coregl
 %{?systemd_requires}
@@ -33,6 +37,12 @@ Summary:    Askuser common library
 %description -n libaskuser-common
 Askuser common library with common functionalities
 
+%package -n askuser-notification
+Summary: User daemon which shows popup with privilege request
+
+%description -n askuser-notification
+User daemon which shows popup with privilege request
+
 %package -n askuser-plugins
 Requires:   cynara
 Requires:   libcynara-client
@@ -54,6 +64,7 @@ cp -a %{SOURCE1001} .
 cp -a %{SOURCE1002} .
 cp -a %{SOURCE1003} .
 cp -a %{SOURCE1004} .
+cp -a %{SOURCE1005} .
 
 %build
 %if 0%{?sec_build_binary_debug_enable}
@@ -77,6 +88,10 @@ rm -rf %{buildroot}
 %find_lang %{name}
 
 %post
+# todo properly use systemd --user
+ln -s /lib/systemd/user/askuser-notification.service \
+/usr/lib/systemd/user/default.target.wants/askuser-notification.service 2> /dev/null
+
 systemctl daemon-reload
 
 if [ $1 = 1 ]; then
@@ -108,6 +123,14 @@ systemctl restart cynara.service
 %attr(755, root, root) /usr/bin/askuser
 /usr/lib/systemd/system/askuser.service
 
+%files -n askuser-notification
+%manifest askuser-notification.manifest
+%license LICENSE
+%attr(755,root,root) /usr/bin/askuser-notification
+/usr/lib/systemd/user/askuser-notification.service
+/usr/share/locale/en/LC_MESSAGES/askuser.mo
+/usr/share/locale/pl/LC_MESSAGES/askuser.mo
+
 %files -n libaskuser-common
 %manifest libaskuser-common.manifest
 %license LICENSE
index 6d39ceb..fa184c5 100644 (file)
@@ -20,6 +20,7 @@ PKG_CHECK_MODULES(AGENT_DEP
     REQUIRED
     cynara-agent
     cynara-plugin
+    cynara-creds-socket
     libsystemd-daemon
     security-privilege-manager
     )
@@ -30,6 +31,7 @@ SET(ASKUSER_SOURCES
     ${ASKUSER_AGENT_PATH}/main/Agent.cpp
     ${ASKUSER_AGENT_PATH}/main/CynaraTalker.cpp
     ${ASKUSER_AGENT_PATH}/main/main.cpp
+    ${ASKUSER_AGENT_PATH}/main/NotificationTalker.cpp
     )
 
 INCLUDE_DIRECTORIES(
@@ -48,4 +50,4 @@ TARGET_LINK_LIBRARIES(${TARGET_ASKUSER}
 
 INSTALL(TARGETS ${TARGET_ASKUSER} DESTINATION ${BIN_INSTALL_DIR})
 
-ADD_SUBDIRECTORY(po)
+ADD_SUBDIRECTORY(notification-daemon)
diff --git a/src/agent/main/NotificationTalker.cpp b/src/agent/main/NotificationTalker.cpp
new file mode 100644 (file)
index 0000000..57d1f33
--- /dev/null
@@ -0,0 +1,309 @@
+/*
+ *  Copyright (c) 2016 Samsung Electronics Co.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+/**
+ * @file        src/daemon/NotificationTalker.cpp
+ * @author      Oskar Świtalski <o.switalski@samsung.com>
+ * @brief       Definition of NotificationTalker class
+ */
+
+#include "NotificationTalker.h"
+
+#include <algorithm>
+#include <cstring>
+#include <cynara-creds-socket.h>
+
+#include <exception/ErrnoException.h>
+#include <exception/CynaraException.h>
+#include <log/alog.h>
+#include <socket/Socket.h>
+#include <translator/Translator.h>
+#include <config/Path.h>
+#include <types/Protocol.h>
+
+namespace AskUser {
+
+namespace Agent {
+
+NotificationTalker::NotificationTalker()
+{
+    m_stopflag = false;
+    m_select.setTimeout(100);
+    m_sockfd = Socket::listen(Path::getSocketPath().c_str());
+}
+
+void NotificationTalker::parseRequest(RequestType type, NotificationRequest request)
+{
+    switch (type) {
+    case RequestType::RT_Close:
+        ALOGD("Close service");
+        stop();
+        return;
+    case RequestType::RT_Action:
+        ALOGD("Add request: " << request.id);
+        addRequest(std::move(request));
+        return;
+    case RequestType::RT_Cancel:
+        ALOGD("Cancel request: " << request.id);
+        removeRequest(request.id);
+        return;
+    default:
+        return;
+    }
+}
+
+void NotificationTalker::addRequest(NotificationRequest &&request)
+{
+    std::lock_guard<std::mutex> lock(m_mutex);
+
+    auto &queue = m_requests[request.data.user];
+    auto it = std::find_if(queue.begin(), queue.end(),
+            [&request](const NotificationRequest &req){return req.id == request.id;}
+        );
+
+    if (it == queue.end()) {
+        queue.emplace_back(std::move(request));
+    } else {
+        ALOGD("Cynara request already exists");
+    }
+}
+
+void NotificationTalker::removeRequest(RequestId id)
+{
+    std::lock_guard<std::mutex> lock(m_mutex);
+
+    for (auto &pair : m_requests) {
+        auto &queue = std::get<1>(pair);
+        auto it = std::find_if(queue.begin(), queue.end(),
+                [&id](const NotificationRequest &req){return req.id == id;}
+            );
+        if (it == queue.end()) {
+            ALOGW("Removing non-existent request");
+            return;
+        }
+        if (it == queue.begin()) {
+            auto user = std::get<0>(pair);
+            auto it2 = m_userToFd.find(user);
+            if (it2 != m_userToFd.end())
+                sendDismiss(std::get<1>(*it2));
+        }
+
+        queue.erase(it);
+    }
+}
+
+void NotificationTalker::setResponseHandler(ResponseHandler responseHandler)
+{
+    m_responseHandler = responseHandler;
+}
+
+void NotificationTalker::stop()
+{
+    m_stopflag = true;
+
+    for (auto& pair : m_fdStatus) {
+        int fd = std::get<0>(pair);
+        Socket::close(fd);
+    }
+
+    m_fdStatus.clear();
+    m_fdToUser.clear();
+    m_userToFd.clear();
+
+    Socket::close(m_sockfd);
+    m_sockfd = 0;
+}
+
+NotificationTalker::~NotificationTalker()
+{
+    for (auto& pair : m_fdStatus) {
+        int fd = std::get<0>(pair);
+        Socket::close(fd);
+    }
+
+    Socket::close(m_sockfd);
+}
+
+void NotificationTalker::sendRequest(int fd, const NotificationRequest &request)
+{
+    m_fdStatus[fd] = false;
+
+    std::string data = Translator::Gui::notificationRequestToData(request.id,
+                                                                  request.data.client,
+                                                                  request.data.privilege);
+    auto size = data.size();
+
+    if (!Socket::send(fd, &size, sizeof(size))) {
+        remove(fd);
+        return;
+    }
+
+    if (!Socket::send(fd, data.c_str(), size)) {
+        remove(fd);
+        return;
+    }
+}
+
+void NotificationTalker::sendDismiss(int fd)
+{
+    if (!m_fdStatus[fd]) {
+        if (!Socket::send(fd, &Protocol::dissmisCode, sizeof(Protocol::dissmisCode))) {
+            remove(fd);
+            return;
+        }
+        m_fdStatus[fd] = true;
+    }
+}
+
+void NotificationTalker::parseResponse(NotificationResponse response, int fd)
+{
+    auto &queue = m_requests[m_fdToUser[fd]];
+    if (queue.empty()) {
+        ALOGD("Request canceled");
+        m_fdStatus[fd] = true;
+        return;
+    }
+
+    NotificationRequest request = queue.front();
+    if (request.id != response.id) {
+        ALOGD("Request canceled");
+        m_fdStatus[fd] = true;
+        return;
+    }
+
+    queue.pop_front();
+    ALOGD("For user: <" << request.data.user
+          << "> client: <" << request.data.client
+          << "> privilege: <" << request.data.privilege
+          << "> received: <" << Translator::Gui::responseToString(response.response) << ">");
+
+    m_responseHandler(response);
+
+    if (!Socket::send(fd, &Protocol::ackCode, sizeof(Protocol::ackCode))) {
+        remove(fd);
+        return;
+    }
+
+    m_fdStatus[fd] = true;
+}
+
+void NotificationTalker::recvResponses(int &rv)
+{
+    for (auto pair : m_userToFd) {
+        if (!rv) break;
+        int fd = std::get<1>(pair);
+
+        if (m_select.isSet(fd)) {
+            --rv;
+
+            NotificationResponse response;
+            if (Socket::recv(fd, &response, sizeof(response))) {
+                parseResponse(response, fd);
+            } else {
+                remove(fd);
+            }
+        }
+    }
+}
+
+void NotificationTalker::newConnection(int &rv)
+{
+    if (m_select.isSet(m_sockfd)) {
+        --rv;
+        int fd = Socket::accept(m_sockfd);
+        try {
+            char *user_c = nullptr;
+
+            int ret = cynara_creds_socket_get_user(fd, USER_METHOD_DEFAULT,&user_c);
+
+            std::unique_ptr<char[]> userPtr(user_c);
+
+            if (ret != CYNARA_API_SUCCESS) {
+                throw CynaraException("cynara_creds_socket_get_user", ret);
+            }
+            std::string user = user_c;
+
+            auto it = m_userToFd.find(user);
+            if (it != m_userToFd.end())
+                remove(std::get<1>(*it));
+
+            m_userToFd[user] = fd;
+            m_fdToUser[fd] = user;
+            m_fdStatus[fd] = true;
+
+            ALOGD("Accepted new conection for user: " << user);
+        } catch (...) {
+            Socket::close(fd);
+            throw;
+        }
+    }
+}
+
+void NotificationTalker::remove(int fd)
+{
+    Socket::close(fd);
+    auto user = m_fdToUser[fd];
+    m_fdToUser.erase(fd);
+    m_userToFd.erase(user);
+    m_fdStatus.erase(fd);
+}
+
+void NotificationTalker::run()
+{
+    try {
+        ALOGD("Notification loop started");
+        while (!m_stopflag) {
+            m_select.add(m_sockfd);
+
+            for (auto pair : m_userToFd)
+                m_select.add(std::get<1>(pair));
+
+            int rv = m_select.exec();
+
+            if (m_stopflag)
+                break;
+
+            if (rv) {
+                newConnection(rv);
+                recvResponses(rv);
+            } else {
+                // timeout
+            }
+
+            /* lock_guard */
+            {
+                std::lock_guard<std::mutex> lock(m_mutex);
+                for (auto pair : m_fdStatus ) {
+                    int fd = std::get<0>(pair);
+                    bool b = std::get<1>(pair);
+                    auto &queue = m_requests[m_fdToUser[fd]];
+                    if (b && !queue.empty()) {
+                        NotificationRequest request = queue.front();
+                        sendRequest(fd, request);
+                    }
+                }
+            } /* lock_guard */
+        }
+        ALOGD("NotificationTalker loop ended");
+    } catch (const std::exception &e) {
+        ALOGE("NotificationTalker: " << e.what());
+    } catch (...) {
+        ALOGE("NotificationTalker: unknown error");
+    }
+}
+
+} /* namespace Agent */
+
+} /* namespace AskUser */
diff --git a/src/agent/main/NotificationTalker.h b/src/agent/main/NotificationTalker.h
new file mode 100644 (file)
index 0000000..a6c77e7
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ *  Copyright (c) 2016 Samsung Electronics Co.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+/**
+ * @file        src/daemon/NotificationTalker.cpp
+ * @author      Oskar Świtalski <o.switalski@samsung.com>
+ * @brief       Declaration of NotificationTalker class
+ */
+
+#pragma once
+
+#include <deque>
+#include <functional>
+#include <map>
+#include <mutex>
+#include <string>
+
+#include <socket/SelectRead.h>
+#include <types/RequestId.h>
+#include <types/NotificationResponse.h>
+#include <types/NotificationRequest.h>
+
+#include <main/Request.h>
+
+namespace AskUser {
+
+namespace Agent {
+
+typedef std::pair<std::string, int> UserToFdPair;
+typedef std::map<std::string, int> UserToFdMap;
+typedef std::map<int, std::string> FdToUserMap;
+typedef std::map<int, bool> FdStatus;
+
+typedef std::map<std::string, std::deque<NotificationRequest>> RequestsQueue;
+
+typedef std::function<void(NotificationResponse)> ResponseHandler;
+
+class NotificationTalker
+{
+public:
+    NotificationTalker();
+
+    void parseRequest(RequestType type, NotificationRequest request);
+    void run();
+    void setResponseHandler(ResponseHandler responseHandler);
+    virtual void stop();
+
+    ~NotificationTalker();
+
+protected:
+    ResponseHandler m_responseHandler;
+
+    UserToFdMap m_userToFd;
+    FdToUserMap m_fdToUser;
+    FdStatus m_fdStatus;
+    Socket::SelectRead m_select;
+    int m_sockfd = 0;
+
+    RequestsQueue m_requests;
+    std::mutex m_mutex;
+
+    bool m_stopflag;
+
+    void parseResponse(NotificationResponse response, int fd);
+    void recvResponses(int &rv);
+
+    void newConnection(int &rv);
+    void remove(int fd);
+
+    virtual void addRequest(NotificationRequest &&request);
+    virtual void removeRequest(RequestId id);
+    virtual void sendRequest(int fd, const NotificationRequest &request);
+    virtual void sendDismiss(int fd);
+};
+
+} /* namespace Agent */
+
+} /* namespace AskUser */
+
diff --git a/src/agent/notification-daemon/AskUserTalker.cpp b/src/agent/notification-daemon/AskUserTalker.cpp
new file mode 100644 (file)
index 0000000..2839380
--- /dev/null
@@ -0,0 +1,205 @@
+/*
+ *  Copyright (c) 2016 Samsung Electronics Co.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+/**
+ * @file        src/notification-daemon/GuiRunner.h
+ * @author      Oskar Świtalski <o.switalski@samsung.com>
+ * @brief       Definition of AskUserTalker class
+ */
+
+#include "AskUserTalker.h"
+
+#include <iostream>
+#include <string>
+
+#include <socket/Socket.h>
+#include <socket/SelectRead.h>
+#include <types/NotificationResponse.h>
+#include <types/Protocol.h>
+#include <types/NotificationRequest.h>
+#include <exception/ErrnoException.h>
+#include <exception/Exception.h>
+#include <translator/Translator.h>
+#include <config/Path.h>
+
+#include <security-manager.h>
+
+namespace AskUser {
+
+namespace Notification {
+
+namespace {
+
+inline void throwOnSecurityPrivilegeError(std::string err, int ret)
+{
+    if (ret != SECURITY_MANAGER_SUCCESS)
+        throw Exception(err + " : " + std::to_string(ret));
+}
+
+inline const char *dropPrefix(const char* app)
+{
+    constexpr char prefix[] = "User::App::";
+    constexpr size_t prefixSize = sizeof(prefix) - 1;
+    return strncmp(app, prefix, prefixSize) ? app : app + prefixSize;
+}
+
+void setSecurityLevel(const std::string &app, const std::string &perm, const std::string &level)
+{
+    int ret;
+
+    policy_update_req *policyUpdateRequest = nullptr;
+    policy_entry *policyEntry = nullptr;
+
+    try {
+        if (level != "Allow" && level != "Deny")
+            throw std::invalid_argument("Not allowed security level <" + level + ">");
+
+        ALOGD("SecurityManager: Setting security level to " << level);
+
+        ret = security_manager_policy_update_req_new(&policyUpdateRequest);
+        throwOnSecurityPrivilegeError("security_manager_policy_update_req_new", ret);
+
+        ret = security_manager_policy_entry_new(&policyEntry);
+        throwOnSecurityPrivilegeError("security_manager_policy_entry_new", ret);
+
+        ret = security_manager_policy_entry_set_application(policyEntry,
+                                                        dropPrefix(app.c_str()));
+        throwOnSecurityPrivilegeError("security_manager_policy_entry_set_application", ret);
+
+        ret = security_manager_policy_entry_set_privilege(policyEntry, perm.c_str());
+        throwOnSecurityPrivilegeError("security_manager_policy_entry_set_privilege", ret);
+
+        ret = security_manager_policy_entry_set_level(policyEntry, level.c_str());
+        throwOnSecurityPrivilegeError("security_manager_policy_entry_admin_set_level", ret);
+
+        ret = security_manager_policy_update_req_add_entry(policyUpdateRequest, policyEntry);
+        throwOnSecurityPrivilegeError("security_manager_policy_update_req_add_entry", ret);
+
+        ret = security_manager_policy_update_send(policyUpdateRequest);
+        throwOnSecurityPrivilegeError("security_manager_policy_update_send", ret);
+
+        ALOGD("SecurityManager: Setting level succeeded");
+    } catch (std::exception &e) {
+        ALOGE("SecurityManager: Failed <" << e.what() << ">");
+    }
+
+    security_manager_policy_entry_free(policyEntry);
+    security_manager_policy_update_req_free(policyUpdateRequest);
+}
+
+} /* namespace */
+
+
+AskUserTalker::AskUserTalker(GuiRunner *gui) : m_gui(gui) {
+    m_gui->setDropHandler([&](){return this->shouldDismiss();});
+}
+
+AskUserTalker::~AskUserTalker()
+{
+    try {
+        Socket::close(sockfd);
+    } catch (const std::exception &e) {
+        ALOGE(std::string("~AskUserTalker") + e.what());
+    } catch (...) {
+        ALOGE("~AskUserTalker: Unknow error");
+    }
+}
+
+void AskUserTalker::run()
+{
+    sockfd = Socket::connect(Path::getSocketPath());
+
+    while (!stopFlag) {
+        size_t size;
+        char *buf;
+        NotificationResponse response;
+
+        ALOGD("Waiting for request...");
+
+        if (!Socket::recv(sockfd, &size, sizeof(size))) {
+            ALOGI("Askuserd closed connection, closing...");
+            break;
+        }
+
+        buf = new char[size];
+
+        if (!Socket::recv(sockfd, buf, size)) {
+            ALOGI("Askuserd closed connection, closing...");
+            break;
+        }
+
+        NotificationRequest request = Translator::Gui::dataToNotificationRequest(buf);
+        delete[] buf;
+        ALOGD("Recieved data " << request.data.client << " " << request.data.privilege);
+
+        response.response = m_gui->popupRun(request.data.client, request.data.privilege);
+        response.id = request.id;
+
+        if (response.response == NResponseType::None) {
+            continue;
+        }
+
+        if (!Socket::send(sockfd, &response, sizeof(response))) {
+            ALOGI("Askuserd closed connection, closing...");
+            break;
+        }
+
+        uint8_t ack = 0x00;
+        if (!Socket::recv(sockfd, &ack, sizeof(ack))) {
+            ALOGI("Askuserd closed connection, closing...");
+            break;
+        }
+
+        if (ack != Protocol::ackCode)
+            throw Exception("Incorrect ack");
+
+        switch (response.response) {
+        case NResponseType::Error:
+            throw Exception(m_gui->getErrorMsg());
+        case NResponseType::Allow:
+        case NResponseType::Never:
+            setSecurityLevel(request.data.client, request.data.privilege,
+                             Translator::Gui::responseToString(response.response));
+        default:
+            break;
+        }
+    }
+}
+
+void AskUserTalker::stop()
+{
+    m_gui->stop();
+    Socket::close(sockfd);
+}
+
+bool AskUserTalker::shouldDismiss()
+{
+    Socket::SelectRead select;
+    select.add(sockfd);
+    if (select.exec() == 0)
+        return false;
+
+    uint8_t a = 0x00;
+    Socket::recv(sockfd, &a, sizeof(a));
+
+    if (a != Protocol::dissmisCode)
+        throw Exception("Incorrect dismiss flag");
+
+    return true;
+}
+
+} /* namespace Notification */
+
+} /* namespace AskUser */
diff --git a/src/agent/notification-daemon/AskUserTalker.h b/src/agent/notification-daemon/AskUserTalker.h
new file mode 100644 (file)
index 0000000..a7b9e72
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ *  Copyright (c) 2016 Samsung Electronics Co.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+/**
+ * @file        src/notification-daemon/GuiRunner.h
+ * @author      Oskar Świtalski <o.switalski@samsung.com>
+ * @brief       Declaration of AskUserTalker class
+ */
+
+#pragma once
+
+#include <functional>
+#include <queue>
+#include <memory>
+#include <mutex>
+
+#include "GuiRunner.h"
+
+namespace AskUser {
+
+namespace Notification {
+
+class AskUserTalker
+{
+public:
+      AskUserTalker(GuiRunner *gui);
+      ~AskUserTalker();
+
+      void run();
+      void stop();
+
+      bool shouldDismiss();
+
+private:
+      GuiRunner *m_gui;
+      int sockfd = 0;
+      bool stopFlag = false;
+};
+
+} /* namespace Notification */
+
+} /* namespace AskUser */
diff --git a/src/agent/notification-daemon/CMakeLists.txt b/src/agent/notification-daemon/CMakeLists.txt
new file mode 100644 (file)
index 0000000..6306965
--- /dev/null
@@ -0,0 +1,42 @@
+SET(NOTIF_PATH ${PROJECT_SOURCE_DIR}/src/agent/notification-daemon/)
+
+PKG_CHECK_MODULES(ASKUSER_NOTIFICATION_DEP
+    REQUIRED
+    elementary
+    cynara-agent
+    libsystemd-daemon
+    security-manager
+    security-privilege-manager
+)
+
+INCLUDE_DIRECTORIES(SYSTEM
+    ${ASKUSER_NOTIFICATION_DEP_INCLUDE_DIRS}
+)
+INCLUDE_DIRECTORIES(
+    ${ASKUSER_PATH}
+    ${ASKUSER_PATH}/common
+)
+
+SET(ASKUSER_NOTIFICATION_SOURCES
+    ${NOTIF_PATH}/main.cpp
+    ${NOTIF_PATH}/GuiRunner.cpp
+    ${NOTIF_PATH}/AskUserTalker.cpp
+   )
+
+
+ADD_EXECUTABLE(${TARGET_ASKUSER_NOTIFICATION} ${ASKUSER_NOTIFICATION_SOURCES})
+
+SET_TARGET_PROPERTIES(${TARGET_ASKUSER_NOTIFICATION} PROPERTIES
+    COMPILE_FLAGS
+    -fpie
+)
+
+TARGET_LINK_LIBRARIES(${TARGET_ASKUSER_NOTIFICATION}
+    ${ASKUSER_NOTIFICATION_DEP_LIBRARIES}
+    ${TARGET_ASKUSER_COMMON}
+    -pie
+)
+
+INSTALL(TARGETS ${TARGET_ASKUSER_NOTIFICATION} DESTINATION ${BIN_INSTALL_DIR})
+
+ADD_SUBDIRECTORY(po)
diff --git a/src/agent/notification-daemon/GuiRunner.cpp b/src/agent/notification-daemon/GuiRunner.cpp
new file mode 100644 (file)
index 0000000..236d90c
--- /dev/null
@@ -0,0 +1,239 @@
+/*
+ *  Copyright (c) 2016 Samsung Electronics Co.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+/**
+ * @file        src/notification-daemon/GuiRunner.cpp
+ * @author      Oskar Świtalski <o.switalski@samsung.com>
+ * @brief       Definition of GuiRunner class
+ */
+
+#include "GuiRunner.h"
+
+#include <exception/ErrnoException.h>
+#include <exception/Exception.h>
+#include <translator/Translator.h>
+#include <libintl.h>
+#include <privilegemgr/privilege_info.h>
+
+namespace AskUser {
+
+namespace Notification {
+
+namespace {
+
+bool should_raise = false;
+
+void unfocused(void *data, Evas_Object *, void *)
+{
+    if (data == NULL)
+        return;
+
+    PopupData *res = static_cast<PopupData*>(data);
+
+    if (should_raise)
+        elm_win_raise(res->win);
+    else
+        elm_exit();
+}
+
+void inline win_close(Evas_Object *win) {
+    should_raise = false;
+    elm_win_lower(win);
+}
+
+void inline answer(void *data, NResponseType response)
+{
+    ALOGD("User selected: " + Translator::Gui::responseToString(response));
+
+    if (data == NULL)
+        return;
+
+    PopupData *res = static_cast<PopupData*>(data);
+    res->type = response;
+    win_close(res->win);
+}
+
+void allow_answer(void *data, Evas_Object *, void *)
+{
+    answer(data, NResponseType::Allow);
+}
+
+void deny_answer(void *data, Evas_Object *, void *)
+{
+    answer(data, NResponseType::Deny);
+}
+
+void never_answer(void *data, Evas_Object *, void *)
+{
+    answer(data, NResponseType::Never);
+}
+
+Eina_Bool timeout_answer(void *data) {
+    if (!data)
+        return ECORE_CALLBACK_RENEW;
+
+    drop *d = static_cast<drop*>(data);
+
+    if (d->handle()) {
+        win_close(d->popup->win);
+    }
+
+    return ECORE_CALLBACK_RENEW;
+}
+
+std::string friendlyPrivilegeName(const std::string &privilege)
+{
+    char *name = nullptr;
+    int res = privilege_info_get_privilege_display_name(privilege.c_str(), &name);
+    if (res != PRVMGR_ERR_NONE || !name) {
+        ALOGE("Unable to get privilege display name for: <" << privilege << ">, err: <" << res << ">");
+        return privilege;
+    }
+    std::string ret(name);
+    free(name);
+    return ret;
+}
+
+} /* namespace */
+
+GuiRunner::GuiRunner()
+{
+    m_popupData = new PopupData({NResponseType::Deny, nullptr});
+}
+
+void GuiRunner::initialize()
+{
+    elm_init(0, NULL);
+
+    //placeholder
+    m_win = elm_win_add(NULL, dgettext(PROJECT_NAME, "SID_PRIVILEGE_REQUEST_DIALOG_TITLE"),
+                                   ELM_WIN_DOCK);
+    elm_win_autodel_set(m_win, EINA_TRUE);
+    elm_win_override_set(m_win, EINA_TRUE);
+    elm_win_alpha_set(m_win, EINA_TRUE);
+
+    // popup
+    m_popup = elm_popup_add(m_win);
+    elm_object_part_text_set(m_popup, "title,text", dgettext(PROJECT_NAME,
+                                                         "SID_PRIVILEGE_REQUEST_DIALOG_TITLE"));
+
+    // box
+    m_box = elm_box_add(m_popup);
+    evas_object_size_hint_weight_set(m_box, EVAS_HINT_EXPAND, 0);
+    evas_object_size_hint_align_set(m_box, EVAS_HINT_FILL, 0.0);
+
+    // content
+    m_content = elm_label_add(m_popup);
+    elm_object_style_set(m_content, "elm.swallow.content");
+    elm_label_line_wrap_set(m_content, ELM_WRAP_MIXED);
+    evas_object_size_hint_weight_set(m_content, EVAS_HINT_EXPAND, 0.0);
+    evas_object_size_hint_align_set(m_content, EVAS_HINT_FILL, EVAS_HINT_FILL);
+
+    evas_object_show(m_content);
+    elm_box_pack_end(m_box, m_content);
+    elm_object_part_content_set(m_popup, "default", m_box);
+
+    // buttons
+    m_allowButton = elm_button_add(m_popup);
+    elm_object_part_content_set(m_popup, "button1", m_allowButton);
+    elm_object_text_set(m_allowButton, dgettext(PROJECT_NAME,
+                                            "SID_PRIVILEGE_REQUEST_DIALOG_BUTTON_ALLOW"));
+
+    m_neverButton = elm_button_add(m_popup);
+    elm_object_text_set(m_neverButton, dgettext(PROJECT_NAME,
+                                            "SID_PRIVILEGE_REQUEST_DIALOG_BUTTON_NEVER"));
+    elm_object_part_content_set(m_popup, "button2", m_neverButton);
+
+    m_denyButton = elm_button_add(m_popup);
+    elm_object_text_set(m_denyButton, dgettext(PROJECT_NAME,
+                                           "SID_PRIVILEGE_REQUEST_DIALOG_BUTTON_DENY"));
+    elm_object_part_content_set(m_popup, "button3", m_denyButton);
+
+    // callbacks
+    evas_object_smart_callback_add(m_win, "unfocused", unfocused, m_popupData);
+    evas_object_smart_callback_add(m_allowButton, "clicked", allow_answer, m_popupData);
+    evas_object_smart_callback_add(m_neverButton, "clicked", never_answer, m_popupData);
+    evas_object_smart_callback_add(m_denyButton, "clicked", deny_answer, m_popupData);
+
+    m_popupData->win = m_win;
+    m_initialized = true;
+
+}
+
+NResponseType GuiRunner::popupRun(const std::string &app, const std::string &perm)
+{
+
+    try {
+        if (!m_dropHandler)
+            throw Exception("DropHandler was not initialized");
+
+        if (!m_initialized)
+            initialize();
+
+        m_running = true;
+        drop *Drop = new drop({m_dropHandler, m_popupData});
+        m_timer = ecore_timer_add(0.1, timeout_answer, Drop);
+
+        // create message
+        char *messageFormat = dgettext(PROJECT_NAME, "SID_PRIVILEGE_REQUEST_DIALOG_MESSAGE");
+        char buf[BUFSIZ];
+        int ret = std::snprintf(buf, sizeof(buf), messageFormat,
+                                app.c_str(),
+                                friendlyPrivilegeName(perm).c_str());
+
+        if (ret < 0)
+            throw ErrnoException("snprintf failed", errno);
+
+        m_popupData->type = NResponseType::None;
+
+        should_raise = true;
+
+        elm_object_text_set(m_content, buf);
+
+        evas_object_show(m_popup);
+        evas_object_show(m_win);
+
+        elm_win_raise(m_win);
+        elm_run();
+
+        ecore_timer_del(m_timer);
+        m_running = false;
+        should_raise = false;
+    } catch (const std::exception &e) {
+        m_popupData->type = NResponseType::Error;
+        m_errorMsg = std::move(e.what());
+    }
+
+    return m_popupData->type;
+}
+
+void GuiRunner::setDropHandler(DropHandler dropHandler)
+{
+    m_dropHandler = dropHandler;
+}
+
+void GuiRunner::stop()
+{
+    if (m_running) {
+        evas_object_hide(m_win);
+        elm_exit();
+    }
+
+    elm_shutdown();
+}
+
+} /* namespace Notification */
+
+} /* namespace AskUser */
diff --git a/src/agent/notification-daemon/GuiRunner.h b/src/agent/notification-daemon/GuiRunner.h
new file mode 100644 (file)
index 0000000..1a323fe
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ *  Copyright (c) 2016 Samsung Electronics Co.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+/**
+ * @file        src/notification-daemon/GuiRunner.h
+ * @author      Oskar Świtalski <o.switalski@samsung.com>
+ * @brief       Declaration of GuiRunner class
+ */
+
+#pragma once
+
+#include <Elementary.h>
+#include <functional>
+#include <string>
+
+#include <types/NotificationResponse.h>
+#include <log/alog.h>
+
+namespace AskUser {
+
+namespace Notification {
+
+typedef std::function<bool()> DropHandler;
+
+struct PopupData {
+    NResponseType type;
+    Evas_Object *win;
+};
+
+struct drop {
+    DropHandler handle;
+    PopupData *popup;
+};
+
+class GuiRunner {
+public:
+    GuiRunner();
+
+    NResponseType popupRun(const std::string &app, const std::string &perm);
+
+    void setDropHandler(DropHandler dropHandler);
+    void stop();
+
+    std::string getErrorMsg() { return m_errorMsg; }
+
+private:
+    PopupData *m_popupData;
+    DropHandler m_dropHandler;
+
+    Evas_Object *m_win;
+    Evas_Object *m_popup;
+    Evas_Object *m_box;
+    Evas_Object *m_content;
+    Evas_Object *m_allowButton;
+    Evas_Object *m_neverButton;
+    Evas_Object *m_denyButton;
+    Ecore_Timer *m_timer;
+
+    bool m_running = false;
+    bool m_initialized = false;
+
+    std::string m_errorMsg;
+
+    void initialize();
+};
+
+} /* namespace Notification */
+
+} /* namespace AskUser */
diff --git a/src/agent/notification-daemon/main.cpp b/src/agent/notification-daemon/main.cpp
new file mode 100644 (file)
index 0000000..c23d6b3
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ *  Copyright (c) 2016 Samsung Electronics Co.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License
+ */
+/**
+ * @file        src/notification-daemon/main.cpp
+ * @author      Oskar Świtalski <o.switalski@samsung.com>
+ * @brief       Main askuser notification daemon file
+ */
+
+#include <clocale>
+#include <csignal>
+#include <cstdlib>
+#include <string>
+#include <systemd/sd-daemon.h>
+#include <thread>
+#include <unistd.h>
+
+#include <exception/Exception.h>
+#include <log/alog.h>
+
+#include "GuiRunner.h"
+#include "AskUserTalker.h"
+
+int main()
+{
+    using namespace AskUser::Notification;
+    init_agent_log();
+
+    char *locale = setlocale(LC_ALL, "");
+    ALOGD("Current locale is: <" << locale << ">");
+
+    try {
+        GuiRunner gui;
+        AskUserTalker askUserTalker(&gui);
+
+        int ret = sd_notify(0, "READY=1");
+        if (ret == 0) {
+            ALOGW("Agent was not configured to notify its status");
+        } else if (ret < 0) {
+            ALOGE("sd_notify failed: [" << ret << "]");
+        }
+
+        askUserTalker.run();
+
+    } catch (std::exception &e) {
+        ALOGE("Askuser-notification stopped because of: <" << e.what() << ">.");
+    } catch (...) {
+        ALOGE("Askuser-notification stopped because of unknown unhandled exception.");
+    }
+
+    return 0;
+}
similarity index 95%
rename from src/agent/po/CMakeLists.txt
rename to src/agent/notification-daemon/po/CMakeLists.txt
index 3cb076f..4e4cc01 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved
+# Copyright (c) 2015-2016 Samsung Electronics Co., Ltd All Rights Reserved
 #
 #    Licensed under the Apache License, Version 2.0 (the "License");
 #    you may not use this file except in compliance with the License.
diff --git a/src/agent/notification-daemon/po/en.po b/src/agent/notification-daemon/po/en.po
new file mode 100644 (file)
index 0000000..4d79bf5
--- /dev/null
@@ -0,0 +1,14 @@
+msgid "SID_PRIVILEGE_REQUEST_DIALOG_TITLE"
+msgstr "Privilege request"
+
+msgid "SID_PRIVILEGE_REQUEST_DIALOG_BUTTON_NEVER"
+msgstr "Never"
+
+msgid "SID_PRIVILEGE_REQUEST_DIALOG_BUTTON_DENY"
+msgstr "Deny"
+
+msgid "SID_PRIVILEGE_REQUEST_DIALOG_BUTTON_ALLOW"
+msgstr "Always"
+
+msgid "SID_PRIVILEGE_REQUEST_DIALOG_MESSAGE"
+msgstr "Application <b>%s</b> requested privilege for <b>%s</b>."
diff --git a/src/agent/notification-daemon/po/pl.po b/src/agent/notification-daemon/po/pl.po
new file mode 100644 (file)
index 0000000..84ebb1e
--- /dev/null
@@ -0,0 +1,14 @@
+msgid "SID_PRIVILEGE_REQUEST_DIALOG_TITLE"
+msgstr "Żądanie dostępu"
+
+msgid "SID_PRIVILEGE_REQUEST_DIALOG_BUTTON_NEVER"
+msgstr "Nigdy"
+
+msgid "SID_PRIVILEGE_REQUEST_DIALOG_BUTTON_DENY"
+msgstr "Odmów"
+
+msgid "SID_PRIVILEGE_REQUEST_DIALOG_BUTTON_ALLOW"
+msgstr "Zawsze"
+
+msgid "SID_PRIVILEGE_REQUEST_DIALOG_MESSAGE"
+msgstr "Aplikacja <b>%s</b> zażądała przywileju do <b>%s</b>."
diff --git a/src/agent/po/en.po b/src/agent/po/en.po
deleted file mode 100644 (file)
index 7eb103e..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-msgid "SID_PRIVILEGE_REQUEST_DIALOG_TITLE"
-msgstr "Privilege request"
-
-msgid "SID_PRIVILEGE_REQUEST_DIALOG_BUTTON_NO_ONCE"
-msgstr "No"
-
-msgid "SID_PRIVILEGE_REQUEST_DIALOG_BUTTON_NO_SESSION"
-msgstr "No session"
-
-msgid "SID_PRIVILEGE_REQUEST_DIALOG_BUTTON_NO_LIFE"
-msgstr "No restart"
-
-msgid "SID_PRIVILEGE_REQUEST_DIALOG_BUTTON_YES_ONCE"
-msgstr "Yes"
-
-msgid "SID_PRIVILEGE_REQUEST_DIALOG_BUTTON_YES_SESSION"
-msgstr "Yes session"
-
-msgid "SID_PRIVILEGE_REQUEST_DIALOG_BUTTON_YES_LIFE"
-msgstr "Yes restart"
-
-msgid "SID_PRIVILEGE_REQUEST_DIALOG_MESSAGE"
-msgstr "Application: %s, ran by user: %s, requested privilege:\n%s\nGrant access to privilege?"
diff --git a/src/agent/po/pl.po b/src/agent/po/pl.po
deleted file mode 100644 (file)
index 82b865b..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-msgid "SID_PRIVILEGE_REQUEST_DIALOG_TITLE"
-msgstr "Żądanie dostępu"
-
-msgid "SID_PRIVILEGE_REQUEST_DIALOG_BUTTON_NO_ONCE"
-msgstr "Nie"
-
-msgid "SID_PRIVILEGE_REQUEST_DIALOG_BUTTON_NO_SESSION"
-msgstr "Nie sesja"
-
-msgid "SID_PRIVILEGE_REQUEST_DIALOG_BUTTON_NO_LIFE"
-msgstr "Nie restart"
-
-msgid "SID_PRIVILEGE_REQUEST_DIALOG_BUTTON_YES_ONCE"
-msgstr "Tak"
-
-msgid "SID_PRIVILEGE_REQUEST_DIALOG_BUTTON_YES_SESSION"
-msgstr "Tak sesja"
-
-msgid "SID_PRIVILEGE_REQUEST_DIALOG_BUTTON_YES_LIFE"
-msgstr "Tak restart"
-
-msgid "SID_PRIVILEGE_REQUEST_DIALOG_MESSAGE"
-msgstr "Aplikacja: %s, uruchomiona przez użytkownika: %s, zażądała zasobu:\n %s\nUdzielić dostępu?"
-
index f9a1284..3355833 100644 (file)
@@ -22,3 +22,8 @@ INSTALL(FILES
     lib/systemd/system
 )
 
+INSTALL(FILES
+    ${CMAKE_SOURCE_DIR}/systemd/askuser-notification.service
+    DESTINATION
+    lib/systemd/user
+)
diff --git a/systemd/askuser-notification.service b/systemd/askuser-notification.service
new file mode 100644 (file)
index 0000000..3253b6c
--- /dev/null
@@ -0,0 +1,20 @@
+[Unit]
+Description=Ask user notification
+
+[Service]
+ExecStart=/usr/bin/askuser-notification
+
+Type=notify
+
+KillMode=process
+TimeoutStopSec=10
+TimeoutStartSec=10
+RestartSec=5
+Restart=always
+
+NoNewPrivileges=true
+
+EnvironmentFile=-/run/tizen-system-env
+
+[Install]
+WantedBy=multi-user.target