Music Hub  ..
A session-wide music playback service
ubuntu.cpp
Go to the documentation of this file.
1 /*
2  * Copyright © 2014 Canonical Ltd.
3  *
4  * This program is free software: you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License version 3,
6  * as published by the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU Lesser General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  *
16  * Authored by: Thomas Voß <thomas.voss@canonical.com>
17  */
18 
20 
22 
24 
25 #include <regex>
26 
28 namespace media = core::ubuntu::media;
29 namespace ubuntu = apparmor::ubuntu;
30 
31 namespace
32 {
33 struct Uri
34 {
35  std::string scheme;
36  std::string authority;
37  std::string path;
38  std::string query;
39  std::string fragment;
40 };
41 
42 // Poor mans version of a uri parser.
43 // See https://tools.ietf.org/html/rfc3986#appendix-B
44 Uri parse_uri(const std::string& s)
45 {
46  // Indices into the regex match go here.
47  struct Index
48  {
49  const std::size_t scheme{2};
50  const std::size_t authority{4};
51  const std::size_t path{5};
52  const std::size_t query{7};
53  const std::size_t fragment{9};
54  } static index;
55 
56  static const std::regex regex{R"delim(^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?)delim"};
57  std::smatch match;
58 
59  if (not std::regex_match(s, match, regex)) throw std::runtime_error
60  {
61  "Not a valid URI: " + s
62  };
63 
64  return Uri
65  {
66  match.str(index.scheme),
67  match.str(index.authority),
68  match.str(index.path),
69  match.str(index.query),
70  match.str(index.fragment)
71  };
72 }
73 
74 static constexpr std::size_t index_package{1};
75 static constexpr std::size_t index_app{2};
76 static const std::string unity_name{"unity8-dash"};
77 static const std::string unity8_snap_name{"snap.unity8-session.unity8-session"};
78 
79 // ad-hoc for mediaplayer-app/music-app until it settles down with proper handling
80 // Bug #1642611
81 static const std::string mediaplayer_snap_name{"snap.mediaplayer-app.mediaplayer-app"};
82 static const std::string music_snap_name{"snap.music-app.music-app"};
83 // Returns true if the context name is a valid Ubuntu app id.
84 // If it is, out is populated with the package and app name.
85 bool process_context_name(const std::string& s, std::smatch& out,
86  std::string& pkg_name)
87 {
88  // See https://wiki.ubuntu.com/AppStore/Interfaces/ApplicationId.
89  static const std::regex short_re{"(.*)_(.*)"};
90  static const std::regex full_re{"(.*)_(.*)_(.*)"};
91  static const std::regex trust_store_re{"(.*)-(.*)"};
92 
93  if ((s == "messaging-app" or s == unity_name or s == unity8_snap_name or
94  s == mediaplayer_snap_name or s == music_snap_name)
95  and std::regex_match(s, out, trust_store_re))
96  {
97  pkg_name = s;
98  return true;
99  }
100 
101  if (std::regex_match(s, out, full_re) or std::regex_match(s, out, short_re))
102  {
103  pkg_name = out[index_package];
104  return true;
105  }
106 
107  return false;
108 }
109 }
110 
111 apparmor::ubuntu::Context::Context(const std::string& name)
112  : apparmor::Context{name},
113  unconfined_{str() == ubuntu::unconfined},
114  unity_{name == unity_name || name == unity8_snap_name},
115  has_package_name_{process_context_name(str(), match_, pkg_name_)}
116 {
117  MH_DEBUG("apparmor profile name: %s", name);
118  MH_DEBUG("is_unconfined(): %s", (is_unconfined() ? "true" : "false"));
119  MH_DEBUG("has_package_name(): %s", (has_package_name() ? "true" : "false"));
120  if (not is_unconfined() and not is_unity() and not has_package_name())
121  throw std::logic_error
122  {
123  "apparmor::ubuntu::Context: Invalid profile name " + str()
124  };
125 }
126 
128 {
129  return unconfined_;
130 }
131 
133 {
134  return unity_;
135 }
136 
138 {
139  return has_package_name_;
140 }
141 
143 {
144  return pkg_name_;
145 }
146 
148 {
149  return std::string{match_[index_package]} + "-" + std::string{match_[index_app]};
150 }
151 
153 {
154 }
155 
157  const std::string& name,
159 {
160  dbus_daemon.get_connection_app_armor_security_async(name, [cb](const std::string& context_name)
161  {
162  cb(apparmor::ubuntu::Context{context_name});
163  });
164 }
165 
167 {
168  if (context.is_unconfined())
169  return Result{true, "Client allowed access since it's unconfined"};
170 
171  Uri parsed_uri = parse_uri(uri);
172 
173  MH_DEBUG("context.profile_name(): %s", context.profile_name());
174  MH_DEBUG("parsed_uri.path: %s", parsed_uri.path);
175 
176  // All confined apps can access their own files
177  if (parsed_uri.path.find(std::string(".local/share/" + context.package_name() + "/")) != std::string::npos ||
178  parsed_uri.path.find(std::string(".cache/" + context.package_name() + "/")) != std::string::npos)
179  {
180  return Result
181  {
182  true,
183  "Client can access content in ~/.local/share/" + context.package_name() + " or ~/.cache/" + context.package_name()
184  };
185  }
186  // Check for trust-store compatible path name using full messaging-app profile_name
187  else if (context.profile_name() == "messaging-app" &&
188  /* Since the full APP_ID is not available yet (see aa_query_file_path()), add an exception: */
189  (parsed_uri.path.find(std::string(".local/share/com.ubuntu." + context.profile_name() + "/")) != std::string::npos ||
190  parsed_uri.path.find(std::string(".cache/com.ubuntu." + context.profile_name() + "/")) != std::string::npos))
191  {
192  return Result
193  {
194  true,
195  "Client can access content in ~/.local/share/" + context.profile_name() + " or ~/.cache/" + context.profile_name()
196  };
197  }
198  else if (parsed_uri.path.find(std::string("opt/click.ubuntu.com/")) != std::string::npos &&
199  parsed_uri.path.find(context.package_name()) != std::string::npos)
200  {
201  return Result{true, "Client can access content in own opt directory"};
202  }
203  else if ((parsed_uri.path.find(std::string("/system/media/audio/ui/")) != std::string::npos ||
204  parsed_uri.path.find(std::string("/android/system/media/audio/ui/")) != std::string::npos) &&
205  context.package_name() == "com.ubuntu.camera")
206  {
207  return Result{true, "Camera app can access ui sounds"};
208  }
209 
210  // TODO: Check if the trust store previously allowed direct access to uri
211 
212  // Check in ~/Music and ~/Videos
213  // TODO: when the trust store lands, check it to see if this app can access the dirs and
214  // then remove the explicit whitelist of the music-app, and gallery-app
215  else if ((context.package_name() == "com.ubuntu.music" || context.package_name() == "com.ubuntu.gallery" ||
216  context.profile_name() == unity_name || context.profile_name() == unity8_snap_name ||
217  context.profile_name() == mediaplayer_snap_name || context.profile_name() == music_snap_name) &&
218  (parsed_uri.path.find(std::string("Music/")) != std::string::npos ||
219  parsed_uri.path.find(std::string("Videos/")) != std::string::npos ||
220  parsed_uri.path.find(std::string("/media")) != std::string::npos))
221  {
222  return Result{true, "Client can access content in ~/Music or ~/Videos"};
223  }
224  else if (parsed_uri.path.find(std::string("/usr/share/sounds")) != std::string::npos)
225  {
226  return Result{true, "Client can access content in /usr/share/sounds"};
227  }
228  else if (parsed_uri.scheme == "http" ||
229  parsed_uri.scheme == "https" ||
230  parsed_uri.scheme == "rtsp")
231  {
232  return Result{true, "Client can access streaming content"};
233  }
234 
235  return Result{false, "Client is not allowed to access: " + uri};
236 }
237 
238 // Returns the platform-default implementation of RequestContextResolver.
240 {
241  return std::make_shared<apparmor::ubuntu::DBusDaemonRequestContextResolver>(es.session);
242 }
243 
244 // Returns the platform-default implementation of RequestAuthenticator.
246 {
247  return std::make_shared<apparmor::ubuntu::ExistingAuthenticator>();
248 }
RequestAuthenticator::Ptr make_platform_default_request_authenticator()
const std::string & str() const
Definition: context.cpp:31
std::shared_ptr< RequestContextResolver > Ptr
Definition: ubuntu.h:94
#define MH_DEBUG(...)
Definition: logger.h:123
RequestContextResolver::Ptr make_platform_default_request_context_resolver(helper::ExternalServices &es)
void resolve_context_for_dbus_name_async(const std::string &name, ResolveCallback) override
Definition: ubuntu.cpp:156
void get_connection_app_armor_security_async(const std::string &name, std::function< void(const std::string &)> handler)
Definition: dbus.h:76
std::function< void(const Context &)> ResolveCallback
Definition: ubuntu.h:97
Result authenticate_open_uri_request(const Context &, const std::string &uri) override
Definition: ubuntu.cpp:166
virtual std::string package_name() const
Definition: ubuntu.cpp:142
std::shared_ptr< RequestAuthenticator > Ptr
Definition: ubuntu.h:134
virtual std::string profile_name() const
Definition: ubuntu.cpp:147