From f950f46fd553ce24ea0b1e579676b90c32840f0a Mon Sep 17 00:00:00 2001 From: =?utf8?q?Oskar=20=C5=9Awitalski?= Date: Wed, 1 Jun 2016 16:45:36 +0200 Subject: [PATCH] Add notification daemon Change-Id: Icf5dc30074d5c538144da39e0d2b1c9c99bbdef3 --- CMakeLists.txt | 4 +- packaging/askuser-notification.manifest | 5 + packaging/askuser.spec | 23 ++ src/agent/CMakeLists.txt | 4 +- src/agent/main/NotificationTalker.cpp | 309 +++++++++++++++++++++ src/agent/main/NotificationTalker.h | 91 ++++++ src/agent/notification-daemon/AskUserTalker.cpp | 205 ++++++++++++++ src/agent/notification-daemon/AskUserTalker.h | 54 ++++ src/agent/notification-daemon/CMakeLists.txt | 42 +++ src/agent/notification-daemon/GuiRunner.cpp | 239 ++++++++++++++++ src/agent/notification-daemon/GuiRunner.h | 81 ++++++ src/agent/notification-daemon/main.cpp | 64 +++++ .../{ => notification-daemon}/po/CMakeLists.txt | 2 +- src/agent/notification-daemon/po/en.po | 14 + src/agent/notification-daemon/po/pl.po | 14 + src/agent/po/en.po | 23 -- src/agent/po/pl.po | 24 -- systemd/CMakeLists.txt | 5 + systemd/askuser-notification.service | 20 ++ 19 files changed, 1173 insertions(+), 50 deletions(-) create mode 100644 packaging/askuser-notification.manifest create mode 100644 src/agent/main/NotificationTalker.cpp create mode 100644 src/agent/main/NotificationTalker.h create mode 100644 src/agent/notification-daemon/AskUserTalker.cpp create mode 100644 src/agent/notification-daemon/AskUserTalker.h create mode 100644 src/agent/notification-daemon/CMakeLists.txt create mode 100644 src/agent/notification-daemon/GuiRunner.cpp create mode 100644 src/agent/notification-daemon/GuiRunner.h create mode 100644 src/agent/notification-daemon/main.cpp rename src/agent/{ => notification-daemon}/po/CMakeLists.txt (95%) create mode 100644 src/agent/notification-daemon/po/en.po create mode 100644 src/agent/notification-daemon/po/pl.po delete mode 100644 src/agent/po/en.po delete mode 100644 src/agent/po/pl.po create mode 100644 systemd/askuser-notification.service diff --git a/CMakeLists.txt b/CMakeLists.txt index 251d5d8..6b21eba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 +# @author Oskar Switalski # ############################# 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 index 0000000..c00c25b --- /dev/null +++ b/packaging/askuser-notification.manifest @@ -0,0 +1,5 @@ + + + + + diff --git a/packaging/askuser.spec b/packaging/askuser.spec index 8a0bdef..d62c65d 100644 --- a/packaging/askuser.spec +++ b/packaging/askuser.spec @@ -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 diff --git a/src/agent/CMakeLists.txt b/src/agent/CMakeLists.txt index 6d39ceb..fa184c5 100644 --- a/src/agent/CMakeLists.txt +++ b/src/agent/CMakeLists.txt @@ -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 index 0000000..57d1f33 --- /dev/null +++ b/src/agent/main/NotificationTalker.cpp @@ -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 + * @brief Definition of NotificationTalker class + */ + +#include "NotificationTalker.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +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 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 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 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 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 index 0000000..a6c77e7 --- /dev/null +++ b/src/agent/main/NotificationTalker.h @@ -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 + * @brief Declaration of NotificationTalker class + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include
+ +namespace AskUser { + +namespace Agent { + +typedef std::pair UserToFdPair; +typedef std::map UserToFdMap; +typedef std::map FdToUserMap; +typedef std::map FdStatus; + +typedef std::map> RequestsQueue; + +typedef std::function 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 index 0000000..2839380 --- /dev/null +++ b/src/agent/notification-daemon/AskUserTalker.cpp @@ -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 + * @brief Definition of AskUserTalker class + */ + +#include "AskUserTalker.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +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 index 0000000..a7b9e72 --- /dev/null +++ b/src/agent/notification-daemon/AskUserTalker.h @@ -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 + * @brief Declaration of AskUserTalker class + */ + +#pragma once + +#include +#include +#include +#include + +#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 index 0000000..6306965 --- /dev/null +++ b/src/agent/notification-daemon/CMakeLists.txt @@ -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 index 0000000..236d90c --- /dev/null +++ b/src/agent/notification-daemon/GuiRunner.cpp @@ -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 + * @brief Definition of GuiRunner class + */ + +#include "GuiRunner.h" + +#include +#include +#include +#include +#include + +namespace AskUser { + +namespace Notification { + +namespace { + +bool should_raise = false; + +void unfocused(void *data, Evas_Object *, void *) +{ + if (data == NULL) + return; + + PopupData *res = static_cast(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(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(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 index 0000000..1a323fe --- /dev/null +++ b/src/agent/notification-daemon/GuiRunner.h @@ -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 + * @brief Declaration of GuiRunner class + */ + +#pragma once + +#include +#include +#include + +#include +#include + +namespace AskUser { + +namespace Notification { + +typedef std::function 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 index 0000000..c23d6b3 --- /dev/null +++ b/src/agent/notification-daemon/main.cpp @@ -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 + * @brief Main askuser notification daemon file + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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; +} diff --git a/src/agent/po/CMakeLists.txt b/src/agent/notification-daemon/po/CMakeLists.txt similarity index 95% rename from src/agent/po/CMakeLists.txt rename to src/agent/notification-daemon/po/CMakeLists.txt index 3cb076f..4e4cc01 100644 --- a/src/agent/po/CMakeLists.txt +++ b/src/agent/notification-daemon/po/CMakeLists.txt @@ -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 index 0000000..4d79bf5 --- /dev/null +++ b/src/agent/notification-daemon/po/en.po @@ -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 %s requested privilege for %s." diff --git a/src/agent/notification-daemon/po/pl.po b/src/agent/notification-daemon/po/pl.po new file mode 100644 index 0000000..84ebb1e --- /dev/null +++ b/src/agent/notification-daemon/po/pl.po @@ -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 %s zażądała przywileju do %s." diff --git a/src/agent/po/en.po b/src/agent/po/en.po deleted file mode 100644 index 7eb103e..0000000 --- a/src/agent/po/en.po +++ /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 index 82b865b..0000000 --- a/src/agent/po/pl.po +++ /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?" - diff --git a/systemd/CMakeLists.txt b/systemd/CMakeLists.txt index f9a1284..3355833 100644 --- a/systemd/CMakeLists.txt +++ b/systemd/CMakeLists.txt @@ -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 index 0000000..3253b6c --- /dev/null +++ b/systemd/askuser-notification.service @@ -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 -- 2.7.4