1
# Copyright (C) 2005-2010 Canonical Ltd
2
# -*- coding: utf-8 -*-
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19
"""Tests for bzr setting permissions.
21
Files which are created underneath .bzr/ should inherit its permissions.
22
So if the directory is group writable, the files and subdirs should be as well.
24
In the future, when we have Repository/Branch/Checkout information, the
25
permissions should be inherited individually, rather than all be the same.
28
# TODO: jam 20051215 There are no tests for ftp yet, because we have no ftp server
29
# TODO: jam 20051215 Currently the default behavior for 'bzr branch' is just
30
# defined by the local umask. This isn't terrible, is it
31
# the truly desired behavior?
36
from cStringIO import StringIO
39
from bzrlib import transport
40
from bzrlib.branch import Branch
41
from bzrlib.bzrdir import BzrDir
42
from bzrlib.tests import TestCaseWithTransport, TestSkipped
43
from bzrlib.tests.test_sftp_transport import TestCaseWithSFTPServer
44
from bzrlib.workingtree import WorkingTree
47
def chmod_r(base, file_mode, dir_mode):
48
"""Recursively chmod from a base directory"""
49
os.chmod(base, dir_mode)
50
for root, dirs, files in os.walk(base):
52
p = os.path.join(root, d)
55
p = os.path.join(root, f)
56
os.chmod(p, file_mode)
59
def check_mode_r(test, base, file_mode, dir_mode, include_base=True):
60
"""Check that all permissions match
62
:param test: The TestCase being run
63
:param base: The path to the root directory to check
64
:param file_mode: The mode for all files
65
:param dir_mode: The mode for all directories
66
:param include_base: If false, only check the subdirectories
68
t = transport.get_transport(".")
70
test.assertTransportMode(t, base, dir_mode)
71
for root, dirs, files in os.walk(base):
73
p = '/'.join([urllib.quote(x) for x in root.split('/\\') + [d]])
74
test.assertTransportMode(t, p, dir_mode)
76
p = os.path.join(root, f)
77
p = '/'.join([urllib.quote(x) for x in root.split('/\\') + [f]])
78
test.assertTransportMode(t, p, file_mode)
81
class TestPermissions(TestCaseWithTransport):
83
def test_new_files(self):
84
if sys.platform == 'win32':
85
raise TestSkipped('chmod has no effect on win32')
87
t = self.make_branch_and_tree('.')
89
open('a', 'wb').write('foo\n')
90
# ensure check_mode_r works with capital-letter file-ids like TREE_ROOT
94
chmod_r('.bzr', 0644, 0755)
95
check_mode_r(self, '.bzr', 0644, 0755)
97
# although we are modifying the filesystem
98
# underneath the objects, they are not locked, and thus it must
99
# be safe for most operations. But here we want to observe a
100
# mode change in the control bits, which current do not refresh
101
# when a new lock is taken out.
102
t = WorkingTree.open('.')
104
self.assertEqualMode(0755, b.control_files._dir_mode)
105
self.assertEqualMode(0644, b.control_files._file_mode)
106
self.assertEqualMode(0755, b.bzrdir._get_dir_mode())
107
self.assertEqualMode(0644, b.bzrdir._get_file_mode())
109
# Modifying a file shouldn't break the permissions
110
open('a', 'wb').write('foo2\n')
112
# The mode should be maintained after commit
113
check_mode_r(self, '.bzr', 0644, 0755)
115
# Adding a new file should maintain the permissions
116
open('b', 'wb').write('new b\n')
119
check_mode_r(self, '.bzr', 0644, 0755)
121
# Recursively update the modes of all files
122
chmod_r('.bzr', 0664, 0775)
123
check_mode_r(self, '.bzr', 0664, 0775)
124
t = WorkingTree.open('.')
126
self.assertEqualMode(0775, b.control_files._dir_mode)
127
self.assertEqualMode(0664, b.control_files._file_mode)
128
self.assertEqualMode(0775, b.bzrdir._get_dir_mode())
129
self.assertEqualMode(0664, b.bzrdir._get_file_mode())
131
open('a', 'wb').write('foo3\n')
133
check_mode_r(self, '.bzr', 0664, 0775)
135
open('c', 'wb').write('new c\n')
138
check_mode_r(self, '.bzr', 0664, 0775)
140
def test_new_files_group_sticky_bit(self):
141
if sys.platform == 'win32':
142
raise TestSkipped('chmod has no effect on win32')
143
elif sys.platform == 'darwin' or sys.platform.startswith('freebsd'):
144
# OS X (and FreeBSD) create temp dirs with the 'wheel' group, which
145
# users are not likely to be in, and this prevents us from setting
147
os.chown(self.test_dir, os.getuid(), os.getgid())
149
t = self.make_branch_and_tree('.')
152
# Test the group sticky bit
153
# Recursively update the modes of all files
154
chmod_r('.bzr', 0664, 02775)
155
check_mode_r(self, '.bzr', 0664, 02775)
156
t = WorkingTree.open('.')
158
self.assertEqualMode(02775, b.control_files._dir_mode)
159
self.assertEqualMode(0664, b.control_files._file_mode)
160
self.assertEqualMode(02775, b.bzrdir._get_dir_mode())
161
self.assertEqualMode(0664, b.bzrdir._get_file_mode())
163
open('a', 'wb').write('foo4\n')
165
check_mode_r(self, '.bzr', 0664, 02775)
167
open('d', 'wb').write('new d\n')
170
check_mode_r(self, '.bzr', 0664, 02775)
173
class TestSftpPermissions(TestCaseWithSFTPServer):
175
def test_new_files(self):
176
if sys.platform == 'win32':
177
raise TestSkipped('chmod has no effect on win32')
178
# Though it would be nice to test that SFTP to a server
179
# which does support chmod has the right effect
181
# bodge around for stubsftpserver not letting use connect
183
_t = transport.get_transport(self.get_url())
186
t_local = self.make_branch_and_tree('local')
187
b_local = t_local.branch
188
open('local/a', 'wb').write('foo\n')
190
t_local.commit('foo')
192
# Delete them because we are modifying the filesystem underneath them
193
chmod_r('local/.bzr', 0644, 0755)
194
check_mode_r(self, 'local/.bzr', 0644, 0755)
196
t = WorkingTree.open('local')
198
self.assertEqualMode(0755, b_local.control_files._dir_mode)
199
self.assertEqualMode(0644, b_local.control_files._file_mode)
200
self.assertEqualMode(0755, b_local.bzrdir._get_dir_mode())
201
self.assertEqualMode(0644, b_local.bzrdir._get_file_mode())
204
sftp_url = self.get_url('sftp')
205
b_sftp = BzrDir.create_branch_and_repo(sftp_url)
209
chmod_r('sftp/.bzr', 0644, 0755)
210
check_mode_r(self, 'sftp/.bzr', 0644, 0755)
212
b_sftp = Branch.open(sftp_url)
213
self.assertEqualMode(0755, b_sftp.control_files._dir_mode)
214
self.assertEqualMode(0644, b_sftp.control_files._file_mode)
215
self.assertEqualMode(0755, b_sftp.bzrdir._get_dir_mode())
216
self.assertEqualMode(0644, b_sftp.bzrdir._get_file_mode())
218
open('local/a', 'wb').write('foo2\n')
219
t_local.commit('foo2')
221
# The mode should be maintained after commit
222
check_mode_r(self, 'sftp/.bzr', 0644, 0755)
224
open('local/b', 'wb').write('new b\n')
226
t_local.commit('new b')
228
check_mode_r(self, 'sftp/.bzr', 0644, 0755)
231
# Recursively update the modes of all files
232
chmod_r('sftp/.bzr', 0664, 0775)
233
check_mode_r(self, 'sftp/.bzr', 0664, 0775)
235
b_sftp = Branch.open(sftp_url)
236
self.assertEqualMode(0775, b_sftp.control_files._dir_mode)
237
self.assertEqualMode(0664, b_sftp.control_files._file_mode)
238
self.assertEqualMode(0775, b_sftp.bzrdir._get_dir_mode())
239
self.assertEqualMode(0664, b_sftp.bzrdir._get_file_mode())
241
open('local/a', 'wb').write('foo3\n')
242
t_local.commit('foo3')
244
check_mode_r(self, 'sftp/.bzr', 0664, 0775)
246
open('local/c', 'wb').write('new c\n')
248
t_local.commit('new c')
250
check_mode_r(self, 'sftp/.bzr', 0664, 0775)
252
def test_sftp_server_modes(self):
253
if sys.platform == 'win32':
254
raise TestSkipped('chmod has no effect on win32')
257
original_umask = os.umask(umask)
260
t = transport.get_transport(self.get_url())
261
# Direct access should be masked by umask
262
t._sftp_open_exclusive('a', mode=0666).write('foo\n')
263
self.assertTransportMode(t, 'a', 0666 &~umask)
265
# but Transport overrides umask
266
t.put_bytes('b', 'txt', mode=0666)
267
self.assertTransportMode(t, 'b', 0666)
269
t._get_sftp().mkdir('c', mode=0777)
270
self.assertTransportMode(t, 'c', 0777 &~umask)
272
t.mkdir('d', mode=0777)
273
self.assertTransportMode(t, 'd', 0777)
275
os.umask(original_umask)