Merge lp:~thomas-voss/trust-store/fix-1504022 into lp:trust-store/15.04

Proposed by Thomas Voß on 2015-11-24
Status: Merged
Approved by: Alberto Mardegan on 2015-11-24
Approved revision: 141
Merged at revision: 140
Proposed branch: lp:~thomas-voss/trust-store/fix-1504022
Merge into: lp:trust-store/15.04
Prerequisite: lp:~thomas-voss/trust-store/integrate-xdg
Diff against target: 1270 lines (+687/-167)
17 files modified
CMakeLists.txt (+1/-1)
debian/control (+1/-0)
po/trust-store.pot (+10/-11)
src/CMakeLists.txt (+7/-0)
src/core/trust/daemon.cpp (+1/-1)
src/core/trust/mir/agent.cpp (+22/-42)
src/core/trust/mir/agent.h (+44/-16)
src/core/trust/mir/click_desktop_entry_app_info_resolver.cpp (+139/-0)
src/core/trust/mir/click_desktop_entry_app_info_resolver.h (+49/-0)
src/core/trust/mir/prompt_main.cpp (+44/-41)
src/core/trust/mir/prompt_main.h (+16/-4)
src/core/trust/mir/prompt_main.qml (+66/-30)
tests/CMakeLists.txt (+20/-0)
tests/click_desktop_entry_app_name_resolver_test.cpp (+75/-0)
tests/mir_agent_test.cpp (+131/-21)
tests/share/applications/valid.pkg_app_0.0.0.desktop (+56/-0)
tests/test_data.h.in (+5/-0)
To merge this branch: bzr merge lp:~thomas-voss/trust-store/fix-1504022
Reviewer Review Type Date Requested Status
Matthew Paul Thomas design Approve on 2015-12-07
Tyler Hicks Approve on 2015-12-01
Alberto Mardegan (community) 2015-11-24 Approve on 2015-11-24
PS Jenkins bot continuous-integration 2015-11-24 Pending
Review via email: mp+278418@code.launchpad.net

This proposal supersedes a proposal from 2015-11-20.

Commit message

Add interface mir::AppNameResolver.

mir::AppNameResolver is used by mir::Agent to map application IDs
to localized application names.

Description of the change

Add interface mir::AppNameResolver.

mir::AppNameResolver is used by mir::Agent to map application IDs
to localized application names.

To post a comment you must log in.
Alberto Mardegan (mardy) wrote : Posted in a previous version of this proposal

A couple of inline comments, I'm not convinced that this way of finding the desktop files is reliable.

review: Needs Fixing
Alberto Mardegan (mardy) wrote : Posted in a previous version of this proposal

Looks good!

review: Approve
Alberto Mardegan (mardy) wrote : Posted in a previous version of this proposal

LGTM

review: Approve
Tyler Hicks (tyhicks) wrote : Posted in a previous version of this proposal

Hello - Thomas had asked for a security review and I just took a look at the code.

The code itself looks nearly perfect. I have a couple questions below but they're minor.

I'm mainly concerned about the concept of using the localized Name of the desktop file in a trust prompt. For something like a click app that comes from an untrusted developer, we have no idea if the localized name in their provided desktop file is an accurate translation or a malicious translation intended to trick the user into granting their app access. I don't know of any logic that could be used in the click reviewers tools to verify that the translations are non-malicious. How do we handle such a situation?

As for the code, I have a couple questions/comments:

 1) What UID does resolve_desktop_entry_or_throw() run under? I ask because it goes mucking around in the user's home dir and is vulnerable to minor TOCTOU issues such as checking to see if /home/USER/applications/APP_ID.desktop is a regular file and then later passing that path to g_key_file_load_from_file(). The user could modify the path to be a symlink to an area of the filesystem that he/she doesn't own. If resolve_desktop_entry_or_throw() runs as the user, there's probably nothing to worry about.

 2) The external XDG stuff that you wrote is a nice idea. I'd recommend packaging it up as a library so that it can be reused in a controllable fashion instead of copying and pasting the code into all of the projects that want to use it.

review: Needs Information
Tyler Hicks (tyhicks) wrote : Posted in a previous version of this proposal

After speaking with jdstrand, he explained why the click reviewers tools don't currently verify the Name field of a desktop file. The desktop file name field can't be forced to match the click package name because the desktop file name should be "pretty" while the click package name is alphanumerics and '-'.

Because of this, I don't see how the desktop file name field can be trusted enough to be used in trust store prompts. :/

Thomas Voß (thomas-voss) wrote : Posted in a previous version of this proposal

Hey Tyler,

thanks for the detailed review and your comments. Let me first try to clarify on the use of the localized name in trust prompts. I actually raised the same issue as you, and it turns out that we are "vulnerable" to applications faking their identity in a couple of places throughout the system. First and foremost: Unity itself relies on the .desktop file entry for displaying the name. We might want to fix the default approach for handling localized application names meant for human consumption in snappy, though.

One possible way to resolve the issue is by altering the prompt to show the localized name alongside the actual application id. Ideally, we would have an expendable field "Details" as part of the prompt containing in-depth information about the request, at the very least the actual app ID together with the UID (and the human-readable user name).

(@1:) The function always runs under the actual user. I will however add a check to make sure that the current user id matches the user id of the request. If not, we will return early, and deny the request.

(@2:) Sure, happy to. I mainly added the external code here to guarantee a swift landing. I will do the packaging and carry out the requesting asap, but would like to make sure that a fix for this bug is not blocked by package/MIR review processes.

Thomas Voß (thomas-voss) wrote : Posted in a previous version of this proposal

One other idea to further clarify the identity of the app might be to include its official icon. At the very least, this would make it easier for the user to associate the source of the trust request with what is visible in the dash and in the launcher.

Thomas Voß (thomas-voss) wrote : Posted in a previous version of this proposal

I reported the potential privilege escalation issue in https://bugs.launchpad.net/trust-store/+bug/1518883. The bug has got a fix attached.

Alberto Mardegan (mardy) wrote : Posted in a previous version of this proposal

LGTM!

review: Approve
Sebastien Bacher (seb128) wrote : Posted in a previous version of this proposal

@Tyler, note that the current situation is not any better, the prompt are using the name of the hook included in the click, which is also not restricted...

Having the appid mentioned looks like a good idea to me though

Thomas Voß (thomas-voss) wrote : Posted in a previous version of this proposal

> @Tyler, note that the current situation is not any better, the prompt are
> using the name of the hook included in the click, which is also not
> restricted...
>

I think that's an issue with our review tools tbh. The trust store relies on apparmor to query the profile name. With that, if an app can just request any name in there, we have got a problem from my pov.

> Having the appid mentioned looks like a good idea to me though

Tyler Hicks (tyhicks) wrote : Posted in a previous version of this proposal

Thomas, I'm not sure that you need to add a user ID check. I was just curious what UID the code was expected to be running in. I'm happy enough with your answer that it runs as the user.

If you think the XDG code will be reused in other projects, it'd be good to package up separately.

Tyler Hicks (tyhicks) wrote : Posted in a previous version of this proposal

> @Tyler, note that the current situation is not any better, the prompt are
> using the name of the hook included in the click, which is also not
> restricted...
>
> Having the appid mentioned looks like a good idea to me though

Yes, I think the appid approach has a chance of greatly improving the situation.

Matthew Paul Thomas (mpt) wrote : Posted in a previous version of this proposal
Download full text (3.5 KiB)

The standard permission prompt message is of the form
    <appname> wants to access <property>.
for example
    Frobnicator Turbo wants to access your location.
so the attack you have in mind is for Frobnicator Turbo to pretend to be another app:
    Browser wants to access your location.

Especially while people are used to hardly anything happening in the background, this might often fail. People would think, "Huh? I'm not using Browser at the moment. Why is it asking me now?"

Showing the app icon would help now, but it would lose its effectiveness once bug 1453795 is fixed.

Including the application id might protect geeks, but I expect most people would ignore it, or think it was a bug, on the grounds that it looks like code. If it was embedded in the prompt text,
    <appname> (<appid>) wants to access <property>.
