Merge lp:~brian.curtin/ubuntuone-client/824252-validate-path-export into lp:ubuntuone-client

Proposed by Brian Curtin
Status: Merged
Approved by: Natalia Bidart
Approved revision: 1189
Merged at revision: 1193
Proposed branch: lp:~brian.curtin/ubuntuone-client/824252-validate-path-export
Merge into: lp:ubuntuone-client
Diff against target: 303 lines (+176/-9)
9 files modified
tests/platform/test_external_interface.py (+10/-0)
tests/syncdaemon/test_interaction_interfaces.py (+10/-0)
tests/syncdaemon/test_vm.py (+107/-1)
ubuntuone/platform/linux/dbus_interface.py (+6/-0)
ubuntuone/platform/tools/__init__.py (+8/-0)
ubuntuone/platform/windows/ipc.py (+5/-0)
ubuntuone/platform/windows/ipc_client.py (+4/-0)
ubuntuone/syncdaemon/interaction_interfaces.py (+7/-0)
ubuntuone/syncdaemon/volume_manager.py (+19/-8)
To merge this branch: bzr merge lp:~brian.curtin/ubuntuone-client/824252-validate-path-export
Reviewer Review Type Date Requested Status
Natalia Bidart (community) Approve
Roberto Alsina (community) Approve
Review via email: mp+91443@code.launchpad.net

Commit message

- Exported validate_path_from_folder from volume_manager instead of ubuntuone-control-panel's backend (lp:824252)

Description of the change

Fix lp:824252 by moving validate_path_for_folder inside volume_manager.VolumeManager and exposing it via the dbus and IPC interfaces. Corrects an earlier None/False issue that was failing on Linux.

To post a comment you must log in.
Revision history for this message
Roberto Alsina (ralsina) wrote :

+1

review: Approve
Revision history for this message
Natalia Bidart (nataliabidart) wrote :

Branch looks good!

Two minor nitpicks:

* Any reason to have this:

from ubuntuone.syncdaemon import config, event_queue, tritcask
from ubuntuone.syncdaemon import volume_manager

instead of:

from ubuntuone.syncdaemon import config, event_queue, tritcask, volume_manager

?

* Can you please add the docstring for this method?

def validate_path_for_folder(self, path):

* Shouldn't we call normpath(path) before checking if the path is inside the user home?

