Show More
@@ -0,0 +1,94 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | ||||
|
3 | # Copyright (C) 2010-2018 RhodeCode GmbH | |||
|
4 | # | |||
|
5 | # This program is free software: you can redistribute it and/or modify | |||
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |||
|
7 | # (only), as published by the Free Software Foundation. | |||
|
8 | # | |||
|
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. | |||
|
13 | # | |||
|
14 | # You should have received a copy of the GNU Affero General Public License | |||
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
|
16 | # | |||
|
17 | # This program is dual-licensed. If you wish to learn more about the | |||
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |||
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |||
|
20 | ||||
|
21 | import os | |||
|
22 | import pytest | |||
|
23 | ||||
|
24 | from rhodecode.tests import ( | |||
|
25 | TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS, | |||
|
26 | TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS) | |||
|
27 | from rhodecode.tests.vcs_operations import ( | |||
|
28 | Command, _check_proper_hg_push, _check_proper_git_push, _add_files_and_push) | |||
|
29 | ||||
|
30 | ||||
|
31 | @pytest.mark.usefixtures("disable_anonymous_user") | |||
|
32 | class TestVCSOperations(object): | |||
|
33 | ||||
|
34 | @pytest.mark.parametrize('username, password', [ | |||
|
35 | (TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS), | |||
|
36 | (TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS), | |||
|
37 | ]) | |||
|
38 | @pytest.mark.parametrize('branch_perm', [ | |||
|
39 | 'branch.none', | |||
|
40 | 'branch.merge', | |||
|
41 | 'branch.push', | |||
|
42 | 'branch.push_force', | |||
|
43 | ]) | |||
|
44 | def test_push_to_protected_branch_fails_with_message_hg( | |||
|
45 | self, rc_web_server, tmpdir, branch_perm, user_util, | |||
|
46 | branch_permission_setter, username, password): | |||
|
47 | repo = user_util.create_repo(repo_type='hg') | |||
|
48 | repo_name = repo.repo_name | |||
|
49 | branch_permission_setter(repo_name, username, permission=branch_perm) | |||
|
50 | ||||
|
51 | clone_url = rc_web_server.repo_clone_url( | |||
|
52 | repo.repo_name, user=username, passwd=password) | |||
|
53 | Command(os.path.dirname(tmpdir.strpath)).execute( | |||
|
54 | 'hg clone', clone_url, tmpdir.strpath) | |||
|
55 | ||||
|
56 | stdout, stderr = _add_files_and_push( | |||
|
57 | 'hg', tmpdir.strpath, clone_url=clone_url) | |||
|
58 | if branch_perm in ['branch.push', 'branch.push_force']: | |||
|
59 | _check_proper_hg_push(stdout, stderr) | |||
|
60 | else: | |||
|
61 | msg = "Branch `default` changes rejected by rule `*`=>{}".format(branch_perm) | |||
|
62 | assert msg in stdout | |||
|
63 | assert "transaction abort" in stdout | |||
|
64 | ||||
|
65 | @pytest.mark.parametrize('username, password', [ | |||
|
66 | (TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS), | |||
|
67 | (TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS), | |||
|
68 | ]) | |||
|
69 | @pytest.mark.parametrize('branch_perm', [ | |||
|
70 | 'branch.none', | |||
|
71 | 'branch.merge', | |||
|
72 | 'branch.push', | |||
|
73 | 'branch.push_force', | |||
|
74 | ]) | |||
|
75 | def test_push_to_protected_branch_fails_with_message_git( | |||
|
76 | self, rc_web_server, tmpdir, branch_perm, user_util, | |||
|
77 | branch_permission_setter, username, password): | |||
|
78 | repo = user_util.create_repo(repo_type='git') | |||
|
79 | repo_name = repo.repo_name | |||
|
80 | branch_permission_setter(repo_name, username, permission=branch_perm) | |||
|
81 | ||||
|
82 | clone_url = rc_web_server.repo_clone_url( | |||
|
83 | repo.repo_name, user=username, passwd=password) | |||
|
84 | Command(os.path.dirname(tmpdir.strpath)).execute( | |||
|
85 | 'git clone', clone_url, tmpdir.strpath) | |||
|
86 | ||||
|
87 | stdout, stderr = _add_files_and_push( | |||
|
88 | 'git', tmpdir.strpath, clone_url=clone_url) | |||
|
89 | if branch_perm in ['branch.push', 'branch.push_force']: | |||
|
90 | _check_proper_git_push(stdout, stderr) | |||
|
91 | else: | |||
|
92 | msg = "Branch `master` changes rejected by rule `*`=>{}".format(branch_perm) | |||
|
93 | assert msg in stderr | |||
|
94 | assert "(pre-receive hook declined)" in stderr |
@@ -0,0 +1,122 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | ||||
|
3 | # Copyright (C) 2010-2018 RhodeCode GmbH | |||
|
4 | # | |||
|
5 | # This program is free software: you can redistribute it and/or modify | |||
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |||
|
7 | # (only), as published by the Free Software Foundation. | |||
|
8 | # | |||
|
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. | |||
|
13 | # | |||
|
14 | # You should have received a copy of the GNU Affero General Public License | |||
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
|
16 | # | |||
|
17 | # This program is dual-licensed. If you wish to learn more about the | |||
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |||
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |||
|
20 | ||||
|
21 | ||||
|
22 | import os | |||
|
23 | import pytest | |||
|
24 | ||||
|
25 | from rhodecode.tests import TEST_USER_ADMIN_LOGIN | |||
|
26 | from rhodecode.tests.vcs_operations import ( | |||
|
27 | Command, _check_proper_hg_push, _check_proper_git_push, | |||
|
28 | _add_files, _add_files_and_push) | |||
|
29 | ||||
|
30 | ||||
|
31 | @pytest.mark.usefixtures("disable_anonymous_user") | |||
|
32 | class TestVCSOperations(object): | |||
|
33 | ||||
|
34 | def test_push_force_hg(self, rc_web_server, tmpdir, user_util): | |||
|
35 | repo = user_util.create_repo(repo_type='hg') | |||
|
36 | clone_url = rc_web_server.repo_clone_url(repo.repo_name) | |||
|
37 | Command(os.path.dirname(tmpdir.strpath)).execute( | |||
|
38 | 'hg clone', clone_url, tmpdir.strpath) | |||
|
39 | ||||
|
40 | stdout, stderr = _add_files_and_push( | |||
|
41 | 'hg', tmpdir.strpath, clone_url=clone_url) | |||
|
42 | _check_proper_hg_push(stdout, stderr) | |||
|
43 | ||||
|
44 | # rewrite history, and push with force | |||
|
45 | Command(tmpdir.strpath).execute( | |||
|
46 | 'hg checkout -r 1 && hg commit -m "starting new head"') | |||
|
47 | _add_files('hg', tmpdir.strpath, clone_url=clone_url) | |||
|
48 | ||||
|
49 | stdout, stderr = Command(tmpdir.strpath).execute( | |||
|
50 | 'hg push --verbose -f {}'.format(clone_url)) | |||
|
51 | ||||
|
52 | _check_proper_hg_push(stdout, stderr) | |||
|
53 | ||||
|
54 | def test_push_force_git(self, rc_web_server, tmpdir, user_util): | |||
|
55 | repo = user_util.create_repo(repo_type='git') | |||
|
56 | clone_url = rc_web_server.repo_clone_url(repo.repo_name) | |||
|
57 | Command(os.path.dirname(tmpdir.strpath)).execute( | |||
|
58 | 'git clone', clone_url, tmpdir.strpath) | |||
|
59 | ||||
|
60 | stdout, stderr = _add_files_and_push( | |||
|
61 | 'git', tmpdir.strpath, clone_url=clone_url) | |||
|
62 | _check_proper_git_push(stdout, stderr) | |||
|
63 | ||||
|
64 | # rewrite history, and push with force | |||
|
65 | Command(tmpdir.strpath).execute( | |||
|
66 | 'git reset --hard HEAD~2') | |||
|
67 | stdout, stderr = Command(tmpdir.strpath).execute( | |||
|
68 | 'git push -f {} master'.format(clone_url)) | |||
|
69 | ||||
|
70 | assert '(forced update)' in stderr | |||
|
71 | ||||
|
72 | def test_push_force_hg_blocked_by_branch_permissions( | |||
|
73 | self, rc_web_server, tmpdir, user_util, branch_permission_setter): | |||
|
74 | repo = user_util.create_repo(repo_type='hg') | |||
|
75 | repo_name = repo.repo_name | |||
|
76 | username = TEST_USER_ADMIN_LOGIN | |||
|
77 | branch_permission_setter(repo_name, username, permission='branch.push') | |||
|
78 | ||||
|
79 | clone_url = rc_web_server.repo_clone_url(repo.repo_name) | |||
|
80 | Command(os.path.dirname(tmpdir.strpath)).execute( | |||
|
81 | 'hg clone', clone_url, tmpdir.strpath) | |||
|
82 | ||||
|
83 | stdout, stderr = _add_files_and_push( | |||
|
84 | 'hg', tmpdir.strpath, clone_url=clone_url) | |||
|
85 | _check_proper_hg_push(stdout, stderr) | |||
|
86 | ||||
|
87 | # rewrite history, and push with force | |||
|
88 | Command(tmpdir.strpath).execute( | |||
|
89 | 'hg checkout -r 1 && hg commit -m "starting new head"') | |||
|
90 | _add_files('hg', tmpdir.strpath, clone_url=clone_url) | |||
|
91 | ||||
|
92 | stdout, stderr = Command(tmpdir.strpath).execute( | |||
|
93 | 'hg push --verbose -f {}'.format(clone_url)) | |||
|
94 | ||||
|
95 | assert "Branch `default` changes rejected by rule `*`=>branch.push" in stdout | |||
|
96 | assert "FORCE PUSH FORBIDDEN" in stdout | |||
|
97 | assert "transaction abort" in stdout | |||
|
98 | ||||
|
99 | def test_push_force_git_blocked_by_branch_permissions( | |||
|
100 | self, rc_web_server, tmpdir, user_util, branch_permission_setter): | |||
|
101 | repo = user_util.create_repo(repo_type='git') | |||
|
102 | repo_name = repo.repo_name | |||
|
103 | username = TEST_USER_ADMIN_LOGIN | |||
|
104 | branch_permission_setter(repo_name, username, permission='branch.push') | |||
|
105 | ||||
|
106 | clone_url = rc_web_server.repo_clone_url(repo.repo_name) | |||
|
107 | Command(os.path.dirname(tmpdir.strpath)).execute( | |||
|
108 | 'git clone', clone_url, tmpdir.strpath) | |||
|
109 | ||||
|
110 | stdout, stderr = _add_files_and_push( | |||
|
111 | 'git', tmpdir.strpath, clone_url=clone_url) | |||
|
112 | _check_proper_git_push(stdout, stderr) | |||
|
113 | ||||
|
114 | # rewrite history, and push with force | |||
|
115 | Command(tmpdir.strpath).execute( | |||
|
116 | 'git reset --hard HEAD~2') | |||
|
117 | stdout, stderr = Command(tmpdir.strpath).execute( | |||
|
118 | 'git push -f {} master'.format(clone_url)) | |||
|
119 | ||||
|
120 | assert "Branch `master` changes rejected by rule `*`=>branch.push" in stderr | |||
|
121 | assert "FORCE PUSH FORBIDDEN" in stderr | |||
|
122 | assert "(pre-receive hook declined)" in stderr |
@@ -0,0 +1,115 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | ||||
|
3 | # Copyright (C) 2010-2018 RhodeCode GmbH | |||
|
4 | # | |||
|
5 | # This program is free software: you can redistribute it and/or modify | |||
|
6 | # it under the terms of the GNU Affero General Public License, version 3 | |||
|
7 | # (only), as published by the Free Software Foundation. | |||
|
8 | # | |||
|
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. | |||
|
13 | # | |||
|
14 | # You should have received a copy of the GNU Affero General Public License | |||
|
15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
|
16 | # | |||
|
17 | # This program is dual-licensed. If you wish to learn more about the | |||
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |||
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |||
|
20 | ||||
|
21 | ||||
|
22 | import os | |||
|
23 | import pytest | |||
|
24 | ||||
|
25 | from rhodecode.tests import TEST_USER_ADMIN_LOGIN | |||
|
26 | from rhodecode.tests.vcs_operations import ( | |||
|
27 | Command, _check_proper_hg_push, _check_proper_git_push, _add_files_and_push) | |||
|
28 | ||||
|
29 | ||||
|
30 | @pytest.mark.usefixtures("disable_anonymous_user") | |||
|
31 | class TestVCSOperations(object): | |||
|
32 | ||||
|
33 | def test_push_new_branch_hg(self, rc_web_server, tmpdir, user_util): | |||
|
34 | repo = user_util.create_repo(repo_type='hg') | |||
|
35 | clone_url = rc_web_server.repo_clone_url(repo.repo_name) | |||
|
36 | Command(os.path.dirname(tmpdir.strpath)).execute( | |||
|
37 | 'hg clone', clone_url, tmpdir.strpath) | |||
|
38 | ||||
|
39 | stdout, stderr = _add_files_and_push( | |||
|
40 | 'hg', tmpdir.strpath, clone_url=clone_url) | |||
|
41 | _check_proper_hg_push(stdout, stderr) | |||
|
42 | ||||
|
43 | # start new branch, and push file into it | |||
|
44 | Command(tmpdir.strpath).execute( | |||
|
45 | 'hg branch dev && hg commit -m "starting dev branch"') | |||
|
46 | stdout, stderr = _add_files_and_push( | |||
|
47 | 'hg', tmpdir.strpath, clone_url=clone_url, target_branch='dev', | |||
|
48 | new_branch=True) | |||
|
49 | ||||
|
50 | _check_proper_hg_push(stdout, stderr) | |||
|
51 | ||||
|
52 | def test_push_new_branch_git(self, rc_web_server, tmpdir, user_util): | |||
|
53 | repo = user_util.create_repo(repo_type='git') | |||
|
54 | clone_url = rc_web_server.repo_clone_url(repo.repo_name) | |||
|
55 | Command(os.path.dirname(tmpdir.strpath)).execute( | |||
|
56 | 'git clone', clone_url, tmpdir.strpath) | |||
|
57 | ||||
|
58 | stdout, stderr = _add_files_and_push( | |||
|
59 | 'git', tmpdir.strpath, clone_url=clone_url) | |||
|
60 | _check_proper_git_push(stdout, stderr) | |||
|
61 | ||||
|
62 | # start new branch, and push file into it | |||
|
63 | Command(tmpdir.strpath).execute('git checkout -b dev') | |||
|
64 | stdout, stderr = _add_files_and_push( | |||
|
65 | 'git', tmpdir.strpath, clone_url=clone_url, target_branch='dev', | |||
|
66 | new_branch=True) | |||
|
67 | ||||
|
68 | _check_proper_git_push(stdout, stderr, branch='dev') | |||
|
69 | ||||
|
70 | def test_push_new_branch_hg_with_branch_permissions_no_force_push( | |||
|
71 | self, rc_web_server, tmpdir, user_util, branch_permission_setter): | |||
|
72 | repo = user_util.create_repo(repo_type='hg') | |||
|
73 | repo_name = repo.repo_name | |||
|
74 | username = TEST_USER_ADMIN_LOGIN | |||
|
75 | branch_permission_setter(repo_name, username, permission='branch.push') | |||
|
76 | ||||
|
77 | clone_url = rc_web_server.repo_clone_url(repo.repo_name) | |||
|
78 | Command(os.path.dirname(tmpdir.strpath)).execute( | |||
|
79 | 'hg clone', clone_url, tmpdir.strpath) | |||
|
80 | ||||
|
81 | stdout, stderr = _add_files_and_push( | |||
|
82 | 'hg', tmpdir.strpath, clone_url=clone_url) | |||
|
83 | _check_proper_hg_push(stdout, stderr) | |||
|
84 | ||||
|
85 | # start new branch, and push file into it | |||
|
86 | Command(tmpdir.strpath).execute( | |||
|
87 | 'hg branch dev && hg commit -m "starting dev branch"') | |||
|
88 | stdout, stderr = _add_files_and_push( | |||
|
89 | 'hg', tmpdir.strpath, clone_url=clone_url, target_branch='dev', | |||
|
90 | new_branch=True) | |||
|
91 | ||||
|
92 | _check_proper_hg_push(stdout, stderr) | |||
|
93 | ||||
|
94 | def test_push_new_branch_git_with_branch_permissions_no_force_push( | |||
|
95 | self, rc_web_server, tmpdir, user_util, branch_permission_setter): | |||
|
96 | repo = user_util.create_repo(repo_type='git') | |||
|
97 | repo_name = repo.repo_name | |||
|
98 | username = TEST_USER_ADMIN_LOGIN | |||
|
99 | branch_permission_setter(repo_name, username, permission='branch.push') | |||
|
100 | ||||
|
101 | clone_url = rc_web_server.repo_clone_url(repo.repo_name) | |||
|
102 | Command(os.path.dirname(tmpdir.strpath)).execute( | |||
|
103 | 'git clone', clone_url, tmpdir.strpath) | |||
|
104 | ||||
|
105 | stdout, stderr = _add_files_and_push( | |||
|
106 | 'git', tmpdir.strpath, clone_url=clone_url) | |||
|
107 | _check_proper_git_push(stdout, stderr) | |||
|
108 | ||||
|
109 | # start new branch, and push file into it | |||
|
110 | Command(tmpdir.strpath).execute('git checkout -b dev') | |||
|
111 | stdout, stderr = _add_files_and_push( | |||
|
112 | 'git', tmpdir.strpath, clone_url=clone_url, target_branch='dev', | |||
|
113 | new_branch=True) | |||
|
114 | ||||
|
115 | _check_proper_git_push(stdout, stderr, branch='dev') |
@@ -404,14 +404,14 b' class PermissionCalculator(object):' | |||||
404 | def __init__( |
|
404 | def __init__( | |
405 | self, user_id, scope, user_is_admin, |
|
405 | self, user_id, scope, user_is_admin, | |
406 | user_inherit_default_permissions, explicit, algo, |
|
406 | user_inherit_default_permissions, explicit, algo, | |
407 | calculate_super_admin=False): |
|
407 | calculate_super_admin_as_user=False): | |
408 |
|
408 | |||
409 | self.user_id = user_id |
|
409 | self.user_id = user_id | |
410 | self.user_is_admin = user_is_admin |
|
410 | self.user_is_admin = user_is_admin | |
411 | self.inherit_default_permissions = user_inherit_default_permissions |
|
411 | self.inherit_default_permissions = user_inherit_default_permissions | |
412 | self.explicit = explicit |
|
412 | self.explicit = explicit | |
413 | self.algo = algo |
|
413 | self.algo = algo | |
414 | self.calculate_super_admin = calculate_super_admin |
|
414 | self.calculate_super_admin_as_user = calculate_super_admin_as_user | |
415 |
|
415 | |||
416 | scope = scope or {} |
|
416 | scope = scope or {} | |
417 | self.scope_repo_id = scope.get('repo_id') |
|
417 | self.scope_repo_id = scope.get('repo_id') | |
@@ -440,8 +440,8 b' class PermissionCalculator(object):' | |||||
440 | self.default_user_id, self.scope_repo_id) |
|
440 | self.default_user_id, self.scope_repo_id) | |
441 |
|
441 | |||
442 | def calculate(self): |
|
442 | def calculate(self): | |
443 | if self.user_is_admin and not self.calculate_super_admin: |
|
443 | if self.user_is_admin and not self.calculate_super_admin_as_user: | |
444 | return self._admin_permissions() |
|
444 | return self._calculate_admin_permissions() | |
445 |
|
445 | |||
446 | self._calculate_global_default_permissions() |
|
446 | self._calculate_global_default_permissions() | |
447 | self._calculate_global_permissions() |
|
447 | self._calculate_global_permissions() | |
@@ -452,7 +452,7 b' class PermissionCalculator(object):' | |||||
452 | self._calculate_user_group_permissions() |
|
452 | self._calculate_user_group_permissions() | |
453 | return self._permission_structure() |
|
453 | return self._permission_structure() | |
454 |
|
454 | |||
455 | def _admin_permissions(self): |
|
455 | def _calculate_admin_permissions(self): | |
456 | """ |
|
456 | """ | |
457 | admin user have all default rights for repositories |
|
457 | admin user have all default rights for repositories | |
458 | and groups set to admin |
|
458 | and groups set to admin | |
@@ -479,13 +479,11 b' class PermissionCalculator(object):' | |||||
479 | self.permissions_user_groups[u_k] = p, PermOrigin.SUPER_ADMIN |
|
479 | self.permissions_user_groups[u_k] = p, PermOrigin.SUPER_ADMIN | |
480 |
|
480 | |||
481 | # branch permissions |
|
481 | # branch permissions | |
482 | # TODO(marcink): validate this, especially |
|
482 | # since super-admin also can have custom rule permissions | |
483 | # how this should work using multiple patterns specified ?? |
|
483 | # we *always* need to calculate those inherited from default, and also explicit | |
484 | # looks ok, but still needs double check !! |
|
484 | self._calculate_default_permissions_repository_branches( | |
485 | for perm in self.default_branch_repo_perms: |
|
485 | user_inherit_object_permissions=False) | |
486 | r_k = perm.UserRepoToPerm.repository.repo_name |
|
486 | self._calculate_repository_branch_permissions() | |
487 | p = 'branch.push_force' |
|
|||
488 | self.permissions_repository_branches[r_k] = '*', p, PermOrigin.SUPER_ADMIN |
|
|||
489 |
|
487 | |||
490 | return self._permission_structure() |
|
488 | return self._permission_structure() | |
491 |
|
489 | |||
@@ -571,27 +569,7 b' class PermissionCalculator(object):' | |||||
571 | for perm in user_perms: |
|
569 | for perm in user_perms: | |
572 | self.permissions_global.add(perm.permission.permission_name) |
|
570 | self.permissions_global.add(perm.permission.permission_name) | |
573 |
|
571 | |||
574 | def _calculate_default_permissions(self): |
|
572 | def _calculate_default_permissions_repositories(self, user_inherit_object_permissions): | |
575 | """ |
|
|||
576 | Set default user permissions for repositories, repository branches, |
|
|||
577 | repository groups, user groups taken from the default user. |
|
|||
578 |
|
||||
579 | Calculate inheritance of object permissions based on what we have now |
|
|||
580 | in GLOBAL permissions. We check if .false is in GLOBAL since this is |
|
|||
581 | explicitly set. Inherit is the opposite of .false being there. |
|
|||
582 |
|
||||
583 | .. note:: |
|
|||
584 |
|
||||
585 | the syntax is little bit odd but what we need to check here is |
|
|||
586 | the opposite of .false permission being in the list so even for |
|
|||
587 | inconsistent state when both .true/.false is there |
|
|||
588 | .false is more important |
|
|||
589 |
|
||||
590 | """ |
|
|||
591 | user_inherit_object_permissions = not ('hg.inherit_default_perms.false' |
|
|||
592 | in self.permissions_global) |
|
|||
593 |
|
||||
594 | # default permissions for repositories, taken from `default` user permissions |
|
|||
595 | for perm in self.default_repo_perms: |
|
573 | for perm in self.default_repo_perms: | |
596 | r_k = perm.UserRepoToPerm.repository.repo_name |
|
574 | r_k = perm.UserRepoToPerm.repository.repo_name | |
597 | p = perm.Permission.permission_name |
|
575 | p = perm.Permission.permission_name | |
@@ -624,7 +602,7 b' class PermissionCalculator(object):' | |||||
624 | o = PermOrigin.SUPER_ADMIN |
|
602 | o = PermOrigin.SUPER_ADMIN | |
625 | self.permissions_repositories[r_k] = p, o |
|
603 | self.permissions_repositories[r_k] = p, o | |
626 |
|
604 | |||
627 | # default permissions branch for repositories, taken from `default` user permissions |
|
605 | def _calculate_default_permissions_repository_branches(self, user_inherit_object_permissions): | |
628 | for perm in self.default_branch_repo_perms: |
|
606 | for perm in self.default_branch_repo_perms: | |
629 |
|
607 | |||
630 | r_k = perm.UserRepoToPerm.repository.repo_name |
|
608 | r_k = perm.UserRepoToPerm.repository.repo_name | |
@@ -641,7 +619,7 b' class PermissionCalculator(object):' | |||||
641 | # special dict that aggregates entries |
|
619 | # special dict that aggregates entries | |
642 | self.permissions_repository_branches[r_k] = pattern, p, o |
|
620 | self.permissions_repository_branches[r_k] = pattern, p, o | |
643 |
|
621 | |||
644 |
|
|
622 | def _calculate_default_permissions_repository_groups(self, user_inherit_object_permissions): | |
645 | for perm in self.default_repo_groups_perms: |
|
623 | for perm in self.default_repo_groups_perms: | |
646 | rg_k = perm.UserRepoGroupToPerm.group.group_name |
|
624 | rg_k = perm.UserRepoGroupToPerm.group.group_name | |
647 | p = perm.Permission.permission_name |
|
625 | p = perm.Permission.permission_name | |
@@ -666,7 +644,7 b' class PermissionCalculator(object):' | |||||
666 | o = PermOrigin.SUPER_ADMIN |
|
644 | o = PermOrigin.SUPER_ADMIN | |
667 | self.permissions_repository_groups[rg_k] = p, o |
|
645 | self.permissions_repository_groups[rg_k] = p, o | |
668 |
|
646 | |||
669 | # default permissions for user groups taken from `default` user permission |
|
647 | def _calculate_default_permissions_user_groups(self, user_inherit_object_permissions): | |
670 | for perm in self.default_user_group_perms: |
|
648 | for perm in self.default_user_group_perms: | |
671 | u_k = perm.UserUserGroupToPerm.user_group.users_group_name |
|
649 | u_k = perm.UserUserGroupToPerm.user_group.users_group_name | |
672 | p = perm.Permission.permission_name |
|
650 | p = perm.Permission.permission_name | |
@@ -691,6 +669,39 b' class PermissionCalculator(object):' | |||||
691 | o = PermOrigin.SUPER_ADMIN |
|
669 | o = PermOrigin.SUPER_ADMIN | |
692 | self.permissions_user_groups[u_k] = p, o |
|
670 | self.permissions_user_groups[u_k] = p, o | |
693 |
|
671 | |||
|
672 | def _calculate_default_permissions(self): | |||
|
673 | """ | |||
|
674 | Set default user permissions for repositories, repository branches, | |||
|
675 | repository groups, user groups taken from the default user. | |||
|
676 | ||||
|
677 | Calculate inheritance of object permissions based on what we have now | |||
|
678 | in GLOBAL permissions. We check if .false is in GLOBAL since this is | |||
|
679 | explicitly set. Inherit is the opposite of .false being there. | |||
|
680 | ||||
|
681 | .. note:: | |||
|
682 | ||||
|
683 | the syntax is little bit odd but what we need to check here is | |||
|
684 | the opposite of .false permission being in the list so even for | |||
|
685 | inconsistent state when both .true/.false is there | |||
|
686 | .false is more important | |||
|
687 | ||||
|
688 | """ | |||
|
689 | user_inherit_object_permissions = not ('hg.inherit_default_perms.false' | |||
|
690 | in self.permissions_global) | |||
|
691 | ||||
|
692 | # default permissions inherited from `default` user permissions | |||
|
693 | self._calculate_default_permissions_repositories( | |||
|
694 | user_inherit_object_permissions) | |||
|
695 | ||||
|
696 | self._calculate_default_permissions_repository_branches( | |||
|
697 | user_inherit_object_permissions) | |||
|
698 | ||||
|
699 | self._calculate_default_permissions_repository_groups( | |||
|
700 | user_inherit_object_permissions) | |||
|
701 | ||||
|
702 | self._calculate_default_permissions_user_groups( | |||
|
703 | user_inherit_object_permissions) | |||
|
704 | ||||
694 | def _calculate_repository_permissions(self): |
|
705 | def _calculate_repository_permissions(self): | |
695 | """ |
|
706 | """ | |
696 | Repository permissions for the current user. |
|
707 | Repository permissions for the current user. | |
@@ -783,6 +794,7 b' class PermissionCalculator(object):' | |||||
783 | # any specified by the group permission |
|
794 | # any specified by the group permission | |
784 | user_repo_branch_perms = Permission.get_default_repo_branch_perms( |
|
795 | user_repo_branch_perms = Permission.get_default_repo_branch_perms( | |
785 | self.user_id, self.scope_repo_id) |
|
796 | self.user_id, self.scope_repo_id) | |
|
797 | ||||
786 | for perm in user_repo_branch_perms: |
|
798 | for perm in user_repo_branch_perms: | |
787 |
|
799 | |||
788 | r_k = perm.UserRepoToPerm.repository.repo_name |
|
800 | r_k = perm.UserRepoToPerm.repository.repo_name | |
@@ -799,7 +811,6 b' class PermissionCalculator(object):' | |||||
799 | # special dict that aggregates entries |
|
811 | # special dict that aggregates entries | |
800 | self.permissions_repository_branches[r_k] = pattern, p, o |
|
812 | self.permissions_repository_branches[r_k] = pattern, p, o | |
801 |
|
813 | |||
802 |
|
||||
803 | def _calculate_repository_group_permissions(self): |
|
814 | def _calculate_repository_group_permissions(self): | |
804 | """ |
|
815 | """ | |
805 | Repository group permissions for the current user. |
|
816 | Repository group permissions for the current user. | |
@@ -1036,14 +1047,14 b' class AuthUser(object):' | |||||
1036 |
|
1047 | |||
1037 | @LazyProperty |
|
1048 | @LazyProperty | |
1038 | def permissions(self): |
|
1049 | def permissions(self): | |
1039 |
return self.get_perms(user=self, cache= |
|
1050 | return self.get_perms(user=self, cache=None) | |
1040 |
|
1051 | |||
1041 | @LazyProperty |
|
1052 | @LazyProperty | |
1042 | def permissions_safe(self): |
|
1053 | def permissions_safe(self): | |
1043 | """ |
|
1054 | """ | |
1044 | Filtered permissions excluding not allowed repositories |
|
1055 | Filtered permissions excluding not allowed repositories | |
1045 | """ |
|
1056 | """ | |
1046 |
perms = self.get_perms(user=self, cache= |
|
1057 | perms = self.get_perms(user=self, cache=None) | |
1047 |
|
1058 | |||
1048 | perms['repositories'] = { |
|
1059 | perms['repositories'] = { | |
1049 | k: v for k, v in perms['repositories'].items() |
|
1060 | k: v for k, v in perms['repositories'].items() | |
@@ -1062,7 +1073,7 b' class AuthUser(object):' | |||||
1062 | @LazyProperty |
|
1073 | @LazyProperty | |
1063 | def permissions_full_details(self): |
|
1074 | def permissions_full_details(self): | |
1064 | return self.get_perms( |
|
1075 | return self.get_perms( | |
1065 |
user=self, cache= |
|
1076 | user=self, cache=None, calculate_super_admin=True) | |
1066 |
|
1077 | |||
1067 | def permissions_with_scope(self, scope): |
|
1078 | def permissions_with_scope(self, scope): | |
1068 | """ |
|
1079 | """ | |
@@ -1088,7 +1099,7 b' class AuthUser(object):' | |||||
1088 | # store in cache to mimic how the @LazyProperty works, |
|
1099 | # store in cache to mimic how the @LazyProperty works, | |
1089 | # the difference here is that we use the unique key calculated |
|
1100 | # the difference here is that we use the unique key calculated | |
1090 | # from params and values |
|
1101 | # from params and values | |
1091 |
return self.get_perms(user=self, cache= |
|
1102 | return self.get_perms(user=self, cache=None, scope=_scope) | |
1092 |
|
1103 | |||
1093 | def get_instance(self): |
|
1104 | def get_instance(self): | |
1094 | return User.get(self.user_id) |
|
1105 | return User.get(self.user_id) | |
@@ -1143,7 +1154,7 b' class AuthUser(object):' | |||||
1143 | log.debug('AuthUser: propagated user is now %s', self) |
|
1154 | log.debug('AuthUser: propagated user is now %s', self) | |
1144 |
|
1155 | |||
1145 | def get_perms(self, user, scope=None, explicit=True, algo='higherwin', |
|
1156 | def get_perms(self, user, scope=None, explicit=True, algo='higherwin', | |
1146 |
calculate_super_admin=False, cache= |
|
1157 | calculate_super_admin=False, cache=None): | |
1147 | """ |
|
1158 | """ | |
1148 | Fills user permission attribute with permissions taken from database |
|
1159 | Fills user permission attribute with permissions taken from database | |
1149 | works for permissions given for repositories, and for permissions that |
|
1160 | works for permissions given for repositories, and for permissions that | |
@@ -1158,6 +1169,9 b' class AuthUser(object):' | |||||
1158 | it's multiple defined, eg user in two different groups. It also |
|
1169 | it's multiple defined, eg user in two different groups. It also | |
1159 | decides if explicit flag is turned off how to specify the permission |
|
1170 | decides if explicit flag is turned off how to specify the permission | |
1160 | for case when user is in a group + have defined separate permission |
|
1171 | for case when user is in a group + have defined separate permission | |
|
1172 | :param calculate_super_admin: calculate permissions for super-admin in the | |||
|
1173 | same way as for regular user without speedups | |||
|
1174 | :param cache: Use caching for calculation, None = let the cache backend decide | |||
1161 | """ |
|
1175 | """ | |
1162 | user_id = user.user_id |
|
1176 | user_id = user.user_id | |
1163 | user_is_admin = user.is_admin |
|
1177 | user_is_admin = user.is_admin | |
@@ -1168,7 +1182,12 b' class AuthUser(object):' | |||||
1168 | cache_seconds = safe_int( |
|
1182 | cache_seconds = safe_int( | |
1169 | rhodecode.CONFIG.get('rc_cache.cache_perms.expiration_time')) |
|
1183 | rhodecode.CONFIG.get('rc_cache.cache_perms.expiration_time')) | |
1170 |
|
1184 | |||
1171 | cache_on = cache or cache_seconds > 0 |
|
1185 | if cache is None: | |
|
1186 | # let the backend cache decide | |||
|
1187 | cache_on = cache_seconds > 0 | |||
|
1188 | else: | |||
|
1189 | cache_on = cache | |||
|
1190 | ||||
1172 | log.debug( |
|
1191 | log.debug( | |
1173 | 'Computing PERMISSION tree for user %s scope `%s` ' |
|
1192 | 'Computing PERMISSION tree for user %s scope `%s` ' | |
1174 | 'with caching: %s[TTL: %ss]' % (user, scope, cache_on, cache_seconds or 0)) |
|
1193 | 'with caching: %s[TTL: %ss]' % (user, scope, cache_on, cache_seconds or 0)) | |
@@ -1186,7 +1205,8 b' class AuthUser(object):' | |||||
1186 | explicit, algo, calculate_super_admin) |
|
1205 | explicit, algo, calculate_super_admin) | |
1187 |
|
1206 | |||
1188 | start = time.time() |
|
1207 | start = time.time() | |
1189 |
result = compute_perm_tree( |
|
1208 | result = compute_perm_tree( | |
|
1209 | 'permissions', user_id, scope, user_is_admin, | |||
1190 |
|
|
1210 | user_inherit_default_permissions, explicit, algo, | |
1191 |
|
|
1211 | calculate_super_admin) | |
1192 |
|
1212 | |||
@@ -1340,6 +1360,35 b' class AuthUser(object):' | |||||
1340 | 'not in %s' % (ip_addr, user_id, allowed_ips)) |
|
1360 | 'not in %s' % (ip_addr, user_id, allowed_ips)) | |
1341 | return False |
|
1361 | return False | |
1342 |
|
1362 | |||
|
1363 | def get_branch_permissions(self, repo_name, perms=None): | |||
|
1364 | perms = perms or self.permissions_with_scope({'repo_name': repo_name}) | |||
|
1365 | branch_perms = perms.get('repository_branches') | |||
|
1366 | return branch_perms | |||
|
1367 | ||||
|
1368 | def get_rule_and_branch_permission(self, repo_name, branch_name): | |||
|
1369 | """ | |||
|
1370 | Check if this AuthUser has defined any permissions for branches. If any of | |||
|
1371 | the rules match in order, we return the matching permissions | |||
|
1372 | """ | |||
|
1373 | ||||
|
1374 | rule = default_perm = '' | |||
|
1375 | ||||
|
1376 | branch_perms = self.get_branch_permissions(repo_name=repo_name) | |||
|
1377 | if not branch_perms: | |||
|
1378 | return rule, default_perm | |||
|
1379 | ||||
|
1380 | repo_branch_perms = branch_perms.get(repo_name) | |||
|
1381 | if not repo_branch_perms: | |||
|
1382 | return rule, default_perm | |||
|
1383 | ||||
|
1384 | # now calculate the permissions | |||
|
1385 | for pattern, branch_perm in repo_branch_perms.items(): | |||
|
1386 | if fnmatch.fnmatch(branch_name, pattern): | |||
|
1387 | rule = '`{}`=>{}'.format(pattern, branch_perm) | |||
|
1388 | return rule, branch_perm | |||
|
1389 | ||||
|
1390 | return rule, default_perm | |||
|
1391 | ||||
1343 | def __repr__(self): |
|
1392 | def __repr__(self): | |
1344 | return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\ |
|
1393 | return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\ | |
1345 | % (self.user_id, self.username, self.ip_addr, self.is_authenticated) |
|
1394 | % (self.user_id, self.username, self.ip_addr, self.is_authenticated) | |
@@ -2084,23 +2133,22 b' class HasPermissionAnyMiddleware(object)' | |||||
2084 | def __init__(self, *perms): |
|
2133 | def __init__(self, *perms): | |
2085 | self.required_perms = set(perms) |
|
2134 | self.required_perms = set(perms) | |
2086 |
|
2135 | |||
2087 | def __call__(self, user, repo_name): |
|
2136 | def __call__(self, auth_user, repo_name): | |
2088 | # repo_name MUST be unicode, since we handle keys in permission |
|
2137 | # repo_name MUST be unicode, since we handle keys in permission | |
2089 | # dict by unicode |
|
2138 | # dict by unicode | |
2090 | repo_name = safe_unicode(repo_name) |
|
2139 | repo_name = safe_unicode(repo_name) | |
2091 | user = AuthUser(user.user_id) |
|
|||
2092 | log.debug( |
|
2140 | log.debug( | |
2093 | 'Checking VCS protocol permissions %s for user:%s repo:`%s`', |
|
2141 | 'Checking VCS protocol permissions %s for user:%s repo:`%s`', | |
2094 | self.required_perms, user, repo_name) |
|
2142 | self.required_perms, auth_user, repo_name) | |
2095 |
|
2143 | |||
2096 | if self.check_permissions(user, repo_name): |
|
2144 | if self.check_permissions(auth_user, repo_name): | |
2097 | log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s', |
|
2145 | log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s', | |
2098 | repo_name, user, 'PermissionMiddleware') |
|
2146 | repo_name, auth_user, 'PermissionMiddleware') | |
2099 | return True |
|
2147 | return True | |
2100 |
|
2148 | |||
2101 | else: |
|
2149 | else: | |
2102 | log.debug('Permission to repo:`%s` DENIED for user:%s @ %s', |
|
2150 | log.debug('Permission to repo:`%s` DENIED for user:%s @ %s', | |
2103 | repo_name, user, 'PermissionMiddleware') |
|
2151 | repo_name, auth_user, 'PermissionMiddleware') | |
2104 | return False |
|
2152 | return False | |
2105 |
|
2153 | |||
2106 | def check_permissions(self, user, repo_name): |
|
2154 | def check_permissions(self, user, repo_name): |
@@ -153,7 +153,7 b' def get_user_agent(environ):' | |||||
153 |
|
153 | |||
154 | def vcs_operation_context( |
|
154 | def vcs_operation_context( | |
155 | environ, repo_name, username, action, scm, check_locking=True, |
|
155 | environ, repo_name, username, action, scm, check_locking=True, | |
156 | is_shadow_repo=False): |
|
156 | is_shadow_repo=False, check_branch_perms=False, detect_force_push=False): | |
157 | """ |
|
157 | """ | |
158 | Generate the context for a vcs operation, e.g. push or pull. |
|
158 | Generate the context for a vcs operation, e.g. push or pull. | |
159 |
|
159 | |||
@@ -193,6 +193,8 b' def vcs_operation_context(' | |||||
193 | 'user_agent': get_user_agent(environ), |
|
193 | 'user_agent': get_user_agent(environ), | |
194 | 'hooks': get_enabled_hook_classes(ui_settings), |
|
194 | 'hooks': get_enabled_hook_classes(ui_settings), | |
195 | 'is_shadow_repo': is_shadow_repo, |
|
195 | 'is_shadow_repo': is_shadow_repo, | |
|
196 | 'detect_force_push': detect_force_push, | |||
|
197 | 'check_branch_perms': check_branch_perms, | |||
196 | } |
|
198 | } | |
197 | return extras |
|
199 | return extras | |
198 |
|
200 |
@@ -107,6 +107,21 b' class HTTPLockedRC(HTTPClientError):' | |||||
107 | self.args = (message, ) |
|
107 | self.args = (message, ) | |
108 |
|
108 | |||
109 |
|
109 | |||
|
110 | class HTTPBranchProtected(HTTPClientError): | |||
|
111 | """ | |||
|
112 | Special Exception For Indicating that branch is protected in RhodeCode, the | |||
|
113 | return code can be overwritten by _code keyword argument passed into constructors | |||
|
114 | """ | |||
|
115 | code = 403 | |||
|
116 | title = explanation = 'Branch Protected' | |||
|
117 | reason = None | |||
|
118 | ||||
|
119 | def __init__(self, message, *args, **kwargs): | |||
|
120 | self.title = self.explanation = message | |||
|
121 | super(HTTPBranchProtected, self).__init__(*args, **kwargs) | |||
|
122 | self.args = (message, ) | |||
|
123 | ||||
|
124 | ||||
110 | class IMCCommitError(Exception): |
|
125 | class IMCCommitError(Exception): | |
111 | pass |
|
126 | pass | |
112 |
|
127 |
@@ -32,7 +32,8 b' from rhodecode import events' | |||||
32 | from rhodecode.lib import helpers as h |
|
32 | from rhodecode.lib import helpers as h | |
33 | from rhodecode.lib import audit_logger |
|
33 | from rhodecode.lib import audit_logger | |
34 | from rhodecode.lib.utils2 import safe_str |
|
34 | from rhodecode.lib.utils2 import safe_str | |
35 |
from rhodecode.lib.exceptions import |
|
35 | from rhodecode.lib.exceptions import ( | |
|
36 | HTTPLockedRC, HTTPBranchProtected, UserCreationError) | |||
36 | from rhodecode.model.db import Repository, User |
|
37 | from rhodecode.model.db import Repository, User | |
37 |
|
38 | |||
38 | log = logging.getLogger(__name__) |
|
39 | log = logging.getLogger(__name__) | |
@@ -94,9 +95,9 b' def pre_push(extras):' | |||||
94 | It bans pushing when the repository is locked. |
|
95 | It bans pushing when the repository is locked. | |
95 | """ |
|
96 | """ | |
96 |
|
97 | |||
97 | usr = User.get_by_username(extras.username) |
|
98 | user = User.get_by_username(extras.username) | |
98 | output = '' |
|
99 | output = '' | |
99 | if extras.locked_by[0] and usr.user_id != int(extras.locked_by[0]): |
|
100 | if extras.locked_by[0] and user.user_id != int(extras.locked_by[0]): | |
100 | locked_by = User.get(extras.locked_by[0]).username |
|
101 | locked_by = User.get(extras.locked_by[0]).username | |
101 | reason = extras.locked_by[2] |
|
102 | reason = extras.locked_by[2] | |
102 | # this exception is interpreted in git/hg middlewares and based |
|
103 | # this exception is interpreted in git/hg middlewares and based | |
@@ -109,9 +110,48 b' def pre_push(extras):' | |||||
109 | else: |
|
110 | else: | |
110 | raise _http_ret |
|
111 | raise _http_ret | |
111 |
|
112 | |||
|
113 | if not is_shadow_repo(extras): | |||
|
114 | if extras.commit_ids and extras.check_branch_perms: | |||
|
115 | ||||
|
116 | auth_user = user.AuthUser() | |||
|
117 | repo = Repository.get_by_repo_name(extras.repository) | |||
|
118 | affected_branches = [] | |||
|
119 | if repo.repo_type == 'hg': | |||
|
120 | for entry in extras.commit_ids: | |||
|
121 | if entry['type'] == 'branch': | |||
|
122 | is_forced = bool(entry['multiple_heads']) | |||
|
123 | affected_branches.append([entry['name'], is_forced]) | |||
|
124 | elif repo.repo_type == 'git': | |||
|
125 | for entry in extras.commit_ids: | |||
|
126 | if entry['type'] == 'heads': | |||
|
127 | is_forced = bool(entry['pruned_sha']) | |||
|
128 | affected_branches.append([entry['name'], is_forced]) | |||
|
129 | ||||
|
130 | for branch_name, is_forced in affected_branches: | |||
|
131 | ||||
|
132 | rule, branch_perm = auth_user.get_rule_and_branch_permission( | |||
|
133 | extras.repository, branch_name) | |||
|
134 | if not branch_perm: | |||
|
135 | # no branch permission found for this branch, just keep checking | |||
|
136 | continue | |||
|
137 | ||||
|
138 | if branch_perm == 'branch.push_force': | |||
|
139 | continue | |||
|
140 | elif branch_perm == 'branch.push' and is_forced is False: | |||
|
141 | continue | |||
|
142 | elif branch_perm == 'branch.push' and is_forced is True: | |||
|
143 | halt_message = 'Branch `{}` changes rejected by rule {}. ' \ | |||
|
144 | 'FORCE PUSH FORBIDDEN.'.format(branch_name, rule) | |||
|
145 | else: | |||
|
146 | halt_message = 'Branch `{}` changes rejected by rule {}.'.format( | |||
|
147 | branch_name, rule) | |||
|
148 | ||||
|
149 | if halt_message: | |||
|
150 | _http_ret = HTTPBranchProtected(halt_message) | |||
|
151 | raise _http_ret | |||
|
152 | ||||
112 | # Propagate to external components. This is done after checking the |
|
153 | # Propagate to external components. This is done after checking the | |
113 | # lock, for consistent behavior. |
|
154 | # lock, for consistent behavior. | |
114 | if not is_shadow_repo(extras): |
|
|||
115 | pre_push_extension(repo_store_path=Repository.base_path(), **extras) |
|
155 | pre_push_extension(repo_store_path=Repository.base_path(), **extras) | |
116 | events.trigger(events.RepoPrePushEvent( |
|
156 | events.trigger(events.RepoPrePushEvent( | |
117 | repo_name=extras.repository, extras=extras)) |
|
157 | repo_name=extras.repository, extras=extras)) |
@@ -29,6 +29,7 b' from BaseHTTPServer import BaseHTTPReque' | |||||
29 | from SocketServer import TCPServer |
|
29 | from SocketServer import TCPServer | |
30 |
|
30 | |||
31 | import rhodecode |
|
31 | import rhodecode | |
|
32 | from rhodecode.lib.exceptions import HTTPLockedRC, HTTPBranchProtected | |||
32 | from rhodecode.model import meta |
|
33 | from rhodecode.model import meta | |
33 | from rhodecode.lib.base import bootstrap_request, bootstrap_config |
|
34 | from rhodecode.lib.base import bootstrap_request, bootstrap_config | |
34 | from rhodecode.lib import hooks_base |
|
35 | from rhodecode.lib import hooks_base | |
@@ -285,7 +286,18 b' class Hooks(object):' | |||||
285 |
|
286 | |||
286 | try: |
|
287 | try: | |
287 | result = hook(extras) |
|
288 | result = hook(extras) | |
288 |
except |
|
289 | except HTTPBranchProtected as handled_error: | |
|
290 | # Those special cases doesn't need error reporting. It's a case of | |||
|
291 | # locked repo or protected branch | |||
|
292 | result = AttributeDict({ | |||
|
293 | 'status': handled_error.code, | |||
|
294 | 'output': handled_error.explanation | |||
|
295 | }) | |||
|
296 | except (HTTPLockedRC, Exception) as error: | |||
|
297 | # locked needs different handling since we need to also | |||
|
298 | # handle PULL operations | |||
|
299 | exc_tb = '' | |||
|
300 | if not isinstance(error, HTTPLockedRC): | |||
289 | exc_tb = traceback.format_exc() |
|
301 | exc_tb = traceback.format_exc() | |
290 | log.exception('Exception when handling hook %s', hook) |
|
302 | log.exception('Exception when handling hook %s', hook) | |
291 | error_args = error.args |
|
303 | error_args = error.args |
@@ -297,7 +297,7 b' class SimpleVCS(object):' | |||||
297 | def is_shadow_repo_dir(self): |
|
297 | def is_shadow_repo_dir(self): | |
298 | return os.path.isdir(self.vcs_repo_name) |
|
298 | return os.path.isdir(self.vcs_repo_name) | |
299 |
|
299 | |||
300 | def _check_permission(self, action, user, repo_name, ip_addr=None, |
|
300 | def _check_permission(self, action, user, auth_user, repo_name, ip_addr=None, | |
301 | plugin_id='', plugin_cache_active=False, cache_ttl=0): |
|
301 | plugin_id='', plugin_cache_active=False, cache_ttl=0): | |
302 | """ |
|
302 | """ | |
303 | Checks permissions using action (push/pull) user and repository |
|
303 | Checks permissions using action (push/pull) user and repository | |
@@ -335,14 +335,14 b' class SimpleVCS(object):' | |||||
335 |
|
335 | |||
336 | if action == 'push': |
|
336 | if action == 'push': | |
337 | perms = ('repository.write', 'repository.admin') |
|
337 | perms = ('repository.write', 'repository.admin') | |
338 | if not HasPermissionAnyMiddleware(*perms)(user, repo_name): |
|
338 | if not HasPermissionAnyMiddleware(*perms)(auth_user, repo_name): | |
339 | return False |
|
339 | return False | |
340 |
|
340 | |||
341 | else: |
|
341 | else: | |
342 | # any other action need at least read permission |
|
342 | # any other action need at least read permission | |
343 | perms = ( |
|
343 | perms = ( | |
344 | 'repository.read', 'repository.write', 'repository.admin') |
|
344 | 'repository.read', 'repository.write', 'repository.admin') | |
345 | if not HasPermissionAnyMiddleware(*perms)(user, repo_name): |
|
345 | if not HasPermissionAnyMiddleware(*perms)(auth_user, repo_name): | |
346 | return False |
|
346 | return False | |
347 |
|
347 | |||
348 | return True |
|
348 | return True | |
@@ -441,14 +441,17 b' class SimpleVCS(object):' | |||||
441 | # ====================================================================== |
|
441 | # ====================================================================== | |
442 | # CHECK ANONYMOUS PERMISSION |
|
442 | # CHECK ANONYMOUS PERMISSION | |
443 | # ====================================================================== |
|
443 | # ====================================================================== | |
|
444 | detect_force_push = False | |||
|
445 | check_branch_perms = False | |||
444 | if action in ['pull', 'push']: |
|
446 | if action in ['pull', 'push']: | |
445 | anonymous_user = User.get_default_user() |
|
447 | user_obj = anonymous_user = User.get_default_user() | |
|
448 | auth_user = user_obj.AuthUser() | |||
446 | username = anonymous_user.username |
|
449 | username = anonymous_user.username | |
447 | if anonymous_user.active: |
|
450 | if anonymous_user.active: | |
448 | plugin_cache_active, cache_ttl = self._get_default_cache_ttl() |
|
451 | plugin_cache_active, cache_ttl = self._get_default_cache_ttl() | |
449 | # ONLY check permissions if the user is activated |
|
452 | # ONLY check permissions if the user is activated | |
450 | anonymous_perm = self._check_permission( |
|
453 | anonymous_perm = self._check_permission( | |
451 | action, anonymous_user, self.acl_repo_name, ip_addr, |
|
454 | action, anonymous_user, auth_user, self.acl_repo_name, ip_addr, | |
452 | plugin_id='anonymous_access', |
|
455 | plugin_id='anonymous_access', | |
453 | plugin_cache_active=plugin_cache_active, |
|
456 | plugin_cache_active=plugin_cache_active, | |
454 | cache_ttl=cache_ttl, |
|
457 | cache_ttl=cache_ttl, | |
@@ -525,6 +528,7 b' class SimpleVCS(object):' | |||||
525 |
|
528 | |||
526 | # check user attributes for password change flag |
|
529 | # check user attributes for password change flag | |
527 | user_obj = user |
|
530 | user_obj = user | |
|
531 | auth_user = user_obj.AuthUser() | |||
528 | if user_obj and user_obj.username != User.DEFAULT_USER and \ |
|
532 | if user_obj and user_obj.username != User.DEFAULT_USER and \ | |
529 | user_obj.user_data.get('force_password_change'): |
|
533 | user_obj.user_data.get('force_password_change'): | |
530 | reason = 'password change required' |
|
534 | reason = 'password change required' | |
@@ -533,19 +537,27 b' class SimpleVCS(object):' | |||||
533 |
|
537 | |||
534 | # check permissions for this repository |
|
538 | # check permissions for this repository | |
535 | perm = self._check_permission( |
|
539 | perm = self._check_permission( | |
536 | action, user, self.acl_repo_name, ip_addr, |
|
540 | action, user, auth_user, self.acl_repo_name, ip_addr, | |
537 | plugin, plugin_cache_active, cache_ttl) |
|
541 | plugin, plugin_cache_active, cache_ttl) | |
538 | if not perm: |
|
542 | if not perm: | |
539 | return HTTPForbidden()(environ, start_response) |
|
543 | return HTTPForbidden()(environ, start_response) | |
540 | environ['rc_auth_user_id'] = user_id |
|
544 | environ['rc_auth_user_id'] = user_id | |
541 |
|
545 | |||
|
546 | if action == 'push': | |||
|
547 | perms = auth_user.get_branch_permissions(self.acl_repo_name) | |||
|
548 | if perms: | |||
|
549 | check_branch_perms = True | |||
|
550 | detect_force_push = True | |||
|
551 | ||||
542 | # extras are injected into UI object and later available |
|
552 | # extras are injected into UI object and later available | |
543 | # in hooks executed by RhodeCode |
|
553 | # in hooks executed by RhodeCode | |
544 | check_locking = _should_check_locking(environ.get('QUERY_STRING')) |
|
554 | check_locking = _should_check_locking(environ.get('QUERY_STRING')) | |
|
555 | ||||
545 | extras = vcs_operation_context( |
|
556 | extras = vcs_operation_context( | |
546 | environ, repo_name=self.acl_repo_name, username=username, |
|
557 | environ, repo_name=self.acl_repo_name, username=username, | |
547 | action=action, scm=self.SCM, check_locking=check_locking, |
|
558 | action=action, scm=self.SCM, check_locking=check_locking, | |
548 | is_shadow_repo=self.is_shadow_repo |
|
559 | is_shadow_repo=self.is_shadow_repo, check_branch_perms=check_branch_perms, | |
|
560 | detect_force_push=detect_force_push | |||
549 | ) |
|
561 | ) | |
550 |
|
562 | |||
551 | # ====================================================================== |
|
563 | # ====================================================================== |
@@ -419,7 +419,7 b' def test_permission_calculator_admin_per' | |||||
419 |
|
419 | |||
420 | calculator = auth.PermissionCalculator( |
|
420 | calculator = auth.PermissionCalculator( | |
421 | user.user_id, {}, False, False, True, 'higherwin') |
|
421 | user.user_id, {}, False, False, True, 'higherwin') | |
422 | permissions = calculator._admin_permissions() |
|
422 | permissions = calculator._calculate_admin_permissions() | |
423 |
|
423 | |||
424 | assert permissions['repositories_groups'][repo_group.group_name] == \ |
|
424 | assert permissions['repositories_groups'][repo_group.group_name] == \ | |
425 | 'group.admin' |
|
425 | 'group.admin' |
@@ -77,22 +77,18 b' class Command(object):' | |||||
77 | assert self.process.returncode == 0 |
|
77 | assert self.process.returncode == 0 | |
78 |
|
78 | |||
79 |
|
79 | |||
80 |
def _add_files |
|
80 | def _add_files(vcs, dest, clone_url=None, tags=None, target_branch=None, | |
81 | """ |
|
81 | new_branch=False, **kwargs): | |
82 | Generate some files, add it to DEST repo and push back |
|
82 | git_ident = "git config user.name {} && git config user.email {}".format( | |
83 | vcs is git or hg and defines what VCS we want to make those files for |
|
83 | 'Marcin KuΕΊminski', 'me@email.com') | |
84 | """ |
|
84 | cwd = path = jn(dest) | |
85 | # commit some stuff into this repo |
|
85 | ||
86 | tags = tags or [] |
|
86 | tags = tags or [] | |
87 | cwd = path = jn(dest) |
|
|||
88 | added_file = jn(path, '%ssetup.py' % tempfile._RandomNameSequence().next()) |
|
87 | added_file = jn(path, '%ssetup.py' % tempfile._RandomNameSequence().next()) | |
89 | Command(cwd).execute('touch %s' % added_file) |
|
88 | Command(cwd).execute('touch %s' % added_file) | |
90 | Command(cwd).execute('%s add %s' % (vcs, added_file)) |
|
89 | Command(cwd).execute('%s add %s' % (vcs, added_file)) | |
91 | author_str = 'Marcin KuΕΊminski <me@email.com>' |
|
90 | author_str = 'Marcin KuΕΊminski <me@email.com>' | |
92 |
|
91 | |||
93 | git_ident = "git config user.name {} && git config user.email {}".format( |
|
|||
94 | 'Marcin KuΕΊminski', 'me@email.com') |
|
|||
95 |
|
||||
96 | for i in range(kwargs.get('files_no', 3)): |
|
92 | for i in range(kwargs.get('files_no', 3)): | |
97 | cmd = """echo 'added_line%s' >> %s""" % (i, added_file) |
|
93 | cmd = """echo 'added_line%s' >> %s""" % (i, added_file) | |
98 | Command(cwd).execute(cmd) |
|
94 | Command(cwd).execute(cmd) | |
@@ -107,30 +103,55 b' def _add_files_and_push(vcs, dest, clone' | |||||
107 |
|
103 | |||
108 | for tag in tags: |
|
104 | for tag in tags: | |
109 | if vcs == 'hg': |
|
105 | if vcs == 'hg': | |
110 |
|
|
106 | Command(cwd).execute( | |
111 | 'hg tag', tag['name']) |
|
107 | 'hg tag', tag['name']) | |
112 | elif vcs == 'git': |
|
108 | elif vcs == 'git': | |
113 | if tag['commit']: |
|
109 | if tag['commit']: | |
114 | # annotated tag |
|
110 | # annotated tag | |
115 | stdout, stderr = Command(cwd).execute( |
|
111 | _stdout, _stderr = Command(cwd).execute( | |
116 | """%s && git tag -a %s -m "%s" """ % ( |
|
112 | """%s && git tag -a %s -m "%s" """ % ( | |
117 | git_ident, tag['name'], tag['commit'])) |
|
113 | git_ident, tag['name'], tag['commit'])) | |
118 | else: |
|
114 | else: | |
119 | # lightweight tag |
|
115 | # lightweight tag | |
120 | stdout, stderr = Command(cwd).execute( |
|
116 | _stdout, _stderr = Command(cwd).execute( | |
121 | """%s && git tag %s""" % ( |
|
117 | """%s && git tag %s""" % ( | |
122 | git_ident, tag['name'])) |
|
118 | git_ident, tag['name'])) | |
123 |
|
119 | |||
|
120 | ||||
|
121 | def _add_files_and_push(vcs, dest, clone_url=None, tags=None, target_branch=None, | |||
|
122 | new_branch=False, **kwargs): | |||
|
123 | """ | |||
|
124 | Generate some files, add it to DEST repo and push back | |||
|
125 | vcs is git or hg and defines what VCS we want to make those files for | |||
|
126 | """ | |||
|
127 | git_ident = "git config user.name {} && git config user.email {}".format( | |||
|
128 | 'Marcin KuΕΊminski', 'me@email.com') | |||
|
129 | cwd = path = jn(dest) | |||
|
130 | ||||
|
131 | # commit some stuff into this repo | |||
|
132 | _add_files(vcs, dest, clone_url, tags, target_branch, new_branch, **kwargs) | |||
|
133 | ||||
|
134 | default_target_branch = { | |||
|
135 | 'git': 'master', | |||
|
136 | 'hg': 'default' | |||
|
137 | }.get(vcs) | |||
|
138 | ||||
|
139 | target_branch = target_branch or default_target_branch | |||
|
140 | ||||
124 | # PUSH it back |
|
141 | # PUSH it back | |
125 | stdout = stderr = None |
|
142 | stdout = stderr = None | |
126 | if vcs == 'hg': |
|
143 | if vcs == 'hg': | |
|
144 | maybe_new_branch = '' | |||
|
145 | if new_branch: | |||
|
146 | maybe_new_branch = '--new-branch' | |||
127 | stdout, stderr = Command(cwd).execute( |
|
147 | stdout, stderr = Command(cwd).execute( | |
128 | 'hg push --verbose', clone_url) |
|
148 | 'hg push --verbose {} -r {} {}'.format(maybe_new_branch, target_branch, clone_url) | |
|
149 | ) | |||
129 | elif vcs == 'git': |
|
150 | elif vcs == 'git': | |
130 | stdout, stderr = Command(cwd).execute( |
|
151 | stdout, stderr = Command(cwd).execute( | |
131 |
""" |
|
152 | """{} && | |
132 |
git push --verbose --tags |
|
153 | git push --verbose --tags {} {}""".format(git_ident, clone_url, target_branch) | |
133 | git_ident, clone_url)) |
|
154 | ) | |
134 |
|
155 | |||
135 | return stdout, stderr |
|
156 | return stdout, stderr | |
136 |
|
157 |
@@ -33,7 +33,8 b' import textwrap' | |||||
33 | import pytest |
|
33 | import pytest | |
34 |
|
34 | |||
35 | from rhodecode import events |
|
35 | from rhodecode import events | |
36 | from rhodecode.model.db import Integration |
|
36 | from rhodecode.model.db import Integration, UserRepoToPerm, Permission, \ | |
|
37 | UserToRepoBranchPermission, User | |||
37 | from rhodecode.model.integration import IntegrationModel |
|
38 | from rhodecode.model.integration import IntegrationModel | |
38 | from rhodecode.model.db import Repository |
|
39 | from rhodecode.model.db import Repository | |
39 | from rhodecode.model.meta import Session |
|
40 | from rhodecode.model.meta import Session | |
@@ -267,3 +268,74 b' def enable_webhook_push_integration(requ' | |||||
267 | Session().delete(integration) |
|
268 | Session().delete(integration) | |
268 | Session().commit() |
|
269 | Session().commit() | |
269 |
|
270 | |||
|
271 | ||||
|
272 | @pytest.fixture | |||
|
273 | def branch_permission_setter(request): | |||
|
274 | """ | |||
|
275 | ||||
|
276 | def my_test(branch_permission_setter) | |||
|
277 | branch_permission_setter(repo_name, username, pattern='*', permission='branch.push') | |||
|
278 | ||||
|
279 | """ | |||
|
280 | ||||
|
281 | rule_id = None | |||
|
282 | write_perm_id = None | |||
|
283 | ||||
|
284 | def _branch_permissions_setter( | |||
|
285 | repo_name, username, pattern='*', permission='branch.push_force'): | |||
|
286 | global rule_id, write_perm_id | |||
|
287 | ||||
|
288 | repo = Repository.get_by_repo_name(repo_name) | |||
|
289 | repo_id = repo.repo_id | |||
|
290 | ||||
|
291 | user = User.get_by_username(username) | |||
|
292 | user_id = user.user_id | |||
|
293 | ||||
|
294 | rule_perm_obj = Permission.get_by_key(permission) | |||
|
295 | ||||
|
296 | write_perm = None | |||
|
297 | ||||
|
298 | # add new entry, based on existing perm entry | |||
|
299 | perm = UserRepoToPerm.query() \ | |||
|
300 | .filter(UserRepoToPerm.repository_id == repo_id) \ | |||
|
301 | .filter(UserRepoToPerm.user_id == user_id) \ | |||
|
302 | .first() | |||
|
303 | ||||
|
304 | if not perm: | |||
|
305 | # such user isn't defined in Permissions for repository | |||
|
306 | # we now on-the-fly add new permission | |||
|
307 | ||||
|
308 | write_perm = UserRepoToPerm() | |||
|
309 | write_perm.permission = Permission.get_by_key('repository.write') | |||
|
310 | write_perm.repository_id = repo_id | |||
|
311 | write_perm.user_id = user_id | |||
|
312 | Session().add(write_perm) | |||
|
313 | Session().flush() | |||
|
314 | ||||
|
315 | perm = write_perm | |||
|
316 | ||||
|
317 | rule = UserToRepoBranchPermission() | |||
|
318 | rule.rule_to_perm_id = perm.repo_to_perm_id | |||
|
319 | rule.branch_pattern = pattern | |||
|
320 | rule.rule_order = 10 | |||
|
321 | rule.permission = rule_perm_obj | |||
|
322 | rule.repository_id = repo_id | |||
|
323 | Session().add(rule) | |||
|
324 | Session().commit() | |||
|
325 | ||||
|
326 | global rule, write_perm | |||
|
327 | ||||
|
328 | return rule | |||
|
329 | ||||
|
330 | @request.addfinalizer | |||
|
331 | def cleanup(): | |||
|
332 | if rule: | |||
|
333 | Session().delete(rule) | |||
|
334 | Session().commit() | |||
|
335 | if write_perm: | |||
|
336 | Session().delete(write_perm) | |||
|
337 | Session().commit() | |||
|
338 | ||||
|
339 | return _branch_permissions_setter | |||
|
340 | ||||
|
341 |
General Comments 0
You need to be logged in to leave comments.
Login now