then an app could even explain it away with a devious name:
    Browser (downloadable from
producing the prompt
    Browser (downloadable from (frobnicator-turbo.comorg) wants to access your location.
The profusion of TLDs leaves that plausible even to geeks who aren't familiar with the exact format of the prompt. Few would notice the unmatched bracket. So if it's included at all, I think it should be secondary text, like in the "Details" section for PolicyKit prompts. <https://wiki.ubuntu.com/AccountPrivileges#Details>

Meanwhile, an app could do much more devious things with its name. Maybe we can address these at the same time, or maybe they need to be tackled separately. For example, an app could give itself a name something like
    Browser wants to access only your bookmarks. It does not
which would produce prompt text of the form
    Browser wants to access only your bookmarks. It does not wants to access your location.
This would be slightly ungrammatical ("does not wants"), but would still fool many people.

How to mitigate this? We could put the app name in quote marks:
    “Browser wants to access only your bookmarks. It does not” wants to access your location.
But then the app could jimmy its own opposing quote marks into its name:
    Browser” wants to access only your bookmarks. It does “*not*
producing the only-slightly-less-believable prompt:
    “Browser” wants to access only your bookmarks. It does “*not*” wants to access your location.
(This idea adapted from <https://www.squarefree.com/dialogs2010/exec-warning-injected.png>.)

(We couldn't prevent that by banning use of quote marks, because there are many other Unicode characters that look like quote marks, including apostrophes: Baldur’s Gate, Sid Meier's Civilization, Tony Hawk's Pro Skater 2, etc.)

We could limit the length of app names to something like 30 characters:
    “Browser wants to access only
That would put the kibosh on those obfuscation attacks ... in most languages. Not in kanji ones, unfortunately:
    浏览器要访问您的书签。它不希望访问您的位置。
But maybe mitigating the attack in most but not all languages is better than mitigating it in none of them.

We could put the app name in color, while the rest of the text is the normal UI color. But apparently that hasn't stopped malware in the past. <https://www.squarefree.com/dialogs2010/activex.png>

Perhaps a har...

Read more...

Thomas Voß (thomas-voss) wrote : Posted in a previous version of this proposal
Download full text (3.7 KiB)

> The standard permission prompt message is of the form
> <appname> wants to access <property>.
> for example
> Frobnicator Turbo wants to access your location.
> so the attack you have in mind is for Frobnicator Turbo to pretend to be
> another app:
> Browser wants to access your location.
>
> Especially while people are used to hardly anything happening in the
> background, this might often fail. People would think, "Huh? I'm not using
> Browser at the moment. Why is it asking me now?"
>
> Showing the app icon would help now, but it would lose its effectiveness once
> bug 1453795 is fixed.
>
> Including the application id might protect geeks, but I expect most people
> would ignore it, or think it was a bug, on the grounds that it looks like
> code. If it was embedded in the prompt text,
> <appname> (<appid>) wants to access <property>.
> then an app could even explain it away with a devious name:
> Browser (downloadable from
> producing the prompt
> Browser (downloadable from (frobnicator-turbo.comorg) wants to access your
> location.
> The profusion of TLDs leaves that plausible even to geeks who aren't familiar
> with the exact format of the prompt. Few would notice the unmatched bracket.
> So if it's included at all, I think it should be secondary text, like in the
> "Details" section for PolicyKit prompts.
> <https://wiki.ubuntu.com/AccountPrivileges#Details>
>
> Meanwhile, an app could do much more devious things with its name. Maybe we
> can address these at the same time, or maybe they need to be tackled
> separately. For example, an app could give itself a name something like
> Browser wants to access only your bookmarks. It does not
> which would produce prompt text of the form
> Browser wants to access only your bookmarks. It does not wants to access
> your location.
> This would be slightly ungrammatical ("does not wants"), but would still fool
> many people.
>
> How to mitigate this? We could put the app name in quote marks:
> “Browser wants to access only your bookmarks. It does not” wants to access
> your location.
> But then the app could jimmy its own opposing quote marks into its name:
> Browser” wants to access only your bookmarks. It does “*not*
> producing the only-slightly-less-believable prompt:
> “Browser” wants to access only your bookmarks. It does “*not*” wants to
> access your location.
> (This idea adapted from <https://www.squarefree.com/dialogs2010/exec-warning-
> injected.png>.)
>
> (We couldn't prevent that by banning use of quote marks, because there are
> many other Unicode characters that look like quote marks, including
> apostrophes: Baldur’s Gate, Sid Meier's Civilization, Tony Hawk's Pro Skater
> 2, etc.)
>
> We could limit the length of app names to something like 30 characters:
> “Browser wants to access only
> That would put the kibosh on those obfuscation attacks ... in most languages.
> Not in kanji ones, unfortunately:
> 浏览器要访问您的书签。它不希望访问您的位置。
> But maybe mitigating the attack in most but not all languages is better than
> mitigating it in none of them.
>
> We could put the app name in color, while the rest of the text is the normal
> U...

Read more...

Thomas Voß (thomas-voss) wrote : Posted in a previous version of this proposal

> Thomas, I'm not sure that you need to add a user ID check. I was just curious
> what UID the code was expected to be running in. I'm happy enough with your
> answer that it runs as the user.
>

You were raising a perfectly valid issue. Requests *should* always be handled by user-specific store instances. The branch I proposed ensures that this actually happens or it fails loudly :)

> If you think the XDG code will be reused in other projects, it'd be good to
> package up separately.

Yup, I will tackle it next after this landing. That will also include changes to account for snappy/click-specific filesystem layouts.

Alberto Mardegan (mardy) wrote :

LGTM!

review: Approve
Thomas Voß (thomas-voss) wrote :

Please find a screenshot of the current version here: http://pasteboard.co/2qhB6op7.png

142. By Thomas Voß on 2015-11-26

Address comments in review and implement new prompt design.

143. By Thomas Voß on 2015-11-27

Ensure that rendering is consistent with app icon rendering on launcher/dash.

144. By Thomas Voß on 2015-11-27

Update pot to account for string changes.

Matthew Paul Thomas (mpt) wrote :

A couple of nitpicks, but the screenshot looks good otherwise.

review: Needs Fixing (design)
145. By Thomas Voß on 2015-11-30

Change color of negative action to neutral, see:
  https://developer.ubuntu.com/api/apps/qml/sdk-14.10/Ubuntu.Components.UbuntuColors/#lightGrey-prop
Fix apostrophe to be a real typographic apostrophe.

146. By Thomas Voß on 2015-11-30

Align with app scope display of icons, see:
  https://code.launchpad.net/~cimi/unity8/new-shadows-1.3/+merge/271611

Tyler Hicks (tyhicks) wrote :

Looks good to me. Nice work on finding a better solution!

review: Approve
Matthew Paul Thomas (mpt) :
review: Approve (design)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2015-11-30 10:46:27 +0000
3+++ CMakeLists.txt 2015-11-30 10:46:27 +0000
4@@ -28,7 +28,7 @@
5 ENDIF(CMAKE_BUILD_TYPE MATCHES [cC][oO][vV][eE][rR][aA][gG][eE])
6
7 find_package(PkgConfig)
8-find_package(Boost COMPONENTS program_options system REQUIRED)
9+find_package(Boost COMPONENTS filesystem program_options system REQUIRED)
10
11 add_subdirectory(3rd_party/xdg)
12
13
14=== modified file 'debian/control'
15--- debian/control 2015-11-30 10:46:27 +0000
16+++ debian/control 2015-11-30 10:46:27 +0000
17@@ -14,6 +14,7 @@
18 libdbus-cpp-dev (>= 4.0.0),
19 libdbus-1-dev,
20 libgflags-dev,
21+ libglib2.0-dev,
22 libgoogle-glog-dev,
23 libgtest-dev,
24 libjson-c-dev,
25
26=== modified file 'po/trust-store.pot'
27--- po/trust-store.pot 2015-08-20 17:46:04 +0000
28+++ po/trust-store.pot 2015-11-30 10:46:27 +0000
29@@ -8,7 +8,7 @@
30 msgstr ""
31 "Project-Id-Version: trust-store\n"
32 "Report-Msgid-Bugs-To: \n"
33-"POT-Creation-Date: 2015-08-20 13:41-0400\n"
34+"POT-Creation-Date: 2015-11-27 13:56+0100\n"
35 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
36 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
37 "Language-Team: LANGUAGE <LL@li.org>\n"
38@@ -17,15 +17,14 @@
39 "Content-Type: text/plain; charset=CHARSET\n"
40 "Content-Transfer-Encoding: 8bit\n"
41
42-#: /tmp/trust-store-i18n/src/core/trust/daemon.cpp:265
43-#, boost-format
44-msgid "Application %1% is trying to access"
45-msgstr ""
46-
47-#: /tmp/trust-store-i18n/src/core/trust/mir/prompt_main.qml:42
48-msgid "Deny"
49-msgstr ""
50-
51-#: /tmp/trust-store-i18n/src/core/trust/mir/prompt_main.qml:49
52+#: /home/tvoss/ubuntu/scratch/fix-1504022/src/core/trust/daemon.cpp:265
53+msgid "is trying to access"
54+msgstr ""
55+
56+#: /home/tvoss/ubuntu/scratch/fix-1504022/src/core/trust/mir/prompt_main.qml:78
57 msgid "Allow"
58 msgstr ""
59+
60+#: /home/tvoss/ubuntu/scratch/fix-1504022/src/core/trust/mir/prompt_main.qml:83
61+msgid "Don't Allow"
62+msgstr ""
63
64=== modified file 'src/CMakeLists.txt'
65--- src/CMakeLists.txt 2015-11-30 10:46:27 +0000
66+++ src/CMakeLists.txt 2015-11-30 10:46:27 +0000
67@@ -20,6 +20,8 @@
68
69 pkg_check_modules(DBUS_CPP dbus-cpp REQUIRED)
70 pkg_check_modules(DBUS dbus-1 REQUIRED)
71+pkg_check_modules(GLIB glib-2.0 REQUIRED)
72+pkg_check_modules(GOBJECT gobject-2.0 REQUIRED)
73 pkg_check_modules(LIBAPPARMOR libapparmor REQUIRED)
74 pkg_check_modules(SQLITE3 sqlite3 REQUIRED)
75
76@@ -28,6 +30,8 @@
77 include_directories(
78 ${DBUS_CPP_INCLUDE_DIRS}
79 ${DBUS_INCLUDE_DIRS}
80+ ${GLIB_INCLUDE_DIRS}
81+ ${GOBJECT_INCLUDE_DIRS}
82 ${LIBAPPARMOR_INCLUDE_DIRS}
83 ${SQLITE3_INCLUDE_DIRS}
84 )
85@@ -70,6 +74,7 @@
86 # to prompt the user for trusting an application to access a trusted
87 # system service.
88 core/trust/mir/agent.cpp
89+ core/trust/mir/click_desktop_entry_app_info_resolver.cpp
90 )
91
92 # Make sure Qt does not inject evil macros like 'signals' and 'slots'.
93@@ -186,6 +191,8 @@
94 ${DBUS_LIBRARIES}
95 ${GFLAGS_LDFLAGS}
96 ${GLOG_LDFLAGS}
97+ ${GLIB_LDFLAGS}
98+ ${GOBJECT_LDFLAGS}
99 ${LIBAPPARMOR_LDFLAGS}
100 ${MIR_CLIENT_LDFLAGS}
101 ${PROCESS_CPP_LDFLAGS}
102
103=== modified file 'src/core/trust/daemon.cpp'
104--- src/core/trust/daemon.cpp 2015-08-31 14:00:41 +0000
105+++ src/core/trust/daemon.cpp 2015-11-30 10:46:27 +0000
106@@ -262,7 +262,7 @@
107 core::trust::remote::helpers::aa_get_task_con_app_id_resolver(),
108 dict.count("description-pattern") > 0 ?
109 dict.at("description-pattern") :
110- core::trust::i18n::tr("Application %1% is trying to access") + " " + service_name + ".",
111+ core::trust::i18n::tr("is trying to access") + " " + service_name + ".",
112 dict.count("verify-process-timestamp") > 0
113 };
114
115
116=== modified file 'src/core/trust/mir/agent.cpp'
117--- src/core/trust/mir/agent.cpp 2015-08-31 13:16:20 +0000
118+++ src/core/trust/mir/agent.cpp 2015-11-30 10:46:27 +0000
119@@ -33,6 +33,11 @@
120
121 namespace mir = core::trust::mir;
122
123+bool mir::operator==(const mir::AppInfo& lhs, const mir::AppInfo& rhs)
124+{
125+ return lhs.icon == rhs.icon && lhs.id == rhs.id && lhs.name == rhs.name;
126+}
127+
128 // Invoked whenever a request for creation of pre-authenticated fds succeeds.
129 void mir::PromptSessionVirtualTable::mir_client_fd_callback(MirPromptSession */*prompt_session*/, size_t count, int const* fds, void* context)
130 {
131@@ -134,27 +139,15 @@
132 {
133 static auto child_setup = []() {};
134
135- // We translate to human readable strings here, and do it a non-translateable way first
136- // We post-process the application id and try to extract the unversioned package name.
137- // Please see https://wiki.ubuntu.com/AppStore/Interfaces/ApplicationId.
138- static const std::regex regex_full_app_id{"(.*)_(.*)_(.*)"};
139- static const std::regex regex_short_app_id{"(.*)_(.*)"};
140- static constexpr std::size_t index_app{2};
141-
142- auto app_name = args.application_id;
143-
144- std::smatch match;
145- if (std::regex_match(app_name, match, regex_full_app_id))
146- app_name = std::string{match[index_app]};
147- else if (std::regex_match(app_name, match, regex_short_app_id))
148- app_name = std::string{match[index_app]};
149-
150- auto description = (boost::format(i18n::tr(args.description, i18n::service_text_domain())) % app_name).str();
151+ auto app_name = args.app_info.name;
152+ auto description = i18n::tr(args.description, i18n::service_text_domain());
153
154 std::vector<std::string> argv
155 {
156 "--" + std::string{core::trust::mir::cli::option_server_socket}, "fd://" + std::to_string(args.fd),
157- "--" + std::string{core::trust::mir::cli::option_title}, app_name,
158+ "--" + std::string{core::trust::mir::cli::option_icon}, args.app_info.icon,
159+ "--" + std::string{core::trust::mir::cli::option_name}, args.app_info.name,
160+ "--" + std::string{core::trust::mir::cli::option_id}, args.app_info.id,
161 "--" + std::string{core::trust::mir::cli::option_description}, description
162 };
163
164@@ -219,17 +212,8 @@
165 };
166 }
167
168-mir::Agent::Agent(
169- // VTable object providing access to Mir's trusted prompting functionality.
170- const mir::ConnectionVirtualTable::Ptr& connection_vtable,
171- // Exec helper for starting up prompt provider child processes with the correct setup
172- // of command line arguments and environment variables.
173- const mir::PromptProviderHelper::Ptr& exec_helper,
174- // A translator function for mapping child process exit states to trust::Request answers.
175- const std::function<core::trust::Request::Answer(const core::posix::wait::Result&)>& translator)
176- : connection_vtable(connection_vtable),
177- exec_helper(exec_helper),
178- translator(translator)
179+mir::Agent::Agent(const mir::Agent::Configuration& config)
180+ : config(config)
181 {
182 }
183
184@@ -260,7 +244,7 @@
185 } scope
186 {
187 // We setup the prompt session and wire up to our own internal callback helper.
188- connection_vtable->create_prompt_session_sync(
189+ config.connection_vtable->create_prompt_session_sync(
190 parameters.application.pid,
191 Agent::on_trust_session_changed_state,
192 &cb_context)
193@@ -273,24 +257,25 @@
194 mir::PromptProviderHelper::InvocationArguments args
195 {
196 fd,
197- parameters.application.id,
198+ config.app_info_resolver->resolve(parameters.application.id),
199 parameters.description
200 };
201
202 // Ask the helper to fire up the prompt provider.
203- cb_context.prompt_provider_process = exec_helper->exec_prompt_provider_with_arguments(args);
204+ cb_context.prompt_provider_process = config.exec_helper->exec_prompt_provider_with_arguments(args);
205 // And subsequently wait for it to finish.
206 auto result = cb_context.prompt_provider_process.wait_for(core::posix::wait::Flags::untraced);
207
208- return translator(result);
209+ return config.translator(result);
210 }
211
212 bool mir::operator==(const mir::PromptProviderHelper::InvocationArguments& lhs, const mir::PromptProviderHelper::InvocationArguments& rhs)
213 {
214- return std::tie(lhs.application_id, lhs.description, lhs.fd) == std::tie(rhs.application_id, rhs.description, rhs.fd);
215+ return std::tie(lhs.app_info, lhs.description, lhs.fd) == std::tie(rhs.app_info, rhs.description, rhs.fd);
216 }
217
218 #include "config.h"
219+#include "click_desktop_entry_app_info_resolver.h"
220
221 MirConnection* mir::connect(const std::string& endpoint, const std::string& name)
222 {
223@@ -318,13 +303,8 @@
224 }
225 };
226
227- return mir::Agent::Ptr
228- {
229- new mir::Agent
230- {
231- cvt,
232- pph,
233- mir::Agent::translator_only_accepting_exit_status_success()
234- }
235- };
236+ mir::AppInfoResolver::Ptr anr{new mir::ClickDesktopEntryAppInfoResolver{}};
237+
238+ mir::Agent::Configuration config{cvt, pph, mir::Agent::translator_only_accepting_exit_status_success(), anr};
239+ return mir::Agent::Ptr{new mir::Agent{config}};
240 }
241
242=== modified file 'src/core/trust/mir/agent.h'
243--- src/core/trust/mir/agent.h 2014-10-07 19:27:18 +0000
244+++ src/core/trust/mir/agent.h 2015-11-30 10:46:27 +0000
245@@ -27,6 +27,8 @@
246 #include <mirclient/mir_toolkit/mir_client_library.h>
247 #include <mirclient/mir_toolkit/mir_prompt_session.h>
248
249+#include <boost/filesystem.hpp>
250+
251 #include <condition_variable>
252 #include <functional>
253 #include <mutex>
254@@ -37,6 +39,17 @@
255 {
256 namespace mir
257 {
258+// Info bundles information about an application.
259+struct AppInfo
260+{
261+ std::string icon; // The icon of the application.
262+ std::string name; // The human-readable, localized name of the application.
263+ std::string id; // The unique id of the application.
264+};
265+
266+// operator== returns true iff lhs is exactly equal to rhs.
267+bool operator==(const AppInfo& lhs, const AppInfo& rhs);
268+
269 // We wrap the Mir prompt session API into a struct to
270 // ease with testing and mocking.
271 class CORE_TRUST_DLL_PUBLIC PromptSessionVirtualTable
272@@ -129,8 +142,8 @@
273 // The pre-authenticated fd that the helper
274 // should use for connecting to Mir.
275 int fd;
276- // The application id of the requesting app.
277- std::string application_id;
278+ // Application-specific information goes here.
279+ AppInfo app_info;
280 // The extended description that should be presented to the user.
281 std::string description;
282 };
283@@ -146,6 +159,17 @@
284 CreationArguments creation_arguments;
285 };
286
287+// An AppNameResolver resolves an application id to a localized application name.
288+struct AppInfoResolver
289+{
290+ // Save us some typing.
291+ typedef std::shared_ptr<AppInfoResolver> Ptr;
292+
293+ virtual ~AppInfoResolver() = default;
294+ // resolve maps app_id to a localized application name.
295+ virtual AppInfo resolve(const std::string& app_id) = 0;
296+};
297+
298 // Implements the trust::Agent interface and dispatches calls to a helper
299 // prompt provider, tying it together with the requesting service and app
300 // by leveraging Mir's trusted session/prompting support.
301@@ -154,6 +178,20 @@
302 // Convenience typedef
303 typedef std::shared_ptr<Agent> Ptr;
304
305+ // A Configuration bundles creation-time configuration options.
306+ struct Configuration
307+ {
308+ // VTable object providing access to Mir's trusted prompting functionality.
309+ ConnectionVirtualTable::Ptr connection_vtable;
310+ // Exec helper for starting up prompt provider child processes with the correct setup
311+ // of command line arguments and environment variables.
312+ PromptProviderHelper::Ptr exec_helper;
313+ // A translator function for mapping child process exit states to trust::Request answers.
314+ std::function<core::trust::Request::Answer(const core::posix::wait::Result&)> translator;
315+ // AppNameResolver used by the agent to map incoming request app ids to application names.
316+ AppInfoResolver::Ptr app_info_resolver;
317+ };
318+
319 // Helper struct for injecting state into on_trust_changed_state_state callbacks.
320 // Used in prompt_user_for_request to wait for the trust session to be stopped.
321 struct OnTrustSessionStateChangedCallbackContext
322@@ -177,26 +215,16 @@
323 // Throws std::logic_error if the process did not exit but was signaled.
324 static std::function<core::trust::Request::Answer(const core::posix::wait::Result&)> translator_only_accepting_exit_status_success();
325
326- // Creates a new MirAgent instance with the given MirConnectionVirtualTable instance.
327- Agent(// VTable object providing access to Mir's trusted prompting functionality.
328- const ConnectionVirtualTable::Ptr& connection_vtable,
329- // Exec helper for starting up prompt provider child processes with the correct setup
330- // of command line arguments and environment variables.
331- const PromptProviderHelper::Ptr& exec_helper,
332- // A translator function for mapping child process exit states to trust::Request answers.
333- const std::function<core::trust::Request::Answer(const core::posix::wait::Result&)>& translator);
334+ // Creates a new MirAgent instance with the given Configuration.
335+ Agent(const Configuration& config);
336
337 // From core::trust::Agent:
338 // Throws a std::logic_error if anything unforeseen happens during execution, thus
339 // indicating that no conclusive answer could be obtained from the user.
340 core::trust::Request::Answer authenticate_request_with_parameters(const RequestParameters& parameters) override;
341
342- // The connection VTable used for creating trusted prompting sessions.
343- ConnectionVirtualTable::Ptr connection_vtable;
344- // Execution helper for firing up prompt provider executables.
345- PromptProviderHelper::Ptr exec_helper;
346- // Translator instance.
347- std::function<core::trust::Request::Answer(const core::posix::wait::Result&)> translator;
348+ // The configured options.
349+ Configuration config;
350 };
351
352 CORE_TRUST_DLL_PUBLIC bool operator==(const PromptProviderHelper::InvocationArguments&, const PromptProviderHelper::InvocationArguments&);
353
354=== added file 'src/core/trust/mir/click_desktop_entry_app_info_resolver.cpp'
355--- src/core/trust/mir/click_desktop_entry_app_info_resolver.cpp 1970-01-01 00:00:00 +0000
356+++ src/core/trust/mir/click_desktop_entry_app_info_resolver.cpp 2015-11-30 10:46:27 +0000
357@@ -0,0 +1,139 @@
358+/*
359+ * Copyright © 2015 Canonical Ltd.
360+ *
361+ * This program is free software: you can redistribute it and/or modify it
362+ * under the terms of the GNU Lesser General Public License version 3,
363+ * as published by the Free Software Foundation.
364+ *
365+ * This program is distributed in the hope that it will be useful,
366+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
367+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
368+ * GNU Lesser General Public License for more details.
369+ *
370+ * You should have received a copy of the GNU Lesser General Public License
371+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
372+ *
373+ * Authored by: Thomas Voß <thomas.voss@canonical.com>
374+ */
375+
376+#include <core/trust/mir/click_desktop_entry_app_info_resolver.h>
377+
378+#include <glib.h>
379+#include <xdg.h>
380+
381+#include <core/posix/this_process.h>
382+
383+#include <boost/algorithm/string.hpp>
384+#include <boost/filesystem.hpp>
385+#include <boost/format.hpp>
386+
387+#include <memory>
388+#include <regex>
389+#include <string>
390+#include <stdexcept>
391+#include <vector>
392+
393+namespace env = core::posix::this_process::env;
394+namespace fs = boost::filesystem;
395+namespace mir = core::trust::mir;
396+
397+namespace
398+{
399+fs::path resolve_desktop_entry_or_throw(const std::string& app_id)
400+{
401+ auto p = xdg::data().home() / "applications" / (app_id + ".desktop");
402+ if (fs::is_regular_file(p))
403+ return p;
404+
405+ fs::path applications{xdg::data().home() / "applications"};
406+ fs::directory_iterator it(applications), itE;
407+ while (it != itE)
408+ {
409+ if (it->path().filename().string().find(app_id) == 0)
410+ return it->path();
411+ ++it;
412+ }
413+
414+ for (auto dir : xdg::data().dirs())
415+ {
416+ auto p = dir / "applications" / (app_id + ".desktop");
417+ if (fs::is_regular_file(p))
418+ return p;
419+ }
420+
421+ throw std::runtime_error{"Could not resolve desktop entry for " + app_id};
422+}
423+
424+// Wrap up a GError with an RAII approach, easing
425+// cleanup if we throw an exception.
426+struct Error
427+{
428+ ~Error() { clear(); }
429+ void clear() { g_clear_error(&error); }
430+
431+ GError* error = nullptr;
432+};
433+
434+std::string name_from_desktop_entry_or_throw(const fs::path& fn)
435+{
436+ Error g;
437+ std::shared_ptr<GKeyFile> key_file{g_key_file_new(), [](GKeyFile* file) { if (file) g_key_file_free(file); }};
438+
439+ if (not g_key_file_load_from_file(key_file.get(), fn.string().c_str(), G_KEY_FILE_NONE, &g.error)) throw std::runtime_error
440+ {
441+ "Failed to load desktop entry [" + std::string(g.error->message) + "]"
442+ };
443+
444+ auto app_name = g_key_file_get_locale_string(key_file.get(), G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NAME, nullptr, &g.error);
445+
446+ if (g.error) throw std::runtime_error
447+ {
448+ "Failed to query localized name [" + std::string(g.error->message) + "]"
449+ };
450+
451+ return app_name;
452+}
453+
454+std::string icon_from_desktop_entry_or_throw(const fs::path& fn)
455+{
456+ Error g;
457+ std::shared_ptr<GKeyFile> key_file{g_key_file_new(), [](GKeyFile* file) { if (file) g_key_file_free(file); }};
458+
459+ if (not g_key_file_load_from_file(key_file.get(), fn.string().c_str(), G_KEY_FILE_NONE, &g.error)) throw std::runtime_error
460+ {
461+ "Failed to load desktop entry [" + std::string(g.error->message) + "]"
462+ };
463+
464+ auto app_icon = g_key_file_get_locale_string(key_file.get(), G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON, nullptr, &g.error);
465+
466+ if (g.error) throw std::runtime_error
467+ {
468+ "Failed to query icon [" + std::string(g.error->message) + "]"
469+ };
470+
471+ // We expect an absolute path to a regular file representing the icon. Ideally, we should
472+ // also run further checks like the file not being part of another app for example.
473+ fs::path p{app_icon};
474+ if (not p.is_absolute() || not fs::is_regular_file(fs::status(p))) throw std::runtime_error
475+ {
476+ "Icon path is either not absolute or not pointing to a regular file [" + std::string{app_icon} + "]"
477+ };
478+
479+ return app_icon;
480+}
481+}
482+
483+mir::ClickDesktopEntryAppInfoResolver::ClickDesktopEntryAppInfoResolver()
484+{
485+}
486+
487+mir::AppInfo mir::ClickDesktopEntryAppInfoResolver::resolve(const std::string& app_id)
488+{
489+ auto de = resolve_desktop_entry_or_throw(app_id);
490+ return mir::AppInfo
491+ {
492+ icon_from_desktop_entry_or_throw(de),
493+ name_from_desktop_entry_or_throw(de),
494+ app_id
495+ };
496+}
497
498=== added file 'src/core/trust/mir/click_desktop_entry_app_info_resolver.h'
499--- src/core/trust/mir/click_desktop_entry_app_info_resolver.h 1970-01-01 00:00:00 +0000
500+++ src/core/trust/mir/click_desktop_entry_app_info_resolver.h 2015-11-30 10:46:27 +0000
501@@ -0,0 +1,49 @@
502+/*
503+ * Copyright © 2015 Canonical Ltd.
504+ *
505+ * This program is free software: you can redistribute it and/or modify it
506+ * under the terms of the GNU Lesser General Public License version 3,
507+ * as published by the Free Software Foundation.
508+ *
509+ * This program is distributed in the hope that it will be useful,
510+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
511+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
512+ * GNU Lesser General Public License for more details.
513+ *
514+ * You should have received a copy of the GNU Lesser General Public License
515+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
516+ *
517+ * Authored by: Thomas Voß <thomas.voss@canonical.com>
518+ */
519+
520+#ifndef CORE_TRUST_MIR_CLICK_DESKTOP_ENTRY_APP_NAME_RESOLVER_H_
521+#define CORE_TRUST_MIR_CLICK_DESKTOP_ENTRY_APP_NAME_RESOLVER_H_
522+
523+#include <core/trust/mir/agent.h>
524+
525+namespace core
526+{
527+namespace trust
528+{
529+namespace mir
530+{
531+// A ClickDesktopEntryAppNameResolver queries the click database of installed
532+// packages to resolve an app's installation folder in the local filesystem.
533+// The directory is searched for a .desktop file, that is then loaded and queried
534+// for the app's localized name.
535+class CORE_TRUST_DLL_PUBLIC ClickDesktopEntryAppInfoResolver : public AppInfoResolver
536+{
537+public:
538+ // ClickDesktopEntryAppNameResolver sets up an instance with default dbs.
539+ ClickDesktopEntryAppInfoResolver();
540+
541+ // resolve queries the click index and an apps desktop file entry for
542+ // obtaining a localized application name. Throws std::runtime_error in
543+ // case of issues.
544+ AppInfo resolve(const std::string& app_id) override;
545+};
546+}
547+}
548+}
549+
550+#endif // CORE_TRUST_MIR_CLICK_DESKTOP_ENTRY_APP_NAME_RESOLVER_H_
551
552=== modified file 'src/core/trust/mir/prompt_main.cpp'
553--- src/core/trust/mir/prompt_main.cpp 2015-08-31 13:16:20 +0000
554+++ src/core/trust/mir/prompt_main.cpp 2015-11-30 10:46:27 +0000
555@@ -74,9 +74,19 @@
556 {
557 // We are throwing exceptions here, which immediately calls abort and still gives a
558 // helpful error message on the terminal.
559- if (vm.count(cli::option_title) == 0) throw std::logic_error
560- {
561- "Missing option title."
562+ if (vm.count(cli::option_icon) == 0) throw std::logic_error
563+ {
564+ "Missing option icon."
565+ };
566+
567+ if (vm.count(cli::option_name) == 0) throw std::logic_error
568+ {
569+ "Missing option name."
570+ };
571+
572+ if (vm.count(cli::option_id) == 0) throw std::logic_error
573+ {
574+ "Missing option id."
575 };
576
577 if (vm.count(cli::option_description) == 0) throw std::logic_error
578@@ -84,17 +94,13 @@
579 "Missing option description"
580 };
581
582- if (vm.count(cli::option_server_socket) == 0) throw std::logic_error
583- {
584- "Missing option mir_server_socket"
585- };
586-
587- std::string mir_server_socket = vm[cli::option_server_socket].as<std::string>();
588-
589- if (mir_server_socket.find("fd://") != 0) throw std::logic_error
590- {
591- "mir_server_socket does not being with fd://"
592- };
593+ if (vm.count(cli::option_server_socket) > 0)
594+ {
595+ if (vm[cli::option_server_socket].as<std::string>().find("fd://") != 0) throw std::logic_error
596+ {
597+ "mir_server_socket does not being with fd://"
598+ };
599+ }
600 }
601 }
602 }
603@@ -104,8 +110,10 @@
604 boost::program_options::options_description options;
605 options.add_options()
606 (cli::option_server_socket, boost::program_options::value<std::string>(), "Mir server socket to connect to.")
607+ (cli::option_icon, boost::program_options::value<std::string>(), "Icon of the requesting application.")
608+ (cli::option_name, boost::program_options::value<std::string>(), "Name of the requesting application.")
609+ (cli::option_id, boost::program_options::value<std::string>(), "Id of the requesting application.")
610 (cli::option_description, boost::program_options::value<std::string>(), "Extended description of the prompt.")
611- (cli::option_title, boost::program_options::value<std::string>(), "Title of the prompt.")
612 (cli::option_testing, "Only checks command-line parameters and does not execute any actions.")
613 (cli::option_testability, "Loads the Qt Testability plugin if provided.");
614
615@@ -124,30 +132,29 @@
616 boost::program_options::store(parsed_options, vm);
617 boost::program_options::notify(vm);
618
619+ // We immediately bail out if verification of command line arguments fails.
620+ testing::validate_command_line_arguments(vm);
621+
622 // We just verify command line arguments in testing and immediately return.
623 if (vm.count(cli::option_testing) > 0)
624- {
625- testing::validate_command_line_arguments(vm); return 0;
626- }
627-
628- // Let's validate the arguments.
629- if (vm.count(cli::option_title) == 0)
630- abort(); // We have to call abort here to make sure that we get signalled.
631-
632- std::string title = vm[cli::option_title].as<std::string>();
633-
634- std::string description;
635- if (vm.count(cli::option_description) > 0)
636- description = vm[cli::option_description].as<std::string>();
637+ return 0;
638+
639+ auto icon = vm[cli::option_icon].as<std::string>();
640+ auto name = vm[cli::option_name].as<std::string>();
641+ auto id = vm[cli::option_id].as<std::string>();
642+ // As per design, we replace "_" in app ids with a "/", thereby
643+ // rendering the app id a little nicer. See:
644+ //
645+ // http://pasteboard.co/2qhB6op7.png
646+ //
647+ // for an example.
648+ std::replace(id.begin(), id.end(), '_', '/');
649+ auto description = vm[cli::option_description].as<std::string>();
650
651 if (vm.count(cli::option_server_socket) > 0)
652 {
653- // We make sure that the env variable is not set prior to adusting it.
654 this_env::unset_or_throw(env::option_mir_socket);
655-
656- this_env::set_or_throw(
657- env::option_mir_socket,
658- vm[cli::option_server_socket].as<std::string>());
659+ this_env::set_or_throw(env::option_mir_socket, vm[cli::option_server_socket].as<std::string>());
660 }
661
662 // We install our default gettext domain prior to anything qt.
663@@ -202,14 +209,10 @@
664 view->setTitle(QGuiApplication::applicationName());
665
666 // Make some properties known to the root context.
667- // The title of the dialog.
668- view->rootContext()->setContextProperty(
669- cli::option_title,
670- title.c_str());
671- // The description of the dialog.
672- view->rootContext()->setContextProperty(
673- cli::option_description,
674- description.c_str());
675+ view->rootContext()->setContextProperty(cli::option_icon, icon.c_str());
676+ view->rootContext()->setContextProperty(cli::option_name, name.c_str());
677+ view->rootContext()->setContextProperty(cli::option_id, id.c_str());
678+ view->rootContext()->setContextProperty(cli::option_description, description.c_str());
679
680 // Point the engine to the right directory. Please note that
681 // the respective value changes with the installation state.
682
683=== modified file 'src/core/trust/mir/prompt_main.h'
684--- src/core/trust/mir/prompt_main.h 2015-02-11 13:04:24 +0000
685+++ src/core/trust/mir/prompt_main.h 2015-11-30 10:46:27 +0000
686@@ -46,10 +46,22 @@
687 "mir_server_socket"
688 };
689
690-/** @brief Title of the prompt. */
691-static constexpr const char* option_title
692-{
693- "title"
694+/** @brief Icon of the requesting app. */
695+static constexpr const char* option_icon
696+{
697+ "icon"
698+};
699+
700+/** @brief Name of the requesting app. */
701+static constexpr const char* option_name
702+{
703+ "name"
704+};
705+
706+/** @brief Id of the requesting app. */
707+static constexpr const char* option_id
708+{
709+ "id"
710 };
711
712 /** @brief Extended description of the prompt. */
713
714=== modified file 'src/core/trust/mir/prompt_main.qml'
715--- src/core/trust/mir/prompt_main.qml 2014-11-06 10:38:33 +0000
716+++ src/core/trust/mir/prompt_main.qml 2015-11-30 10:46:27 +0000
717@@ -15,40 +15,76 @@
718 */
719
720 import QtQuick 2.0
721-import Ubuntu.Components 1.1
722-import Ubuntu.Components.Popups 1.0
723-
724-Rectangle {
725- width: units.gu(40)
726- height: units.gu(71)
727- color: "transparent"
728-
729- property var dialogTitle: title
730- property var dialogDescription: description
731+import Ubuntu.Components 1.3
732+import Ubuntu.Components.Popups 1.3
733+
734+Item {
735+
736+ property var appIcon: icon
737+ property var appName: name
738+ property var appId: id
739+ property var serviceDescription: description
740
741 signal quit(int code)
742
743- Component.onCompleted: dialog.show();
744-
745- Dialog {
746+ Component {
747 id: dialog
748-
749- title: dialogTitle
750- text: dialogDescription
751- fadingAnimation: PropertyAnimation { duration: 0 }
752-
753- Button {
754- objectName: "deny"
755- text: i18n.tr("Deny")
756- color: UbuntuColors.red
757- onClicked: quit(1)
758- }
759-
760- Button {
761- objectName: "allow"
762- text: i18n.tr("Allow")
763- color: UbuntuColors.green
764- onClicked: quit(0)
765+ Dialog {
766+ id: dialogue
767+ Column {
768+ spacing: units.gu(0.5)
769+ UbuntuShape {
770+ anchors.horizontalCenter: parent.horizontalCenter
771+ id: iconShape
772+ radius: "medium"
773+ aspect: UbuntuShape.DropShadow
774+ anchors.margins: units.gu(1)
775+ sourceFillMode: UbuntuShape.PreserveAspectCrop
776+ source: Image {
777+ id: icon
778+ sourceSize.width: iconShape.width
779+ sourceSize.height: iconShape.height
780+ source: appIcon
781+ }
782+ }
783+ Label {
784+ anchors.horizontalCenter: parent.horizontalCenter
785+ text: appName
786+ horizontalAlignment: Text.AlignHCenter
787+ width: parent.width
788+ elide: Text.ElideRight
789+ wrapMode: Text.Wrap
790+ maximumLineCount: 2
791+ }
792+ Label {
793+ anchors.horizontalCenter: parent.horizontalCenter
794+ text: appId
795+ color: UbuntuColors.lightGrey
796+ fontSize: "small"
797+ width: parent.width
798+ horizontalAlignment: Text.AlignHCenter
799+ elide: Text.ElideMiddle
800+ maximumLineCount: 1
801+ }
802+ }
803+ Label {
804+ anchors.horizontalCenter: parent.horizontalCenter
805+ text: serviceDescription
806+ horizontalAlignment: Text.AlignHCenter
807+ width: parent.width
808+ wrapMode: Text.Wrap
809+ }
810+ Button {
811+ text: i18n.tr("Allow")
812+ color: UbuntuColors.green
813+ onClicked: quit(0)
814+ }
815+ Button {
816+ text: i18n.tr("Don’t Allow")
817+ color: UbuntuColors.lightGrey
818+ onClicked: quit(1)
819+ }
820 }
821 }
822+ Component.onCompleted: PopupUtils.open(dialog)
823 }
824
825=== modified file 'tests/CMakeLists.txt'
826--- tests/CMakeLists.txt 2015-11-30 10:46:27 +0000
827+++ tests/CMakeLists.txt 2015-11-30 10:46:27 +0000
828@@ -242,6 +242,11 @@
829 mir_agent_test.cpp
830 )
831
832+ add_executable(
833+ click_desktop_entry_app_name_resolver_test
834+ click_desktop_entry_app_name_resolver_test.cpp
835+ )
836+
837 target_link_libraries(
838 mir_agent_test
839
840@@ -256,7 +261,22 @@
841 ${PROCESS_CPP_LIBRARIES}
842 )
843
844+ target_link_libraries(
845+ click_desktop_entry_app_name_resolver_test
846+
847+ trust-store
848+
849+ gmock
850+
851+ gtest
852+ gtest_main
853+
854+ ${PROCESS_CPP_LIBRARIES}
855+ )
856+
857+
858 add_test(mir_agent_test ${CMAKE_CURRENT_BINARY_DIR}/mir_agent_test --gtest_filter=*-*requires_mir)
859+ add_test(click_desktop_entry_app_name_resolver_test ${CMAKE_CURRENT_BINARY_DIR}/click_desktop_entry_app_name_resolver_test)
860
861 install(
862 TARGETS mir_agent_test
863
864=== added file 'tests/click_desktop_entry_app_name_resolver_test.cpp'
865--- tests/click_desktop_entry_app_name_resolver_test.cpp 1970-01-01 00:00:00 +0000
866+++ tests/click_desktop_entry_app_name_resolver_test.cpp 2015-11-30 10:46:27 +0000
867@@ -0,0 +1,75 @@
868+/*
869+ * Copyright © 2013 Canonical Ltd.
870+ *
871+ * This program is free software: you can redistribute it and/or modify it
872+ * under the terms of the GNU Lesser General Public License version 3,
873+ * as published by the Free Software Foundation.
874+ *
875+ * This program is distributed in the hope that it will be useful,
876+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
877+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
878+ * GNU Lesser General Public License for more details.
879+ *
880+ * You should have received a copy of the GNU Lesser General Public License
881+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
882+ *
883+ * Authored by: Thomas Voß <thomas.voss@canonical.com>
884+ */
885+
886+#include <core/trust/mir/click_desktop_entry_app_info_resolver.h>
887+#include <core/posix/this_process.h>
888+#include "test_data.h"
889+
890+#include <gtest/gtest.h>
891+
892+#include <fstream>
893+
894+namespace env = core::posix::this_process::env;
895+namespace mir = core::trust::mir;
896+
897+namespace
898+{
899+bool ensure_icon_file()
900+{
901+ std::ofstream out{"/tmp/test.icon"};
902+ out << "test.icon";
903+ return out.good();
904+}
905+}
906+
907+TEST(ClickDesktopEntryAppInfoResolver, throws_for_invalid_app_id)
908+{
909+ ASSERT_TRUE(ensure_icon_file());
910+ mir::ClickDesktopEntryAppInfoResolver resolver;
911+ EXPECT_THROW(resolver.resolve("something:not:right"), std::runtime_error);
912+}
913+
914+TEST(ClickDesktopEntryAppInfoResolver, resolves_existing_desktop_entry_from_xdg_data_home)
915+{
916+ ASSERT_TRUE(ensure_icon_file());
917+ env::unset_or_throw("XDG_DATA_HOME");
918+ env::set_or_throw("XDG_DATA_HOME", core::trust::testing::current_source_dir + std::string("/share"));
919+ mir::ClickDesktopEntryAppInfoResolver resolver;
920+ EXPECT_NO_THROW(resolver.resolve("valid.pkg_app_0.0.0"));
921+}
922+
923+TEST(ClickDesktopEntryAppInfoResolver, robustly_resolves_existing_desktop_entry_from_xdg_data_home)
924+{
925+ ASSERT_TRUE(ensure_icon_file());
926+ env::unset_or_throw("XDG_DATA_HOME");
927+ env::set_or_throw("XDG_DATA_HOME", core::trust::testing::current_source_dir + std::string("/share"));
928+
929+ mir::ClickDesktopEntryAppInfoResolver resolver;
930+ EXPECT_NO_THROW(resolver.resolve("valid.pkg_app"));
931+}
932+
933+TEST(ClickDesktopEntryAppInfoResolver, resolves_existing_desktop_entry_from_xdg_data_dirs)
934+{
935+ ASSERT_TRUE(ensure_icon_file());
936+ env::unset_or_throw("XDG_DATA_HOME"); env::unset_or_throw("XDG_DATA_DIRS");
937+ env::set_or_throw("XDG_DATA_HOME", core::trust::testing::current_source_dir + std::string("/empty"));
938+ env::set_or_throw("XDG_DATA_DIRS", core::trust::testing::current_source_dir + std::string("/share"));
939+
940+ mir::ClickDesktopEntryAppInfoResolver resolver;
941+ resolver.resolve("valid.pkg_app_0.0.0");
942+}
943
944=== added directory 'tests/empty'
945=== added directory 'tests/empty/applications'
946=== modified file 'tests/mir_agent_test.cpp'
947--- tests/mir_agent_test.cpp 2015-11-30 10:46:27 +0000
948+++ tests/mir_agent_test.cpp 2015-11-30 10:46:27 +0000
949@@ -99,6 +99,11 @@
950 MOCK_METHOD1(translate, core::trust::Request::Answer(const core::posix::wait::Result&));
951 };
952
953+struct MockAppInfoResolver : public core::trust::mir::AppInfoResolver
954+{
955+ MOCK_METHOD1(resolve, core::trust::mir::AppInfo(const std::string&));
956+};
957+
958 std::shared_ptr<MockConnectionVirtualTable> a_mocked_connection_vtable()
959 {
960 return std::make_shared<testing::NiceMock<MockConnectionVirtualTable>>();
961@@ -117,6 +122,11 @@
962 "/bin/false"
963 });
964 }
965+
966+std::shared_ptr<MockAppInfoResolver> a_mocked_app_info_resolver()
967+{
968+ return std::make_shared<MockAppInfoResolver>();
969+}
970 }
971
972 TEST(DefaultProcessStateTranslator, throws_for_signalled_process)
973@@ -172,7 +182,11 @@
974 core::trust::mir::PromptProviderHelper::InvocationArguments iargs
975 {
976 42,
977- "does.not.exist.application",
978+ {
979+ "/tmp",
980+ "Does not exist",
981+ "does.not.exist.application"
982+ },
983 "Just an extended description for %1%"
984 };
985
986@@ -193,6 +207,8 @@
987 using namespace ::testing;
988
989 const core::trust::Pid app_pid {21};
990+ const std::string app_icon{"/tmp"};
991+ const std::string app_name {"Does not exist"};
992 const std::string app_id {"does.not.exist.application"};
993 const core::trust::Feature feature{42};
994 const std::string app_description {"This is just an extended description %1%"};
995@@ -201,7 +217,11 @@
996 const core::trust::mir::PromptProviderHelper::InvocationArguments reference_invocation_args
997 {
998 pre_authenticated_fd,
999- app_id,
1000+ {
1001+ app_icon,
1002+ app_name,
1003+ app_id
1004+ },
1005 app_description
1006 };
1007
1008@@ -209,6 +229,7 @@
1009 auto prompt_session_vtable = a_mocked_prompt_session_vtable();
1010
1011 auto prompt_provider_exec_helper = a_mocked_prompt_provider_calling_bin_false();
1012+ auto app_info_resolver = a_mocked_app_info_resolver();
1013
1014 ON_CALL(*connection_vtable, create_prompt_session_sync(_, _, _))
1015 .WillByDefault(Return(prompt_session_vtable));
1016@@ -219,6 +240,9 @@
1017 ON_CALL(*prompt_session_vtable, add_prompt_provider_sync(_))
1018 .WillByDefault(Return(true));
1019
1020+ ON_CALL(*app_info_resolver, resolve(_))
1021+ .WillByDefault(Return(core::trust::mir::AppInfo{app_icon, app_name, app_id}));
1022+
1023 EXPECT_CALL(*connection_vtable, create_prompt_session_sync(app_pid, _, _)).Times(1);
1024 EXPECT_CALL(*prompt_session_vtable, new_fd_for_prompt_provider()).Times(1);
1025
1026@@ -228,21 +252,94 @@
1027
1028 core::trust::mir::Agent agent
1029 {
1030- connection_vtable,
1031- prompt_provider_exec_helper,
1032- core::trust::mir::Agent::translator_only_accepting_exit_status_success()
1033- };
1034-
1035- EXPECT_EQ(core::trust::Request::Answer::denied, // /bin/false exits with failure.
1036- agent.authenticate_request_with_parameters(
1037- core::trust::Agent::RequestParameters
1038- {
1039- core::trust::Uid{::getuid()},
1040- app_pid,
1041- app_id,
1042- feature,
1043- app_description
1044- }));
1045+ core::trust::mir::Agent::Configuration
1046+ {
1047+ connection_vtable,
1048+ prompt_provider_exec_helper,
1049+ core::trust::mir::Agent::translator_only_accepting_exit_status_success(),
1050+ app_info_resolver
1051+ }
1052+ };
1053+
1054+ EXPECT_EQ(core::trust::Request::Answer::denied, // /bin/false exits with failure.
1055+ agent.authenticate_request_with_parameters(
1056+ core::trust::Agent::RequestParameters
1057+ {
1058+ core::trust::Uid{::getuid()},
1059+ app_pid,
1060+ app_id,
1061+ feature,
1062+ app_description
1063+ }));
1064+}
1065+
1066+TEST(MirAgent, calls_into_app_info_resolver_with_app_id)
1067+{
1068+ using namespace ::testing;
1069+
1070+ const core::trust::Pid app_pid {21};
1071+ const std::string app_id {"does.not.exist.application"};
1072+ const std::string app_icon{"/tmp"};
1073+ const std::string app_name{"MeMyselfAndI"};
1074+ const core::trust::Feature feature{42};
1075+ const std::string app_description {"This is just an extended description for %1%"};
1076+ const int pre_authenticated_fd {42};
1077+
1078+ const core::trust::mir::PromptProviderHelper::InvocationArguments reference_invocation_args
1079+ {
1080+ pre_authenticated_fd,
1081+ {
1082+ app_icon,
1083+ app_name,
1084+ app_id
1085+ },
1086+ app_description
1087+ };
1088+
1089+ auto connection_vtable = a_mocked_connection_vtable();
1090+ auto prompt_session_vtable = a_mocked_prompt_session_vtable();
1091+
1092+ auto prompt_provider_exec_helper = a_mocked_prompt_provider_calling_bin_false();
1093+ auto app_info_resolver = a_mocked_app_info_resolver();
1094+
1095+ ON_CALL(*connection_vtable, create_prompt_session_sync(_, _, _))
1096+ .WillByDefault(Return(prompt_session_vtable));
1097+
1098+ ON_CALL(*prompt_session_vtable, new_fd_for_prompt_provider())
1099+ .WillByDefault(Return(pre_authenticated_fd));
1100+
1101+ ON_CALL(*prompt_session_vtable, add_prompt_provider_sync(_))
1102+ .WillByDefault(Return(true));
1103+
1104+ EXPECT_CALL(*app_info_resolver, resolve(app_id)).Times(1)
1105+ .WillRepeatedly(
1106+ Return(core::trust::mir::AppInfo{app_icon, app_name, app_id}));
1107+ EXPECT_CALL(*prompt_provider_exec_helper,
1108+ exec_prompt_provider_with_arguments(
1109+ reference_invocation_args)).Times(1);
1110+
1111+ core::trust::mir::Agent agent
1112+ {
1113+ core::trust::mir::Agent::Configuration
1114+ {
1115+ connection_vtable,
1116+ prompt_provider_exec_helper,
1117+ core::trust::mir::Agent::translator_only_accepting_exit_status_success(),
1118+ app_info_resolver
1119+ }
1120+ };
1121+
1122+ EXPECT_EQ(core::trust::Request::Answer::denied, // /bin/false exits with failure.
1123+ agent.authenticate_request_with_parameters(
1124+ core::trust::Agent::RequestParameters
1125+ {
1126+ core::trust::Uid{::getuid()},
1127+ app_pid,
1128+ app_id,
1129+ feature,
1130+ app_description
1131+ }));
1132+
1133 }
1134
1135 TEST(MirAgent, sig_kills_prompt_provider_process_on_status_change)
1136@@ -250,6 +347,8 @@
1137 using namespace ::testing;
1138
1139 const core::trust::Pid app_pid {21};
1140+ const std::string app_icon{"/tmp"};
1141+ const std::string app_name{"MeMyselfAndI"};
1142 const std::string app_id {"does.not.exist.application"};
1143 const core::trust::Feature feature{42};
1144 const std::string app_description {"This is just an extended description for %1%"};
1145@@ -270,6 +369,8 @@
1146 auto prompt_provider_helper = std::make_shared<MockPromptProviderHelper>(
1147 core::trust::mir::PromptProviderHelper::CreationArguments{"/bin/false"});
1148
1149+ auto app_info_resolver = a_mocked_app_info_resolver();
1150+
1151 void* prompt_session_state_callback_context{nullptr};
1152
1153 ON_CALL(*prompt_provider_helper, exec_prompt_provider_with_arguments(_))
1154@@ -289,6 +390,9 @@
1155 Return(
1156 true));
1157
1158+ ON_CALL(*app_info_resolver, resolve(_))
1159+ .WillByDefault(Return(core::trust::mir::AppInfo{app_icon, app_name, app_id}));
1160+
1161 // An invocation results in a session being created. In addition,
1162 // we store pointers to callback and context provided by the implementation
1163 // for being able to later trigger the callback.
1164@@ -300,9 +404,13 @@
1165
1166 core::trust::mir::Agent agent
1167 {
1168- connection_vtable,
1169- prompt_provider_helper,
1170- core::trust::mir::Agent::translator_only_accepting_exit_status_success()
1171+ core::trust::mir::Agent::Configuration
1172+ {
1173+ connection_vtable,
1174+ prompt_provider_helper,
1175+ core::trust::mir::Agent::translator_only_accepting_exit_status_success(),
1176+ app_info_resolver
1177+ }
1178 };
1179
1180 std::thread asynchronously_stop_the_prompting_session
1181@@ -416,7 +524,9 @@
1182 std::vector<std::string> argv
1183 {
1184 "--" + std::string{core::trust::mir::cli::option_server_socket} + "=" + mir_socket(),
1185- "--" + std::string{core::trust::mir::cli::option_title} + "=" + pretty_function,
1186+ "--" + std::string{core::trust::mir::cli::option_icon}, pretty_function,
1187+ "--" + std::string{core::trust::mir::cli::option_name}, pretty_function,
1188+ "--" + std::string{core::trust::mir::cli::option_id}, pretty_function,
1189 "--" + std::string{core::trust::mir::cli::option_description} + "=" + pretty_function,
1190 // We have to circumvent unity8's authentication mechanism and just provide
1191 // the desktop_file_hint as part of the command line.
1192
1193=== added directory 'tests/share'
1194=== added directory 'tests/share/applications'
1195=== added file 'tests/share/applications/valid.pkg_app_0.0.0.desktop'
1196--- tests/share/applications/valid.pkg_app_0.0.0.desktop 1970-01-01 00:00:00 +0000
1197+++ tests/share/applications/valid.pkg_app_0.0.0.desktop 2015-11-30 10:46:27 +0000
1198@@ -0,0 +1,56 @@
1199+[Desktop Entry]
1200+Type=Application
1201+Icon=/tmp/test.icon
1202+Name=Camera
1203+Name[am]=ካሜራ
1204+Name[ar]=الكاميرا
1205+Name[ast]=Cámara
1206+Name[az]=Kamera
1207+Name[bg]=Камера
1208+Name[br]=Kamera
1209+Name[bs]=Kamera
1210+Name[ca]=Càmera
1211+Name[ca@valencia]=Càmera
1212+Name[cs]=Fotoaparát
1213+Name[cy]=Camera
1214+Name[da]=Kamera
1215+Name[de]=Kamera
1216+Name[el]=Κάμερα
1217+Name[en_AU]=Camera
1218+Name[en_GB]=Camera
1219+Name[es]=Cámara
1220+Name[eu]=Kamera
1221+Name[fa]=دوربین
1222+Name[fi]=Kamera
1223+Name[fr]=Appareil photo
1224+Name[gd]=Camara
1225+Name[gl]=Cámara
1226+Name[he]=מצלמה
1227+Name[hi]=कैमरा
1228+Name[hr]=Fotoaparat
1229+Name[hu]=Kamera
1230+Name[hy]=Կամերա
1231+Name[is]=Myndavél
1232+Name[it]=Fotocamera
1233+Name[ja]=カメラ
1234+Name[km]=ម៉ាស៊ីន<U+200B>ថ<U+200B>ត
1235+Name[ko]=카메라
1236+Name[lo]=ກ້ອງ
1237+Name[lv]=Fotokamera
1238+Name[ml]=ക്യാമറ
1239+Name[ms]=Kamera
1240+Name[my]=ကင<U+103A>မရာ
1241+Name[nb]=Kamera
1242+Name[nl]=Camera
1243+Name[pa]=ਕੈਮਰਾ
1244+Name[pl]=Aparat
1245+Name[pt]=Câmara
1246+Name[pt_BR]=Câmera
1247+Name[ro]=Aparat foto
1248+Name[ru]=Камера
1249+Name[sl]=Fotoaparat
1250+Name[sr]=Камера
1251+Name[sv]=Kamera
1252+Name[ta]=புகைப்பட கருவி
1253+Name[te]=కెమేరా
1254+Name[tr]=Fotoğraf Makinesi
1255
1256=== modified file 'tests/test_data.h.in'
1257--- tests/test_data.h.in 2014-08-12 14:25:41 +0000
1258+++ tests/test_data.h.in 2015-11-30 10:46:27 +0000
1259@@ -25,6 +25,11 @@
1260 {
1261 namespace testing
1262 {
1263+static constexpr const char* current_source_dir
1264+{
1265+ "@CMAKE_CURRENT_SOURCE_DIR@"
1266+};
1267+
1268 static constexpr const char* trust_prompt_executable_in_build_dir
1269 {
1270 "@CMAKE_BINARY_DIR@/src/trust-prompt"

Subscribers

People subscribed via source and target branches