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