Tests are green in both OSes, yey! (except for bug #929546 which is not relevant to this branch)
Thanks!

review: Needs Fixing
1189. By Brian Curtin

Address Natalia's review comments.
Rearranged import, added docstring, moved normpath call to earlier.

Revision history for this message
Brian Curtin (brian.curtin) wrote :

> Branch looks good!

rev 1189 makes the changes you suggested.

Revision history for this message
Natalia Bidart (nataliabidart) wrote :

Looks great!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'tests/platform/test_external_interface.py'
2--- tests/platform/test_external_interface.py 2012-01-02 17:53:12 +0000
3+++ tests/platform/test_external_interface.py 2012-02-09 23:36:18 +0000
4@@ -551,6 +551,16 @@
5 in_signature='s', out_signature=None)
6
7 @defer.inlineCallbacks
8+ def test_validate_path(self):
9+ """Test validate_path."""
10+ result = False
11+ path = 'test'
12+ yield self.assert_method_called(self.service.folders,
13+ 'validate_path', result, path)
14+ self.assert_remote_method('validate_path',
15+ in_signature='s', out_signature='b')
16+
17+ @defer.inlineCallbacks
18 def test_get_folders(self):
19 """Test get_folders."""
20 result = [{'folder_id': '1'}, {'folder_id': '2'}]
21
22=== modified file 'tests/syncdaemon/test_interaction_interfaces.py'
23--- tests/syncdaemon/test_interaction_interfaces.py 2012-01-17 20:00:44 +0000
24+++ tests/syncdaemon/test_interaction_interfaces.py 2012-02-09 23:36:18 +0000
25@@ -1041,6 +1041,16 @@
26
27 self.assertEqual(self._called, ((folder_id,), {}))
28
29+ def test_validate_path(self):
30+ """Test the validate_path method."""
31+ # Use a lambda instead of _set_called since we need to return a tuple.
32+ self.patch(self.main.vm, 'validate_path_for_folder',
33+ lambda arg: (True, arg))
34+ path = u'this/is/a/test'
35+ rv = self.sd_obj.validate_path(path)
36+
37+ self.assertTrue(rv)
38+
39 @defer.inlineCallbacks
40 def test_get_folders(self):
41 """Test the get_folders method."""
42
43=== modified file 'tests/syncdaemon/test_vm.py'
44--- tests/syncdaemon/test_vm.py 2012-01-18 20:47:18 +0000
45+++ tests/syncdaemon/test_vm.py 2012-02-09 23:36:18 +0000
46@@ -44,7 +44,7 @@
47 FakeMain,
48 )
49 from ubuntuone import platform
50-from ubuntuone.syncdaemon import config, event_queue, tritcask
51+from ubuntuone.syncdaemon import config, event_queue, tritcask, volume_manager
52 from ubuntuone.syncdaemon.volume_manager import (
53 ACCESS_LEVEL_RO,
54 ACCESS_LEVEL_RW,
55@@ -2713,6 +2713,112 @@
56 self.assertEquals(str(root_volume.node_id), root_node_id)
57 self.assertEquals(root_volume.free_bytes, free_bytes)
58
59+ def test_validate_UDF_path_inside_home(self):
60+ """Test proper validation of path for creating folders."""
61+ folder_path = os.path.join(self.home_dir, 'Test Me')
62+
63+ result, msg = self.vm.validate_path_for_folder(folder_path)
64+ self.assertTrue(result)
65+ self.assertIs(msg, "",
66+ '%r must be a valid path for creating a folder.' % folder_path)
67+
68+ def test_validate_UDF_path_if_folder_shares_a_prefix_with_an_udf(self):
69+ """Test proper validation of path for creating folders.
70+
71+ If the user chooses a folder with the same prefix as an UDF, but
72+ outside every UDF, the path is valid.
73+
74+ """
75+ tricky_path = self.root_dir
76+ assert not tricky_path.endswith(os.path.sep)
77+ tricky_path += ' Suffix'
78+ assert tricky_path.startswith(self.root_dir)
79+
80+ result, msg = self.vm.validate_path_for_folder(tricky_path)
81+ self.assertTrue(result)
82+ self.assertIs(msg, "",
83+ '%r must be a valid path for creating a folder.' % tricky_path)
84+
85+ def test_validate_UDF_path_not_valid_if_outside_home(self):
86+ """A folder outside ~ is not valid."""
87+ outside_home = os.path.abspath(os.path.join(self.home_dir, os.path.pardir))
88+
89+ result, msg = self.vm.validate_path_for_folder(outside_home)
90+ self.assertFalse(result)
91+ self.assertIsNot(msg, "",
92+ '%r must be an invalid path for creating a folder.' % outside_home)
93+
94+ def test_validate_UDF_not_valid_if_folder_inside_root(self):
95+ """A folder inside the root is not valid."""
96+ root_path = self.root_dir
97+ # create a valid path inside the root
98+ inside_root = os.path.abspath(os.path.join(root_path, 'test'))
99+
100+ result, msg = self.vm.validate_path_for_folder(inside_root)
101+ self.assertFalse(result)
102+ self.assertIsNot(msg, "",
103+ '%r must be an invalid path for creating a folder.' % inside_root)
104+
105+ @defer.inlineCallbacks
106+ def test_validate_UDF_not_valid_if_folder_inside_an_udf(self):
107+ """A folder inside an UDF is not valid."""
108+ udf = self._create_udf(subscribed=True)
109+ yield self.vm.add_udf(udf)
110+ # create a valid path inside an existing UDF
111+ inside_udf = os.path.abspath(os.path.join(udf.path, 'test'))
112+
113+ result, msg = self.vm.validate_path_for_folder(inside_udf)
114+ self.assertFalse(result)
115+ self.assertIsNot(msg, "",
116+ '%r must be an invalid path for creating a folder.' % inside_udf)
117+
118+ @defer.inlineCallbacks
119+ def test_validate_UDF_not_valid_if_folder_is_parent_of_an_udf(self):
120+ """A folder parent of an UDF is not valid."""
121+ udf = self._create_udf(subscribed=True)
122+ yield self.vm.add_udf(udf)
123+ # create a valid path that is parent from an existing UDF
124+ udf_parent = os.path.abspath(os.path.join(self.home_dir, os.path.pardir))
125+
126+ result, msg = self.vm.validate_path_for_folder(udf_parent)
127+ self.assertFalse(result)
128+ self.assertIsNot(msg, "",
129+ '%r must be an invalid path for creating a folder.' % udf_parent)
130+
131+ def test_not_valid_if_folder_is_link(self):
132+ """A link path is not valid."""
133+ self.patch(volume_manager, 'is_link', lambda p: True)
134+ path_link = os.path.join(self.home_dir, 'Test Me')
135+
136+ result, msg = self.vm.validate_path_for_folder(path_link)
137+ self.assertFalse(result)
138+ self.assertIsNot(msg, "",
139+ '%r must be an invalid path for creating a folder.' % path_link)
140+
141+ @defer.inlineCallbacks
142+ def test_no_UDFs_outside_home(self):
143+ udf = self._create_udf(subscribed=True)
144+ yield self.vm.add_udf(udf)
145+ # Use a directory surely outside home
146+ # Drive root on Windows, or root on linux
147+ outside_path = os.path.splitdrive(udf.path)[0] or "/"
148+ # patch FakeAQ
149+ def create_udf(path, name, marker):
150+ """Fake create_udf"""
151+ d = dict(volume_id=uuid.uuid4(), node_id=uuid.uuid4(),
152+ marker=marker)
153+ self.main.event_q.push("AQ_CREATE_UDF_OK", **d)
154+
155+ self.main.action_q.create_udf = create_udf
156+ d = defer.Deferred()
157+ self._listen_for('VM_UDF_CREATE_ERROR', d.callback)
158+ self._listen_for('VM_UDF_CREATED', lambda r: d.errback(Exception(r)))
159+ self.vm.create_udf(outside_path)
160+ result = yield d
161+ self.assertEqual(1, len(list(self.vm.udfs.keys())))
162+ self.assertEqual(result,
163+ dict(path=outside_path, error="UDFs must be within home"))
164+
165 @defer.inlineCallbacks
166 def test_no_UDFs_inside_root(self):
167 """Test that a UDF can't be created inside the root"""
168
169=== modified file 'ubuntuone/platform/linux/dbus_interface.py'
170--- ubuntuone/platform/linux/dbus_interface.py 2012-01-02 20:24:31 +0000
171+++ ubuntuone/platform/linux/dbus_interface.py 2012-02-09 23:36:18 +0000
172@@ -699,6 +699,12 @@
173 """Delete the folder specified by folder_id"""
174 return self.service.folders.delete(folder_id)
175
176+ @dbus.service.method(DBUS_IFACE_FOLDERS_NAME,
177+ in_signature='s', out_signature='b')
178+ def validate_path(self, path):
179+ """Return True if the path is valid for a folder"""
180+ return self.service.folders.validate_path(path)
181+
182 @dbus.service.method(DBUS_IFACE_FOLDERS_NAME, out_signature='aa{ss}')
183 def get_folders(self):
184 """Return the list of folders (a list of dicts)"""
185
186=== modified file 'ubuntuone/platform/tools/__init__.py'
187--- ubuntuone/platform/tools/__init__.py 2011-12-19 13:39:46 +0000
188+++ ubuntuone/platform/tools/__init__.py 2012-02-09 23:36:18 +0000
189@@ -441,6 +441,14 @@
190
191 @defer.inlineCallbacks
192 @log_call(logger.debug)
193+ def validate_path(self, path):
194+ """Return True if the path is valid for a folder."""
195+ result = yield self.proxy.call_method('folders', 'validate', path)
196+ self.log.debug('valid: %r', result)
197+ defer.returnValue(result)
198+
199+ @defer.inlineCallbacks
200+ @log_call(logger.debug)
201 def get_folders(self):
202 """Return the list of folders (a list of dicts)."""
203 results = yield self.proxy.call_method('folders', 'get_folders')
204
205=== modified file 'ubuntuone/platform/windows/ipc.py'
206--- ubuntuone/platform/windows/ipc.py 2012-01-03 12:40:04 +0000
207+++ ubuntuone/platform/windows/ipc.py 2012-02-09 23:36:18 +0000
208@@ -773,6 +773,7 @@
209 remote_calls = [
210 'create',
211 'delete',
212+ 'validate_path',
213 'get_folders',
214 'subscribe',
215 'unsubscribe',
216@@ -799,6 +800,10 @@
217 """Delete the folder specified by folder_id"""
218 return self.service.folders.delete(folder_id)
219
220+ def validate_path(self, path):
221+ """Return True if the path is valid for a folder."""
222+ return self.service.folders.validate_path(path)
223+
224 def get_folders(self):
225 """Return the list of folders (a list of dicts)"""
226 return self.service.folders.get_folders()
227
228=== modified file 'ubuntuone/platform/windows/ipc_client.py'
229--- ubuntuone/platform/windows/ipc_client.py 2012-01-03 12:40:04 +0000
230+++ ubuntuone/platform/windows/ipc_client.py 2012-02-09 23:36:18 +0000
231@@ -579,6 +579,10 @@
232 """Delete the folder specified by folder_id"""
233
234 @remote
235+ def validate_path(self, path):
236+ """Return True if the path is valid for a folder"""
237+
238+ @remote
239 def get_folders(self):
240 """Return the list of folders (a list of dicts)"""
241
242
243=== modified file 'ubuntuone/syncdaemon/interaction_interfaces.py'
244--- ubuntuone/syncdaemon/interaction_interfaces.py 2012-01-15 19:31:07 +0000
245+++ ubuntuone/syncdaemon/interaction_interfaces.py 2012-02-09 23:36:18 +0000
246@@ -674,6 +674,13 @@
247 """Delete the folder specified by folder_id."""
248 self.vm.delete_volume(folder_id)
249
250+ @unicode_to_bytes
251+ @log_call(logger.debug)
252+ def validate_path(self, path):
253+ """Return True if the path is valid for a folder."""
254+ # Returns (bool, str), but we only care about the bool from here on.
255+ return self.vm.validate_path_for_folder(path)[0]
256+
257 @log_call(logger.debug)
258 def get_folders(self):
259 """Return the list of folders (a list of dicts)."""
260
261=== modified file 'ubuntuone/syncdaemon/volume_manager.py'
262--- ubuntuone/syncdaemon/volume_manager.py 2011-12-20 15:35:51 +0000
263+++ ubuntuone/syncdaemon/volume_manager.py 2012-02-09 23:36:18 +0000
264@@ -1178,20 +1178,31 @@
265 return True
266 return False
267
268- def create_udf(self, path):
269- """Request the creation of a UDF to AQ."""
270- self.log.debug('create udf: %r', path)
271+ def validate_path_for_folder(self, path):
272+ """Validate 'folder_path' for folder creation."""
273 path = normpath(path)
274+ user_home = expand_user('~')
275+ # handle folder_path not within '~'
276+ if not path.startswith(user_home):
277+ return (False, "UDFs must be within home")
278+
279 # check if path is the realpath, bail out if not
280 if is_link(path):
281- self.m.event_q.push('VM_UDF_CREATE_ERROR', path=path,
282- error="UDFs can not be a symlink")
283- return
284+ return (False, "UDFs can not be a symlink")
285+
286 # check if the path it's ok (outside root and
287 # isn't a ancestor or child of another UDF)
288 if self._is_nested_udf(path):
289- self.m.event_q.push('VM_UDF_CREATE_ERROR', path=path,
290- error="UDFs can not be nested")
291+ return (False, "UDFs can not be nested")
292+
293+ return (True, "")
294+
295+ def create_udf(self, path):
296+ """Request the creation of a UDF to AQ."""
297+ self.log.debug('create udf: %r', path)
298+ valid, msg = self.validate_path_for_folder(path)
299+ if not valid:
300+ self.m.event_q.push('VM_UDF_CREATE_ERROR', path=path, error=msg)
301 return
302
303 try:

Subscribers

People subscribed via source and target branches