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

Proposed by Thomas Voß
Status: Superseded
Proposed branch: lp:~thomas-voss/trust-store/fix-1504022
Merge into: lp:trust-store/15.04
Diff against target: 1644 lines (+1211/-92)
20 files modified
3rd_party/xdg/CMakeLists.txt (+22/-0)
3rd_party/xdg/LICENSE (+166/-0)
3rd_party/xdg/README.md (+87/-0)
3rd_party/xdg/xdg.cpp (+203/-0)
3rd_party/xdg/xdg.h (+118/-0)
3rd_party/xdg/xdg_test.cpp (+130/-0)
CMakeLists.txt (+4/-1)
debian/control (+3/-0)
debian/source/format (+1/-1)
src/CMakeLists.txt (+8/-0)
src/core/trust/impl/sqlite3/store.cpp (+2/-8)
src/core/trust/mir/agent.cpp (+11/-38)
src/core/trust/mir/agent.h (+29/-14)
src/core/trust/mir/click_desktop_entry_app_name_resolver.cpp (+106/-0)
src/core/trust/mir/click_desktop_entry_app_name_resolver.h (+49/-0)
tests/CMakeLists.txt (+29/-8)
tests/click_desktop_entry_app_name_resolver_test.cpp (+60/-0)
tests/mir_agent_test.cpp (+123/-22)
tests/share/applications/valid.pkg_app_0.0.0.desktop (+55/-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
Tyler Hicks Needs Information
Alberto Mardegan (community) Approve
Review via email: mp+277266@code.launchpad.net

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

This proposal has been superseded by a proposal from 2015-11-15.

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.
Revision history for this message
Alberto Mardegan (mardy) wrote :

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

review: Needs Fixing
138. By Thomas Voß

Robustify desktop entry lookup, relying on xdg dirs to search for matching entries.

139. By Thomas Voß

Rename test for mir::ClickDesktopEntryAppNameResolver.

Revision history for this message
Alberto Mardegan (mardy) wrote :

Looks good!

review: Approve
140. By Thomas Voß

Add missing , in debian/control.

141. By Thomas Voß

rely on xdg:: for querying xdg base spec directories.

Revision history for this message
Tyler Hicks (tyhicks) wrote :

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
Revision history for this message
Tyler Hicks (tyhicks) wrote :

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. :/

Revision history for this message
Thomas Voß (thomas-voss) wrote :

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.

Revision history for this message
Thomas Voß (thomas-voss) wrote :

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.

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.

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

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory '3rd_party'
2=== added directory '3rd_party/xdg'
3=== added file '3rd_party/xdg/CMakeLists.txt'
4--- 3rd_party/xdg/CMakeLists.txt 1970-01-01 00:00:00 +0000
5+++ 3rd_party/xdg/CMakeLists.txt 2015-11-15 21:10:18 +0000
6@@ -0,0 +1,22 @@
7+project(xdg)
8+
9+cmake_minimum_required(VERSION 2.8)
10+
11+find_package(Boost COMPONENTS filesystem system unit_test_framework)
12+
13+include_directories(
14+ .
15+ ${Boost_INCLUDE_DIRS}
16+)
17+
18+add_library(xdg xdg.cpp)
19+set_property(TARGET xdg PROPERTY CXX_STANDARD 11)
20+target_link_libraries(xdg ${Boost_LIBRARIES})
21+
22+enable_testing()
23+add_definitions(-DBOOST_TEST_DYN_LINK -DBOOST_TEST_MAIN -DBOOST_TEST_MODULE=xdg)
24+add_executable(xdg_test xdg_test.cpp)
25+set_property(TARGET xdg_test PROPERTY CXX_STANDARD 11)
26+target_link_libraries(xdg_test xdg)
27+
28+add_test(xdg_test xdg_test)
29
30=== added file '3rd_party/xdg/LICENSE'
31--- 3rd_party/xdg/LICENSE 1970-01-01 00:00:00 +0000
32+++ 3rd_party/xdg/LICENSE 2015-11-15 21:10:18 +0000
33@@ -0,0 +1,166 @@
34+ GNU LESSER GENERAL PUBLIC LICENSE
35+ Version 3, 29 June 2007
36+
37+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
38+ Everyone is permitted to copy and distribute verbatim copies
39+ of this license document, but changing it is not allowed.
40+
41+
42+ This version of the GNU Lesser General Public License incorporates
43+the terms and conditions of version 3 of the GNU General Public
44+License, supplemented by the additional permissions listed below.
45+
46+ 0. Additional Definitions.
47+
48+ As used herein, "this License" refers to version 3 of the GNU Lesser
49+General Public License, and the "GNU GPL" refers to version 3 of the GNU
50+General Public License.
51+
52+ "The Library" refers to a covered work governed by this License,
53+other than an Application or a Combined Work as defined below.
54+
55+ An "Application" is any work that makes use of an interface provided
56+by the Library, but which is not otherwise based on the Library.
57+Defining a subclass of a class defined by the Library is deemed a mode
58+of using an interface provided by the Library.
59+
60+ A "Combined Work" is a work produced by combining or linking an
61+Application with the Library. The particular version of the Library
62+with which the Combined Work was made is also called the "Linked
63+Version".
64+
65+ The "Minimal Corresponding Source" for a Combined Work means the
66+Corresponding Source for the Combined Work, excluding any source code
67+for portions of the Combined Work that, considered in isolation, are
68+based on the Application, and not on the Linked Version.
69+
70+ The "Corresponding Application Code" for a Combined Work means the
71+object code and/or source code for the Application, including any data
72+and utility programs needed for reproducing the Combined Work from the
73+Application, but excluding the System Libraries of the Combined Work.
74+
75+ 1. Exception to Section 3 of the GNU GPL.
76+
77+ You may convey a covered work under sections 3 and 4 of this License
78+without being bound by section 3 of the GNU GPL.
79+
80+ 2. Conveying Modified Versions.
81+
82+ If you modify a copy of the Library, and, in your modifications, a
83+facility refers to a function or data to be supplied by an Application
84+that uses the facility (other than as an argument passed when the
85+facility is invoked), then you may convey a copy of the modified
86+version:
87+
88+ a) under this License, provided that you make a good faith effort to
89+ ensure that, in the event an Application does not supply the
90+ function or data, the facility still operates, and performs
91+ whatever part of its purpose remains meaningful, or
92+
93+ b) under the GNU GPL, with none of the additional permissions of
94+ this License applicable to that copy.
95+
96+ 3. Object Code Incorporating Material from Library Header Files.
97+
98+ The object code form of an Application may incorporate material from
99+a header file that is part of the Library. You may convey such object
100+code under terms of your choice, provided that, if the incorporated
101+material is not limited to numerical parameters, data structure
102+layouts and accessors, or small macros, inline functions and templates
103+(ten or fewer lines in length), you do both of the following:
104+
105+ a) Give prominent notice with each copy of the object code that the
106+ Library is used in it and that the Library and its use are
107+ covered by this License.
108+
109+ b) Accompany the object code with a copy of the GNU GPL and this license
110+ document.
111+
112+ 4. Combined Works.
113+
114+ You may convey a Combined Work under terms of your choice that,
115+taken together, effectively do not restrict modification of the
116+portions of the Library contained in the Combined Work and reverse
117+engineering for debugging such modifications, if you also do each of
118+the following:
119+
120+ a) Give prominent notice with each copy of the Combined Work that
121+ the Library is used in it and that the Library and its use are
122+ covered by this License.
123+
124+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
125+ document.
126+
127+ c) For a Combined Work that displays copyright notices during
128+ execution, include the copyright notice for the Library among
129+ these notices, as well as a reference directing the user to the
130+ copies of the GNU GPL and this license document.
131+
132+ d) Do one of the following:
133+
134+ 0) Convey the Minimal Corresponding Source under the terms of this
135+ License, and the Corresponding Application Code in a form
136+ suitable for, and under terms that permit, the user to
137+ recombine or relink the Application with a modified version of
138+ the Linked Version to produce a modified Combined Work, in the
139+ manner specified by section 6 of the GNU GPL for conveying
140+ Corresponding Source.
141+
142+ 1) Use a suitable shared library mechanism for linking with the
143+ Library. A suitable mechanism is one that (a) uses at run time
144+ a copy of the Library already present on the user's computer
145+ system, and (b) will operate properly with a modified version
146+ of the Library that is interface-compatible with the Linked
147+ Version.
148+
149+ e) Provide Installation Information, but only if you would otherwise
150+ be required to provide such information under section 6 of the
151+ GNU GPL, and only to the extent that such information is
152+ necessary to install and execute a modified version of the
153+ Combined Work produced by recombining or relinking the
154+ Application with a modified version of the Linked Version. (If
155+ you use option 4d0, the Installation Information must accompany
156+ the Minimal Corresponding Source and Corresponding Application
157+ Code. If you use option 4d1, you must provide the Installation
158+ Information in the manner specified by section 6 of the GNU GPL
159+ for conveying Corresponding Source.)
160+
161+ 5. Combined Libraries.
162+
163+ You may place library facilities that are a work based on the
164+Library side by side in a single library together with other library
165+facilities that are not Applications and are not covered by this
166+License, and convey such a combined library under terms of your
167+choice, if you do both of the following:
168+
169+ a) Accompany the combined library with a copy of the same work based
170+ on the Library, uncombined with any other library facilities,
171+ conveyed under the terms of this License.
172+
173+ b) Give prominent notice with the combined library that part of it
174+ is a work based on the Library, and explaining where to find the
175+ accompanying uncombined form of the same work.
176+
177+ 6. Revised Versions of the GNU Lesser General Public License.
178+
179+ The Free Software Foundation may publish revised and/or new versions
180+of the GNU Lesser General Public License from time to time. Such new
181+versions will be similar in spirit to the present version, but may
182+differ in detail to address new problems or concerns.
183+
184+ Each version is given a distinguishing version number. If the
185+Library as you received it specifies that a certain numbered version
186+of the GNU Lesser General Public License "or any later version"
187+applies to it, you have the option of following the terms and
188+conditions either of that published version or of any later version
189+published by the Free Software Foundation. If the Library as you
190+received it does not specify a version number of the GNU Lesser
191+General Public License, you may choose any version of the GNU Lesser
192+General Public License ever published by the Free Software Foundation.
193+
194+ If the Library as you received it specifies that a proxy can decide
195+whether future versions of the GNU Lesser General Public License shall
196+apply, that proxy's public statement of acceptance of any version is
197+permanent authorization for you to choose that version for the
198+Library.
199+
200
201=== added file '3rd_party/xdg/README.md'
202--- 3rd_party/xdg/README.md 1970-01-01 00:00:00 +0000
203+++ 3rd_party/xdg/README.md 2015-11-15 21:10:18 +0000
204@@ -0,0 +1,87 @@
205+# xdg
206+
207+A straightforward implementation of the XDG Base Directory Specification in C++11.
208+I became tired of retyping and retesting the same functionality over and over again,
209+so I decided to place my own little helper here.
210+
211+## Dependencies
212+
213+ - boost::filesystem: For handling all things filesystem paths.
214+ - boost::system:: Required by boost::filesystem.
215+ - boost::test: For testing purposes obviously.
216+
217+Install with
218+```bash
219+sudo apt-get install libboost-filesystem-dev libboost-system-dev libboost-test-dev
220+```
221+
222+## Quick'n'Easy Integration
223+
224+xdg provides free functions that are easy to integrate with existing projects.
225+```cpp
226+#include <xdg.h>
227+
228+#include <iostream>
229+
230+int main()
231+{
232+ std::cout << xdg::data().home() << std::endl;
233+ std::cout << xdg::config().home() << std::endl;
234+ std::cout << xdg::cache().home() << std::endl;
235+ std::cout << xdg::runtime().dir() << std::endl;
236+
237+ return 0;
238+}
239+```
240+
241+## Complete Integration
242+
243+The interface xdg::BaseDirSpecification can be used to integrate xdg base directory
244+queries into a code base such that interaction with the xdg::BaseDirSpecification is testable.
245+
246+```cpp
247+#include <xdg.h>
248+
249+class MyClass
250+{
251+public:
252+ MyClass(const std::shared_ptr<xdg::BaseDirSpecification>& bds) : bds{bds}
253+ {
254+ }
255+
256+ void do_something()
257+ {
258+ // Query the user-specific config directory.
259+ auto path = bds->config().home();
260+ // Do something with the config files.
261+ }
262+private:
263+ std::shared_ptr<xdg::BaseDirSpecification> bds;
264+};
265+
266+// In the testing setup, under the assumption of Google Test and Google Mock.
267+namespace
268+{
269+struct MockConfig : public xdg::Config
270+{
271+ // ...
272+};
273+struct MockBaseDirSpecification : public xdg::BaseDirSpecification
274+{
275+ // ...
276+};
277+}
278+
279+TEST(MyClass, do_something_queries_config_home_directory)
280+{
281+ using namespace ::testing;
282+ auto config = std::make_shared<NiceMock<MockConfig>>();
283+ EXPECT_CALL(*config, home()).Times(1).ReturnRepeatedly("/tmp");
284+
285+ auto bds = std::make_shared<NiceMock<MockBaseDirSpecification>>();
286+ ON_CALL(*bds, config()).WillByDefault(ReturnRef(*config));
287+
288+ MyClass mc{bds};
289+ mc.do_something();
290+}
291+```
292
293=== added file '3rd_party/xdg/xdg.cpp'
294--- 3rd_party/xdg/xdg.cpp 1970-01-01 00:00:00 +0000
295+++ 3rd_party/xdg/xdg.cpp 2015-11-15 21:10:18 +0000
296@@ -0,0 +1,203 @@
297+// Copyright (C) 2015 Thomas Voß <thomas.voss.bochum@gmail.com>
298+//
299+// This library is free software: you can redistribute it and/or modify
300+// it under the terms of the GNU Lesser General Public License as published
301+// by the Free Software Foundation, either version 3 of the License, or
302+// (at your option) any later version.
303+//
304+// This program is distributed in the hope that it will be useful,
305+// but WITHOUT ANY WARRANTY; without even the implied warranty of
306+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
307+// GNU General Public License for more details.
308+//
309+// You should have received a copy of the GNU Lesser General Public License
310+// along with this program. If not, see <http://www.gnu.org/licenses/>.
311+
312+#include <xdg.h>
313+
314+#include <boost/algorithm/string.hpp>
315+
316+#include <cstdlib>
317+#include <stdexcept>
318+
319+namespace fs = boost::filesystem;
320+
321+namespace
322+{
323+
324+fs::path throw_if_not_absolute(const fs::path& p)
325+{
326+ if (p.has_root_directory())
327+ return p;
328+
329+ throw std::runtime_error{"Directores MUST be absolute."};
330+}
331+
332+namespace env
333+{
334+std::string get(const std::string& key, const std::string& default_value)
335+{
336+ if (auto value = std::getenv(key.c_str()))
337+ return value;
338+ return default_value;
339+}
340+
341+std::string get_or_throw(const std::string& key)
342+{
343+ if (auto value = std::getenv(key.c_str()))
344+ {
345+ return value;
346+ }
347+
348+ throw std::runtime_error{key + " not set in environment"};
349+}
350+
351+constexpr const char* xdg_data_home{"XDG_DATA_HOME"};
352+constexpr const char* xdg_data_dirs{"XDG_DATA_DIRS"};
353+constexpr const char* xdg_config_home{"XDG_CONFIG_HOME"};
354+constexpr const char* xdg_config_dirs{"XDG_CONFIG_DIRS"};
355+constexpr const char* xdg_cache_home{"XDG_CACHE_HOME"};
356+constexpr const char* xdg_runtime_dir{"XDG_RUNTIME_DIR"};
357+}
358+
359+namespace impl
360+{
361+class BaseDirSpecification : public xdg::BaseDirSpecification
362+{
363+public:
364+ static const BaseDirSpecification& instance()
365+ {
366+ static const BaseDirSpecification spec;
367+ return spec;
368+ }
369+
370+ BaseDirSpecification()
371+ {
372+ }
373+
374+ const xdg::Data& data() const override
375+ {
376+ return data_;
377+ }
378+
379+ const xdg::Config& config() const override
380+ {
381+ return config_;
382+ }
383+
384+ const xdg::Cache& cache() const override
385+ {
386+ return cache_;
387+ }
388+
389+ const xdg::Runtime& runtime() const override
390+ {
391+ return runtime_;
392+ }
393+
394+private:
395+ xdg::Data data_;
396+ xdg::Config config_;
397+ xdg::Cache cache_;
398+ xdg::Runtime runtime_;
399+};
400+}
401+}
402+
403+fs::path xdg::Data::home() const
404+{
405+ auto v = env::get(env::xdg_data_home, "");
406+ if (v.empty())
407+ return throw_if_not_absolute(fs::path{env::get_or_throw("HOME")} / ".local" / "share");
408+
409+ return throw_if_not_absolute(fs::path(v));
410+}
411+
412+std::vector<fs::path> xdg::Data::dirs() const
413+{
414+ auto v = env::get(env::xdg_data_dirs, "");
415+ if (v.empty())
416+ return {fs::path{"/usr/local/share"}, fs::path{"/usr/share"}};
417+
418+ std::vector<std::string> tokens;
419+ tokens = boost::split(tokens, v, boost::is_any_of(":"));
420+ std::vector<fs::path> result;
421+ for (const auto& token : tokens)
422+ {
423+ result.push_back(throw_if_not_absolute(fs::path(token)));
424+ }
425+ return result;
426+}
427+
428+fs::path xdg::Config::home() const
429+{
430+ auto v = env::get(env::xdg_config_home, "");
431+ if (v.empty())
432+ return throw_if_not_absolute(fs::path{env::get_or_throw("HOME")} / ".config");
433+
434+ return throw_if_not_absolute(fs::path(v));
435+}
436+
437+std::vector<fs::path> xdg::Config::dirs() const
438+{
439+ auto v = env::get(env::xdg_config_dirs, "");
440+ if (v.empty())
441+ return {fs::path{"/etc/xdg"}};
442+
443+ std::vector<std::string> tokens;
444+ tokens = boost::split(tokens, v, boost::is_any_of(":"));
445+ std::vector<fs::path> result;
446+ for (const auto& token : tokens)
447+ {
448+ fs::path p(token);
449+ result.push_back(throw_if_not_absolute(p));
450+ }
451+ return result;
452+}
453+
454+fs::path xdg::Cache::home() const
455+{
456+ auto v = env::get(env::xdg_cache_home, "");
457+ if (v.empty())
458+ return throw_if_not_absolute(fs::path{env::get_or_throw("HOME")} / ".cache");
459+
460+ return throw_if_not_absolute(fs::path(v));
461+}
462+
463+fs::path xdg::Runtime::dir() const
464+{
465+ auto v = env::get(env::xdg_config_home, "");
466+ if (v.empty())
467+ {
468+ // We do not fall back gracefully and instead throw, dispatching to calling
469+ // code for handling the case of a safe user-specfic runtime directory missing.
470+ throw std::runtime_error{"Runtime directory not set"};
471+ }
472+
473+ return throw_if_not_absolute(fs::path(v));
474+}
475+
476+std::shared_ptr<xdg::BaseDirSpecification> xdg::BaseDirSpecification::create()
477+{
478+ return std::make_shared<impl::BaseDirSpecification>();
479+}
480+
481+const xdg::Data& xdg::data()
482+{
483+ return impl::BaseDirSpecification::instance().data();
484+}
485+
486+const xdg::Config& xdg::config()
487+{
488+ return impl::BaseDirSpecification::instance().config();
489+}
490+
491+const xdg::Cache& xdg::cache()
492+{
493+ return impl::BaseDirSpecification::instance().cache();
494+}
495+
496+const xdg::Runtime& xdg::runtime()
497+{
498+ return impl::BaseDirSpecification::instance().runtime();
499+}
500
501=== added file '3rd_party/xdg/xdg.h'
502--- 3rd_party/xdg/xdg.h 1970-01-01 00:00:00 +0000
503+++ 3rd_party/xdg/xdg.h 2015-11-15 21:10:18 +0000
504@@ -0,0 +1,118 @@
505+// Copyright (C) 2015 Thomas Voß <thomas.voss.bochum@gmail.com>
506+//
507+// This library is free software: you can redistribute it and/or modify
508+// it under the terms of the GNU Lesser General Public License as published
509+// by the Free Software Foundation, either version 3 of the License, or
510+// (at your option) any later version.
511+//
512+// This program is distributed in the hope that it will be useful,
513+// but WITHOUT ANY WARRANTY; without even the implied warranty of
514+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
515+// GNU General Public License for more details.
516+//
517+// You should have received a copy of the GNU Lesser General Public License
518+// along with this program. If not, see <http://www.gnu.org/licenses/>.
519+#ifndef XDG_H_
520+#define XDG_H_
521+
522+#include <boost/filesystem.hpp>
523+
524+#include <string>
525+#include <vector>
526+
527+namespace xdg
528+{
529+// NotCopyable deletes the copy c'tor and the assignment operator.
530+struct NotCopyable
531+{
532+ NotCopyable() = default;
533+ NotCopyable(const NotCopyable&) = delete;
534+ virtual ~NotCopyable() = default;
535+ NotCopyable& operator=(const NotCopyable&) = delete;
536+};
537+
538+// NotMoveable deletes the move c'tor and the move assignment operator.
539+struct NotMoveable
540+{
541+ NotMoveable() = default;
542+ NotMoveable(NotMoveable&&) = delete;
543+ virtual ~NotMoveable() = default;
544+ NotMoveable& operator=(NotMoveable&&) = delete;
545+};
546+
547+// Data provides functions to query the XDG_DATA_* entries.
548+class Data : NotCopyable, NotMoveable
549+{
550+public:
551+ // home returns the base directory relative to which user specific
552+ // data files should be stored.
553+ virtual boost::filesystem::path home() const;
554+ // dirs returns the preference-ordered set of base directories to
555+ // search for data files in addition to the $XDG_DATA_HOME base
556+ // directory.
557+ virtual std::vector<boost::filesystem::path> dirs() const;
558+};
559+
560+// Config provides functions to query the XDG_CONFIG_* entries.
561+class Config : NotCopyable, NotMoveable
562+{
563+public:
564+ // home returns the base directory relative to which user specific
565+ // configuration files should be stored.
566+ virtual boost::filesystem::path home() const;
567+ // dirs returns the preference-ordered set of base directories to
568+ // search for configuration files in addition to the
569+ // $XDG_CONFIG_HOME base directory.
570+ virtual std::vector<boost::filesystem::path> dirs() const;
571+};
572+
573+// Cache provides functions to query the XDG_CACHE_HOME entry.
574+class Cache : NotCopyable, NotMoveable
575+{
576+public:
577+ // home returns the base directory relative to which user specific
578+ // non-essential data files should be stored.
579+ virtual boost::filesystem::path home() const;
580+};
581+
582+// Runtime provides functions to query the XDG_RUNTIME_DIR entry.
583+class Runtime : NotCopyable, NotMoveable
584+{
585+public:
586+ // home returns the base directory relative to which user-specific
587+ // non-essential runtime files and other file objects (such as
588+ // sockets, named pipes, ...) should be stored.
589+ virtual boost::filesystem::path dir() const;
590+};
591+
592+// A BaseDirSpecification implements the XDG base dir specification:
593+// http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
594+class BaseDirSpecification : NotCopyable, NotMoveable
595+{
596+public:
597+ // create returns an Implementation of BaseDirSpecification.
598+ static std::shared_ptr<BaseDirSpecification> create();
599+
600+ // data returns an immutable Data instance.
601+ virtual const Data& data() const = 0;
602+ // config returns an immutable Config instance.
603+ virtual const Config& config() const = 0;
604+ // cache returns an immutable Cache instance.
605+ virtual const Cache& cache() const = 0;
606+ // runtime returns an immutable Runtime instance.
607+ virtual const Runtime& runtime() const = 0;
608+protected:
609+ BaseDirSpecification() = default;
610+};
611+
612+// data returns an immutable reference to a Data instance.
613+const Data& data();
614+// config returns an immutable reference to a Config instance.
615+const Config& config();
616+// cache returns an immutable reference to a Cache instance.
617+const Cache& cache();
618+// runtime returns an immutable reference to a Runtime instance.
619+const Runtime& runtime();
620+}
621+
622+#endif // XDG_H_
623
624=== added file '3rd_party/xdg/xdg_test.cpp'
625--- 3rd_party/xdg/xdg_test.cpp 1970-01-01 00:00:00 +0000
626+++ 3rd_party/xdg/xdg_test.cpp 2015-11-15 21:10:18 +0000
627@@ -0,0 +1,130 @@
628+// Copyright (C) 2015 Thomas Voß <thomas.voss.bochum@gmail.com>
629+//
630+// This library is free software: you can redistribute it and/or modify
631+// it under the terms of the GNU Lesser General Public License as published
632+// by the Free Software Foundation, either version 3 of the License, or
633+// (at your option) any later version.
634+//
635+// This program is distributed in the hope that it will be useful,
636+// but WITHOUT ANY WARRANTY; without even the implied warranty of
637+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
638+// GNU General Public License for more details.
639+//
640+// You should have received a copy of the GNU Lesser General Public License
641+// along with this program. If not, see <http://www.gnu.org/licenses/>.
642+
643+#include <xdg.h>
644+
645+#include <boost/test/unit_test.hpp>
646+
647+#include <cstdlib>
648+#include <iostream>
649+
650+BOOST_AUTO_TEST_CASE(XdgDataHomeThrowsForRelativeDirectoryFromEnv)
651+{
652+ ::setenv("XDG_DATA_HOME", "tmp", 1);
653+ BOOST_CHECK_THROW(xdg::BaseDirSpecification::create()->data().home(), std::runtime_error);
654+ BOOST_CHECK_THROW(xdg::data().home(), std::runtime_error);
655+}
656+
657+BOOST_AUTO_TEST_CASE(XdgDataHomeReturnsDefaultValueForEmptyEnv)
658+{
659+ ::setenv("HOME", "/tmp", 1);
660+ ::setenv("XDG_DATA_HOME", "", 1);
661+ BOOST_CHECK_EQUAL("/tmp/.local/share", xdg::BaseDirSpecification::create()->data().home());
662+ BOOST_CHECK_EQUAL("/tmp/.local/share", xdg::data().home());
663+}
664+
665+BOOST_AUTO_TEST_CASE(XdgDataDirsCorrectlyTokenizesEnv)
666+{
667+ ::setenv("XDG_DATA_DIRS", "/tmp:/tmp", 1);
668+ BOOST_CHECK(2 == xdg::BaseDirSpecification::create()->data().dirs().size());
669+ BOOST_CHECK(2 == xdg::data().dirs().size());
670+}
671+
672+BOOST_AUTO_TEST_CASE(XdgDataDirsThrowsForRelativeDirectoryFromEnv)
673+{
674+ ::setenv("XDG_DATA_DIRS", "/tmp:tmp", 1);
675+ BOOST_CHECK_THROW(xdg::BaseDirSpecification::create()->data().dirs(), std::runtime_error);
676+ BOOST_CHECK_THROW(xdg::data().dirs(), std::runtime_error);
677+}
678+
679+BOOST_AUTO_TEST_CASE(XdgDataDirsReturnsDefaultValueForEmptyEnv)
680+{
681+ ::setenv("XDG_DATA_DIRS", "", 1);
682+ auto dirs = xdg::data().dirs();
683+ BOOST_CHECK_EQUAL("/usr/local/share", dirs[0]);
684+ BOOST_CHECK_EQUAL("/usr/share", dirs[1]);
685+
686+ dirs = xdg::BaseDirSpecification::create()->data().dirs();
687+ BOOST_CHECK_EQUAL("/usr/local/share", dirs[0]);
688+ BOOST_CHECK_EQUAL("/usr/share", dirs[1]);
689+}
690+
691+BOOST_AUTO_TEST_CASE(XdgConfigHomeThrowsForRelativeDirectoryFromEnv)
692+{
693+ ::setenv("XDG_CONFIG_HOME", "tmp", 1);
694+ BOOST_CHECK_THROW(xdg::BaseDirSpecification::create()->config().home(), std::runtime_error);
695+ BOOST_CHECK_THROW(xdg::config().home(), std::runtime_error);
696+}
697+
698+BOOST_AUTO_TEST_CASE(XdgConfigHomeReturnsDefaultValueForEmptyEnv)
699+{
700+ ::setenv("HOME", "/tmp", 1);
701+ ::setenv("XDG_CONFIG_HOME", "", 1);
702+ BOOST_CHECK_EQUAL("/tmp/.config", xdg::BaseDirSpecification::create()->config().home());
703+ BOOST_CHECK_EQUAL("/tmp/.config", xdg::config().home());
704+}
705+
706+BOOST_AUTO_TEST_CASE(XdgConfigDirsCorrectlyTokenizesEnv)
707+{
708+ ::setenv("XDG_CONFIG_DIRS", "/tmp:/tmp", 1);
709+ BOOST_CHECK(2 == xdg::BaseDirSpecification::create()->config().dirs().size());
710+ BOOST_CHECK(2 == xdg::config().dirs().size());
711+}
712+
713+BOOST_AUTO_TEST_CASE(XdgConfigDirsThrowsForRelativeDirectoryFromEnv)
714+{
715+ ::setenv("XDG_CONFIG_DIRS", "/tmp:tmp", 1);
716+ BOOST_CHECK_THROW(xdg::BaseDirSpecification::create()->config().dirs(), std::runtime_error);
717+ BOOST_CHECK_THROW(xdg::config().dirs(), std::runtime_error);
718+}
719+
720+BOOST_AUTO_TEST_CASE(XdgConfigDirsReturnsDefaultValueForEmptyEnv)
721+{
722+ ::setenv("XDG_CONFIG_DIRS", "", 1);
723+ auto dirs = xdg::config().dirs();
724+ BOOST_CHECK_EQUAL("/etc/xdg", dirs[0]);
725+ dirs = xdg::BaseDirSpecification::create()->config().dirs();
726+ BOOST_CHECK_EQUAL("/etc/xdg", dirs[0]);
727+}
728+
729+BOOST_AUTO_TEST_CASE(XdgCacheHomeThrowsForRelativeDirectoryFromEnv)
730+{
731+ ::setenv("XDG_CACHE_HOME", "tmp", 1);
732+ BOOST_CHECK_THROW(xdg::BaseDirSpecification::create()->cache().home(), std::runtime_error);
733+ BOOST_CHECK_THROW(xdg::cache().home(), std::runtime_error);
734+}
735+
736+BOOST_AUTO_TEST_CASE(XdgCacheHomeReturnsDefaultValueForEmptyEnv)
737+{
738+ ::setenv("HOME", "/tmp", 1);
739+ ::setenv("XDG_CACHE_HOME", "", 1);
740+ BOOST_CHECK_EQUAL("/tmp/.cache", xdg::BaseDirSpecification::create()->cache().home());
741+ BOOST_CHECK_EQUAL("/tmp/.cache", xdg::cache().home());
742+}
743+
744+BOOST_AUTO_TEST_CASE(XdgRuntimeDirThrowsForRelativeDirectoryFromEnv)
745+{
746+ ::setenv("XDG_RUNTIME_DIR", "tmp", 1);
747+ BOOST_CHECK_THROW(xdg::BaseDirSpecification::create()->runtime().dir(), std::runtime_error);
748+ BOOST_CHECK_THROW(xdg::runtime().dir(), std::runtime_error);
749+}
750+
751+BOOST_AUTO_TEST_CASE(XdgRuntimeDirThrowsForEmptyEnv)
752+{
753+ ::setenv("XDG_RUNTIME_DIR", "", 1);
754+ BOOST_CHECK_THROW(xdg::BaseDirSpecification::create()->runtime().dir(), std::runtime_error);
755+ BOOST_CHECK_THROW(xdg::runtime().dir(), std::runtime_error);
756+}
757+
758
759=== modified file 'CMakeLists.txt'
760--- CMakeLists.txt 2015-08-31 13:16:20 +0000
761+++ CMakeLists.txt 2015-11-15 21:10:18 +0000
762@@ -28,7 +28,9 @@
763 ENDIF(CMAKE_BUILD_TYPE MATCHES [cC][oO][vV][eE][rR][aA][gG][eE])
764
765 find_package(PkgConfig)
766-find_package(Boost COMPONENTS program_options system REQUIRED)
767+find_package(Boost COMPONENTS filesystem program_options system REQUIRED)
768+
769+add_subdirectory(3rd_party/xdg)
770
771 option(
772 TRUST_STORE_MIR_AGENT_ENABLED
773@@ -54,6 +56,7 @@
774
775 include_directories(
776 include/
777+ 3rd_party/xdg
778
779 ${GFLAGS_INCLUDE_DIRS}
780 ${GLOG_INCLUDE_DIRS}
781
782=== modified file 'debian/control'
783--- debian/control 2015-08-31 13:16:20 +0000
784+++ debian/control 2015-11-15 21:10:18 +0000
785@@ -7,11 +7,14 @@
786 google-mock,
787 graphviz,
788 libapparmor-dev,
789+ libboost-filesystem-dev,
790 libboost-program-options-dev,
791 libboost-system-dev,
792+ libboost-test-dev,
793 libdbus-cpp-dev (>= 4.0.0),
794 libdbus-1-dev,
795 libgflags-dev,
796+ libglib2.0-dev,
797 libgoogle-glog-dev,
798 libgtest-dev,
799 libjson-c-dev,
800
801=== modified file 'debian/source/format'
802--- debian/source/format 2014-08-14 13:05:04 +0000
803+++ debian/source/format 2015-11-15 21:10:18 +0000
804@@ -1,1 +1,1 @@
805-3.0 (quilt)
806+3.0 (native)
807
808=== modified file 'src/CMakeLists.txt'
809--- src/CMakeLists.txt 2015-08-31 13:16:20 +0000
810+++ src/CMakeLists.txt 2015-11-15 21:10:18 +0000
811@@ -20,6 +20,8 @@
812
813 pkg_check_modules(DBUS_CPP dbus-cpp REQUIRED)
814 pkg_check_modules(DBUS dbus-1 REQUIRED)
815+pkg_check_modules(GLIB glib-2.0 REQUIRED)
816+pkg_check_modules(GOBJECT gobject-2.0 REQUIRED)
817 pkg_check_modules(LIBAPPARMOR libapparmor REQUIRED)
818 pkg_check_modules(SQLITE3 sqlite3 REQUIRED)
819
820@@ -28,6 +30,8 @@
821 include_directories(
822 ${DBUS_CPP_INCLUDE_DIRS}
823 ${DBUS_INCLUDE_DIRS}
824+ ${GLIB_INCLUDE_DIRS}
825+ ${GOBJECT_INCLUDE_DIRS}
826 ${LIBAPPARMOR_INCLUDE_DIRS}
827 ${SQLITE3_INCLUDE_DIRS}
828 )
829@@ -70,6 +74,7 @@
830 # to prompt the user for trusting an application to access a trusted
831 # system service.
832 core/trust/mir/agent.cpp
833+ core/trust/mir/click_desktop_entry_app_name_resolver.cpp
834 )
835
836 # Make sure Qt does not inject evil macros like 'signals' and 'slots'.
837@@ -180,11 +185,14 @@
838 trust-store
839
840 dbus-cpp
841+ xdg
842
843 ${Boost_LIBRARIES}
844 ${DBUS_LIBRARIES}
845 ${GFLAGS_LDFLAGS}
846 ${GLOG_LDFLAGS}
847+ ${GLIB_LDFLAGS}
848+ ${GOBJECT_LDFLAGS}
849 ${LIBAPPARMOR_LDFLAGS}
850 ${MIR_CLIENT_LDFLAGS}
851 ${PROCESS_CPP_LDFLAGS}
852
853=== modified file 'src/core/trust/impl/sqlite3/store.cpp'
854--- src/core/trust/impl/sqlite3/store.cpp 2014-11-14 12:17:24 +0000
855+++ src/core/trust/impl/sqlite3/store.cpp 2015-11-15 21:10:18 +0000
856@@ -21,6 +21,7 @@
857 #include <core/posix/this_process.h>
858
859 #include <sqlite3.h>
860+#include <xdg.h>
861
862 #include <cstring>
863 #include <iostream>
864@@ -32,16 +33,9 @@
865
866 namespace core
867 {
868-std::string home()
869-{
870- return core::posix::this_process::env::get_or_throw("HOME");
871-}
872-
873 std::string runtime_persistent_data_dir()
874 {
875- return core::posix::this_process::env::get(
876- "XDG_DATA_HOME",
877- home() + "/.local/share");
878+ return xdg::data().home().string();
879 }
880
881 struct Directory
882
883=== modified file 'src/core/trust/mir/agent.cpp'
884--- src/core/trust/mir/agent.cpp 2015-08-31 13:16:20 +0000
885+++ src/core/trust/mir/agent.cpp 2015-11-15 21:10:18 +0000
886@@ -134,21 +134,7 @@
887 {
888 static auto child_setup = []() {};
889
890- // We translate to human readable strings here, and do it a non-translateable way first
891- // We post-process the application id and try to extract the unversioned package name.
892- // Please see https://wiki.ubuntu.com/AppStore/Interfaces/ApplicationId.
893- static const std::regex regex_full_app_id{"(.*)_(.*)_(.*)"};
894- static const std::regex regex_short_app_id{"(.*)_(.*)"};
895- static constexpr std::size_t index_app{2};
896-
897 auto app_name = args.application_id;
898-
899- std::smatch match;
900- if (std::regex_match(app_name, match, regex_full_app_id))
901- app_name = std::string{match[index_app]};
902- else if (std::regex_match(app_name, match, regex_short_app_id))
903- app_name = std::string{match[index_app]};
904-
905 auto description = (boost::format(i18n::tr(args.description, i18n::service_text_domain())) % app_name).str();
906
907 std::vector<std::string> argv
908@@ -219,17 +205,8 @@
909 };
910 }
911
912-mir::Agent::Agent(
913- // VTable object providing access to Mir's trusted prompting functionality.
914- const mir::ConnectionVirtualTable::Ptr& connection_vtable,
915- // Exec helper for starting up prompt provider child processes with the correct setup
916- // of command line arguments and environment variables.
917- const mir::PromptProviderHelper::Ptr& exec_helper,
918- // A translator function for mapping child process exit states to trust::Request answers.
919- const std::function<core::trust::Request::Answer(const core::posix::wait::Result&)>& translator)
920- : connection_vtable(connection_vtable),
921- exec_helper(exec_helper),
922- translator(translator)
923+mir::Agent::Agent(const mir::Agent::Configuration& config)
924+ : config(config)
925 {
926 }
927
928@@ -260,7 +237,7 @@
929 } scope
930 {
931 // We setup the prompt session and wire up to our own internal callback helper.
932- connection_vtable->create_prompt_session_sync(
933+ config.connection_vtable->create_prompt_session_sync(
934 parameters.application.pid,
935 Agent::on_trust_session_changed_state,
936 &cb_context)
937@@ -273,16 +250,16 @@
938 mir::PromptProviderHelper::InvocationArguments args
939 {
940 fd,
941- parameters.application.id,
942+ config.app_name_resolver->resolve(parameters.application.id),
943 parameters.description
944 };
945
946 // Ask the helper to fire up the prompt provider.
947- cb_context.prompt_provider_process = exec_helper->exec_prompt_provider_with_arguments(args);
948+ cb_context.prompt_provider_process = config.exec_helper->exec_prompt_provider_with_arguments(args);
949 // And subsequently wait for it to finish.
950 auto result = cb_context.prompt_provider_process.wait_for(core::posix::wait::Flags::untraced);
951
952- return translator(result);
953+ return config.translator(result);
954 }
955
956 bool mir::operator==(const mir::PromptProviderHelper::InvocationArguments& lhs, const mir::PromptProviderHelper::InvocationArguments& rhs)
957@@ -291,6 +268,7 @@
958 }
959
960 #include "config.h"
961+#include "click_desktop_entry_app_name_resolver.h"
962
963 MirConnection* mir::connect(const std::string& endpoint, const std::string& name)
964 {
965@@ -318,13 +296,8 @@
966 }
967 };
968
969- return mir::Agent::Ptr
970- {
971- new mir::Agent
972- {
973- cvt,
974- pph,
975- mir::Agent::translator_only_accepting_exit_status_success()
976- }
977- };
978+ mir::AppNameResolver::Ptr anr{new mir::ClickDesktopEntryAppNameResolver{}};
979+
980+ mir::Agent::Configuration config{cvt, pph, mir::Agent::translator_only_accepting_exit_status_success(), anr};
981+ return mir::Agent::Ptr{new mir::Agent{config}};
982 }
983
984=== modified file 'src/core/trust/mir/agent.h'
985--- src/core/trust/mir/agent.h 2014-10-07 19:27:18 +0000
986+++ src/core/trust/mir/agent.h 2015-11-15 21:10:18 +0000
987@@ -146,6 +146,17 @@
988 CreationArguments creation_arguments;
989 };
990
991+// An AppNameResolver resolves an application id to a localized application name.
992+struct AppNameResolver
993+{
994+ // Save us some typing.
995+ typedef std::shared_ptr<AppNameResolver> Ptr;
996+
997+ virtual ~AppNameResolver() = default;
998+ // resolve maps app_id to a localized application name.
999+ virtual std::string resolve(const std::string& app_id) = 0;
1000+};
1001+
1002 // Implements the trust::Agent interface and dispatches calls to a helper
1003 // prompt provider, tying it together with the requesting service and app
1004 // by leveraging Mir's trusted session/prompting support.
1005@@ -154,6 +165,20 @@
1006 // Convenience typedef
1007 typedef std::shared_ptr<Agent> Ptr;
1008
1009+ // A Configuration bundles creation-time configuration options.
1010+ struct Configuration
1011+ {
1012+ // VTable object providing access to Mir's trusted prompting functionality.
1013+ ConnectionVirtualTable::Ptr connection_vtable;
1014+ // Exec helper for starting up prompt provider child processes with the correct setup
1015+ // of command line arguments and environment variables.
1016+ PromptProviderHelper::Ptr exec_helper;
1017+ // A translator function for mapping child process exit states to trust::Request answers.
1018+ std::function<core::trust::Request::Answer(const core::posix::wait::Result&)> translator;
1019+ // AppNameResolver used by the agent to map incoming request app ids to application names.
1020+ AppNameResolver::Ptr app_name_resolver;
1021+ };
1022+
1023 // Helper struct for injecting state into on_trust_changed_state_state callbacks.
1024 // Used in prompt_user_for_request to wait for the trust session to be stopped.
1025 struct OnTrustSessionStateChangedCallbackContext
1026@@ -177,26 +202,16 @@
1027 // Throws std::logic_error if the process did not exit but was signaled.
1028 static std::function<core::trust::Request::Answer(const core::posix::wait::Result&)> translator_only_accepting_exit_status_success();
1029
1030- // Creates a new MirAgent instance with the given MirConnectionVirtualTable instance.
1031- Agent(// VTable object providing access to Mir's trusted prompting functionality.
1032- const ConnectionVirtualTable::Ptr& connection_vtable,
1033- // Exec helper for starting up prompt provider child processes with the correct setup
1034- // of command line arguments and environment variables.
1035- const PromptProviderHelper::Ptr& exec_helper,
1036- // A translator function for mapping child process exit states to trust::Request answers.
1037- const std::function<core::trust::Request::Answer(const core::posix::wait::Result&)>& translator);
1038+ // Creates a new MirAgent instance with the given Configuration.
1039+ Agent(const Configuration& config);
1040
1041 // From core::trust::Agent:
1042 // Throws a std::logic_error if anything unforeseen happens during execution, thus
1043 // indicating that no conclusive answer could be obtained from the user.
1044 core::trust::Request::Answer authenticate_request_with_parameters(const RequestParameters& parameters) override;
1045
1046- // The connection VTable used for creating trusted prompting sessions.
1047- ConnectionVirtualTable::Ptr connection_vtable;
1048- // Execution helper for firing up prompt provider executables.
1049- PromptProviderHelper::Ptr exec_helper;
1050- // Translator instance.
1051- std::function<core::trust::Request::Answer(const core::posix::wait::Result&)> translator;
1052+ // The configured options.
1053+ Configuration config;
1054 };
1055
1056 CORE_TRUST_DLL_PUBLIC bool operator==(const PromptProviderHelper::InvocationArguments&, const PromptProviderHelper::InvocationArguments&);
1057
1058=== added file 'src/core/trust/mir/click_desktop_entry_app_name_resolver.cpp'
1059--- src/core/trust/mir/click_desktop_entry_app_name_resolver.cpp 1970-01-01 00:00:00 +0000
1060+++ src/core/trust/mir/click_desktop_entry_app_name_resolver.cpp 2015-11-15 21:10:18 +0000
1061@@ -0,0 +1,106 @@
1062+/*
1063+ * Copyright © 2015 Canonical Ltd.
1064+ *
1065+ * This program is free software: you can redistribute it and/or modify it
1066+ * under the terms of the GNU Lesser General Public License version 3,
1067+ * as published by the Free Software Foundation.
1068+ *
1069+ * This program is distributed in the hope that it will be useful,
1070+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1071+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1072+ * GNU Lesser General Public License for more details.
1073+ *
1074+ * You should have received a copy of the GNU Lesser General Public License
1075+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1076+ *
1077+ * Authored by: Thomas Voß <thomas.voss@canonical.com>
1078+ */
1079+
1080+#include <core/trust/mir/click_desktop_entry_app_name_resolver.h>
1081+
1082+#include <glib.h>
1083+#include <xdg.h>
1084+
1085+#include <core/posix/this_process.h>
1086+
1087+#include <boost/algorithm/string.hpp>
1088+#include <boost/filesystem.hpp>
1089+#include <boost/format.hpp>
1090+
1091+#include <memory>
1092+#include <regex>
1093+#include <string>
1094+#include <stdexcept>
1095+#include <vector>
1096+
1097+namespace env = core::posix::this_process::env;
1098+namespace fs = boost::filesystem;
1099+namespace mir = core::trust::mir;
1100+
1101+namespace
1102+{
1103+boost::filesystem::path resolve_desktop_entry_or_throw(const std::string& app_id)
1104+{
1105+ boost::format pattern("%1%/applications/%2%.desktop");
1106+ fs::path p{(pattern % xdg::data().home().string() % app_id).str()};
1107+ if (fs::is_regular_file(p))
1108+ return p;
1109+
1110+ fs::path applications{xdg::data().home() / "applications"};
1111+ fs::directory_iterator it(applications), itE;
1112+ while (it != itE)
1113+ {
1114+ if (it->path().filename().string().find(app_id) == 0)
1115+ return it->path();
1116+ ++it;
1117+ }
1118+
1119+ for (auto dir : xdg::data().dirs())
1120+ {
1121+ fs::path p{(pattern % dir.string() % app_id).str()};
1122+ if (fs::is_regular_file(p))
1123+ return p;
1124+ }
1125+
1126+ throw std::runtime_error{"Could not resolve desktop entry for " + app_id};
1127+}
1128+
1129+// Wrap up a GError with an RAII approach, easing
1130+// cleanup if we throw an exception.
1131+struct Error
1132+{
1133+ ~Error() { clear(); }
1134+ void clear() { g_clear_error(&error); }
1135+
1136+ GError* error = nullptr;
1137+};
1138+
1139+std::string name_from_desktop_entry_or_throw(const fs::path& fn)
1140+{
1141+ Error g;
1142+ std::shared_ptr<GKeyFile> key_file{g_key_file_new(), [](GKeyFile* file) { if (file) g_key_file_free(file); }};
1143+
1144+ 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
1145+ {
1146+ "Failed to load desktop entry [" + std::string(g.error->message) + "]"
1147+ };
1148+
1149+ 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);
1150+
1151+ if (g.error) throw std::runtime_error
1152+ {
1153+ "Failed to query localized name [" + std::string(g.error->message) + "]"
1154+ };
1155+
1156+ return app_name;
1157+}
1158+}
1159+
1160+mir::ClickDesktopEntryAppNameResolver::ClickDesktopEntryAppNameResolver()
1161+{
1162+}
1163+
1164+std::string mir::ClickDesktopEntryAppNameResolver::resolve(const std::string& app_id)
1165+{
1166+ return name_from_desktop_entry_or_throw(resolve_desktop_entry_or_throw(app_id));
1167+}
1168
1169=== added file 'src/core/trust/mir/click_desktop_entry_app_name_resolver.h'
1170--- src/core/trust/mir/click_desktop_entry_app_name_resolver.h 1970-01-01 00:00:00 +0000
1171+++ src/core/trust/mir/click_desktop_entry_app_name_resolver.h 2015-11-15 21:10:18 +0000
1172@@ -0,0 +1,49 @@
1173+/*
1174+ * Copyright © 2015 Canonical Ltd.
1175+ *
1176+ * This program is free software: you can redistribute it and/or modify it
1177+ * under the terms of the GNU Lesser General Public License version 3,
1178+ * as published by the Free Software Foundation.
1179+ *
1180+ * This program is distributed in the hope that it will be useful,
1181+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1182+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1183+ * GNU Lesser General Public License for more details.
1184+ *
1185+ * You should have received a copy of the GNU Lesser General Public License
1186+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1187+ *
1188+ * Authored by: Thomas Voß <thomas.voss@canonical.com>
1189+ */
1190+
1191+#ifndef CORE_TRUST_MIR_CLICK_DESKTOP_ENTRY_APP_NAME_RESOLVER_H_
1192+#define CORE_TRUST_MIR_CLICK_DESKTOP_ENTRY_APP_NAME_RESOLVER_H_
1193+
1194+#include <core/trust/mir/agent.h>
1195+
1196+namespace core
1197+{
1198+namespace trust
1199+{
1200+namespace mir
1201+{
1202+// A ClickDesktopEntryAppNameResolver queries the click database of installed
1203+// packages to resolve an app's installation folder in the local filesystem.
1204+// The directory is searched for a .desktop file, that is then loaded and queried
1205+// for the app's localized name.
1206+class CORE_TRUST_DLL_PUBLIC ClickDesktopEntryAppNameResolver : public AppNameResolver
1207+{
1208+public:
1209+ // ClickDesktopEntryAppNameResolver sets up an instance with default dbs.
1210+ ClickDesktopEntryAppNameResolver();
1211+
1212+ // resolve queries the click index and an apps desktop file entry for
1213+ // obtaining a localized application name. Throws std::runtime_error in
1214+ // case of issues.
1215+ std::string resolve(const std::string& app_id) override;
1216+};
1217+}
1218+}
1219+}
1220+
1221+#endif // CORE_TRUST_MIR_CLICK_DESKTOP_ENTRY_APP_NAME_RESOLVER_H_
1222
1223=== modified file 'tests/CMakeLists.txt'
1224--- tests/CMakeLists.txt 2014-11-14 12:17:24 +0000
1225+++ tests/CMakeLists.txt 2015-11-15 21:10:18 +0000
1226@@ -242,20 +242,41 @@
1227 mir_agent_test.cpp
1228 )
1229
1230+ add_executable(
1231+ click_desktop_entry_app_name_resolver_test
1232+ click_desktop_entry_app_name_resolver_test.cpp
1233+ )
1234+
1235 target_link_libraries(
1236 mir_agent_test
1237
1238 trust-store
1239-
1240- gmock
1241-
1242- gtest
1243- gtest_main
1244-
1245- ${PROCESS_CPP_LIBRARIES}
1246- )
1247+ xdg
1248+
1249+ gmock
1250+
1251+ gtest
1252+ gtest_main
1253+
1254+ ${PROCESS_CPP_LIBRARIES}
1255+ )
1256+
1257+ target_link_libraries(
1258+ click_desktop_entry_app_name_resolver_test
1259+
1260+ trust-store
1261+
1262+ gmock
1263+
1264+ gtest
1265+ gtest_main
1266+
1267+ ${PROCESS_CPP_LIBRARIES}
1268+ )
1269+
1270
1271 add_test(mir_agent_test ${CMAKE_CURRENT_BINARY_DIR}/mir_agent_test --gtest_filter=*-*requires_mir)
1272+ add_test(click_desktop_entry_app_name_resolver_test ${CMAKE_CURRENT_BINARY_DIR}/click_desktop_entry_app_name_resolver_test)
1273
1274 install(
1275 TARGETS mir_agent_test
1276
1277=== added file 'tests/click_desktop_entry_app_name_resolver_test.cpp'
1278--- tests/click_desktop_entry_app_name_resolver_test.cpp 1970-01-01 00:00:00 +0000
1279+++ tests/click_desktop_entry_app_name_resolver_test.cpp 2015-11-15 21:10:18 +0000
1280@@ -0,0 +1,60 @@
1281+/*
1282+ * Copyright © 2013 Canonical Ltd.
1283+ *
1284+ * This program is free software: you can redistribute it and/or modify it
1285+ * under the terms of the GNU Lesser General Public License version 3,
1286+ * as published by the Free Software Foundation.
1287+ *
1288+ * This program is distributed in the hope that it will be useful,
1289+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1290+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1291+ * GNU Lesser General Public License for more details.
1292+ *
1293+ * You should have received a copy of the GNU Lesser General Public License
1294+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1295+ *
1296+ * Authored by: Thomas Voß <thomas.voss@canonical.com>
1297+ */
1298+
1299+#include <core/trust/mir/click_desktop_entry_app_name_resolver.h>
1300+#include <core/posix/this_process.h>
1301+#include "test_data.h"
1302+
1303+#include <gtest/gtest.h>
1304+
1305+namespace env = core::posix::this_process::env;
1306+namespace mir = core::trust::mir;
1307+
1308+TEST(ClickDesktopEntryAppNameResolver, throws_for_invalid_app_id)
1309+{
1310+ mir::ClickDesktopEntryAppNameResolver resolver;
1311+ EXPECT_THROW(resolver.resolve(""), std::runtime_error);
1312+ EXPECT_THROW(resolver.resolve("something:not:right"), std::runtime_error);
1313+}
1314+
1315+TEST(ClickDesktopEntryAppNameResolver, resolves_existing_desktop_entry_from_xdg_data_home)
1316+{
1317+ env::unset_or_throw("XDG_DATA_HOME");
1318+ env::set_or_throw("XDG_DATA_HOME", core::trust::testing::current_source_dir + std::string("/share"));
1319+ mir::ClickDesktopEntryAppNameResolver resolver;
1320+ EXPECT_NO_THROW(resolver.resolve("valid.pkg_app_0.0.0"));
1321+}
1322+
1323+TEST(ClickDesktopEntryAppNameResolver, robustly_resolves_existing_desktop_entry_from_xdg_data_home)
1324+{
1325+ env::unset_or_throw("XDG_DATA_HOME");
1326+ env::set_or_throw("XDG_DATA_HOME", core::trust::testing::current_source_dir + std::string("/share"));
1327+
1328+ mir::ClickDesktopEntryAppNameResolver resolver;
1329+ EXPECT_NO_THROW(resolver.resolve("valid.pkg_app"));
1330+}
1331+
1332+TEST(ClickDesktopEntryAppNameResolver, resolves_existing_desktop_entry_from_xdg_data_dirs)
1333+{
1334+ env::unset_or_throw("XDG_DATA_HOME"); env::unset_or_throw("XDG_DATA_DIRS");
1335+ env::set_or_throw("XDG_DATA_HOME", core::trust::testing::current_source_dir + std::string("/empty"));
1336+ env::set_or_throw("XDG_DATA_DIRS", core::trust::testing::current_source_dir + std::string("/share"));
1337+
1338+ mir::ClickDesktopEntryAppNameResolver resolver;
1339+ resolver.resolve("valid.pkg_app_0.0.0");
1340+}
1341
1342=== added directory 'tests/empty'
1343=== added directory 'tests/empty/applications'
1344=== modified file 'tests/mir_agent_test.cpp'
1345--- tests/mir_agent_test.cpp 2014-10-07 19:27:18 +0000
1346+++ tests/mir_agent_test.cpp 2015-11-15 21:10:18 +0000
1347@@ -99,6 +99,11 @@
1348 MOCK_METHOD1(translate, core::trust::Request::Answer(const core::posix::wait::Result&));
1349 };
1350
1351+struct MockAppNameResolver : public core::trust::mir::AppNameResolver
1352+{
1353+ MOCK_METHOD1(resolve, std::string(const std::string&));
1354+};
1355+
1356 std::shared_ptr<MockConnectionVirtualTable> a_mocked_connection_vtable()
1357 {
1358 return std::make_shared<testing::NiceMock<MockConnectionVirtualTable>>();
1359@@ -117,6 +122,11 @@
1360 "/bin/false"
1361 });
1362 }
1363+
1364+std::shared_ptr<MockAppNameResolver> a_mocked_app_name_resolver()
1365+{
1366+ return std::make_shared<MockAppNameResolver>();
1367+}
1368 }
1369
1370 TEST(DefaultProcessStateTranslator, throws_for_signalled_process)
1371@@ -209,6 +219,7 @@
1372 auto prompt_session_vtable = a_mocked_prompt_session_vtable();
1373
1374 auto prompt_provider_exec_helper = a_mocked_prompt_provider_calling_bin_false();
1375+ auto app_name_resolver = a_mocked_app_name_resolver();
1376
1377 ON_CALL(*connection_vtable, create_prompt_session_sync(_, _, _))
1378 .WillByDefault(Return(prompt_session_vtable));
1379@@ -219,6 +230,9 @@
1380 ON_CALL(*prompt_session_vtable, add_prompt_provider_sync(_))
1381 .WillByDefault(Return(true));
1382
1383+ ON_CALL(*app_name_resolver, resolve(_))
1384+ .WillByDefault(ReturnArg<0>());
1385+
1386 EXPECT_CALL(*connection_vtable, create_prompt_session_sync(app_pid, _, _)).Times(1);
1387 EXPECT_CALL(*prompt_session_vtable, new_fd_for_prompt_provider()).Times(1);
1388
1389@@ -228,21 +242,87 @@
1390
1391 core::trust::mir::Agent agent
1392 {
1393- connection_vtable,
1394- prompt_provider_exec_helper,
1395- core::trust::mir::Agent::translator_only_accepting_exit_status_success()
1396- };
1397-
1398- EXPECT_EQ(core::trust::Request::Answer::denied, // /bin/false exits with failure.
1399- agent.authenticate_request_with_parameters(
1400- core::trust::Agent::RequestParameters
1401- {
1402- core::trust::Uid{::getuid()},
1403- app_pid,
1404- app_id,
1405- feature,
1406- app_description
1407- }));
1408+ core::trust::mir::Agent::Configuration
1409+ {
1410+ connection_vtable,
1411+ prompt_provider_exec_helper,
1412+ core::trust::mir::Agent::translator_only_accepting_exit_status_success(),
1413+ app_name_resolver
1414+ }
1415+ };
1416+
1417+ EXPECT_EQ(core::trust::Request::Answer::denied, // /bin/false exits with failure.
1418+ agent.authenticate_request_with_parameters(
1419+ core::trust::Agent::RequestParameters
1420+ {
1421+ core::trust::Uid{::getuid()},
1422+ app_pid,
1423+ app_id,
1424+ feature,
1425+ app_description
1426+ }));
1427+}
1428+
1429+TEST(MirAgent, calls_into_app_name_resolver_with_app_id)
1430+{
1431+ using namespace ::testing;
1432+
1433+ const core::trust::Pid app_pid {21};
1434+ const std::string app_id {"does.not.exist.application"};
1435+ const std::string app_name{"MeMyselfAndI"};
1436+ const core::trust::Feature feature{42};
1437+ const std::string app_description {"This is just an extended description for %1%"};
1438+ const int pre_authenticated_fd {42};
1439+
1440+ const core::trust::mir::PromptProviderHelper::InvocationArguments reference_invocation_args
1441+ {
1442+ pre_authenticated_fd,
1443+ app_name,
1444+ app_description
1445+ };
1446+
1447+ auto connection_vtable = a_mocked_connection_vtable();
1448+ auto prompt_session_vtable = a_mocked_prompt_session_vtable();
1449+
1450+ auto prompt_provider_exec_helper = a_mocked_prompt_provider_calling_bin_false();
1451+ auto app_name_resolver = a_mocked_app_name_resolver();
1452+
1453+ ON_CALL(*connection_vtable, create_prompt_session_sync(_, _, _))
1454+ .WillByDefault(Return(prompt_session_vtable));
1455+
1456+ ON_CALL(*prompt_session_vtable, new_fd_for_prompt_provider())
1457+ .WillByDefault(Return(pre_authenticated_fd));
1458+
1459+ ON_CALL(*prompt_session_vtable, add_prompt_provider_sync(_))
1460+ .WillByDefault(Return(true));
1461+
1462+ EXPECT_CALL(*app_name_resolver, resolve(app_id)).Times(1).WillRepeatedly(Return(app_name));
1463+ EXPECT_CALL(*prompt_provider_exec_helper,
1464+ exec_prompt_provider_with_arguments(
1465+ reference_invocation_args)).Times(1);
1466+
1467+ core::trust::mir::Agent agent
1468+ {
1469+ core::trust::mir::Agent::Configuration
1470+ {
1471+ connection_vtable,
1472+ prompt_provider_exec_helper,
1473+ core::trust::mir::Agent::translator_only_accepting_exit_status_success(),
1474+ app_name_resolver
1475+ }
1476+ };
1477+
1478+ EXPECT_EQ(core::trust::Request::Answer::denied, // /bin/false exits with failure.
1479+ agent.authenticate_request_with_parameters(
1480+ core::trust::Agent::RequestParameters
1481+ {
1482+ core::trust::Uid{::getuid()},
1483+ app_pid,
1484+ app_id,
1485+ feature,
1486+ app_description
1487+ }));
1488+
1489 }
1490
1491 TEST(MirAgent, sig_kills_prompt_provider_process_on_status_change)
1492@@ -270,6 +350,8 @@
1493 auto prompt_provider_helper = std::make_shared<MockPromptProviderHelper>(
1494 core::trust::mir::PromptProviderHelper::CreationArguments{"/bin/false"});
1495
1496+ auto app_name_resolver = a_mocked_app_name_resolver();
1497+
1498 void* prompt_session_state_callback_context{nullptr};
1499
1500 ON_CALL(*prompt_provider_helper, exec_prompt_provider_with_arguments(_))
1501@@ -289,6 +371,9 @@
1502 Return(
1503 true));
1504
1505+ ON_CALL(*app_name_resolver, resolve(_))
1506+ .WillByDefault(ReturnArg<0>());
1507+
1508 // An invocation results in a session being created. In addition,
1509 // we store pointers to callback and context provided by the implementation
1510 // for being able to later trigger the callback.
1511@@ -300,9 +385,13 @@
1512
1513 core::trust::mir::Agent agent
1514 {
1515- connection_vtable,
1516- prompt_provider_helper,
1517- core::trust::mir::Agent::translator_only_accepting_exit_status_success()
1518+ core::trust::mir::Agent::Configuration
1519+ {
1520+ connection_vtable,
1521+ prompt_provider_helper,
1522+ core::trust::mir::Agent::translator_only_accepting_exit_status_success(),
1523+ app_name_resolver
1524+ }
1525 };
1526
1527 std::thread asynchronously_stop_the_prompting_session
1528@@ -368,6 +457,8 @@
1529 #include <core/trust/mir/config.h>
1530 #include <core/trust/mir/prompt_main.h>
1531
1532+#include <xdg.h>
1533+
1534 namespace
1535 {
1536 std::map<std::string, std::string> a_copy_of_the_env()
1537@@ -383,15 +474,25 @@
1538 std::string mir_socket()
1539 {
1540 // We either take the XDG_RUNTIME_DIR or fall back to /tmp if XDG_RUNTIME_DIR is not set.
1541- std::string dir = core::posix::this_process::env::get("XDG_RUNTIME_DIR", "/tmp");
1542- return dir + "/mir_socket";
1543+ try
1544+ {
1545+ return xdg::runtime().dir().string() + "/mir_socket";
1546+ } catch(...)
1547+ {
1548+ return "/tmp/mir_socket";
1549+ }
1550 }
1551
1552 std::string trusted_mir_socket()
1553 {
1554 // We either take the XDG_RUNTIME_DIR or fall back to /tmp if XDG_RUNTIME_DIR is not set.
1555- std::string dir = core::posix::this_process::env::get("XDG_RUNTIME_DIR", "/tmp");
1556- return dir + "/mir_socket_trusted";
1557+ try
1558+ {
1559+ return xdg::runtime().dir().string() + "/mir_socket_trusted";
1560+ } catch(...)
1561+ {
1562+ return "/tmp/mir_socket_trusted";
1563+ }
1564 }
1565 }
1566
1567
1568=== added directory 'tests/share'
1569=== added directory 'tests/share/applications'
1570=== added file 'tests/share/applications/valid.pkg_app_0.0.0.desktop'
1571--- tests/share/applications/valid.pkg_app_0.0.0.desktop 1970-01-01 00:00:00 +0000
1572+++ tests/share/applications/valid.pkg_app_0.0.0.desktop 2015-11-15 21:10:18 +0000
1573@@ -0,0 +1,55 @@
1574+[Desktop Entry]
1575+Type=Application
1576+Name=Camera
1577+Name[am]=ካሜራ
1578+Name[ar]=الكاميرا
1579+Name[ast]=Cámara
1580+Name[az]=Kamera
1581+Name[bg]=Камера
1582+Name[br]=Kamera
1583+Name[bs]=Kamera
1584+Name[ca]=Càmera
1585+Name[ca@valencia]=Càmera
1586+Name[cs]=Fotoaparát
1587+Name[cy]=Camera
1588+Name[da]=Kamera
1589+Name[de]=Kamera
1590+Name[el]=Κάμερα
1591+Name[en_AU]=Camera
1592+Name[en_GB]=Camera
1593+Name[es]=Cámara
1594+Name[eu]=Kamera
1595+Name[fa]=دوربین
1596+Name[fi]=Kamera
1597+Name[fr]=Appareil photo
1598+Name[gd]=Camara
1599+Name[gl]=Cámara
1600+Name[he]=מצלמה
1601+Name[hi]=कैमरा
1602+Name[hr]=Fotoaparat
1603+Name[hu]=Kamera
1604+Name[hy]=Կամերա
1605+Name[is]=Myndavél
1606+Name[it]=Fotocamera
1607+Name[ja]=カメラ
1608+Name[km]=ម៉ាស៊ីន<U+200B>ថ<U+200B>ត
1609+Name[ko]=카메라
1610+Name[lo]=ກ້ອງ
1611+Name[lv]=Fotokamera
1612+Name[ml]=ക്യാമറ
1613+Name[ms]=Kamera
1614+Name[my]=ကင<U+103A>မရာ
1615+Name[nb]=Kamera
1616+Name[nl]=Camera
1617+Name[pa]=ਕੈਮਰਾ
1618+Name[pl]=Aparat
1619+Name[pt]=Câmara
1620+Name[pt_BR]=Câmera
1621+Name[ro]=Aparat foto
1622+Name[ru]=Камера
1623+Name[sl]=Fotoaparat
1624+Name[sr]=Камера
1625+Name[sv]=Kamera
1626+Name[ta]=புகைப்பட கருவி
1627+Name[te]=కెమేరా
1628+Name[tr]=Fotoğraf Makinesi
1629
1630=== modified file 'tests/test_data.h.in'
1631--- tests/test_data.h.in 2014-08-12 14:25:41 +0000
1632+++ tests/test_data.h.in 2015-11-15 21:10:18 +0000
1633@@ -25,6 +25,11 @@
1634 {
1635 namespace testing
1636 {
1637+static constexpr const char* current_source_dir
1638+{
1639+ "@CMAKE_CURRENT_SOURCE_DIR@"
1640+};
1641+
1642 static constexpr const char* trust_prompt_executable_in_build_dir
1643 {
1644 "@CMAKE_BINARY_DIR@/src/trust-prompt"

Subscribers

People subscribed via source and target branches