##// END OF EJS Templates
branch-permissions: handle vcs operations and branch permissions....
marcink -
r2979:095dcb4b default
parent child Browse files
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')
@@ -1,2295 +1,2343 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 authentication and permission libraries
22 authentication and permission libraries
23 """
23 """
24
24
25 import os
25 import os
26 import time
26 import time
27 import inspect
27 import inspect
28 import collections
28 import collections
29 import fnmatch
29 import fnmatch
30 import hashlib
30 import hashlib
31 import itertools
31 import itertools
32 import logging
32 import logging
33 import random
33 import random
34 import traceback
34 import traceback
35 from functools import wraps
35 from functools import wraps
36
36
37 import ipaddress
37 import ipaddress
38
38
39 from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
39 from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
40 from sqlalchemy.orm.exc import ObjectDeletedError
40 from sqlalchemy.orm.exc import ObjectDeletedError
41 from sqlalchemy.orm import joinedload
41 from sqlalchemy.orm import joinedload
42 from zope.cachedescriptors.property import Lazy as LazyProperty
42 from zope.cachedescriptors.property import Lazy as LazyProperty
43
43
44 import rhodecode
44 import rhodecode
45 from rhodecode.model import meta
45 from rhodecode.model import meta
46 from rhodecode.model.meta import Session
46 from rhodecode.model.meta import Session
47 from rhodecode.model.user import UserModel
47 from rhodecode.model.user import UserModel
48 from rhodecode.model.db import (
48 from rhodecode.model.db import (
49 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
49 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
50 UserIpMap, UserApiKeys, RepoGroup, UserGroup)
50 UserIpMap, UserApiKeys, RepoGroup, UserGroup)
51 from rhodecode.lib import rc_cache
51 from rhodecode.lib import rc_cache
52 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5, safe_int, sha1
52 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5, safe_int, sha1
53 from rhodecode.lib.utils import (
53 from rhodecode.lib.utils import (
54 get_repo_slug, get_repo_group_slug, get_user_group_slug)
54 get_repo_slug, get_repo_group_slug, get_user_group_slug)
55 from rhodecode.lib.caching_query import FromCache
55 from rhodecode.lib.caching_query import FromCache
56
56
57
57
58 if rhodecode.is_unix:
58 if rhodecode.is_unix:
59 import bcrypt
59 import bcrypt
60
60
61 log = logging.getLogger(__name__)
61 log = logging.getLogger(__name__)
62
62
63 csrf_token_key = "csrf_token"
63 csrf_token_key = "csrf_token"
64
64
65
65
66 class PasswordGenerator(object):
66 class PasswordGenerator(object):
67 """
67 """
68 This is a simple class for generating password from different sets of
68 This is a simple class for generating password from different sets of
69 characters
69 characters
70 usage::
70 usage::
71
71
72 passwd_gen = PasswordGenerator()
72 passwd_gen = PasswordGenerator()
73 #print 8-letter password containing only big and small letters
73 #print 8-letter password containing only big and small letters
74 of alphabet
74 of alphabet
75 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
75 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
76 """
76 """
77 ALPHABETS_NUM = r'''1234567890'''
77 ALPHABETS_NUM = r'''1234567890'''
78 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
78 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
79 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
79 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
80 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
80 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
81 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
81 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
82 + ALPHABETS_NUM + ALPHABETS_SPECIAL
82 + ALPHABETS_NUM + ALPHABETS_SPECIAL
83 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
83 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
84 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
84 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
85 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
85 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
86 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
86 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
87
87
88 def __init__(self, passwd=''):
88 def __init__(self, passwd=''):
89 self.passwd = passwd
89 self.passwd = passwd
90
90
91 def gen_password(self, length, type_=None):
91 def gen_password(self, length, type_=None):
92 if type_ is None:
92 if type_ is None:
93 type_ = self.ALPHABETS_FULL
93 type_ = self.ALPHABETS_FULL
94 self.passwd = ''.join([random.choice(type_) for _ in range(length)])
94 self.passwd = ''.join([random.choice(type_) for _ in range(length)])
95 return self.passwd
95 return self.passwd
96
96
97
97
98 class _RhodeCodeCryptoBase(object):
98 class _RhodeCodeCryptoBase(object):
99 ENC_PREF = None
99 ENC_PREF = None
100
100
101 def hash_create(self, str_):
101 def hash_create(self, str_):
102 """
102 """
103 hash the string using
103 hash the string using
104
104
105 :param str_: password to hash
105 :param str_: password to hash
106 """
106 """
107 raise NotImplementedError
107 raise NotImplementedError
108
108
109 def hash_check_with_upgrade(self, password, hashed):
109 def hash_check_with_upgrade(self, password, hashed):
110 """
110 """
111 Returns tuple in which first element is boolean that states that
111 Returns tuple in which first element is boolean that states that
112 given password matches it's hashed version, and the second is new hash
112 given password matches it's hashed version, and the second is new hash
113 of the password, in case this password should be migrated to new
113 of the password, in case this password should be migrated to new
114 cipher.
114 cipher.
115 """
115 """
116 checked_hash = self.hash_check(password, hashed)
116 checked_hash = self.hash_check(password, hashed)
117 return checked_hash, None
117 return checked_hash, None
118
118
119 def hash_check(self, password, hashed):
119 def hash_check(self, password, hashed):
120 """
120 """
121 Checks matching password with it's hashed value.
121 Checks matching password with it's hashed value.
122
122
123 :param password: password
123 :param password: password
124 :param hashed: password in hashed form
124 :param hashed: password in hashed form
125 """
125 """
126 raise NotImplementedError
126 raise NotImplementedError
127
127
128 def _assert_bytes(self, value):
128 def _assert_bytes(self, value):
129 """
129 """
130 Passing in an `unicode` object can lead to hard to detect issues
130 Passing in an `unicode` object can lead to hard to detect issues
131 if passwords contain non-ascii characters. Doing a type check
131 if passwords contain non-ascii characters. Doing a type check
132 during runtime, so that such mistakes are detected early on.
132 during runtime, so that such mistakes are detected early on.
133 """
133 """
134 if not isinstance(value, str):
134 if not isinstance(value, str):
135 raise TypeError(
135 raise TypeError(
136 "Bytestring required as input, got %r." % (value, ))
136 "Bytestring required as input, got %r." % (value, ))
137
137
138
138
139 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
139 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
140 ENC_PREF = ('$2a$10', '$2b$10')
140 ENC_PREF = ('$2a$10', '$2b$10')
141
141
142 def hash_create(self, str_):
142 def hash_create(self, str_):
143 self._assert_bytes(str_)
143 self._assert_bytes(str_)
144 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
144 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
145
145
146 def hash_check_with_upgrade(self, password, hashed):
146 def hash_check_with_upgrade(self, password, hashed):
147 """
147 """
148 Returns tuple in which first element is boolean that states that
148 Returns tuple in which first element is boolean that states that
149 given password matches it's hashed version, and the second is new hash
149 given password matches it's hashed version, and the second is new hash
150 of the password, in case this password should be migrated to new
150 of the password, in case this password should be migrated to new
151 cipher.
151 cipher.
152
152
153 This implements special upgrade logic which works like that:
153 This implements special upgrade logic which works like that:
154 - check if the given password == bcrypted hash, if yes then we
154 - check if the given password == bcrypted hash, if yes then we
155 properly used password and it was already in bcrypt. Proceed
155 properly used password and it was already in bcrypt. Proceed
156 without any changes
156 without any changes
157 - if bcrypt hash check is not working try with sha256. If hash compare
157 - if bcrypt hash check is not working try with sha256. If hash compare
158 is ok, it means we using correct but old hashed password. indicate
158 is ok, it means we using correct but old hashed password. indicate
159 hash change and proceed
159 hash change and proceed
160 """
160 """
161
161
162 new_hash = None
162 new_hash = None
163
163
164 # regular pw check
164 # regular pw check
165 password_match_bcrypt = self.hash_check(password, hashed)
165 password_match_bcrypt = self.hash_check(password, hashed)
166
166
167 # now we want to know if the password was maybe from sha256
167 # now we want to know if the password was maybe from sha256
168 # basically calling _RhodeCodeCryptoSha256().hash_check()
168 # basically calling _RhodeCodeCryptoSha256().hash_check()
169 if not password_match_bcrypt:
169 if not password_match_bcrypt:
170 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
170 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
171 new_hash = self.hash_create(password) # make new bcrypt hash
171 new_hash = self.hash_create(password) # make new bcrypt hash
172 password_match_bcrypt = True
172 password_match_bcrypt = True
173
173
174 return password_match_bcrypt, new_hash
174 return password_match_bcrypt, new_hash
175
175
176 def hash_check(self, password, hashed):
176 def hash_check(self, password, hashed):
177 """
177 """
178 Checks matching password with it's hashed value.
178 Checks matching password with it's hashed value.
179
179
180 :param password: password
180 :param password: password
181 :param hashed: password in hashed form
181 :param hashed: password in hashed form
182 """
182 """
183 self._assert_bytes(password)
183 self._assert_bytes(password)
184 try:
184 try:
185 return bcrypt.hashpw(password, hashed) == hashed
185 return bcrypt.hashpw(password, hashed) == hashed
186 except ValueError as e:
186 except ValueError as e:
187 # we're having a invalid salt here probably, we should not crash
187 # we're having a invalid salt here probably, we should not crash
188 # just return with False as it would be a wrong password.
188 # just return with False as it would be a wrong password.
189 log.debug('Failed to check password hash using bcrypt %s',
189 log.debug('Failed to check password hash using bcrypt %s',
190 safe_str(e))
190 safe_str(e))
191
191
192 return False
192 return False
193
193
194
194
195 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
195 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
196 ENC_PREF = '_'
196 ENC_PREF = '_'
197
197
198 def hash_create(self, str_):
198 def hash_create(self, str_):
199 self._assert_bytes(str_)
199 self._assert_bytes(str_)
200 return hashlib.sha256(str_).hexdigest()
200 return hashlib.sha256(str_).hexdigest()
201
201
202 def hash_check(self, password, hashed):
202 def hash_check(self, password, hashed):
203 """
203 """
204 Checks matching password with it's hashed value.
204 Checks matching password with it's hashed value.
205
205
206 :param password: password
206 :param password: password
207 :param hashed: password in hashed form
207 :param hashed: password in hashed form
208 """
208 """
209 self._assert_bytes(password)
209 self._assert_bytes(password)
210 return hashlib.sha256(password).hexdigest() == hashed
210 return hashlib.sha256(password).hexdigest() == hashed
211
211
212
212
213 class _RhodeCodeCryptoTest(_RhodeCodeCryptoBase):
213 class _RhodeCodeCryptoTest(_RhodeCodeCryptoBase):
214 ENC_PREF = '_'
214 ENC_PREF = '_'
215
215
216 def hash_create(self, str_):
216 def hash_create(self, str_):
217 self._assert_bytes(str_)
217 self._assert_bytes(str_)
218 return sha1(str_)
218 return sha1(str_)
219
219
220 def hash_check(self, password, hashed):
220 def hash_check(self, password, hashed):
221 """
221 """
222 Checks matching password with it's hashed value.
222 Checks matching password with it's hashed value.
223
223
224 :param password: password
224 :param password: password
225 :param hashed: password in hashed form
225 :param hashed: password in hashed form
226 """
226 """
227 self._assert_bytes(password)
227 self._assert_bytes(password)
228 return sha1(password) == hashed
228 return sha1(password) == hashed
229
229
230
230
231 def crypto_backend():
231 def crypto_backend():
232 """
232 """
233 Return the matching crypto backend.
233 Return the matching crypto backend.
234
234
235 Selection is based on if we run tests or not, we pick sha1-test backend to run
235 Selection is based on if we run tests or not, we pick sha1-test backend to run
236 tests faster since BCRYPT is expensive to calculate
236 tests faster since BCRYPT is expensive to calculate
237 """
237 """
238 if rhodecode.is_test:
238 if rhodecode.is_test:
239 RhodeCodeCrypto = _RhodeCodeCryptoTest()
239 RhodeCodeCrypto = _RhodeCodeCryptoTest()
240 else:
240 else:
241 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
241 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
242
242
243 return RhodeCodeCrypto
243 return RhodeCodeCrypto
244
244
245
245
246 def get_crypt_password(password):
246 def get_crypt_password(password):
247 """
247 """
248 Create the hash of `password` with the active crypto backend.
248 Create the hash of `password` with the active crypto backend.
249
249
250 :param password: The cleartext password.
250 :param password: The cleartext password.
251 :type password: unicode
251 :type password: unicode
252 """
252 """
253 password = safe_str(password)
253 password = safe_str(password)
254 return crypto_backend().hash_create(password)
254 return crypto_backend().hash_create(password)
255
255
256
256
257 def check_password(password, hashed):
257 def check_password(password, hashed):
258 """
258 """
259 Check if the value in `password` matches the hash in `hashed`.
259 Check if the value in `password` matches the hash in `hashed`.
260
260
261 :param password: The cleartext password.
261 :param password: The cleartext password.
262 :type password: unicode
262 :type password: unicode
263
263
264 :param hashed: The expected hashed version of the password.
264 :param hashed: The expected hashed version of the password.
265 :type hashed: The hash has to be passed in in text representation.
265 :type hashed: The hash has to be passed in in text representation.
266 """
266 """
267 password = safe_str(password)
267 password = safe_str(password)
268 return crypto_backend().hash_check(password, hashed)
268 return crypto_backend().hash_check(password, hashed)
269
269
270
270
271 def generate_auth_token(data, salt=None):
271 def generate_auth_token(data, salt=None):
272 """
272 """
273 Generates API KEY from given string
273 Generates API KEY from given string
274 """
274 """
275
275
276 if salt is None:
276 if salt is None:
277 salt = os.urandom(16)
277 salt = os.urandom(16)
278 return hashlib.sha1(safe_str(data) + salt).hexdigest()
278 return hashlib.sha1(safe_str(data) + salt).hexdigest()
279
279
280
280
281 def get_came_from(request):
281 def get_came_from(request):
282 """
282 """
283 get query_string+path from request sanitized after removing auth_token
283 get query_string+path from request sanitized after removing auth_token
284 """
284 """
285 _req = request
285 _req = request
286
286
287 path = _req.path
287 path = _req.path
288 if 'auth_token' in _req.GET:
288 if 'auth_token' in _req.GET:
289 # sanitize the request and remove auth_token for redirection
289 # sanitize the request and remove auth_token for redirection
290 _req.GET.pop('auth_token')
290 _req.GET.pop('auth_token')
291 qs = _req.query_string
291 qs = _req.query_string
292 if qs:
292 if qs:
293 path += '?' + qs
293 path += '?' + qs
294
294
295 return path
295 return path
296
296
297
297
298 class CookieStoreWrapper(object):
298 class CookieStoreWrapper(object):
299
299
300 def __init__(self, cookie_store):
300 def __init__(self, cookie_store):
301 self.cookie_store = cookie_store
301 self.cookie_store = cookie_store
302
302
303 def __repr__(self):
303 def __repr__(self):
304 return 'CookieStore<%s>' % (self.cookie_store)
304 return 'CookieStore<%s>' % (self.cookie_store)
305
305
306 def get(self, key, other=None):
306 def get(self, key, other=None):
307 if isinstance(self.cookie_store, dict):
307 if isinstance(self.cookie_store, dict):
308 return self.cookie_store.get(key, other)
308 return self.cookie_store.get(key, other)
309 elif isinstance(self.cookie_store, AuthUser):
309 elif isinstance(self.cookie_store, AuthUser):
310 return self.cookie_store.__dict__.get(key, other)
310 return self.cookie_store.__dict__.get(key, other)
311
311
312
312
313 def _cached_perms_data(user_id, scope, user_is_admin,
313 def _cached_perms_data(user_id, scope, user_is_admin,
314 user_inherit_default_permissions, explicit, algo,
314 user_inherit_default_permissions, explicit, algo,
315 calculate_super_admin):
315 calculate_super_admin):
316
316
317 permissions = PermissionCalculator(
317 permissions = PermissionCalculator(
318 user_id, scope, user_is_admin, user_inherit_default_permissions,
318 user_id, scope, user_is_admin, user_inherit_default_permissions,
319 explicit, algo, calculate_super_admin)
319 explicit, algo, calculate_super_admin)
320 return permissions.calculate()
320 return permissions.calculate()
321
321
322
322
323 class PermOrigin(object):
323 class PermOrigin(object):
324 SUPER_ADMIN = 'superadmin'
324 SUPER_ADMIN = 'superadmin'
325
325
326 REPO_USER = 'user:%s'
326 REPO_USER = 'user:%s'
327 REPO_USERGROUP = 'usergroup:%s'
327 REPO_USERGROUP = 'usergroup:%s'
328 REPO_OWNER = 'repo.owner'
328 REPO_OWNER = 'repo.owner'
329 REPO_DEFAULT = 'repo.default'
329 REPO_DEFAULT = 'repo.default'
330 REPO_DEFAULT_NO_INHERIT = 'repo.default.no.inherit'
330 REPO_DEFAULT_NO_INHERIT = 'repo.default.no.inherit'
331 REPO_PRIVATE = 'repo.private'
331 REPO_PRIVATE = 'repo.private'
332
332
333 REPOGROUP_USER = 'user:%s'
333 REPOGROUP_USER = 'user:%s'
334 REPOGROUP_USERGROUP = 'usergroup:%s'
334 REPOGROUP_USERGROUP = 'usergroup:%s'
335 REPOGROUP_OWNER = 'group.owner'
335 REPOGROUP_OWNER = 'group.owner'
336 REPOGROUP_DEFAULT = 'group.default'
336 REPOGROUP_DEFAULT = 'group.default'
337 REPOGROUP_DEFAULT_NO_INHERIT = 'group.default.no.inherit'
337 REPOGROUP_DEFAULT_NO_INHERIT = 'group.default.no.inherit'
338
338
339 USERGROUP_USER = 'user:%s'
339 USERGROUP_USER = 'user:%s'
340 USERGROUP_USERGROUP = 'usergroup:%s'
340 USERGROUP_USERGROUP = 'usergroup:%s'
341 USERGROUP_OWNER = 'usergroup.owner'
341 USERGROUP_OWNER = 'usergroup.owner'
342 USERGROUP_DEFAULT = 'usergroup.default'
342 USERGROUP_DEFAULT = 'usergroup.default'
343 USERGROUP_DEFAULT_NO_INHERIT = 'usergroup.default.no.inherit'
343 USERGROUP_DEFAULT_NO_INHERIT = 'usergroup.default.no.inherit'
344
344
345
345
346 class PermOriginDict(dict):
346 class PermOriginDict(dict):
347 """
347 """
348 A special dict used for tracking permissions along with their origins.
348 A special dict used for tracking permissions along with their origins.
349
349
350 `__setitem__` has been overridden to expect a tuple(perm, origin)
350 `__setitem__` has been overridden to expect a tuple(perm, origin)
351 `__getitem__` will return only the perm
351 `__getitem__` will return only the perm
352 `.perm_origin_stack` will return the stack of (perm, origin) set per key
352 `.perm_origin_stack` will return the stack of (perm, origin) set per key
353
353
354 >>> perms = PermOriginDict()
354 >>> perms = PermOriginDict()
355 >>> perms['resource'] = 'read', 'default'
355 >>> perms['resource'] = 'read', 'default'
356 >>> perms['resource']
356 >>> perms['resource']
357 'read'
357 'read'
358 >>> perms['resource'] = 'write', 'admin'
358 >>> perms['resource'] = 'write', 'admin'
359 >>> perms['resource']
359 >>> perms['resource']
360 'write'
360 'write'
361 >>> perms.perm_origin_stack
361 >>> perms.perm_origin_stack
362 {'resource': [('read', 'default'), ('write', 'admin')]}
362 {'resource': [('read', 'default'), ('write', 'admin')]}
363 """
363 """
364
364
365 def __init__(self, *args, **kw):
365 def __init__(self, *args, **kw):
366 dict.__init__(self, *args, **kw)
366 dict.__init__(self, *args, **kw)
367 self.perm_origin_stack = collections.OrderedDict()
367 self.perm_origin_stack = collections.OrderedDict()
368
368
369 def __setitem__(self, key, (perm, origin)):
369 def __setitem__(self, key, (perm, origin)):
370 self.perm_origin_stack.setdefault(key, []).append(
370 self.perm_origin_stack.setdefault(key, []).append(
371 (perm, origin))
371 (perm, origin))
372 dict.__setitem__(self, key, perm)
372 dict.__setitem__(self, key, perm)
373
373
374
374
375 class BranchPermOriginDict(PermOriginDict):
375 class BranchPermOriginDict(PermOriginDict):
376 """
376 """
377 Dedicated branch permissions dict, with tracking of patterns and origins.
377 Dedicated branch permissions dict, with tracking of patterns and origins.
378
378
379 >>> perms = BranchPermOriginDict()
379 >>> perms = BranchPermOriginDict()
380 >>> perms['resource'] = '*pattern', 'read', 'default'
380 >>> perms['resource'] = '*pattern', 'read', 'default'
381 >>> perms['resource']
381 >>> perms['resource']
382 {'*pattern': 'read'}
382 {'*pattern': 'read'}
383 >>> perms['resource'] = '*pattern', 'write', 'admin'
383 >>> perms['resource'] = '*pattern', 'write', 'admin'
384 >>> perms['resource']
384 >>> perms['resource']
385 {'*pattern': 'write'}
385 {'*pattern': 'write'}
386 >>> perms.perm_origin_stack
386 >>> perms.perm_origin_stack
387 {'resource': {'*pattern': [('read', 'default'), ('write', 'admin')]}}
387 {'resource': {'*pattern': [('read', 'default'), ('write', 'admin')]}}
388 """
388 """
389 def __setitem__(self, key, (pattern, perm, origin)):
389 def __setitem__(self, key, (pattern, perm, origin)):
390
390
391 self.perm_origin_stack.setdefault(key, {}) \
391 self.perm_origin_stack.setdefault(key, {}) \
392 .setdefault(pattern, []).append((perm, origin))
392 .setdefault(pattern, []).append((perm, origin))
393
393
394 if key in self:
394 if key in self:
395 self[key].__setitem__(pattern, perm)
395 self[key].__setitem__(pattern, perm)
396 else:
396 else:
397 patterns = collections.OrderedDict()
397 patterns = collections.OrderedDict()
398 patterns[pattern] = perm
398 patterns[pattern] = perm
399 dict.__setitem__(self, key, patterns)
399 dict.__setitem__(self, key, patterns)
400
400
401
401
402 class PermissionCalculator(object):
402 class PermissionCalculator(object):
403
403
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')
418 self.scope_repo_group_id = scope.get('repo_group_id')
418 self.scope_repo_group_id = scope.get('repo_group_id')
419 self.scope_user_group_id = scope.get('user_group_id')
419 self.scope_user_group_id = scope.get('user_group_id')
420
420
421 self.default_user_id = User.get_default_user(cache=True).user_id
421 self.default_user_id = User.get_default_user(cache=True).user_id
422
422
423 self.permissions_repositories = PermOriginDict()
423 self.permissions_repositories = PermOriginDict()
424 self.permissions_repository_groups = PermOriginDict()
424 self.permissions_repository_groups = PermOriginDict()
425 self.permissions_user_groups = PermOriginDict()
425 self.permissions_user_groups = PermOriginDict()
426 self.permissions_repository_branches = BranchPermOriginDict()
426 self.permissions_repository_branches = BranchPermOriginDict()
427 self.permissions_global = set()
427 self.permissions_global = set()
428
428
429 self.default_repo_perms = Permission.get_default_repo_perms(
429 self.default_repo_perms = Permission.get_default_repo_perms(
430 self.default_user_id, self.scope_repo_id)
430 self.default_user_id, self.scope_repo_id)
431 self.default_repo_groups_perms = Permission.get_default_group_perms(
431 self.default_repo_groups_perms = Permission.get_default_group_perms(
432 self.default_user_id, self.scope_repo_group_id)
432 self.default_user_id, self.scope_repo_group_id)
433 self.default_user_group_perms = \
433 self.default_user_group_perms = \
434 Permission.get_default_user_group_perms(
434 Permission.get_default_user_group_perms(
435 self.default_user_id, self.scope_user_group_id)
435 self.default_user_id, self.scope_user_group_id)
436
436
437 # default branch perms
437 # default branch perms
438 self.default_branch_repo_perms = \
438 self.default_branch_repo_perms = \
439 Permission.get_default_repo_branch_perms(
439 Permission.get_default_repo_branch_perms(
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()
448 self._calculate_default_permissions()
448 self._calculate_default_permissions()
449 self._calculate_repository_permissions()
449 self._calculate_repository_permissions()
450 self._calculate_repository_branch_permissions()
450 self._calculate_repository_branch_permissions()
451 self._calculate_repository_group_permissions()
451 self._calculate_repository_group_permissions()
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
459 """
459 """
460 self.permissions_global.add('hg.admin')
460 self.permissions_global.add('hg.admin')
461 self.permissions_global.add('hg.create.write_on_repogroup.true')
461 self.permissions_global.add('hg.create.write_on_repogroup.true')
462
462
463 # repositories
463 # repositories
464 for perm in self.default_repo_perms:
464 for perm in self.default_repo_perms:
465 r_k = perm.UserRepoToPerm.repository.repo_name
465 r_k = perm.UserRepoToPerm.repository.repo_name
466 p = 'repository.admin'
466 p = 'repository.admin'
467 self.permissions_repositories[r_k] = p, PermOrigin.SUPER_ADMIN
467 self.permissions_repositories[r_k] = p, PermOrigin.SUPER_ADMIN
468
468
469 # repository groups
469 # repository groups
470 for perm in self.default_repo_groups_perms:
470 for perm in self.default_repo_groups_perms:
471 rg_k = perm.UserRepoGroupToPerm.group.group_name
471 rg_k = perm.UserRepoGroupToPerm.group.group_name
472 p = 'group.admin'
472 p = 'group.admin'
473 self.permissions_repository_groups[rg_k] = p, PermOrigin.SUPER_ADMIN
473 self.permissions_repository_groups[rg_k] = p, PermOrigin.SUPER_ADMIN
474
474
475 # user groups
475 # user groups
476 for perm in self.default_user_group_perms:
476 for perm in self.default_user_group_perms:
477 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
477 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
478 p = 'usergroup.admin'
478 p = 'usergroup.admin'
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
492 def _calculate_global_default_permissions(self):
490 def _calculate_global_default_permissions(self):
493 """
491 """
494 global permissions taken from the default user
492 global permissions taken from the default user
495 """
493 """
496 default_global_perms = UserToPerm.query()\
494 default_global_perms = UserToPerm.query()\
497 .filter(UserToPerm.user_id == self.default_user_id)\
495 .filter(UserToPerm.user_id == self.default_user_id)\
498 .options(joinedload(UserToPerm.permission))
496 .options(joinedload(UserToPerm.permission))
499
497
500 for perm in default_global_perms:
498 for perm in default_global_perms:
501 self.permissions_global.add(perm.permission.permission_name)
499 self.permissions_global.add(perm.permission.permission_name)
502
500
503 if self.user_is_admin:
501 if self.user_is_admin:
504 self.permissions_global.add('hg.admin')
502 self.permissions_global.add('hg.admin')
505 self.permissions_global.add('hg.create.write_on_repogroup.true')
503 self.permissions_global.add('hg.create.write_on_repogroup.true')
506
504
507 def _calculate_global_permissions(self):
505 def _calculate_global_permissions(self):
508 """
506 """
509 Set global system permissions with user permissions or permissions
507 Set global system permissions with user permissions or permissions
510 taken from the user groups of the current user.
508 taken from the user groups of the current user.
511
509
512 The permissions include repo creating, repo group creating, forking
510 The permissions include repo creating, repo group creating, forking
513 etc.
511 etc.
514 """
512 """
515
513
516 # now we read the defined permissions and overwrite what we have set
514 # now we read the defined permissions and overwrite what we have set
517 # before those can be configured from groups or users explicitly.
515 # before those can be configured from groups or users explicitly.
518
516
519 # In case we want to extend this list we should make sure
517 # In case we want to extend this list we should make sure
520 # this is in sync with User.DEFAULT_USER_PERMISSIONS definitions
518 # this is in sync with User.DEFAULT_USER_PERMISSIONS definitions
521 _configurable = frozenset([
519 _configurable = frozenset([
522 'hg.fork.none', 'hg.fork.repository',
520 'hg.fork.none', 'hg.fork.repository',
523 'hg.create.none', 'hg.create.repository',
521 'hg.create.none', 'hg.create.repository',
524 'hg.usergroup.create.false', 'hg.usergroup.create.true',
522 'hg.usergroup.create.false', 'hg.usergroup.create.true',
525 'hg.repogroup.create.false', 'hg.repogroup.create.true',
523 'hg.repogroup.create.false', 'hg.repogroup.create.true',
526 'hg.create.write_on_repogroup.false', 'hg.create.write_on_repogroup.true',
524 'hg.create.write_on_repogroup.false', 'hg.create.write_on_repogroup.true',
527 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
525 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
528 ])
526 ])
529
527
530 # USER GROUPS comes first user group global permissions
528 # USER GROUPS comes first user group global permissions
531 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
529 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
532 .options(joinedload(UserGroupToPerm.permission))\
530 .options(joinedload(UserGroupToPerm.permission))\
533 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
531 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
534 UserGroupMember.users_group_id))\
532 UserGroupMember.users_group_id))\
535 .filter(UserGroupMember.user_id == self.user_id)\
533 .filter(UserGroupMember.user_id == self.user_id)\
536 .order_by(UserGroupToPerm.users_group_id)\
534 .order_by(UserGroupToPerm.users_group_id)\
537 .all()
535 .all()
538
536
539 # need to group here by groups since user can be in more than
537 # need to group here by groups since user can be in more than
540 # one group, so we get all groups
538 # one group, so we get all groups
541 _explicit_grouped_perms = [
539 _explicit_grouped_perms = [
542 [x, list(y)] for x, y in
540 [x, list(y)] for x, y in
543 itertools.groupby(user_perms_from_users_groups,
541 itertools.groupby(user_perms_from_users_groups,
544 lambda _x: _x.users_group)]
542 lambda _x: _x.users_group)]
545
543
546 for gr, perms in _explicit_grouped_perms:
544 for gr, perms in _explicit_grouped_perms:
547 # since user can be in multiple groups iterate over them and
545 # since user can be in multiple groups iterate over them and
548 # select the lowest permissions first (more explicit)
546 # select the lowest permissions first (more explicit)
549 # TODO(marcink): do this^^
547 # TODO(marcink): do this^^
550
548
551 # group doesn't inherit default permissions so we actually set them
549 # group doesn't inherit default permissions so we actually set them
552 if not gr.inherit_default_permissions:
550 if not gr.inherit_default_permissions:
553 # NEED TO IGNORE all previously set configurable permissions
551 # NEED TO IGNORE all previously set configurable permissions
554 # and replace them with explicitly set from this user
552 # and replace them with explicitly set from this user
555 # group permissions
553 # group permissions
556 self.permissions_global = self.permissions_global.difference(
554 self.permissions_global = self.permissions_global.difference(
557 _configurable)
555 _configurable)
558 for perm in perms:
556 for perm in perms:
559 self.permissions_global.add(perm.permission.permission_name)
557 self.permissions_global.add(perm.permission.permission_name)
560
558
561 # user explicit global permissions
559 # user explicit global permissions
562 user_perms = Session().query(UserToPerm)\
560 user_perms = Session().query(UserToPerm)\
563 .options(joinedload(UserToPerm.permission))\
561 .options(joinedload(UserToPerm.permission))\
564 .filter(UserToPerm.user_id == self.user_id).all()
562 .filter(UserToPerm.user_id == self.user_id).all()
565
563
566 if not self.inherit_default_permissions:
564 if not self.inherit_default_permissions:
567 # NEED TO IGNORE all configurable permissions and
565 # NEED TO IGNORE all configurable permissions and
568 # replace them with explicitly set from this user permissions
566 # replace them with explicitly set from this user permissions
569 self.permissions_global = self.permissions_global.difference(
567 self.permissions_global = self.permissions_global.difference(
570 _configurable)
568 _configurable)
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
598 o = PermOrigin.REPO_DEFAULT
576 o = PermOrigin.REPO_DEFAULT
599 self.permissions_repositories[r_k] = p, o
577 self.permissions_repositories[r_k] = p, o
600
578
601 # if we decide this user isn't inheriting permissions from
579 # if we decide this user isn't inheriting permissions from
602 # default user we set him to .none so only explicit
580 # default user we set him to .none so only explicit
603 # permissions work
581 # permissions work
604 if not user_inherit_object_permissions:
582 if not user_inherit_object_permissions:
605 p = 'repository.none'
583 p = 'repository.none'
606 o = PermOrigin.REPO_DEFAULT_NO_INHERIT
584 o = PermOrigin.REPO_DEFAULT_NO_INHERIT
607 self.permissions_repositories[r_k] = p, o
585 self.permissions_repositories[r_k] = p, o
608
586
609 if perm.Repository.private and not (
587 if perm.Repository.private and not (
610 perm.Repository.user_id == self.user_id):
588 perm.Repository.user_id == self.user_id):
611 # disable defaults for private repos,
589 # disable defaults for private repos,
612 p = 'repository.none'
590 p = 'repository.none'
613 o = PermOrigin.REPO_PRIVATE
591 o = PermOrigin.REPO_PRIVATE
614 self.permissions_repositories[r_k] = p, o
592 self.permissions_repositories[r_k] = p, o
615
593
616 elif perm.Repository.user_id == self.user_id:
594 elif perm.Repository.user_id == self.user_id:
617 # set admin if owner
595 # set admin if owner
618 p = 'repository.admin'
596 p = 'repository.admin'
619 o = PermOrigin.REPO_OWNER
597 o = PermOrigin.REPO_OWNER
620 self.permissions_repositories[r_k] = p, o
598 self.permissions_repositories[r_k] = p, o
621
599
622 if self.user_is_admin:
600 if self.user_is_admin:
623 p = 'repository.admin'
601 p = 'repository.admin'
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
631 p = perm.Permission.permission_name
609 p = perm.Permission.permission_name
632 pattern = perm.UserToRepoBranchPermission.branch_pattern
610 pattern = perm.UserToRepoBranchPermission.branch_pattern
633 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
611 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
634
612
635 if not self.explicit:
613 if not self.explicit:
636 # TODO(marcink): fix this for multiple entries
614 # TODO(marcink): fix this for multiple entries
637 cur_perm = self.permissions_repository_branches.get(r_k) or 'branch.none'
615 cur_perm = self.permissions_repository_branches.get(r_k) or 'branch.none'
638 p = self._choose_permission(p, cur_perm)
616 p = self._choose_permission(p, cur_perm)
639
617
640 # NOTE(marcink): register all pattern/perm instances in this
618 # NOTE(marcink): register all pattern/perm instances in this
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 # default permissions for repository groups taken from `default` user permission
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
648 o = PermOrigin.REPOGROUP_DEFAULT
626 o = PermOrigin.REPOGROUP_DEFAULT
649 self.permissions_repository_groups[rg_k] = p, o
627 self.permissions_repository_groups[rg_k] = p, o
650
628
651 # if we decide this user isn't inheriting permissions from default
629 # if we decide this user isn't inheriting permissions from default
652 # user we set him to .none so only explicit permissions work
630 # user we set him to .none so only explicit permissions work
653 if not user_inherit_object_permissions:
631 if not user_inherit_object_permissions:
654 p = 'group.none'
632 p = 'group.none'
655 o = PermOrigin.REPOGROUP_DEFAULT_NO_INHERIT
633 o = PermOrigin.REPOGROUP_DEFAULT_NO_INHERIT
656 self.permissions_repository_groups[rg_k] = p, o
634 self.permissions_repository_groups[rg_k] = p, o
657
635
658 if perm.RepoGroup.user_id == self.user_id:
636 if perm.RepoGroup.user_id == self.user_id:
659 # set admin if owner
637 # set admin if owner
660 p = 'group.admin'
638 p = 'group.admin'
661 o = PermOrigin.REPOGROUP_OWNER
639 o = PermOrigin.REPOGROUP_OWNER
662 self.permissions_repository_groups[rg_k] = p, o
640 self.permissions_repository_groups[rg_k] = p, o
663
641
664 if self.user_is_admin:
642 if self.user_is_admin:
665 p = 'group.admin'
643 p = 'group.admin'
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
673 o = PermOrigin.USERGROUP_DEFAULT
651 o = PermOrigin.USERGROUP_DEFAULT
674 self.permissions_user_groups[u_k] = p, o
652 self.permissions_user_groups[u_k] = p, o
675
653
676 # if we decide this user isn't inheriting permissions from default
654 # if we decide this user isn't inheriting permissions from default
677 # user we set him to .none so only explicit permissions work
655 # user we set him to .none so only explicit permissions work
678 if not user_inherit_object_permissions:
656 if not user_inherit_object_permissions:
679 p = 'usergroup.none'
657 p = 'usergroup.none'
680 o = PermOrigin.USERGROUP_DEFAULT_NO_INHERIT
658 o = PermOrigin.USERGROUP_DEFAULT_NO_INHERIT
681 self.permissions_user_groups[u_k] = p, o
659 self.permissions_user_groups[u_k] = p, o
682
660
683 if perm.UserGroup.user_id == self.user_id:
661 if perm.UserGroup.user_id == self.user_id:
684 # set admin if owner
662 # set admin if owner
685 p = 'usergroup.admin'
663 p = 'usergroup.admin'
686 o = PermOrigin.USERGROUP_OWNER
664 o = PermOrigin.USERGROUP_OWNER
687 self.permissions_user_groups[u_k] = p, o
665 self.permissions_user_groups[u_k] = p, o
688
666
689 if self.user_is_admin:
667 if self.user_is_admin:
690 p = 'usergroup.admin'
668 p = 'usergroup.admin'
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.
697
708
698 Check if the user is part of user groups for this repository and
709 Check if the user is part of user groups for this repository and
699 fill in the permission from it. `_choose_permission` decides of which
710 fill in the permission from it. `_choose_permission` decides of which
700 permission should be selected based on selected method.
711 permission should be selected based on selected method.
701 """
712 """
702
713
703 # user group for repositories permissions
714 # user group for repositories permissions
704 user_repo_perms_from_user_group = Permission\
715 user_repo_perms_from_user_group = Permission\
705 .get_default_repo_perms_from_user_group(
716 .get_default_repo_perms_from_user_group(
706 self.user_id, self.scope_repo_id)
717 self.user_id, self.scope_repo_id)
707
718
708 multiple_counter = collections.defaultdict(int)
719 multiple_counter = collections.defaultdict(int)
709 for perm in user_repo_perms_from_user_group:
720 for perm in user_repo_perms_from_user_group:
710 r_k = perm.UserGroupRepoToPerm.repository.repo_name
721 r_k = perm.UserGroupRepoToPerm.repository.repo_name
711 multiple_counter[r_k] += 1
722 multiple_counter[r_k] += 1
712 p = perm.Permission.permission_name
723 p = perm.Permission.permission_name
713 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
724 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
714 .users_group.users_group_name
725 .users_group.users_group_name
715
726
716 if multiple_counter[r_k] > 1:
727 if multiple_counter[r_k] > 1:
717 cur_perm = self.permissions_repositories[r_k]
728 cur_perm = self.permissions_repositories[r_k]
718 p = self._choose_permission(p, cur_perm)
729 p = self._choose_permission(p, cur_perm)
719
730
720 self.permissions_repositories[r_k] = p, o
731 self.permissions_repositories[r_k] = p, o
721
732
722 if perm.Repository.user_id == self.user_id:
733 if perm.Repository.user_id == self.user_id:
723 # set admin if owner
734 # set admin if owner
724 p = 'repository.admin'
735 p = 'repository.admin'
725 o = PermOrigin.REPO_OWNER
736 o = PermOrigin.REPO_OWNER
726 self.permissions_repositories[r_k] = p, o
737 self.permissions_repositories[r_k] = p, o
727
738
728 if self.user_is_admin:
739 if self.user_is_admin:
729 p = 'repository.admin'
740 p = 'repository.admin'
730 o = PermOrigin.SUPER_ADMIN
741 o = PermOrigin.SUPER_ADMIN
731 self.permissions_repositories[r_k] = p, o
742 self.permissions_repositories[r_k] = p, o
732
743
733 # user explicit permissions for repositories, overrides any specified
744 # user explicit permissions for repositories, overrides any specified
734 # by the group permission
745 # by the group permission
735 user_repo_perms = Permission.get_default_repo_perms(
746 user_repo_perms = Permission.get_default_repo_perms(
736 self.user_id, self.scope_repo_id)
747 self.user_id, self.scope_repo_id)
737 for perm in user_repo_perms:
748 for perm in user_repo_perms:
738 r_k = perm.UserRepoToPerm.repository.repo_name
749 r_k = perm.UserRepoToPerm.repository.repo_name
739 p = perm.Permission.permission_name
750 p = perm.Permission.permission_name
740 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
751 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
741
752
742 if not self.explicit:
753 if not self.explicit:
743 cur_perm = self.permissions_repositories.get(
754 cur_perm = self.permissions_repositories.get(
744 r_k, 'repository.none')
755 r_k, 'repository.none')
745 p = self._choose_permission(p, cur_perm)
756 p = self._choose_permission(p, cur_perm)
746
757
747 self.permissions_repositories[r_k] = p, o
758 self.permissions_repositories[r_k] = p, o
748
759
749 if perm.Repository.user_id == self.user_id:
760 if perm.Repository.user_id == self.user_id:
750 # set admin if owner
761 # set admin if owner
751 p = 'repository.admin'
762 p = 'repository.admin'
752 o = PermOrigin.REPO_OWNER
763 o = PermOrigin.REPO_OWNER
753 self.permissions_repositories[r_k] = p, o
764 self.permissions_repositories[r_k] = p, o
754
765
755 if self.user_is_admin:
766 if self.user_is_admin:
756 p = 'repository.admin'
767 p = 'repository.admin'
757 o = PermOrigin.SUPER_ADMIN
768 o = PermOrigin.SUPER_ADMIN
758 self.permissions_repositories[r_k] = p, o
769 self.permissions_repositories[r_k] = p, o
759
770
760 def _calculate_repository_branch_permissions(self):
771 def _calculate_repository_branch_permissions(self):
761 # user group for repositories permissions
772 # user group for repositories permissions
762 user_repo_branch_perms_from_user_group = Permission\
773 user_repo_branch_perms_from_user_group = Permission\
763 .get_default_repo_branch_perms_from_user_group(
774 .get_default_repo_branch_perms_from_user_group(
764 self.user_id, self.scope_repo_id)
775 self.user_id, self.scope_repo_id)
765
776
766 multiple_counter = collections.defaultdict(int)
777 multiple_counter = collections.defaultdict(int)
767 for perm in user_repo_branch_perms_from_user_group:
778 for perm in user_repo_branch_perms_from_user_group:
768 r_k = perm.UserGroupRepoToPerm.repository.repo_name
779 r_k = perm.UserGroupRepoToPerm.repository.repo_name
769 p = perm.Permission.permission_name
780 p = perm.Permission.permission_name
770 pattern = perm.UserGroupToRepoBranchPermission.branch_pattern
781 pattern = perm.UserGroupToRepoBranchPermission.branch_pattern
771 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
782 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
772 .users_group.users_group_name
783 .users_group.users_group_name
773
784
774 multiple_counter[r_k] += 1
785 multiple_counter[r_k] += 1
775 if multiple_counter[r_k] > 1:
786 if multiple_counter[r_k] > 1:
776 # TODO(marcink): fix this for multi branch support, and multiple entries
787 # TODO(marcink): fix this for multi branch support, and multiple entries
777 cur_perm = self.permissions_repository_branches[r_k]
788 cur_perm = self.permissions_repository_branches[r_k]
778 p = self._choose_permission(p, cur_perm)
789 p = self._choose_permission(p, cur_perm)
779
790
780 self.permissions_repository_branches[r_k] = pattern, p, o
791 self.permissions_repository_branches[r_k] = pattern, p, o
781
792
782 # user explicit branch permissions for repositories, overrides
793 # user explicit branch permissions for repositories, overrides
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
789 p = perm.Permission.permission_name
801 p = perm.Permission.permission_name
790 pattern = perm.UserToRepoBranchPermission.branch_pattern
802 pattern = perm.UserToRepoBranchPermission.branch_pattern
791 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
803 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
792
804
793 if not self.explicit:
805 if not self.explicit:
794 # TODO(marcink): fix this for multiple entries
806 # TODO(marcink): fix this for multiple entries
795 cur_perm = self.permissions_repository_branches.get(r_k) or 'branch.none'
807 cur_perm = self.permissions_repository_branches.get(r_k) or 'branch.none'
796 p = self._choose_permission(p, cur_perm)
808 p = self._choose_permission(p, cur_perm)
797
809
798 # NOTE(marcink): register all pattern/perm instances in this
810 # NOTE(marcink): register all pattern/perm instances in this
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.
806
817
807 Check if the user is part of user groups for repository groups and
818 Check if the user is part of user groups for repository groups and
808 fill in the permissions from it. `_choose_permission` decides of which
819 fill in the permissions from it. `_choose_permission` decides of which
809 permission should be selected based on selected method.
820 permission should be selected based on selected method.
810 """
821 """
811 # user group for repo groups permissions
822 # user group for repo groups permissions
812 user_repo_group_perms_from_user_group = Permission\
823 user_repo_group_perms_from_user_group = Permission\
813 .get_default_group_perms_from_user_group(
824 .get_default_group_perms_from_user_group(
814 self.user_id, self.scope_repo_group_id)
825 self.user_id, self.scope_repo_group_id)
815
826
816 multiple_counter = collections.defaultdict(int)
827 multiple_counter = collections.defaultdict(int)
817 for perm in user_repo_group_perms_from_user_group:
828 for perm in user_repo_group_perms_from_user_group:
818 rg_k = perm.UserGroupRepoGroupToPerm.group.group_name
829 rg_k = perm.UserGroupRepoGroupToPerm.group.group_name
819 multiple_counter[rg_k] += 1
830 multiple_counter[rg_k] += 1
820 o = PermOrigin.REPOGROUP_USERGROUP % perm.UserGroupRepoGroupToPerm\
831 o = PermOrigin.REPOGROUP_USERGROUP % perm.UserGroupRepoGroupToPerm\
821 .users_group.users_group_name
832 .users_group.users_group_name
822 p = perm.Permission.permission_name
833 p = perm.Permission.permission_name
823
834
824 if multiple_counter[rg_k] > 1:
835 if multiple_counter[rg_k] > 1:
825 cur_perm = self.permissions_repository_groups[rg_k]
836 cur_perm = self.permissions_repository_groups[rg_k]
826 p = self._choose_permission(p, cur_perm)
837 p = self._choose_permission(p, cur_perm)
827 self.permissions_repository_groups[rg_k] = p, o
838 self.permissions_repository_groups[rg_k] = p, o
828
839
829 if perm.RepoGroup.user_id == self.user_id:
840 if perm.RepoGroup.user_id == self.user_id:
830 # set admin if owner, even for member of other user group
841 # set admin if owner, even for member of other user group
831 p = 'group.admin'
842 p = 'group.admin'
832 o = PermOrigin.REPOGROUP_OWNER
843 o = PermOrigin.REPOGROUP_OWNER
833 self.permissions_repository_groups[rg_k] = p, o
844 self.permissions_repository_groups[rg_k] = p, o
834
845
835 if self.user_is_admin:
846 if self.user_is_admin:
836 p = 'group.admin'
847 p = 'group.admin'
837 o = PermOrigin.SUPER_ADMIN
848 o = PermOrigin.SUPER_ADMIN
838 self.permissions_repository_groups[rg_k] = p, o
849 self.permissions_repository_groups[rg_k] = p, o
839
850
840 # user explicit permissions for repository groups
851 # user explicit permissions for repository groups
841 user_repo_groups_perms = Permission.get_default_group_perms(
852 user_repo_groups_perms = Permission.get_default_group_perms(
842 self.user_id, self.scope_repo_group_id)
853 self.user_id, self.scope_repo_group_id)
843 for perm in user_repo_groups_perms:
854 for perm in user_repo_groups_perms:
844 rg_k = perm.UserRepoGroupToPerm.group.group_name
855 rg_k = perm.UserRepoGroupToPerm.group.group_name
845 o = PermOrigin.REPOGROUP_USER % perm.UserRepoGroupToPerm\
856 o = PermOrigin.REPOGROUP_USER % perm.UserRepoGroupToPerm\
846 .user.username
857 .user.username
847 p = perm.Permission.permission_name
858 p = perm.Permission.permission_name
848
859
849 if not self.explicit:
860 if not self.explicit:
850 cur_perm = self.permissions_repository_groups.get(
861 cur_perm = self.permissions_repository_groups.get(
851 rg_k, 'group.none')
862 rg_k, 'group.none')
852 p = self._choose_permission(p, cur_perm)
863 p = self._choose_permission(p, cur_perm)
853
864
854 self.permissions_repository_groups[rg_k] = p, o
865 self.permissions_repository_groups[rg_k] = p, o
855
866
856 if perm.RepoGroup.user_id == self.user_id:
867 if perm.RepoGroup.user_id == self.user_id:
857 # set admin if owner
868 # set admin if owner
858 p = 'group.admin'
869 p = 'group.admin'
859 o = PermOrigin.REPOGROUP_OWNER
870 o = PermOrigin.REPOGROUP_OWNER
860 self.permissions_repository_groups[rg_k] = p, o
871 self.permissions_repository_groups[rg_k] = p, o
861
872
862 if self.user_is_admin:
873 if self.user_is_admin:
863 p = 'group.admin'
874 p = 'group.admin'
864 o = PermOrigin.SUPER_ADMIN
875 o = PermOrigin.SUPER_ADMIN
865 self.permissions_repository_groups[rg_k] = p, o
876 self.permissions_repository_groups[rg_k] = p, o
866
877
867 def _calculate_user_group_permissions(self):
878 def _calculate_user_group_permissions(self):
868 """
879 """
869 User group permissions for the current user.
880 User group permissions for the current user.
870 """
881 """
871 # user group for user group permissions
882 # user group for user group permissions
872 user_group_from_user_group = Permission\
883 user_group_from_user_group = Permission\
873 .get_default_user_group_perms_from_user_group(
884 .get_default_user_group_perms_from_user_group(
874 self.user_id, self.scope_user_group_id)
885 self.user_id, self.scope_user_group_id)
875
886
876 multiple_counter = collections.defaultdict(int)
887 multiple_counter = collections.defaultdict(int)
877 for perm in user_group_from_user_group:
888 for perm in user_group_from_user_group:
878 ug_k = perm.UserGroupUserGroupToPerm\
889 ug_k = perm.UserGroupUserGroupToPerm\
879 .target_user_group.users_group_name
890 .target_user_group.users_group_name
880 multiple_counter[ug_k] += 1
891 multiple_counter[ug_k] += 1
881 o = PermOrigin.USERGROUP_USERGROUP % perm.UserGroupUserGroupToPerm\
892 o = PermOrigin.USERGROUP_USERGROUP % perm.UserGroupUserGroupToPerm\
882 .user_group.users_group_name
893 .user_group.users_group_name
883 p = perm.Permission.permission_name
894 p = perm.Permission.permission_name
884
895
885 if multiple_counter[ug_k] > 1:
896 if multiple_counter[ug_k] > 1:
886 cur_perm = self.permissions_user_groups[ug_k]
897 cur_perm = self.permissions_user_groups[ug_k]
887 p = self._choose_permission(p, cur_perm)
898 p = self._choose_permission(p, cur_perm)
888
899
889 self.permissions_user_groups[ug_k] = p, o
900 self.permissions_user_groups[ug_k] = p, o
890
901
891 if perm.UserGroup.user_id == self.user_id:
902 if perm.UserGroup.user_id == self.user_id:
892 # set admin if owner, even for member of other user group
903 # set admin if owner, even for member of other user group
893 p = 'usergroup.admin'
904 p = 'usergroup.admin'
894 o = PermOrigin.USERGROUP_OWNER
905 o = PermOrigin.USERGROUP_OWNER
895 self.permissions_user_groups[ug_k] = p, o
906 self.permissions_user_groups[ug_k] = p, o
896
907
897 if self.user_is_admin:
908 if self.user_is_admin:
898 p = 'usergroup.admin'
909 p = 'usergroup.admin'
899 o = PermOrigin.SUPER_ADMIN
910 o = PermOrigin.SUPER_ADMIN
900 self.permissions_user_groups[ug_k] = p, o
911 self.permissions_user_groups[ug_k] = p, o
901
912
902 # user explicit permission for user groups
913 # user explicit permission for user groups
903 user_user_groups_perms = Permission.get_default_user_group_perms(
914 user_user_groups_perms = Permission.get_default_user_group_perms(
904 self.user_id, self.scope_user_group_id)
915 self.user_id, self.scope_user_group_id)
905 for perm in user_user_groups_perms:
916 for perm in user_user_groups_perms:
906 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
917 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
907 o = PermOrigin.USERGROUP_USER % perm.UserUserGroupToPerm\
918 o = PermOrigin.USERGROUP_USER % perm.UserUserGroupToPerm\
908 .user.username
919 .user.username
909 p = perm.Permission.permission_name
920 p = perm.Permission.permission_name
910
921
911 if not self.explicit:
922 if not self.explicit:
912 cur_perm = self.permissions_user_groups.get(
923 cur_perm = self.permissions_user_groups.get(
913 ug_k, 'usergroup.none')
924 ug_k, 'usergroup.none')
914 p = self._choose_permission(p, cur_perm)
925 p = self._choose_permission(p, cur_perm)
915
926
916 self.permissions_user_groups[ug_k] = p, o
927 self.permissions_user_groups[ug_k] = p, o
917
928
918 if perm.UserGroup.user_id == self.user_id:
929 if perm.UserGroup.user_id == self.user_id:
919 # set admin if owner
930 # set admin if owner
920 p = 'usergroup.admin'
931 p = 'usergroup.admin'
921 o = PermOrigin.USERGROUP_OWNER
932 o = PermOrigin.USERGROUP_OWNER
922 self.permissions_user_groups[ug_k] = p, o
933 self.permissions_user_groups[ug_k] = p, o
923
934
924 if self.user_is_admin:
935 if self.user_is_admin:
925 p = 'usergroup.admin'
936 p = 'usergroup.admin'
926 o = PermOrigin.SUPER_ADMIN
937 o = PermOrigin.SUPER_ADMIN
927 self.permissions_user_groups[ug_k] = p, o
938 self.permissions_user_groups[ug_k] = p, o
928
939
929 def _choose_permission(self, new_perm, cur_perm):
940 def _choose_permission(self, new_perm, cur_perm):
930 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
941 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
931 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
942 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
932 if self.algo == 'higherwin':
943 if self.algo == 'higherwin':
933 if new_perm_val > cur_perm_val:
944 if new_perm_val > cur_perm_val:
934 return new_perm
945 return new_perm
935 return cur_perm
946 return cur_perm
936 elif self.algo == 'lowerwin':
947 elif self.algo == 'lowerwin':
937 if new_perm_val < cur_perm_val:
948 if new_perm_val < cur_perm_val:
938 return new_perm
949 return new_perm
939 return cur_perm
950 return cur_perm
940
951
941 def _permission_structure(self):
952 def _permission_structure(self):
942 return {
953 return {
943 'global': self.permissions_global,
954 'global': self.permissions_global,
944 'repositories': self.permissions_repositories,
955 'repositories': self.permissions_repositories,
945 'repository_branches': self.permissions_repository_branches,
956 'repository_branches': self.permissions_repository_branches,
946 'repositories_groups': self.permissions_repository_groups,
957 'repositories_groups': self.permissions_repository_groups,
947 'user_groups': self.permissions_user_groups,
958 'user_groups': self.permissions_user_groups,
948 }
959 }
949
960
950
961
951 def allowed_auth_token_access(view_name, auth_token, whitelist=None):
962 def allowed_auth_token_access(view_name, auth_token, whitelist=None):
952 """
963 """
953 Check if given controller_name is in whitelist of auth token access
964 Check if given controller_name is in whitelist of auth token access
954 """
965 """
955 if not whitelist:
966 if not whitelist:
956 from rhodecode import CONFIG
967 from rhodecode import CONFIG
957 whitelist = aslist(
968 whitelist = aslist(
958 CONFIG.get('api_access_controllers_whitelist'), sep=',')
969 CONFIG.get('api_access_controllers_whitelist'), sep=',')
959 # backward compat translation
970 # backward compat translation
960 compat = {
971 compat = {
961 # old controller, new VIEW
972 # old controller, new VIEW
962 'ChangesetController:*': 'RepoCommitsView:*',
973 'ChangesetController:*': 'RepoCommitsView:*',
963 'ChangesetController:changeset_patch': 'RepoCommitsView:repo_commit_patch',
974 'ChangesetController:changeset_patch': 'RepoCommitsView:repo_commit_patch',
964 'ChangesetController:changeset_raw': 'RepoCommitsView:repo_commit_raw',
975 'ChangesetController:changeset_raw': 'RepoCommitsView:repo_commit_raw',
965 'FilesController:raw': 'RepoCommitsView:repo_commit_raw',
976 'FilesController:raw': 'RepoCommitsView:repo_commit_raw',
966 'FilesController:archivefile': 'RepoFilesView:repo_archivefile',
977 'FilesController:archivefile': 'RepoFilesView:repo_archivefile',
967 'GistsController:*': 'GistView:*',
978 'GistsController:*': 'GistView:*',
968 }
979 }
969
980
970 log.debug(
981 log.debug(
971 'Allowed views for AUTH TOKEN access: %s' % (whitelist,))
982 'Allowed views for AUTH TOKEN access: %s' % (whitelist,))
972 auth_token_access_valid = False
983 auth_token_access_valid = False
973
984
974 for entry in whitelist:
985 for entry in whitelist:
975 token_match = True
986 token_match = True
976 if entry in compat:
987 if entry in compat:
977 # translate from old Controllers to Pyramid Views
988 # translate from old Controllers to Pyramid Views
978 entry = compat[entry]
989 entry = compat[entry]
979
990
980 if '@' in entry:
991 if '@' in entry:
981 # specific AuthToken
992 # specific AuthToken
982 entry, allowed_token = entry.split('@', 1)
993 entry, allowed_token = entry.split('@', 1)
983 token_match = auth_token == allowed_token
994 token_match = auth_token == allowed_token
984
995
985 if fnmatch.fnmatch(view_name, entry) and token_match:
996 if fnmatch.fnmatch(view_name, entry) and token_match:
986 auth_token_access_valid = True
997 auth_token_access_valid = True
987 break
998 break
988
999
989 if auth_token_access_valid:
1000 if auth_token_access_valid:
990 log.debug('view: `%s` matches entry in whitelist: %s'
1001 log.debug('view: `%s` matches entry in whitelist: %s'
991 % (view_name, whitelist))
1002 % (view_name, whitelist))
992 else:
1003 else:
993 msg = ('view: `%s` does *NOT* match any entry in whitelist: %s'
1004 msg = ('view: `%s` does *NOT* match any entry in whitelist: %s'
994 % (view_name, whitelist))
1005 % (view_name, whitelist))
995 if auth_token:
1006 if auth_token:
996 # if we use auth token key and don't have access it's a warning
1007 # if we use auth token key and don't have access it's a warning
997 log.warning(msg)
1008 log.warning(msg)
998 else:
1009 else:
999 log.debug(msg)
1010 log.debug(msg)
1000
1011
1001 return auth_token_access_valid
1012 return auth_token_access_valid
1002
1013
1003
1014
1004 class AuthUser(object):
1015 class AuthUser(object):
1005 """
1016 """
1006 A simple object that handles all attributes of user in RhodeCode
1017 A simple object that handles all attributes of user in RhodeCode
1007
1018
1008 It does lookup based on API key,given user, or user present in session
1019 It does lookup based on API key,given user, or user present in session
1009 Then it fills all required information for such user. It also checks if
1020 Then it fills all required information for such user. It also checks if
1010 anonymous access is enabled and if so, it returns default user as logged in
1021 anonymous access is enabled and if so, it returns default user as logged in
1011 """
1022 """
1012 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
1023 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
1013
1024
1014 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
1025 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
1015
1026
1016 self.user_id = user_id
1027 self.user_id = user_id
1017 self._api_key = api_key
1028 self._api_key = api_key
1018
1029
1019 self.api_key = None
1030 self.api_key = None
1020 self.username = username
1031 self.username = username
1021 self.ip_addr = ip_addr
1032 self.ip_addr = ip_addr
1022 self.name = ''
1033 self.name = ''
1023 self.lastname = ''
1034 self.lastname = ''
1024 self.first_name = ''
1035 self.first_name = ''
1025 self.last_name = ''
1036 self.last_name = ''
1026 self.email = ''
1037 self.email = ''
1027 self.is_authenticated = False
1038 self.is_authenticated = False
1028 self.admin = False
1039 self.admin = False
1029 self.inherit_default_permissions = False
1040 self.inherit_default_permissions = False
1030 self.password = ''
1041 self.password = ''
1031
1042
1032 self.anonymous_user = None # propagated on propagate_data
1043 self.anonymous_user = None # propagated on propagate_data
1033 self.propagate_data()
1044 self.propagate_data()
1034 self._instance = None
1045 self._instance = None
1035 self._permissions_scoped_cache = {} # used to bind scoped calculation
1046 self._permissions_scoped_cache = {} # used to bind scoped calculation
1036
1047
1037 @LazyProperty
1048 @LazyProperty
1038 def permissions(self):
1049 def permissions(self):
1039 return self.get_perms(user=self, cache=False)
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=False)
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()
1050 if v != 'repository.none'}
1061 if v != 'repository.none'}
1051 perms['repositories_groups'] = {
1062 perms['repositories_groups'] = {
1052 k: v for k, v in perms['repositories_groups'].items()
1063 k: v for k, v in perms['repositories_groups'].items()
1053 if v != 'group.none'}
1064 if v != 'group.none'}
1054 perms['user_groups'] = {
1065 perms['user_groups'] = {
1055 k: v for k, v in perms['user_groups'].items()
1066 k: v for k, v in perms['user_groups'].items()
1056 if v != 'usergroup.none'}
1067 if v != 'usergroup.none'}
1057 perms['repository_branches'] = {
1068 perms['repository_branches'] = {
1058 k: v for k, v in perms['repository_branches'].iteritems()
1069 k: v for k, v in perms['repository_branches'].iteritems()
1059 if v != 'branch.none'}
1070 if v != 'branch.none'}
1060 return perms
1071 return perms
1061
1072
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=False, calculate_super_admin=True)
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 """
1069 Call the get_perms function with scoped data. The scope in that function
1080 Call the get_perms function with scoped data. The scope in that function
1070 narrows the SQL calls to the given ID of objects resulting in fetching
1081 narrows the SQL calls to the given ID of objects resulting in fetching
1071 Just particular permission we want to obtain. If scope is an empty dict
1082 Just particular permission we want to obtain. If scope is an empty dict
1072 then it basically narrows the scope to GLOBAL permissions only.
1083 then it basically narrows the scope to GLOBAL permissions only.
1073
1084
1074 :param scope: dict
1085 :param scope: dict
1075 """
1086 """
1076 if 'repo_name' in scope:
1087 if 'repo_name' in scope:
1077 obj = Repository.get_by_repo_name(scope['repo_name'])
1088 obj = Repository.get_by_repo_name(scope['repo_name'])
1078 if obj:
1089 if obj:
1079 scope['repo_id'] = obj.repo_id
1090 scope['repo_id'] = obj.repo_id
1080 _scope = collections.OrderedDict()
1091 _scope = collections.OrderedDict()
1081 _scope['repo_id'] = -1
1092 _scope['repo_id'] = -1
1082 _scope['user_group_id'] = -1
1093 _scope['user_group_id'] = -1
1083 _scope['repo_group_id'] = -1
1094 _scope['repo_group_id'] = -1
1084
1095
1085 for k in sorted(scope.keys()):
1096 for k in sorted(scope.keys()):
1086 _scope[k] = scope[k]
1097 _scope[k] = scope[k]
1087
1098
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=False, scope=_scope)
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)
1095
1106
1096 def propagate_data(self):
1107 def propagate_data(self):
1097 """
1108 """
1098 Fills in user data and propagates values to this instance. Maps fetched
1109 Fills in user data and propagates values to this instance. Maps fetched
1099 user attributes to this class instance attributes
1110 user attributes to this class instance attributes
1100 """
1111 """
1101 log.debug('AuthUser: starting data propagation for new potential user')
1112 log.debug('AuthUser: starting data propagation for new potential user')
1102 user_model = UserModel()
1113 user_model = UserModel()
1103 anon_user = self.anonymous_user = User.get_default_user(cache=True)
1114 anon_user = self.anonymous_user = User.get_default_user(cache=True)
1104 is_user_loaded = False
1115 is_user_loaded = False
1105
1116
1106 # lookup by userid
1117 # lookup by userid
1107 if self.user_id is not None and self.user_id != anon_user.user_id:
1118 if self.user_id is not None and self.user_id != anon_user.user_id:
1108 log.debug('Trying Auth User lookup by USER ID: `%s`', self.user_id)
1119 log.debug('Trying Auth User lookup by USER ID: `%s`', self.user_id)
1109 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
1120 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
1110
1121
1111 # try go get user by api key
1122 # try go get user by api key
1112 elif self._api_key and self._api_key != anon_user.api_key:
1123 elif self._api_key and self._api_key != anon_user.api_key:
1113 log.debug('Trying Auth User lookup by API KEY: `%s`', self._api_key)
1124 log.debug('Trying Auth User lookup by API KEY: `%s`', self._api_key)
1114 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
1125 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
1115
1126
1116 # lookup by username
1127 # lookup by username
1117 elif self.username:
1128 elif self.username:
1118 log.debug('Trying Auth User lookup by USER NAME: `%s`', self.username)
1129 log.debug('Trying Auth User lookup by USER NAME: `%s`', self.username)
1119 is_user_loaded = user_model.fill_data(self, username=self.username)
1130 is_user_loaded = user_model.fill_data(self, username=self.username)
1120 else:
1131 else:
1121 log.debug('No data in %s that could been used to log in', self)
1132 log.debug('No data in %s that could been used to log in', self)
1122
1133
1123 if not is_user_loaded:
1134 if not is_user_loaded:
1124 log.debug(
1135 log.debug(
1125 'Failed to load user. Fallback to default user %s', anon_user)
1136 'Failed to load user. Fallback to default user %s', anon_user)
1126 # if we cannot authenticate user try anonymous
1137 # if we cannot authenticate user try anonymous
1127 if anon_user.active:
1138 if anon_user.active:
1128 log.debug('default user is active, using it as a session user')
1139 log.debug('default user is active, using it as a session user')
1129 user_model.fill_data(self, user_id=anon_user.user_id)
1140 user_model.fill_data(self, user_id=anon_user.user_id)
1130 # then we set this user is logged in
1141 # then we set this user is logged in
1131 self.is_authenticated = True
1142 self.is_authenticated = True
1132 else:
1143 else:
1133 log.debug('default user is NOT active')
1144 log.debug('default user is NOT active')
1134 # in case of disabled anonymous user we reset some of the
1145 # in case of disabled anonymous user we reset some of the
1135 # parameters so such user is "corrupted", skipping the fill_data
1146 # parameters so such user is "corrupted", skipping the fill_data
1136 for attr in ['user_id', 'username', 'admin', 'active']:
1147 for attr in ['user_id', 'username', 'admin', 'active']:
1137 setattr(self, attr, None)
1148 setattr(self, attr, None)
1138 self.is_authenticated = False
1149 self.is_authenticated = False
1139
1150
1140 if not self.username:
1151 if not self.username:
1141 self.username = 'None'
1152 self.username = 'None'
1142
1153
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=False):
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
1150 are granted to groups
1161 are granted to groups
1151
1162
1152 :param user: instance of User object from database
1163 :param user: instance of User object from database
1153 :param explicit: In case there are permissions both for user and a group
1164 :param explicit: In case there are permissions both for user and a group
1154 that user is part of, explicit flag will defiine if user will
1165 that user is part of, explicit flag will defiine if user will
1155 explicitly override permissions from group, if it's False it will
1166 explicitly override permissions from group, if it's False it will
1156 make decision based on the algo
1167 make decision based on the algo
1157 :param algo: algorithm to decide what permission should be choose if
1168 :param algo: algorithm to decide what permission should be choose if
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
1164
1178
1165 # inheritance of global permissions like create repo/fork repo etc
1179 # inheritance of global permissions like create repo/fork repo etc
1166 user_inherit_default_permissions = user.inherit_default_permissions
1180 user_inherit_default_permissions = user.inherit_default_permissions
1167
1181
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))
1175
1194
1176 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
1195 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
1177 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1196 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1178
1197
1179 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
1198 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
1180 condition=cache_on)
1199 condition=cache_on)
1181 def compute_perm_tree(cache_name,
1200 def compute_perm_tree(cache_name,
1182 user_id, scope, user_is_admin,user_inherit_default_permissions,
1201 user_id, scope, user_is_admin,user_inherit_default_permissions,
1183 explicit, algo, calculate_super_admin):
1202 explicit, algo, calculate_super_admin):
1184 return _cached_perms_data(
1203 return _cached_perms_data(
1185 user_id, scope, user_is_admin, user_inherit_default_permissions,
1204 user_id, scope, user_is_admin, user_inherit_default_permissions,
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('permissions', user_id, scope, user_is_admin,
1208 result = compute_perm_tree(
1190 user_inherit_default_permissions, explicit, algo,
1209 'permissions', user_id, scope, user_is_admin,
1191 calculate_super_admin)
1210 user_inherit_default_permissions, explicit, algo,
1211 calculate_super_admin)
1192
1212
1193 result_repr = []
1213 result_repr = []
1194 for k in result:
1214 for k in result:
1195 result_repr.append((k, len(result[k])))
1215 result_repr.append((k, len(result[k])))
1196 total = time.time() - start
1216 total = time.time() - start
1197 log.debug('PERMISSION tree for user %s computed in %.3fs: %s' % (
1217 log.debug('PERMISSION tree for user %s computed in %.3fs: %s' % (
1198 user, total, result_repr))
1218 user, total, result_repr))
1199
1219
1200 return result
1220 return result
1201
1221
1202 @property
1222 @property
1203 def is_default(self):
1223 def is_default(self):
1204 return self.username == User.DEFAULT_USER
1224 return self.username == User.DEFAULT_USER
1205
1225
1206 @property
1226 @property
1207 def is_admin(self):
1227 def is_admin(self):
1208 return self.admin
1228 return self.admin
1209
1229
1210 @property
1230 @property
1211 def is_user_object(self):
1231 def is_user_object(self):
1212 return self.user_id is not None
1232 return self.user_id is not None
1213
1233
1214 @property
1234 @property
1215 def repositories_admin(self):
1235 def repositories_admin(self):
1216 """
1236 """
1217 Returns list of repositories you're an admin of
1237 Returns list of repositories you're an admin of
1218 """
1238 """
1219 return [
1239 return [
1220 x[0] for x in self.permissions['repositories'].items()
1240 x[0] for x in self.permissions['repositories'].items()
1221 if x[1] == 'repository.admin']
1241 if x[1] == 'repository.admin']
1222
1242
1223 @property
1243 @property
1224 def repository_groups_admin(self):
1244 def repository_groups_admin(self):
1225 """
1245 """
1226 Returns list of repository groups you're an admin of
1246 Returns list of repository groups you're an admin of
1227 """
1247 """
1228 return [
1248 return [
1229 x[0] for x in self.permissions['repositories_groups'].items()
1249 x[0] for x in self.permissions['repositories_groups'].items()
1230 if x[1] == 'group.admin']
1250 if x[1] == 'group.admin']
1231
1251
1232 @property
1252 @property
1233 def user_groups_admin(self):
1253 def user_groups_admin(self):
1234 """
1254 """
1235 Returns list of user groups you're an admin of
1255 Returns list of user groups you're an admin of
1236 """
1256 """
1237 return [
1257 return [
1238 x[0] for x in self.permissions['user_groups'].items()
1258 x[0] for x in self.permissions['user_groups'].items()
1239 if x[1] == 'usergroup.admin']
1259 if x[1] == 'usergroup.admin']
1240
1260
1241 def repo_acl_ids(self, perms=None, name_filter=None, cache=False):
1261 def repo_acl_ids(self, perms=None, name_filter=None, cache=False):
1242 """
1262 """
1243 Returns list of repository ids that user have access to based on given
1263 Returns list of repository ids that user have access to based on given
1244 perms. The cache flag should be only used in cases that are used for
1264 perms. The cache flag should be only used in cases that are used for
1245 display purposes, NOT IN ANY CASE for permission checks.
1265 display purposes, NOT IN ANY CASE for permission checks.
1246 """
1266 """
1247 from rhodecode.model.scm import RepoList
1267 from rhodecode.model.scm import RepoList
1248 if not perms:
1268 if not perms:
1249 perms = [
1269 perms = [
1250 'repository.read', 'repository.write', 'repository.admin']
1270 'repository.read', 'repository.write', 'repository.admin']
1251
1271
1252 def _cached_repo_acl(user_id, perm_def, _name_filter):
1272 def _cached_repo_acl(user_id, perm_def, _name_filter):
1253 qry = Repository.query()
1273 qry = Repository.query()
1254 if _name_filter:
1274 if _name_filter:
1255 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1275 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1256 qry = qry.filter(
1276 qry = qry.filter(
1257 Repository.repo_name.ilike(ilike_expression))
1277 Repository.repo_name.ilike(ilike_expression))
1258
1278
1259 return [x.repo_id for x in
1279 return [x.repo_id for x in
1260 RepoList(qry, perm_set=perm_def)]
1280 RepoList(qry, perm_set=perm_def)]
1261
1281
1262 return _cached_repo_acl(self.user_id, perms, name_filter)
1282 return _cached_repo_acl(self.user_id, perms, name_filter)
1263
1283
1264 def repo_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1284 def repo_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1265 """
1285 """
1266 Returns list of repository group ids that user have access to based on given
1286 Returns list of repository group ids that user have access to based on given
1267 perms. The cache flag should be only used in cases that are used for
1287 perms. The cache flag should be only used in cases that are used for
1268 display purposes, NOT IN ANY CASE for permission checks.
1288 display purposes, NOT IN ANY CASE for permission checks.
1269 """
1289 """
1270 from rhodecode.model.scm import RepoGroupList
1290 from rhodecode.model.scm import RepoGroupList
1271 if not perms:
1291 if not perms:
1272 perms = [
1292 perms = [
1273 'group.read', 'group.write', 'group.admin']
1293 'group.read', 'group.write', 'group.admin']
1274
1294
1275 def _cached_repo_group_acl(user_id, perm_def, _name_filter):
1295 def _cached_repo_group_acl(user_id, perm_def, _name_filter):
1276 qry = RepoGroup.query()
1296 qry = RepoGroup.query()
1277 if _name_filter:
1297 if _name_filter:
1278 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1298 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1279 qry = qry.filter(
1299 qry = qry.filter(
1280 RepoGroup.group_name.ilike(ilike_expression))
1300 RepoGroup.group_name.ilike(ilike_expression))
1281
1301
1282 return [x.group_id for x in
1302 return [x.group_id for x in
1283 RepoGroupList(qry, perm_set=perm_def)]
1303 RepoGroupList(qry, perm_set=perm_def)]
1284
1304
1285 return _cached_repo_group_acl(self.user_id, perms, name_filter)
1305 return _cached_repo_group_acl(self.user_id, perms, name_filter)
1286
1306
1287 def user_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1307 def user_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1288 """
1308 """
1289 Returns list of user group ids that user have access to based on given
1309 Returns list of user group ids that user have access to based on given
1290 perms. The cache flag should be only used in cases that are used for
1310 perms. The cache flag should be only used in cases that are used for
1291 display purposes, NOT IN ANY CASE for permission checks.
1311 display purposes, NOT IN ANY CASE for permission checks.
1292 """
1312 """
1293 from rhodecode.model.scm import UserGroupList
1313 from rhodecode.model.scm import UserGroupList
1294 if not perms:
1314 if not perms:
1295 perms = [
1315 perms = [
1296 'usergroup.read', 'usergroup.write', 'usergroup.admin']
1316 'usergroup.read', 'usergroup.write', 'usergroup.admin']
1297
1317
1298 def _cached_user_group_acl(user_id, perm_def, name_filter):
1318 def _cached_user_group_acl(user_id, perm_def, name_filter):
1299 qry = UserGroup.query()
1319 qry = UserGroup.query()
1300 if name_filter:
1320 if name_filter:
1301 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1321 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1302 qry = qry.filter(
1322 qry = qry.filter(
1303 UserGroup.users_group_name.ilike(ilike_expression))
1323 UserGroup.users_group_name.ilike(ilike_expression))
1304
1324
1305 return [x.users_group_id for x in
1325 return [x.users_group_id for x in
1306 UserGroupList(qry, perm_set=perm_def)]
1326 UserGroupList(qry, perm_set=perm_def)]
1307
1327
1308 return _cached_user_group_acl(self.user_id, perms, name_filter)
1328 return _cached_user_group_acl(self.user_id, perms, name_filter)
1309
1329
1310 @property
1330 @property
1311 def ip_allowed(self):
1331 def ip_allowed(self):
1312 """
1332 """
1313 Checks if ip_addr used in constructor is allowed from defined list of
1333 Checks if ip_addr used in constructor is allowed from defined list of
1314 allowed ip_addresses for user
1334 allowed ip_addresses for user
1315
1335
1316 :returns: boolean, True if ip is in allowed ip range
1336 :returns: boolean, True if ip is in allowed ip range
1317 """
1337 """
1318 # check IP
1338 # check IP
1319 inherit = self.inherit_default_permissions
1339 inherit = self.inherit_default_permissions
1320 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
1340 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
1321 inherit_from_default=inherit)
1341 inherit_from_default=inherit)
1322 @property
1342 @property
1323 def personal_repo_group(self):
1343 def personal_repo_group(self):
1324 return RepoGroup.get_user_personal_repo_group(self.user_id)
1344 return RepoGroup.get_user_personal_repo_group(self.user_id)
1325
1345
1326 @LazyProperty
1346 @LazyProperty
1327 def feed_token(self):
1347 def feed_token(self):
1328 return self.get_instance().feed_token
1348 return self.get_instance().feed_token
1329
1349
1330 @classmethod
1350 @classmethod
1331 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
1351 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
1332 allowed_ips = AuthUser.get_allowed_ips(
1352 allowed_ips = AuthUser.get_allowed_ips(
1333 user_id, cache=True, inherit_from_default=inherit_from_default)
1353 user_id, cache=True, inherit_from_default=inherit_from_default)
1334 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
1354 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
1335 log.debug('IP:%s for user %s is in range of %s' % (
1355 log.debug('IP:%s for user %s is in range of %s' % (
1336 ip_addr, user_id, allowed_ips))
1356 ip_addr, user_id, allowed_ips))
1337 return True
1357 return True
1338 else:
1358 else:
1339 log.info('Access for IP:%s forbidden for user %s, '
1359 log.info('Access for IP:%s forbidden for user %s, '
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)
1346
1395
1347 def set_authenticated(self, authenticated=True):
1396 def set_authenticated(self, authenticated=True):
1348 if self.user_id != self.anonymous_user.user_id:
1397 if self.user_id != self.anonymous_user.user_id:
1349 self.is_authenticated = authenticated
1398 self.is_authenticated = authenticated
1350
1399
1351 def get_cookie_store(self):
1400 def get_cookie_store(self):
1352 return {
1401 return {
1353 'username': self.username,
1402 'username': self.username,
1354 'password': md5(self.password or ''),
1403 'password': md5(self.password or ''),
1355 'user_id': self.user_id,
1404 'user_id': self.user_id,
1356 'is_authenticated': self.is_authenticated
1405 'is_authenticated': self.is_authenticated
1357 }
1406 }
1358
1407
1359 @classmethod
1408 @classmethod
1360 def from_cookie_store(cls, cookie_store):
1409 def from_cookie_store(cls, cookie_store):
1361 """
1410 """
1362 Creates AuthUser from a cookie store
1411 Creates AuthUser from a cookie store
1363
1412
1364 :param cls:
1413 :param cls:
1365 :param cookie_store:
1414 :param cookie_store:
1366 """
1415 """
1367 user_id = cookie_store.get('user_id')
1416 user_id = cookie_store.get('user_id')
1368 username = cookie_store.get('username')
1417 username = cookie_store.get('username')
1369 api_key = cookie_store.get('api_key')
1418 api_key = cookie_store.get('api_key')
1370 return AuthUser(user_id, api_key, username)
1419 return AuthUser(user_id, api_key, username)
1371
1420
1372 @classmethod
1421 @classmethod
1373 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1422 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1374 _set = set()
1423 _set = set()
1375
1424
1376 if inherit_from_default:
1425 if inherit_from_default:
1377 def_user_id = User.get_default_user(cache=True).user_id
1426 def_user_id = User.get_default_user(cache=True).user_id
1378 default_ips = UserIpMap.query().filter(UserIpMap.user_id == def_user_id)
1427 default_ips = UserIpMap.query().filter(UserIpMap.user_id == def_user_id)
1379 if cache:
1428 if cache:
1380 default_ips = default_ips.options(
1429 default_ips = default_ips.options(
1381 FromCache("sql_cache_short", "get_user_ips_default"))
1430 FromCache("sql_cache_short", "get_user_ips_default"))
1382
1431
1383 # populate from default user
1432 # populate from default user
1384 for ip in default_ips:
1433 for ip in default_ips:
1385 try:
1434 try:
1386 _set.add(ip.ip_addr)
1435 _set.add(ip.ip_addr)
1387 except ObjectDeletedError:
1436 except ObjectDeletedError:
1388 # since we use heavy caching sometimes it happens that
1437 # since we use heavy caching sometimes it happens that
1389 # we get deleted objects here, we just skip them
1438 # we get deleted objects here, we just skip them
1390 pass
1439 pass
1391
1440
1392 # NOTE:(marcink) we don't want to load any rules for empty
1441 # NOTE:(marcink) we don't want to load any rules for empty
1393 # user_id which is the case of access of non logged users when anonymous
1442 # user_id which is the case of access of non logged users when anonymous
1394 # access is disabled
1443 # access is disabled
1395 user_ips = []
1444 user_ips = []
1396 if user_id:
1445 if user_id:
1397 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1446 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1398 if cache:
1447 if cache:
1399 user_ips = user_ips.options(
1448 user_ips = user_ips.options(
1400 FromCache("sql_cache_short", "get_user_ips_%s" % user_id))
1449 FromCache("sql_cache_short", "get_user_ips_%s" % user_id))
1401
1450
1402 for ip in user_ips:
1451 for ip in user_ips:
1403 try:
1452 try:
1404 _set.add(ip.ip_addr)
1453 _set.add(ip.ip_addr)
1405 except ObjectDeletedError:
1454 except ObjectDeletedError:
1406 # since we use heavy caching sometimes it happens that we get
1455 # since we use heavy caching sometimes it happens that we get
1407 # deleted objects here, we just skip them
1456 # deleted objects here, we just skip them
1408 pass
1457 pass
1409 return _set or {ip for ip in ['0.0.0.0/0', '::/0']}
1458 return _set or {ip for ip in ['0.0.0.0/0', '::/0']}
1410
1459
1411
1460
1412 def set_available_permissions(settings):
1461 def set_available_permissions(settings):
1413 """
1462 """
1414 This function will propagate pyramid settings with all available defined
1463 This function will propagate pyramid settings with all available defined
1415 permission given in db. We don't want to check each time from db for new
1464 permission given in db. We don't want to check each time from db for new
1416 permissions since adding a new permission also requires application restart
1465 permissions since adding a new permission also requires application restart
1417 ie. to decorate new views with the newly created permission
1466 ie. to decorate new views with the newly created permission
1418
1467
1419 :param settings: current pyramid registry.settings
1468 :param settings: current pyramid registry.settings
1420
1469
1421 """
1470 """
1422 log.debug('auth: getting information about all available permissions')
1471 log.debug('auth: getting information about all available permissions')
1423 try:
1472 try:
1424 sa = meta.Session
1473 sa = meta.Session
1425 all_perms = sa.query(Permission).all()
1474 all_perms = sa.query(Permission).all()
1426 settings.setdefault('available_permissions',
1475 settings.setdefault('available_permissions',
1427 [x.permission_name for x in all_perms])
1476 [x.permission_name for x in all_perms])
1428 log.debug('auth: set available permissions')
1477 log.debug('auth: set available permissions')
1429 except Exception:
1478 except Exception:
1430 log.exception('Failed to fetch permissions from the database.')
1479 log.exception('Failed to fetch permissions from the database.')
1431 raise
1480 raise
1432
1481
1433
1482
1434 def get_csrf_token(session, force_new=False, save_if_missing=True):
1483 def get_csrf_token(session, force_new=False, save_if_missing=True):
1435 """
1484 """
1436 Return the current authentication token, creating one if one doesn't
1485 Return the current authentication token, creating one if one doesn't
1437 already exist and the save_if_missing flag is present.
1486 already exist and the save_if_missing flag is present.
1438
1487
1439 :param session: pass in the pyramid session, else we use the global ones
1488 :param session: pass in the pyramid session, else we use the global ones
1440 :param force_new: force to re-generate the token and store it in session
1489 :param force_new: force to re-generate the token and store it in session
1441 :param save_if_missing: save the newly generated token if it's missing in
1490 :param save_if_missing: save the newly generated token if it's missing in
1442 session
1491 session
1443 """
1492 """
1444 # NOTE(marcink): probably should be replaced with below one from pyramid 1.9
1493 # NOTE(marcink): probably should be replaced with below one from pyramid 1.9
1445 # from pyramid.csrf import get_csrf_token
1494 # from pyramid.csrf import get_csrf_token
1446
1495
1447 if (csrf_token_key not in session and save_if_missing) or force_new:
1496 if (csrf_token_key not in session and save_if_missing) or force_new:
1448 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1497 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1449 session[csrf_token_key] = token
1498 session[csrf_token_key] = token
1450 if hasattr(session, 'save'):
1499 if hasattr(session, 'save'):
1451 session.save()
1500 session.save()
1452 return session.get(csrf_token_key)
1501 return session.get(csrf_token_key)
1453
1502
1454
1503
1455 def get_request(perm_class_instance):
1504 def get_request(perm_class_instance):
1456 from pyramid.threadlocal import get_current_request
1505 from pyramid.threadlocal import get_current_request
1457 pyramid_request = get_current_request()
1506 pyramid_request = get_current_request()
1458 return pyramid_request
1507 return pyramid_request
1459
1508
1460
1509
1461 # CHECK DECORATORS
1510 # CHECK DECORATORS
1462 class CSRFRequired(object):
1511 class CSRFRequired(object):
1463 """
1512 """
1464 Decorator for authenticating a form
1513 Decorator for authenticating a form
1465
1514
1466 This decorator uses an authorization token stored in the client's
1515 This decorator uses an authorization token stored in the client's
1467 session for prevention of certain Cross-site request forgery (CSRF)
1516 session for prevention of certain Cross-site request forgery (CSRF)
1468 attacks (See
1517 attacks (See
1469 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1518 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1470 information).
1519 information).
1471
1520
1472 For use with the ``webhelpers.secure_form`` helper functions.
1521 For use with the ``webhelpers.secure_form`` helper functions.
1473
1522
1474 """
1523 """
1475 def __init__(self, token=csrf_token_key, header='X-CSRF-Token',
1524 def __init__(self, token=csrf_token_key, header='X-CSRF-Token',
1476 except_methods=None):
1525 except_methods=None):
1477 self.token = token
1526 self.token = token
1478 self.header = header
1527 self.header = header
1479 self.except_methods = except_methods or []
1528 self.except_methods = except_methods or []
1480
1529
1481 def __call__(self, func):
1530 def __call__(self, func):
1482 return get_cython_compat_decorator(self.__wrapper, func)
1531 return get_cython_compat_decorator(self.__wrapper, func)
1483
1532
1484 def _get_csrf(self, _request):
1533 def _get_csrf(self, _request):
1485 return _request.POST.get(self.token, _request.headers.get(self.header))
1534 return _request.POST.get(self.token, _request.headers.get(self.header))
1486
1535
1487 def check_csrf(self, _request, cur_token):
1536 def check_csrf(self, _request, cur_token):
1488 supplied_token = self._get_csrf(_request)
1537 supplied_token = self._get_csrf(_request)
1489 return supplied_token and supplied_token == cur_token
1538 return supplied_token and supplied_token == cur_token
1490
1539
1491 def _get_request(self):
1540 def _get_request(self):
1492 return get_request(self)
1541 return get_request(self)
1493
1542
1494 def __wrapper(self, func, *fargs, **fkwargs):
1543 def __wrapper(self, func, *fargs, **fkwargs):
1495 request = self._get_request()
1544 request = self._get_request()
1496
1545
1497 if request.method in self.except_methods:
1546 if request.method in self.except_methods:
1498 return func(*fargs, **fkwargs)
1547 return func(*fargs, **fkwargs)
1499
1548
1500 cur_token = get_csrf_token(request.session, save_if_missing=False)
1549 cur_token = get_csrf_token(request.session, save_if_missing=False)
1501 if self.check_csrf(request, cur_token):
1550 if self.check_csrf(request, cur_token):
1502 if request.POST.get(self.token):
1551 if request.POST.get(self.token):
1503 del request.POST[self.token]
1552 del request.POST[self.token]
1504 return func(*fargs, **fkwargs)
1553 return func(*fargs, **fkwargs)
1505 else:
1554 else:
1506 reason = 'token-missing'
1555 reason = 'token-missing'
1507 supplied_token = self._get_csrf(request)
1556 supplied_token = self._get_csrf(request)
1508 if supplied_token and cur_token != supplied_token:
1557 if supplied_token and cur_token != supplied_token:
1509 reason = 'token-mismatch [%s:%s]' % (
1558 reason = 'token-mismatch [%s:%s]' % (
1510 cur_token or ''[:6], supplied_token or ''[:6])
1559 cur_token or ''[:6], supplied_token or ''[:6])
1511
1560
1512 csrf_message = \
1561 csrf_message = \
1513 ("Cross-site request forgery detected, request denied. See "
1562 ("Cross-site request forgery detected, request denied. See "
1514 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1563 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1515 "more information.")
1564 "more information.")
1516 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1565 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1517 'REMOTE_ADDR:%s, HEADERS:%s' % (
1566 'REMOTE_ADDR:%s, HEADERS:%s' % (
1518 request, reason, request.remote_addr, request.headers))
1567 request, reason, request.remote_addr, request.headers))
1519
1568
1520 raise HTTPForbidden(explanation=csrf_message)
1569 raise HTTPForbidden(explanation=csrf_message)
1521
1570
1522
1571
1523 class LoginRequired(object):
1572 class LoginRequired(object):
1524 """
1573 """
1525 Must be logged in to execute this function else
1574 Must be logged in to execute this function else
1526 redirect to login page
1575 redirect to login page
1527
1576
1528 :param api_access: if enabled this checks only for valid auth token
1577 :param api_access: if enabled this checks only for valid auth token
1529 and grants access based on valid token
1578 and grants access based on valid token
1530 """
1579 """
1531 def __init__(self, auth_token_access=None):
1580 def __init__(self, auth_token_access=None):
1532 self.auth_token_access = auth_token_access
1581 self.auth_token_access = auth_token_access
1533
1582
1534 def __call__(self, func):
1583 def __call__(self, func):
1535 return get_cython_compat_decorator(self.__wrapper, func)
1584 return get_cython_compat_decorator(self.__wrapper, func)
1536
1585
1537 def _get_request(self):
1586 def _get_request(self):
1538 return get_request(self)
1587 return get_request(self)
1539
1588
1540 def __wrapper(self, func, *fargs, **fkwargs):
1589 def __wrapper(self, func, *fargs, **fkwargs):
1541 from rhodecode.lib import helpers as h
1590 from rhodecode.lib import helpers as h
1542 cls = fargs[0]
1591 cls = fargs[0]
1543 user = cls._rhodecode_user
1592 user = cls._rhodecode_user
1544 request = self._get_request()
1593 request = self._get_request()
1545 _ = request.translate
1594 _ = request.translate
1546
1595
1547 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1596 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1548 log.debug('Starting login restriction checks for user: %s' % (user,))
1597 log.debug('Starting login restriction checks for user: %s' % (user,))
1549 # check if our IP is allowed
1598 # check if our IP is allowed
1550 ip_access_valid = True
1599 ip_access_valid = True
1551 if not user.ip_allowed:
1600 if not user.ip_allowed:
1552 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr,))),
1601 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr,))),
1553 category='warning')
1602 category='warning')
1554 ip_access_valid = False
1603 ip_access_valid = False
1555
1604
1556 # check if we used an APIKEY and it's a valid one
1605 # check if we used an APIKEY and it's a valid one
1557 # defined white-list of controllers which API access will be enabled
1606 # defined white-list of controllers which API access will be enabled
1558 _auth_token = request.GET.get(
1607 _auth_token = request.GET.get(
1559 'auth_token', '') or request.GET.get('api_key', '')
1608 'auth_token', '') or request.GET.get('api_key', '')
1560 auth_token_access_valid = allowed_auth_token_access(
1609 auth_token_access_valid = allowed_auth_token_access(
1561 loc, auth_token=_auth_token)
1610 loc, auth_token=_auth_token)
1562
1611
1563 # explicit controller is enabled or API is in our whitelist
1612 # explicit controller is enabled or API is in our whitelist
1564 if self.auth_token_access or auth_token_access_valid:
1613 if self.auth_token_access or auth_token_access_valid:
1565 log.debug('Checking AUTH TOKEN access for %s' % (cls,))
1614 log.debug('Checking AUTH TOKEN access for %s' % (cls,))
1566 db_user = user.get_instance()
1615 db_user = user.get_instance()
1567
1616
1568 if db_user:
1617 if db_user:
1569 if self.auth_token_access:
1618 if self.auth_token_access:
1570 roles = self.auth_token_access
1619 roles = self.auth_token_access
1571 else:
1620 else:
1572 roles = [UserApiKeys.ROLE_HTTP]
1621 roles = [UserApiKeys.ROLE_HTTP]
1573 token_match = db_user.authenticate_by_token(
1622 token_match = db_user.authenticate_by_token(
1574 _auth_token, roles=roles)
1623 _auth_token, roles=roles)
1575 else:
1624 else:
1576 log.debug('Unable to fetch db instance for auth user: %s', user)
1625 log.debug('Unable to fetch db instance for auth user: %s', user)
1577 token_match = False
1626 token_match = False
1578
1627
1579 if _auth_token and token_match:
1628 if _auth_token and token_match:
1580 auth_token_access_valid = True
1629 auth_token_access_valid = True
1581 log.debug('AUTH TOKEN ****%s is VALID' % (_auth_token[-4:],))
1630 log.debug('AUTH TOKEN ****%s is VALID' % (_auth_token[-4:],))
1582 else:
1631 else:
1583 auth_token_access_valid = False
1632 auth_token_access_valid = False
1584 if not _auth_token:
1633 if not _auth_token:
1585 log.debug("AUTH TOKEN *NOT* present in request")
1634 log.debug("AUTH TOKEN *NOT* present in request")
1586 else:
1635 else:
1587 log.warning(
1636 log.warning(
1588 "AUTH TOKEN ****%s *NOT* valid" % _auth_token[-4:])
1637 "AUTH TOKEN ****%s *NOT* valid" % _auth_token[-4:])
1589
1638
1590 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
1639 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
1591 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1640 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1592 else 'AUTH_TOKEN_AUTH'
1641 else 'AUTH_TOKEN_AUTH'
1593
1642
1594 if ip_access_valid and (
1643 if ip_access_valid and (
1595 user.is_authenticated or auth_token_access_valid):
1644 user.is_authenticated or auth_token_access_valid):
1596 log.info(
1645 log.info(
1597 'user %s authenticating with:%s IS authenticated on func %s'
1646 'user %s authenticating with:%s IS authenticated on func %s'
1598 % (user, reason, loc))
1647 % (user, reason, loc))
1599
1648
1600 return func(*fargs, **fkwargs)
1649 return func(*fargs, **fkwargs)
1601 else:
1650 else:
1602 log.warning(
1651 log.warning(
1603 'user %s authenticating with:%s NOT authenticated on '
1652 'user %s authenticating with:%s NOT authenticated on '
1604 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s'
1653 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s'
1605 % (user, reason, loc, ip_access_valid,
1654 % (user, reason, loc, ip_access_valid,
1606 auth_token_access_valid))
1655 auth_token_access_valid))
1607 # we preserve the get PARAM
1656 # we preserve the get PARAM
1608 came_from = get_came_from(request)
1657 came_from = get_came_from(request)
1609
1658
1610 log.debug('redirecting to login page with %s' % (came_from,))
1659 log.debug('redirecting to login page with %s' % (came_from,))
1611 raise HTTPFound(
1660 raise HTTPFound(
1612 h.route_path('login', _query={'came_from': came_from}))
1661 h.route_path('login', _query={'came_from': came_from}))
1613
1662
1614
1663
1615 class NotAnonymous(object):
1664 class NotAnonymous(object):
1616 """
1665 """
1617 Must be logged in to execute this function else
1666 Must be logged in to execute this function else
1618 redirect to login page
1667 redirect to login page
1619 """
1668 """
1620
1669
1621 def __call__(self, func):
1670 def __call__(self, func):
1622 return get_cython_compat_decorator(self.__wrapper, func)
1671 return get_cython_compat_decorator(self.__wrapper, func)
1623
1672
1624 def _get_request(self):
1673 def _get_request(self):
1625 return get_request(self)
1674 return get_request(self)
1626
1675
1627 def __wrapper(self, func, *fargs, **fkwargs):
1676 def __wrapper(self, func, *fargs, **fkwargs):
1628 import rhodecode.lib.helpers as h
1677 import rhodecode.lib.helpers as h
1629 cls = fargs[0]
1678 cls = fargs[0]
1630 self.user = cls._rhodecode_user
1679 self.user = cls._rhodecode_user
1631 request = self._get_request()
1680 request = self._get_request()
1632 _ = request.translate
1681 _ = request.translate
1633 log.debug('Checking if user is not anonymous @%s' % cls)
1682 log.debug('Checking if user is not anonymous @%s' % cls)
1634
1683
1635 anonymous = self.user.username == User.DEFAULT_USER
1684 anonymous = self.user.username == User.DEFAULT_USER
1636
1685
1637 if anonymous:
1686 if anonymous:
1638 came_from = get_came_from(request)
1687 came_from = get_came_from(request)
1639 h.flash(_('You need to be a registered user to '
1688 h.flash(_('You need to be a registered user to '
1640 'perform this action'),
1689 'perform this action'),
1641 category='warning')
1690 category='warning')
1642 raise HTTPFound(
1691 raise HTTPFound(
1643 h.route_path('login', _query={'came_from': came_from}))
1692 h.route_path('login', _query={'came_from': came_from}))
1644 else:
1693 else:
1645 return func(*fargs, **fkwargs)
1694 return func(*fargs, **fkwargs)
1646
1695
1647
1696
1648 class PermsDecorator(object):
1697 class PermsDecorator(object):
1649 """
1698 """
1650 Base class for controller decorators, we extract the current user from
1699 Base class for controller decorators, we extract the current user from
1651 the class itself, which has it stored in base controllers
1700 the class itself, which has it stored in base controllers
1652 """
1701 """
1653
1702
1654 def __init__(self, *required_perms):
1703 def __init__(self, *required_perms):
1655 self.required_perms = set(required_perms)
1704 self.required_perms = set(required_perms)
1656
1705
1657 def __call__(self, func):
1706 def __call__(self, func):
1658 return get_cython_compat_decorator(self.__wrapper, func)
1707 return get_cython_compat_decorator(self.__wrapper, func)
1659
1708
1660 def _get_request(self):
1709 def _get_request(self):
1661 return get_request(self)
1710 return get_request(self)
1662
1711
1663 def __wrapper(self, func, *fargs, **fkwargs):
1712 def __wrapper(self, func, *fargs, **fkwargs):
1664 import rhodecode.lib.helpers as h
1713 import rhodecode.lib.helpers as h
1665 cls = fargs[0]
1714 cls = fargs[0]
1666 _user = cls._rhodecode_user
1715 _user = cls._rhodecode_user
1667 request = self._get_request()
1716 request = self._get_request()
1668 _ = request.translate
1717 _ = request.translate
1669
1718
1670 log.debug('checking %s permissions %s for %s %s',
1719 log.debug('checking %s permissions %s for %s %s',
1671 self.__class__.__name__, self.required_perms, cls, _user)
1720 self.__class__.__name__, self.required_perms, cls, _user)
1672
1721
1673 if self.check_permissions(_user):
1722 if self.check_permissions(_user):
1674 log.debug('Permission granted for %s %s', cls, _user)
1723 log.debug('Permission granted for %s %s', cls, _user)
1675 return func(*fargs, **fkwargs)
1724 return func(*fargs, **fkwargs)
1676
1725
1677 else:
1726 else:
1678 log.debug('Permission denied for %s %s', cls, _user)
1727 log.debug('Permission denied for %s %s', cls, _user)
1679 anonymous = _user.username == User.DEFAULT_USER
1728 anonymous = _user.username == User.DEFAULT_USER
1680
1729
1681 if anonymous:
1730 if anonymous:
1682 came_from = get_came_from(self._get_request())
1731 came_from = get_came_from(self._get_request())
1683 h.flash(_('You need to be signed in to view this page'),
1732 h.flash(_('You need to be signed in to view this page'),
1684 category='warning')
1733 category='warning')
1685 raise HTTPFound(
1734 raise HTTPFound(
1686 h.route_path('login', _query={'came_from': came_from}))
1735 h.route_path('login', _query={'came_from': came_from}))
1687
1736
1688 else:
1737 else:
1689 # redirect with 404 to prevent resource discovery
1738 # redirect with 404 to prevent resource discovery
1690 raise HTTPNotFound()
1739 raise HTTPNotFound()
1691
1740
1692 def check_permissions(self, user):
1741 def check_permissions(self, user):
1693 """Dummy function for overriding"""
1742 """Dummy function for overriding"""
1694 raise NotImplementedError(
1743 raise NotImplementedError(
1695 'You have to write this function in child class')
1744 'You have to write this function in child class')
1696
1745
1697
1746
1698 class HasPermissionAllDecorator(PermsDecorator):
1747 class HasPermissionAllDecorator(PermsDecorator):
1699 """
1748 """
1700 Checks for access permission for all given predicates. All of them
1749 Checks for access permission for all given predicates. All of them
1701 have to be meet in order to fulfill the request
1750 have to be meet in order to fulfill the request
1702 """
1751 """
1703
1752
1704 def check_permissions(self, user):
1753 def check_permissions(self, user):
1705 perms = user.permissions_with_scope({})
1754 perms = user.permissions_with_scope({})
1706 if self.required_perms.issubset(perms['global']):
1755 if self.required_perms.issubset(perms['global']):
1707 return True
1756 return True
1708 return False
1757 return False
1709
1758
1710
1759
1711 class HasPermissionAnyDecorator(PermsDecorator):
1760 class HasPermissionAnyDecorator(PermsDecorator):
1712 """
1761 """
1713 Checks for access permission for any of given predicates. In order to
1762 Checks for access permission for any of given predicates. In order to
1714 fulfill the request any of predicates must be meet
1763 fulfill the request any of predicates must be meet
1715 """
1764 """
1716
1765
1717 def check_permissions(self, user):
1766 def check_permissions(self, user):
1718 perms = user.permissions_with_scope({})
1767 perms = user.permissions_with_scope({})
1719 if self.required_perms.intersection(perms['global']):
1768 if self.required_perms.intersection(perms['global']):
1720 return True
1769 return True
1721 return False
1770 return False
1722
1771
1723
1772
1724 class HasRepoPermissionAllDecorator(PermsDecorator):
1773 class HasRepoPermissionAllDecorator(PermsDecorator):
1725 """
1774 """
1726 Checks for access permission for all given predicates for specific
1775 Checks for access permission for all given predicates for specific
1727 repository. All of them have to be meet in order to fulfill the request
1776 repository. All of them have to be meet in order to fulfill the request
1728 """
1777 """
1729 def _get_repo_name(self):
1778 def _get_repo_name(self):
1730 _request = self._get_request()
1779 _request = self._get_request()
1731 return get_repo_slug(_request)
1780 return get_repo_slug(_request)
1732
1781
1733 def check_permissions(self, user):
1782 def check_permissions(self, user):
1734 perms = user.permissions
1783 perms = user.permissions
1735 repo_name = self._get_repo_name()
1784 repo_name = self._get_repo_name()
1736
1785
1737 try:
1786 try:
1738 user_perms = {perms['repositories'][repo_name]}
1787 user_perms = {perms['repositories'][repo_name]}
1739 except KeyError:
1788 except KeyError:
1740 log.debug('cannot locate repo with name: `%s` in permissions defs',
1789 log.debug('cannot locate repo with name: `%s` in permissions defs',
1741 repo_name)
1790 repo_name)
1742 return False
1791 return False
1743
1792
1744 log.debug('checking `%s` permissions for repo `%s`',
1793 log.debug('checking `%s` permissions for repo `%s`',
1745 user_perms, repo_name)
1794 user_perms, repo_name)
1746 if self.required_perms.issubset(user_perms):
1795 if self.required_perms.issubset(user_perms):
1747 return True
1796 return True
1748 return False
1797 return False
1749
1798
1750
1799
1751 class HasRepoPermissionAnyDecorator(PermsDecorator):
1800 class HasRepoPermissionAnyDecorator(PermsDecorator):
1752 """
1801 """
1753 Checks for access permission for any of given predicates for specific
1802 Checks for access permission for any of given predicates for specific
1754 repository. In order to fulfill the request any of predicates must be meet
1803 repository. In order to fulfill the request any of predicates must be meet
1755 """
1804 """
1756 def _get_repo_name(self):
1805 def _get_repo_name(self):
1757 _request = self._get_request()
1806 _request = self._get_request()
1758 return get_repo_slug(_request)
1807 return get_repo_slug(_request)
1759
1808
1760 def check_permissions(self, user):
1809 def check_permissions(self, user):
1761 perms = user.permissions
1810 perms = user.permissions
1762 repo_name = self._get_repo_name()
1811 repo_name = self._get_repo_name()
1763
1812
1764 try:
1813 try:
1765 user_perms = {perms['repositories'][repo_name]}
1814 user_perms = {perms['repositories'][repo_name]}
1766 except KeyError:
1815 except KeyError:
1767 log.debug(
1816 log.debug(
1768 'cannot locate repo with name: `%s` in permissions defs',
1817 'cannot locate repo with name: `%s` in permissions defs',
1769 repo_name)
1818 repo_name)
1770 return False
1819 return False
1771
1820
1772 log.debug('checking `%s` permissions for repo `%s`',
1821 log.debug('checking `%s` permissions for repo `%s`',
1773 user_perms, repo_name)
1822 user_perms, repo_name)
1774 if self.required_perms.intersection(user_perms):
1823 if self.required_perms.intersection(user_perms):
1775 return True
1824 return True
1776 return False
1825 return False
1777
1826
1778
1827
1779 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1828 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1780 """
1829 """
1781 Checks for access permission for all given predicates for specific
1830 Checks for access permission for all given predicates for specific
1782 repository group. All of them have to be meet in order to
1831 repository group. All of them have to be meet in order to
1783 fulfill the request
1832 fulfill the request
1784 """
1833 """
1785 def _get_repo_group_name(self):
1834 def _get_repo_group_name(self):
1786 _request = self._get_request()
1835 _request = self._get_request()
1787 return get_repo_group_slug(_request)
1836 return get_repo_group_slug(_request)
1788
1837
1789 def check_permissions(self, user):
1838 def check_permissions(self, user):
1790 perms = user.permissions
1839 perms = user.permissions
1791 group_name = self._get_repo_group_name()
1840 group_name = self._get_repo_group_name()
1792 try:
1841 try:
1793 user_perms = {perms['repositories_groups'][group_name]}
1842 user_perms = {perms['repositories_groups'][group_name]}
1794 except KeyError:
1843 except KeyError:
1795 log.debug(
1844 log.debug(
1796 'cannot locate repo group with name: `%s` in permissions defs',
1845 'cannot locate repo group with name: `%s` in permissions defs',
1797 group_name)
1846 group_name)
1798 return False
1847 return False
1799
1848
1800 log.debug('checking `%s` permissions for repo group `%s`',
1849 log.debug('checking `%s` permissions for repo group `%s`',
1801 user_perms, group_name)
1850 user_perms, group_name)
1802 if self.required_perms.issubset(user_perms):
1851 if self.required_perms.issubset(user_perms):
1803 return True
1852 return True
1804 return False
1853 return False
1805
1854
1806
1855
1807 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1856 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1808 """
1857 """
1809 Checks for access permission for any of given predicates for specific
1858 Checks for access permission for any of given predicates for specific
1810 repository group. In order to fulfill the request any
1859 repository group. In order to fulfill the request any
1811 of predicates must be met
1860 of predicates must be met
1812 """
1861 """
1813 def _get_repo_group_name(self):
1862 def _get_repo_group_name(self):
1814 _request = self._get_request()
1863 _request = self._get_request()
1815 return get_repo_group_slug(_request)
1864 return get_repo_group_slug(_request)
1816
1865
1817 def check_permissions(self, user):
1866 def check_permissions(self, user):
1818 perms = user.permissions
1867 perms = user.permissions
1819 group_name = self._get_repo_group_name()
1868 group_name = self._get_repo_group_name()
1820
1869
1821 try:
1870 try:
1822 user_perms = {perms['repositories_groups'][group_name]}
1871 user_perms = {perms['repositories_groups'][group_name]}
1823 except KeyError:
1872 except KeyError:
1824 log.debug(
1873 log.debug(
1825 'cannot locate repo group with name: `%s` in permissions defs',
1874 'cannot locate repo group with name: `%s` in permissions defs',
1826 group_name)
1875 group_name)
1827 return False
1876 return False
1828
1877
1829 log.debug('checking `%s` permissions for repo group `%s`',
1878 log.debug('checking `%s` permissions for repo group `%s`',
1830 user_perms, group_name)
1879 user_perms, group_name)
1831 if self.required_perms.intersection(user_perms):
1880 if self.required_perms.intersection(user_perms):
1832 return True
1881 return True
1833 return False
1882 return False
1834
1883
1835
1884
1836 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1885 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1837 """
1886 """
1838 Checks for access permission for all given predicates for specific
1887 Checks for access permission for all given predicates for specific
1839 user group. All of them have to be meet in order to fulfill the request
1888 user group. All of them have to be meet in order to fulfill the request
1840 """
1889 """
1841 def _get_user_group_name(self):
1890 def _get_user_group_name(self):
1842 _request = self._get_request()
1891 _request = self._get_request()
1843 return get_user_group_slug(_request)
1892 return get_user_group_slug(_request)
1844
1893
1845 def check_permissions(self, user):
1894 def check_permissions(self, user):
1846 perms = user.permissions
1895 perms = user.permissions
1847 group_name = self._get_user_group_name()
1896 group_name = self._get_user_group_name()
1848 try:
1897 try:
1849 user_perms = {perms['user_groups'][group_name]}
1898 user_perms = {perms['user_groups'][group_name]}
1850 except KeyError:
1899 except KeyError:
1851 return False
1900 return False
1852
1901
1853 if self.required_perms.issubset(user_perms):
1902 if self.required_perms.issubset(user_perms):
1854 return True
1903 return True
1855 return False
1904 return False
1856
1905
1857
1906
1858 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1907 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1859 """
1908 """
1860 Checks for access permission for any of given predicates for specific
1909 Checks for access permission for any of given predicates for specific
1861 user group. In order to fulfill the request any of predicates must be meet
1910 user group. In order to fulfill the request any of predicates must be meet
1862 """
1911 """
1863 def _get_user_group_name(self):
1912 def _get_user_group_name(self):
1864 _request = self._get_request()
1913 _request = self._get_request()
1865 return get_user_group_slug(_request)
1914 return get_user_group_slug(_request)
1866
1915
1867 def check_permissions(self, user):
1916 def check_permissions(self, user):
1868 perms = user.permissions
1917 perms = user.permissions
1869 group_name = self._get_user_group_name()
1918 group_name = self._get_user_group_name()
1870 try:
1919 try:
1871 user_perms = {perms['user_groups'][group_name]}
1920 user_perms = {perms['user_groups'][group_name]}
1872 except KeyError:
1921 except KeyError:
1873 return False
1922 return False
1874
1923
1875 if self.required_perms.intersection(user_perms):
1924 if self.required_perms.intersection(user_perms):
1876 return True
1925 return True
1877 return False
1926 return False
1878
1927
1879
1928
1880 # CHECK FUNCTIONS
1929 # CHECK FUNCTIONS
1881 class PermsFunction(object):
1930 class PermsFunction(object):
1882 """Base function for other check functions"""
1931 """Base function for other check functions"""
1883
1932
1884 def __init__(self, *perms):
1933 def __init__(self, *perms):
1885 self.required_perms = set(perms)
1934 self.required_perms = set(perms)
1886 self.repo_name = None
1935 self.repo_name = None
1887 self.repo_group_name = None
1936 self.repo_group_name = None
1888 self.user_group_name = None
1937 self.user_group_name = None
1889
1938
1890 def __bool__(self):
1939 def __bool__(self):
1891 frame = inspect.currentframe()
1940 frame = inspect.currentframe()
1892 stack_trace = traceback.format_stack(frame)
1941 stack_trace = traceback.format_stack(frame)
1893 log.error('Checking bool value on a class instance of perm '
1942 log.error('Checking bool value on a class instance of perm '
1894 'function is not allowed: %s' % ''.join(stack_trace))
1943 'function is not allowed: %s' % ''.join(stack_trace))
1895 # rather than throwing errors, here we always return False so if by
1944 # rather than throwing errors, here we always return False so if by
1896 # accident someone checks truth for just an instance it will always end
1945 # accident someone checks truth for just an instance it will always end
1897 # up in returning False
1946 # up in returning False
1898 return False
1947 return False
1899 __nonzero__ = __bool__
1948 __nonzero__ = __bool__
1900
1949
1901 def __call__(self, check_location='', user=None):
1950 def __call__(self, check_location='', user=None):
1902 if not user:
1951 if not user:
1903 log.debug('Using user attribute from global request')
1952 log.debug('Using user attribute from global request')
1904 request = self._get_request()
1953 request = self._get_request()
1905 user = request.user
1954 user = request.user
1906
1955
1907 # init auth user if not already given
1956 # init auth user if not already given
1908 if not isinstance(user, AuthUser):
1957 if not isinstance(user, AuthUser):
1909 log.debug('Wrapping user %s into AuthUser', user)
1958 log.debug('Wrapping user %s into AuthUser', user)
1910 user = AuthUser(user.user_id)
1959 user = AuthUser(user.user_id)
1911
1960
1912 cls_name = self.__class__.__name__
1961 cls_name = self.__class__.__name__
1913 check_scope = self._get_check_scope(cls_name)
1962 check_scope = self._get_check_scope(cls_name)
1914 check_location = check_location or 'unspecified location'
1963 check_location = check_location or 'unspecified location'
1915
1964
1916 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
1965 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
1917 self.required_perms, user, check_scope, check_location)
1966 self.required_perms, user, check_scope, check_location)
1918 if not user:
1967 if not user:
1919 log.warning('Empty user given for permission check')
1968 log.warning('Empty user given for permission check')
1920 return False
1969 return False
1921
1970
1922 if self.check_permissions(user):
1971 if self.check_permissions(user):
1923 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1972 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
1924 check_scope, user, check_location)
1973 check_scope, user, check_location)
1925 return True
1974 return True
1926
1975
1927 else:
1976 else:
1928 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1977 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
1929 check_scope, user, check_location)
1978 check_scope, user, check_location)
1930 return False
1979 return False
1931
1980
1932 def _get_request(self):
1981 def _get_request(self):
1933 return get_request(self)
1982 return get_request(self)
1934
1983
1935 def _get_check_scope(self, cls_name):
1984 def _get_check_scope(self, cls_name):
1936 return {
1985 return {
1937 'HasPermissionAll': 'GLOBAL',
1986 'HasPermissionAll': 'GLOBAL',
1938 'HasPermissionAny': 'GLOBAL',
1987 'HasPermissionAny': 'GLOBAL',
1939 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
1988 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
1940 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
1989 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
1941 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
1990 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
1942 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
1991 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
1943 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
1992 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
1944 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
1993 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
1945 }.get(cls_name, '?:%s' % cls_name)
1994 }.get(cls_name, '?:%s' % cls_name)
1946
1995
1947 def check_permissions(self, user):
1996 def check_permissions(self, user):
1948 """Dummy function for overriding"""
1997 """Dummy function for overriding"""
1949 raise Exception('You have to write this function in child class')
1998 raise Exception('You have to write this function in child class')
1950
1999
1951
2000
1952 class HasPermissionAll(PermsFunction):
2001 class HasPermissionAll(PermsFunction):
1953 def check_permissions(self, user):
2002 def check_permissions(self, user):
1954 perms = user.permissions_with_scope({})
2003 perms = user.permissions_with_scope({})
1955 if self.required_perms.issubset(perms.get('global')):
2004 if self.required_perms.issubset(perms.get('global')):
1956 return True
2005 return True
1957 return False
2006 return False
1958
2007
1959
2008
1960 class HasPermissionAny(PermsFunction):
2009 class HasPermissionAny(PermsFunction):
1961 def check_permissions(self, user):
2010 def check_permissions(self, user):
1962 perms = user.permissions_with_scope({})
2011 perms = user.permissions_with_scope({})
1963 if self.required_perms.intersection(perms.get('global')):
2012 if self.required_perms.intersection(perms.get('global')):
1964 return True
2013 return True
1965 return False
2014 return False
1966
2015
1967
2016
1968 class HasRepoPermissionAll(PermsFunction):
2017 class HasRepoPermissionAll(PermsFunction):
1969 def __call__(self, repo_name=None, check_location='', user=None):
2018 def __call__(self, repo_name=None, check_location='', user=None):
1970 self.repo_name = repo_name
2019 self.repo_name = repo_name
1971 return super(HasRepoPermissionAll, self).__call__(check_location, user)
2020 return super(HasRepoPermissionAll, self).__call__(check_location, user)
1972
2021
1973 def _get_repo_name(self):
2022 def _get_repo_name(self):
1974 if not self.repo_name:
2023 if not self.repo_name:
1975 _request = self._get_request()
2024 _request = self._get_request()
1976 self.repo_name = get_repo_slug(_request)
2025 self.repo_name = get_repo_slug(_request)
1977 return self.repo_name
2026 return self.repo_name
1978
2027
1979 def check_permissions(self, user):
2028 def check_permissions(self, user):
1980 self.repo_name = self._get_repo_name()
2029 self.repo_name = self._get_repo_name()
1981 perms = user.permissions
2030 perms = user.permissions
1982 try:
2031 try:
1983 user_perms = {perms['repositories'][self.repo_name]}
2032 user_perms = {perms['repositories'][self.repo_name]}
1984 except KeyError:
2033 except KeyError:
1985 return False
2034 return False
1986 if self.required_perms.issubset(user_perms):
2035 if self.required_perms.issubset(user_perms):
1987 return True
2036 return True
1988 return False
2037 return False
1989
2038
1990
2039
1991 class HasRepoPermissionAny(PermsFunction):
2040 class HasRepoPermissionAny(PermsFunction):
1992 def __call__(self, repo_name=None, check_location='', user=None):
2041 def __call__(self, repo_name=None, check_location='', user=None):
1993 self.repo_name = repo_name
2042 self.repo_name = repo_name
1994 return super(HasRepoPermissionAny, self).__call__(check_location, user)
2043 return super(HasRepoPermissionAny, self).__call__(check_location, user)
1995
2044
1996 def _get_repo_name(self):
2045 def _get_repo_name(self):
1997 if not self.repo_name:
2046 if not self.repo_name:
1998 _request = self._get_request()
2047 _request = self._get_request()
1999 self.repo_name = get_repo_slug(_request)
2048 self.repo_name = get_repo_slug(_request)
2000 return self.repo_name
2049 return self.repo_name
2001
2050
2002 def check_permissions(self, user):
2051 def check_permissions(self, user):
2003 self.repo_name = self._get_repo_name()
2052 self.repo_name = self._get_repo_name()
2004 perms = user.permissions
2053 perms = user.permissions
2005 try:
2054 try:
2006 user_perms = {perms['repositories'][self.repo_name]}
2055 user_perms = {perms['repositories'][self.repo_name]}
2007 except KeyError:
2056 except KeyError:
2008 return False
2057 return False
2009 if self.required_perms.intersection(user_perms):
2058 if self.required_perms.intersection(user_perms):
2010 return True
2059 return True
2011 return False
2060 return False
2012
2061
2013
2062
2014 class HasRepoGroupPermissionAny(PermsFunction):
2063 class HasRepoGroupPermissionAny(PermsFunction):
2015 def __call__(self, group_name=None, check_location='', user=None):
2064 def __call__(self, group_name=None, check_location='', user=None):
2016 self.repo_group_name = group_name
2065 self.repo_group_name = group_name
2017 return super(HasRepoGroupPermissionAny, self).__call__(
2066 return super(HasRepoGroupPermissionAny, self).__call__(
2018 check_location, user)
2067 check_location, user)
2019
2068
2020 def check_permissions(self, user):
2069 def check_permissions(self, user):
2021 perms = user.permissions
2070 perms = user.permissions
2022 try:
2071 try:
2023 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2072 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2024 except KeyError:
2073 except KeyError:
2025 return False
2074 return False
2026 if self.required_perms.intersection(user_perms):
2075 if self.required_perms.intersection(user_perms):
2027 return True
2076 return True
2028 return False
2077 return False
2029
2078
2030
2079
2031 class HasRepoGroupPermissionAll(PermsFunction):
2080 class HasRepoGroupPermissionAll(PermsFunction):
2032 def __call__(self, group_name=None, check_location='', user=None):
2081 def __call__(self, group_name=None, check_location='', user=None):
2033 self.repo_group_name = group_name
2082 self.repo_group_name = group_name
2034 return super(HasRepoGroupPermissionAll, self).__call__(
2083 return super(HasRepoGroupPermissionAll, self).__call__(
2035 check_location, user)
2084 check_location, user)
2036
2085
2037 def check_permissions(self, user):
2086 def check_permissions(self, user):
2038 perms = user.permissions
2087 perms = user.permissions
2039 try:
2088 try:
2040 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2089 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2041 except KeyError:
2090 except KeyError:
2042 return False
2091 return False
2043 if self.required_perms.issubset(user_perms):
2092 if self.required_perms.issubset(user_perms):
2044 return True
2093 return True
2045 return False
2094 return False
2046
2095
2047
2096
2048 class HasUserGroupPermissionAny(PermsFunction):
2097 class HasUserGroupPermissionAny(PermsFunction):
2049 def __call__(self, user_group_name=None, check_location='', user=None):
2098 def __call__(self, user_group_name=None, check_location='', user=None):
2050 self.user_group_name = user_group_name
2099 self.user_group_name = user_group_name
2051 return super(HasUserGroupPermissionAny, self).__call__(
2100 return super(HasUserGroupPermissionAny, self).__call__(
2052 check_location, user)
2101 check_location, user)
2053
2102
2054 def check_permissions(self, user):
2103 def check_permissions(self, user):
2055 perms = user.permissions
2104 perms = user.permissions
2056 try:
2105 try:
2057 user_perms = {perms['user_groups'][self.user_group_name]}
2106 user_perms = {perms['user_groups'][self.user_group_name]}
2058 except KeyError:
2107 except KeyError:
2059 return False
2108 return False
2060 if self.required_perms.intersection(user_perms):
2109 if self.required_perms.intersection(user_perms):
2061 return True
2110 return True
2062 return False
2111 return False
2063
2112
2064
2113
2065 class HasUserGroupPermissionAll(PermsFunction):
2114 class HasUserGroupPermissionAll(PermsFunction):
2066 def __call__(self, user_group_name=None, check_location='', user=None):
2115 def __call__(self, user_group_name=None, check_location='', user=None):
2067 self.user_group_name = user_group_name
2116 self.user_group_name = user_group_name
2068 return super(HasUserGroupPermissionAll, self).__call__(
2117 return super(HasUserGroupPermissionAll, self).__call__(
2069 check_location, user)
2118 check_location, user)
2070
2119
2071 def check_permissions(self, user):
2120 def check_permissions(self, user):
2072 perms = user.permissions
2121 perms = user.permissions
2073 try:
2122 try:
2074 user_perms = {perms['user_groups'][self.user_group_name]}
2123 user_perms = {perms['user_groups'][self.user_group_name]}
2075 except KeyError:
2124 except KeyError:
2076 return False
2125 return False
2077 if self.required_perms.issubset(user_perms):
2126 if self.required_perms.issubset(user_perms):
2078 return True
2127 return True
2079 return False
2128 return False
2080
2129
2081
2130
2082 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
2131 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
2083 class HasPermissionAnyMiddleware(object):
2132 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):
2107 perms = user.permissions_with_scope({'repo_name': repo_name})
2155 perms = user.permissions_with_scope({'repo_name': repo_name})
2108
2156
2109 try:
2157 try:
2110 user_perms = {perms['repositories'][repo_name]}
2158 user_perms = {perms['repositories'][repo_name]}
2111 except Exception:
2159 except Exception:
2112 log.exception('Error while accessing user permissions')
2160 log.exception('Error while accessing user permissions')
2113 return False
2161 return False
2114
2162
2115 if self.required_perms.intersection(user_perms):
2163 if self.required_perms.intersection(user_perms):
2116 return True
2164 return True
2117 return False
2165 return False
2118
2166
2119
2167
2120 # SPECIAL VERSION TO HANDLE API AUTH
2168 # SPECIAL VERSION TO HANDLE API AUTH
2121 class _BaseApiPerm(object):
2169 class _BaseApiPerm(object):
2122 def __init__(self, *perms):
2170 def __init__(self, *perms):
2123 self.required_perms = set(perms)
2171 self.required_perms = set(perms)
2124
2172
2125 def __call__(self, check_location=None, user=None, repo_name=None,
2173 def __call__(self, check_location=None, user=None, repo_name=None,
2126 group_name=None, user_group_name=None):
2174 group_name=None, user_group_name=None):
2127 cls_name = self.__class__.__name__
2175 cls_name = self.__class__.__name__
2128 check_scope = 'global:%s' % (self.required_perms,)
2176 check_scope = 'global:%s' % (self.required_perms,)
2129 if repo_name:
2177 if repo_name:
2130 check_scope += ', repo_name:%s' % (repo_name,)
2178 check_scope += ', repo_name:%s' % (repo_name,)
2131
2179
2132 if group_name:
2180 if group_name:
2133 check_scope += ', repo_group_name:%s' % (group_name,)
2181 check_scope += ', repo_group_name:%s' % (group_name,)
2134
2182
2135 if user_group_name:
2183 if user_group_name:
2136 check_scope += ', user_group_name:%s' % (user_group_name,)
2184 check_scope += ', user_group_name:%s' % (user_group_name,)
2137
2185
2138 log.debug(
2186 log.debug(
2139 'checking cls:%s %s %s @ %s'
2187 'checking cls:%s %s %s @ %s'
2140 % (cls_name, self.required_perms, check_scope, check_location))
2188 % (cls_name, self.required_perms, check_scope, check_location))
2141 if not user:
2189 if not user:
2142 log.debug('Empty User passed into arguments')
2190 log.debug('Empty User passed into arguments')
2143 return False
2191 return False
2144
2192
2145 # process user
2193 # process user
2146 if not isinstance(user, AuthUser):
2194 if not isinstance(user, AuthUser):
2147 user = AuthUser(user.user_id)
2195 user = AuthUser(user.user_id)
2148 if not check_location:
2196 if not check_location:
2149 check_location = 'unspecified'
2197 check_location = 'unspecified'
2150 if self.check_permissions(user.permissions, repo_name, group_name,
2198 if self.check_permissions(user.permissions, repo_name, group_name,
2151 user_group_name):
2199 user_group_name):
2152 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2200 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2153 check_scope, user, check_location)
2201 check_scope, user, check_location)
2154 return True
2202 return True
2155
2203
2156 else:
2204 else:
2157 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2205 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2158 check_scope, user, check_location)
2206 check_scope, user, check_location)
2159 return False
2207 return False
2160
2208
2161 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2209 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2162 user_group_name=None):
2210 user_group_name=None):
2163 """
2211 """
2164 implement in child class should return True if permissions are ok,
2212 implement in child class should return True if permissions are ok,
2165 False otherwise
2213 False otherwise
2166
2214
2167 :param perm_defs: dict with permission definitions
2215 :param perm_defs: dict with permission definitions
2168 :param repo_name: repo name
2216 :param repo_name: repo name
2169 """
2217 """
2170 raise NotImplementedError()
2218 raise NotImplementedError()
2171
2219
2172
2220
2173 class HasPermissionAllApi(_BaseApiPerm):
2221 class HasPermissionAllApi(_BaseApiPerm):
2174 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2222 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2175 user_group_name=None):
2223 user_group_name=None):
2176 if self.required_perms.issubset(perm_defs.get('global')):
2224 if self.required_perms.issubset(perm_defs.get('global')):
2177 return True
2225 return True
2178 return False
2226 return False
2179
2227
2180
2228
2181 class HasPermissionAnyApi(_BaseApiPerm):
2229 class HasPermissionAnyApi(_BaseApiPerm):
2182 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2230 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2183 user_group_name=None):
2231 user_group_name=None):
2184 if self.required_perms.intersection(perm_defs.get('global')):
2232 if self.required_perms.intersection(perm_defs.get('global')):
2185 return True
2233 return True
2186 return False
2234 return False
2187
2235
2188
2236
2189 class HasRepoPermissionAllApi(_BaseApiPerm):
2237 class HasRepoPermissionAllApi(_BaseApiPerm):
2190 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2238 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2191 user_group_name=None):
2239 user_group_name=None):
2192 try:
2240 try:
2193 _user_perms = {perm_defs['repositories'][repo_name]}
2241 _user_perms = {perm_defs['repositories'][repo_name]}
2194 except KeyError:
2242 except KeyError:
2195 log.warning(traceback.format_exc())
2243 log.warning(traceback.format_exc())
2196 return False
2244 return False
2197 if self.required_perms.issubset(_user_perms):
2245 if self.required_perms.issubset(_user_perms):
2198 return True
2246 return True
2199 return False
2247 return False
2200
2248
2201
2249
2202 class HasRepoPermissionAnyApi(_BaseApiPerm):
2250 class HasRepoPermissionAnyApi(_BaseApiPerm):
2203 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2251 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2204 user_group_name=None):
2252 user_group_name=None):
2205 try:
2253 try:
2206 _user_perms = {perm_defs['repositories'][repo_name]}
2254 _user_perms = {perm_defs['repositories'][repo_name]}
2207 except KeyError:
2255 except KeyError:
2208 log.warning(traceback.format_exc())
2256 log.warning(traceback.format_exc())
2209 return False
2257 return False
2210 if self.required_perms.intersection(_user_perms):
2258 if self.required_perms.intersection(_user_perms):
2211 return True
2259 return True
2212 return False
2260 return False
2213
2261
2214
2262
2215 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
2263 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
2216 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2264 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2217 user_group_name=None):
2265 user_group_name=None):
2218 try:
2266 try:
2219 _user_perms = {perm_defs['repositories_groups'][group_name]}
2267 _user_perms = {perm_defs['repositories_groups'][group_name]}
2220 except KeyError:
2268 except KeyError:
2221 log.warning(traceback.format_exc())
2269 log.warning(traceback.format_exc())
2222 return False
2270 return False
2223 if self.required_perms.intersection(_user_perms):
2271 if self.required_perms.intersection(_user_perms):
2224 return True
2272 return True
2225 return False
2273 return False
2226
2274
2227
2275
2228 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
2276 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
2229 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2277 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2230 user_group_name=None):
2278 user_group_name=None):
2231 try:
2279 try:
2232 _user_perms = {perm_defs['repositories_groups'][group_name]}
2280 _user_perms = {perm_defs['repositories_groups'][group_name]}
2233 except KeyError:
2281 except KeyError:
2234 log.warning(traceback.format_exc())
2282 log.warning(traceback.format_exc())
2235 return False
2283 return False
2236 if self.required_perms.issubset(_user_perms):
2284 if self.required_perms.issubset(_user_perms):
2237 return True
2285 return True
2238 return False
2286 return False
2239
2287
2240
2288
2241 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
2289 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
2242 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2290 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2243 user_group_name=None):
2291 user_group_name=None):
2244 try:
2292 try:
2245 _user_perms = {perm_defs['user_groups'][user_group_name]}
2293 _user_perms = {perm_defs['user_groups'][user_group_name]}
2246 except KeyError:
2294 except KeyError:
2247 log.warning(traceback.format_exc())
2295 log.warning(traceback.format_exc())
2248 return False
2296 return False
2249 if self.required_perms.intersection(_user_perms):
2297 if self.required_perms.intersection(_user_perms):
2250 return True
2298 return True
2251 return False
2299 return False
2252
2300
2253
2301
2254 def check_ip_access(source_ip, allowed_ips=None):
2302 def check_ip_access(source_ip, allowed_ips=None):
2255 """
2303 """
2256 Checks if source_ip is a subnet of any of allowed_ips.
2304 Checks if source_ip is a subnet of any of allowed_ips.
2257
2305
2258 :param source_ip:
2306 :param source_ip:
2259 :param allowed_ips: list of allowed ips together with mask
2307 :param allowed_ips: list of allowed ips together with mask
2260 """
2308 """
2261 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
2309 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
2262 source_ip_address = ipaddress.ip_address(safe_unicode(source_ip))
2310 source_ip_address = ipaddress.ip_address(safe_unicode(source_ip))
2263 if isinstance(allowed_ips, (tuple, list, set)):
2311 if isinstance(allowed_ips, (tuple, list, set)):
2264 for ip in allowed_ips:
2312 for ip in allowed_ips:
2265 ip = safe_unicode(ip)
2313 ip = safe_unicode(ip)
2266 try:
2314 try:
2267 network_address = ipaddress.ip_network(ip, strict=False)
2315 network_address = ipaddress.ip_network(ip, strict=False)
2268 if source_ip_address in network_address:
2316 if source_ip_address in network_address:
2269 log.debug('IP %s is network %s' %
2317 log.debug('IP %s is network %s' %
2270 (source_ip_address, network_address))
2318 (source_ip_address, network_address))
2271 return True
2319 return True
2272 # for any case we cannot determine the IP, don't crash just
2320 # for any case we cannot determine the IP, don't crash just
2273 # skip it and log as error, we want to say forbidden still when
2321 # skip it and log as error, we want to say forbidden still when
2274 # sending bad IP
2322 # sending bad IP
2275 except Exception:
2323 except Exception:
2276 log.error(traceback.format_exc())
2324 log.error(traceback.format_exc())
2277 continue
2325 continue
2278 return False
2326 return False
2279
2327
2280
2328
2281 def get_cython_compat_decorator(wrapper, func):
2329 def get_cython_compat_decorator(wrapper, func):
2282 """
2330 """
2283 Creates a cython compatible decorator. The previously used
2331 Creates a cython compatible decorator. The previously used
2284 decorator.decorator() function seems to be incompatible with cython.
2332 decorator.decorator() function seems to be incompatible with cython.
2285
2333
2286 :param wrapper: __wrapper method of the decorator class
2334 :param wrapper: __wrapper method of the decorator class
2287 :param func: decorated function
2335 :param func: decorated function
2288 """
2336 """
2289 @wraps(func)
2337 @wraps(func)
2290 def local_wrapper(*args, **kwds):
2338 def local_wrapper(*args, **kwds):
2291 return wrapper(func, *args, **kwds)
2339 return wrapper(func, *args, **kwds)
2292 local_wrapper.__wrapped__ = func
2340 local_wrapper.__wrapped__ = func
2293 return local_wrapper
2341 return local_wrapper
2294
2342
2295
2343
@@ -1,546 +1,548 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 The base Controller API
22 The base Controller API
23 Provides the BaseController class for subclassing. And usage in different
23 Provides the BaseController class for subclassing. And usage in different
24 controllers
24 controllers
25 """
25 """
26
26
27 import logging
27 import logging
28 import socket
28 import socket
29
29
30 import markupsafe
30 import markupsafe
31 import ipaddress
31 import ipaddress
32
32
33 from paste.auth.basic import AuthBasicAuthenticator
33 from paste.auth.basic import AuthBasicAuthenticator
34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
36
36
37 import rhodecode
37 import rhodecode
38 from rhodecode.authentication.base import VCS_TYPE
38 from rhodecode.authentication.base import VCS_TYPE
39 from rhodecode.lib import auth, utils2
39 from rhodecode.lib import auth, utils2
40 from rhodecode.lib import helpers as h
40 from rhodecode.lib import helpers as h
41 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
41 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
42 from rhodecode.lib.exceptions import UserCreationError
42 from rhodecode.lib.exceptions import UserCreationError
43 from rhodecode.lib.utils import (password_changed, get_enabled_hook_classes)
43 from rhodecode.lib.utils import (password_changed, get_enabled_hook_classes)
44 from rhodecode.lib.utils2 import (
44 from rhodecode.lib.utils2 import (
45 str2bool, safe_unicode, AttributeDict, safe_int, sha1, aslist, safe_str)
45 str2bool, safe_unicode, AttributeDict, safe_int, sha1, aslist, safe_str)
46 from rhodecode.model.db import Repository, User, ChangesetComment
46 from rhodecode.model.db import Repository, User, ChangesetComment
47 from rhodecode.model.notification import NotificationModel
47 from rhodecode.model.notification import NotificationModel
48 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
48 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
49
49
50 log = logging.getLogger(__name__)
50 log = logging.getLogger(__name__)
51
51
52
52
53 def _filter_proxy(ip):
53 def _filter_proxy(ip):
54 """
54 """
55 Passed in IP addresses in HEADERS can be in a special format of multiple
55 Passed in IP addresses in HEADERS can be in a special format of multiple
56 ips. Those comma separated IPs are passed from various proxies in the
56 ips. Those comma separated IPs are passed from various proxies in the
57 chain of request processing. The left-most being the original client.
57 chain of request processing. The left-most being the original client.
58 We only care about the first IP which came from the org. client.
58 We only care about the first IP which came from the org. client.
59
59
60 :param ip: ip string from headers
60 :param ip: ip string from headers
61 """
61 """
62 if ',' in ip:
62 if ',' in ip:
63 _ips = ip.split(',')
63 _ips = ip.split(',')
64 _first_ip = _ips[0].strip()
64 _first_ip = _ips[0].strip()
65 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
65 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
66 return _first_ip
66 return _first_ip
67 return ip
67 return ip
68
68
69
69
70 def _filter_port(ip):
70 def _filter_port(ip):
71 """
71 """
72 Removes a port from ip, there are 4 main cases to handle here.
72 Removes a port from ip, there are 4 main cases to handle here.
73 - ipv4 eg. 127.0.0.1
73 - ipv4 eg. 127.0.0.1
74 - ipv6 eg. ::1
74 - ipv6 eg. ::1
75 - ipv4+port eg. 127.0.0.1:8080
75 - ipv4+port eg. 127.0.0.1:8080
76 - ipv6+port eg. [::1]:8080
76 - ipv6+port eg. [::1]:8080
77
77
78 :param ip:
78 :param ip:
79 """
79 """
80 def is_ipv6(ip_addr):
80 def is_ipv6(ip_addr):
81 if hasattr(socket, 'inet_pton'):
81 if hasattr(socket, 'inet_pton'):
82 try:
82 try:
83 socket.inet_pton(socket.AF_INET6, ip_addr)
83 socket.inet_pton(socket.AF_INET6, ip_addr)
84 except socket.error:
84 except socket.error:
85 return False
85 return False
86 else:
86 else:
87 # fallback to ipaddress
87 # fallback to ipaddress
88 try:
88 try:
89 ipaddress.IPv6Address(safe_unicode(ip_addr))
89 ipaddress.IPv6Address(safe_unicode(ip_addr))
90 except Exception:
90 except Exception:
91 return False
91 return False
92 return True
92 return True
93
93
94 if ':' not in ip: # must be ipv4 pure ip
94 if ':' not in ip: # must be ipv4 pure ip
95 return ip
95 return ip
96
96
97 if '[' in ip and ']' in ip: # ipv6 with port
97 if '[' in ip and ']' in ip: # ipv6 with port
98 return ip.split(']')[0][1:].lower()
98 return ip.split(']')[0][1:].lower()
99
99
100 # must be ipv6 or ipv4 with port
100 # must be ipv6 or ipv4 with port
101 if is_ipv6(ip):
101 if is_ipv6(ip):
102 return ip
102 return ip
103 else:
103 else:
104 ip, _port = ip.split(':')[:2] # means ipv4+port
104 ip, _port = ip.split(':')[:2] # means ipv4+port
105 return ip
105 return ip
106
106
107
107
108 def get_ip_addr(environ):
108 def get_ip_addr(environ):
109 proxy_key = 'HTTP_X_REAL_IP'
109 proxy_key = 'HTTP_X_REAL_IP'
110 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
110 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
111 def_key = 'REMOTE_ADDR'
111 def_key = 'REMOTE_ADDR'
112 _filters = lambda x: _filter_port(_filter_proxy(x))
112 _filters = lambda x: _filter_port(_filter_proxy(x))
113
113
114 ip = environ.get(proxy_key)
114 ip = environ.get(proxy_key)
115 if ip:
115 if ip:
116 return _filters(ip)
116 return _filters(ip)
117
117
118 ip = environ.get(proxy_key2)
118 ip = environ.get(proxy_key2)
119 if ip:
119 if ip:
120 return _filters(ip)
120 return _filters(ip)
121
121
122 ip = environ.get(def_key, '0.0.0.0')
122 ip = environ.get(def_key, '0.0.0.0')
123 return _filters(ip)
123 return _filters(ip)
124
124
125
125
126 def get_server_ip_addr(environ, log_errors=True):
126 def get_server_ip_addr(environ, log_errors=True):
127 hostname = environ.get('SERVER_NAME')
127 hostname = environ.get('SERVER_NAME')
128 try:
128 try:
129 return socket.gethostbyname(hostname)
129 return socket.gethostbyname(hostname)
130 except Exception as e:
130 except Exception as e:
131 if log_errors:
131 if log_errors:
132 # in some cases this lookup is not possible, and we don't want to
132 # in some cases this lookup is not possible, and we don't want to
133 # make it an exception in logs
133 # make it an exception in logs
134 log.exception('Could not retrieve server ip address: %s', e)
134 log.exception('Could not retrieve server ip address: %s', e)
135 return hostname
135 return hostname
136
136
137
137
138 def get_server_port(environ):
138 def get_server_port(environ):
139 return environ.get('SERVER_PORT')
139 return environ.get('SERVER_PORT')
140
140
141
141
142 def get_access_path(environ):
142 def get_access_path(environ):
143 path = environ.get('PATH_INFO')
143 path = environ.get('PATH_INFO')
144 org_req = environ.get('pylons.original_request')
144 org_req = environ.get('pylons.original_request')
145 if org_req:
145 if org_req:
146 path = org_req.environ.get('PATH_INFO')
146 path = org_req.environ.get('PATH_INFO')
147 return path
147 return path
148
148
149
149
150 def get_user_agent(environ):
150 def get_user_agent(environ):
151 return environ.get('HTTP_USER_AGENT')
151 return environ.get('HTTP_USER_AGENT')
152
152
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
160 This context is passed over the layers so that hooks triggered by the
160 This context is passed over the layers so that hooks triggered by the
161 vcs operation know details like the user, the user's IP address etc.
161 vcs operation know details like the user, the user's IP address etc.
162
162
163 :param check_locking: Allows to switch of the computation of the locking
163 :param check_locking: Allows to switch of the computation of the locking
164 data. This serves mainly the need of the simplevcs middleware to be
164 data. This serves mainly the need of the simplevcs middleware to be
165 able to disable this for certain operations.
165 able to disable this for certain operations.
166
166
167 """
167 """
168 # Tri-state value: False: unlock, None: nothing, True: lock
168 # Tri-state value: False: unlock, None: nothing, True: lock
169 make_lock = None
169 make_lock = None
170 locked_by = [None, None, None]
170 locked_by = [None, None, None]
171 is_anonymous = username == User.DEFAULT_USER
171 is_anonymous = username == User.DEFAULT_USER
172 user = User.get_by_username(username)
172 user = User.get_by_username(username)
173 if not is_anonymous and check_locking:
173 if not is_anonymous and check_locking:
174 log.debug('Checking locking on repository "%s"', repo_name)
174 log.debug('Checking locking on repository "%s"', repo_name)
175 repo = Repository.get_by_repo_name(repo_name)
175 repo = Repository.get_by_repo_name(repo_name)
176 make_lock, __, locked_by = repo.get_locking_state(
176 make_lock, __, locked_by = repo.get_locking_state(
177 action, user.user_id)
177 action, user.user_id)
178 user_id = user.user_id
178 user_id = user.user_id
179 settings_model = VcsSettingsModel(repo=repo_name)
179 settings_model = VcsSettingsModel(repo=repo_name)
180 ui_settings = settings_model.get_ui_settings()
180 ui_settings = settings_model.get_ui_settings()
181
181
182 extras = {
182 extras = {
183 'ip': get_ip_addr(environ),
183 'ip': get_ip_addr(environ),
184 'username': username,
184 'username': username,
185 'user_id': user_id,
185 'user_id': user_id,
186 'action': action,
186 'action': action,
187 'repository': repo_name,
187 'repository': repo_name,
188 'scm': scm,
188 'scm': scm,
189 'config': rhodecode.CONFIG['__file__'],
189 'config': rhodecode.CONFIG['__file__'],
190 'make_lock': make_lock,
190 'make_lock': make_lock,
191 'locked_by': locked_by,
191 'locked_by': locked_by,
192 'server_url': utils2.get_server_url(environ),
192 'server_url': utils2.get_server_url(environ),
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
199
201
200 class BasicAuth(AuthBasicAuthenticator):
202 class BasicAuth(AuthBasicAuthenticator):
201
203
202 def __init__(self, realm, authfunc, registry, auth_http_code=None,
204 def __init__(self, realm, authfunc, registry, auth_http_code=None,
203 initial_call_detection=False, acl_repo_name=None):
205 initial_call_detection=False, acl_repo_name=None):
204 self.realm = realm
206 self.realm = realm
205 self.initial_call = initial_call_detection
207 self.initial_call = initial_call_detection
206 self.authfunc = authfunc
208 self.authfunc = authfunc
207 self.registry = registry
209 self.registry = registry
208 self.acl_repo_name = acl_repo_name
210 self.acl_repo_name = acl_repo_name
209 self._rc_auth_http_code = auth_http_code
211 self._rc_auth_http_code = auth_http_code
210
212
211 def _get_response_from_code(self, http_code):
213 def _get_response_from_code(self, http_code):
212 try:
214 try:
213 return get_exception(safe_int(http_code))
215 return get_exception(safe_int(http_code))
214 except Exception:
216 except Exception:
215 log.exception('Failed to fetch response for code %s' % http_code)
217 log.exception('Failed to fetch response for code %s' % http_code)
216 return HTTPForbidden
218 return HTTPForbidden
217
219
218 def get_rc_realm(self):
220 def get_rc_realm(self):
219 return safe_str(self.registry.rhodecode_settings.get('rhodecode_realm'))
221 return safe_str(self.registry.rhodecode_settings.get('rhodecode_realm'))
220
222
221 def build_authentication(self):
223 def build_authentication(self):
222 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
224 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
223 if self._rc_auth_http_code and not self.initial_call:
225 if self._rc_auth_http_code and not self.initial_call:
224 # return alternative HTTP code if alternative http return code
226 # return alternative HTTP code if alternative http return code
225 # is specified in RhodeCode config, but ONLY if it's not the
227 # is specified in RhodeCode config, but ONLY if it's not the
226 # FIRST call
228 # FIRST call
227 custom_response_klass = self._get_response_from_code(
229 custom_response_klass = self._get_response_from_code(
228 self._rc_auth_http_code)
230 self._rc_auth_http_code)
229 return custom_response_klass(headers=head)
231 return custom_response_klass(headers=head)
230 return HTTPUnauthorized(headers=head)
232 return HTTPUnauthorized(headers=head)
231
233
232 def authenticate(self, environ):
234 def authenticate(self, environ):
233 authorization = AUTHORIZATION(environ)
235 authorization = AUTHORIZATION(environ)
234 if not authorization:
236 if not authorization:
235 return self.build_authentication()
237 return self.build_authentication()
236 (authmeth, auth) = authorization.split(' ', 1)
238 (authmeth, auth) = authorization.split(' ', 1)
237 if 'basic' != authmeth.lower():
239 if 'basic' != authmeth.lower():
238 return self.build_authentication()
240 return self.build_authentication()
239 auth = auth.strip().decode('base64')
241 auth = auth.strip().decode('base64')
240 _parts = auth.split(':', 1)
242 _parts = auth.split(':', 1)
241 if len(_parts) == 2:
243 if len(_parts) == 2:
242 username, password = _parts
244 username, password = _parts
243 auth_data = self.authfunc(
245 auth_data = self.authfunc(
244 username, password, environ, VCS_TYPE,
246 username, password, environ, VCS_TYPE,
245 registry=self.registry, acl_repo_name=self.acl_repo_name)
247 registry=self.registry, acl_repo_name=self.acl_repo_name)
246 if auth_data:
248 if auth_data:
247 return {'username': username, 'auth_data': auth_data}
249 return {'username': username, 'auth_data': auth_data}
248 if username and password:
250 if username and password:
249 # we mark that we actually executed authentication once, at
251 # we mark that we actually executed authentication once, at
250 # that point we can use the alternative auth code
252 # that point we can use the alternative auth code
251 self.initial_call = False
253 self.initial_call = False
252
254
253 return self.build_authentication()
255 return self.build_authentication()
254
256
255 __call__ = authenticate
257 __call__ = authenticate
256
258
257
259
258 def calculate_version_hash(config):
260 def calculate_version_hash(config):
259 return sha1(
261 return sha1(
260 config.get('beaker.session.secret', '') +
262 config.get('beaker.session.secret', '') +
261 rhodecode.__version__)[:8]
263 rhodecode.__version__)[:8]
262
264
263
265
264 def get_current_lang(request):
266 def get_current_lang(request):
265 # NOTE(marcink): remove after pyramid move
267 # NOTE(marcink): remove after pyramid move
266 try:
268 try:
267 return translation.get_lang()[0]
269 return translation.get_lang()[0]
268 except:
270 except:
269 pass
271 pass
270
272
271 return getattr(request, '_LOCALE_', request.locale_name)
273 return getattr(request, '_LOCALE_', request.locale_name)
272
274
273
275
274 def attach_context_attributes(context, request, user_id):
276 def attach_context_attributes(context, request, user_id):
275 """
277 """
276 Attach variables into template context called `c`.
278 Attach variables into template context called `c`.
277 """
279 """
278 config = request.registry.settings
280 config = request.registry.settings
279
281
280
282
281 rc_config = SettingsModel().get_all_settings(cache=True)
283 rc_config = SettingsModel().get_all_settings(cache=True)
282
284
283 context.rhodecode_version = rhodecode.__version__
285 context.rhodecode_version = rhodecode.__version__
284 context.rhodecode_edition = config.get('rhodecode.edition')
286 context.rhodecode_edition = config.get('rhodecode.edition')
285 # unique secret + version does not leak the version but keep consistency
287 # unique secret + version does not leak the version but keep consistency
286 context.rhodecode_version_hash = calculate_version_hash(config)
288 context.rhodecode_version_hash = calculate_version_hash(config)
287
289
288 # Default language set for the incoming request
290 # Default language set for the incoming request
289 context.language = get_current_lang(request)
291 context.language = get_current_lang(request)
290
292
291 # Visual options
293 # Visual options
292 context.visual = AttributeDict({})
294 context.visual = AttributeDict({})
293
295
294 # DB stored Visual Items
296 # DB stored Visual Items
295 context.visual.show_public_icon = str2bool(
297 context.visual.show_public_icon = str2bool(
296 rc_config.get('rhodecode_show_public_icon'))
298 rc_config.get('rhodecode_show_public_icon'))
297 context.visual.show_private_icon = str2bool(
299 context.visual.show_private_icon = str2bool(
298 rc_config.get('rhodecode_show_private_icon'))
300 rc_config.get('rhodecode_show_private_icon'))
299 context.visual.stylify_metatags = str2bool(
301 context.visual.stylify_metatags = str2bool(
300 rc_config.get('rhodecode_stylify_metatags'))
302 rc_config.get('rhodecode_stylify_metatags'))
301 context.visual.dashboard_items = safe_int(
303 context.visual.dashboard_items = safe_int(
302 rc_config.get('rhodecode_dashboard_items', 100))
304 rc_config.get('rhodecode_dashboard_items', 100))
303 context.visual.admin_grid_items = safe_int(
305 context.visual.admin_grid_items = safe_int(
304 rc_config.get('rhodecode_admin_grid_items', 100))
306 rc_config.get('rhodecode_admin_grid_items', 100))
305 context.visual.repository_fields = str2bool(
307 context.visual.repository_fields = str2bool(
306 rc_config.get('rhodecode_repository_fields'))
308 rc_config.get('rhodecode_repository_fields'))
307 context.visual.show_version = str2bool(
309 context.visual.show_version = str2bool(
308 rc_config.get('rhodecode_show_version'))
310 rc_config.get('rhodecode_show_version'))
309 context.visual.use_gravatar = str2bool(
311 context.visual.use_gravatar = str2bool(
310 rc_config.get('rhodecode_use_gravatar'))
312 rc_config.get('rhodecode_use_gravatar'))
311 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
313 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
312 context.visual.default_renderer = rc_config.get(
314 context.visual.default_renderer = rc_config.get(
313 'rhodecode_markup_renderer', 'rst')
315 'rhodecode_markup_renderer', 'rst')
314 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
316 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
315 context.visual.rhodecode_support_url = \
317 context.visual.rhodecode_support_url = \
316 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
318 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
317
319
318 context.visual.affected_files_cut_off = 60
320 context.visual.affected_files_cut_off = 60
319
321
320 context.pre_code = rc_config.get('rhodecode_pre_code')
322 context.pre_code = rc_config.get('rhodecode_pre_code')
321 context.post_code = rc_config.get('rhodecode_post_code')
323 context.post_code = rc_config.get('rhodecode_post_code')
322 context.rhodecode_name = rc_config.get('rhodecode_title')
324 context.rhodecode_name = rc_config.get('rhodecode_title')
323 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
325 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
324 # if we have specified default_encoding in the request, it has more
326 # if we have specified default_encoding in the request, it has more
325 # priority
327 # priority
326 if request.GET.get('default_encoding'):
328 if request.GET.get('default_encoding'):
327 context.default_encodings.insert(0, request.GET.get('default_encoding'))
329 context.default_encodings.insert(0, request.GET.get('default_encoding'))
328 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
330 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
329 context.clone_uri_ssh_tmpl = rc_config.get('rhodecode_clone_uri_ssh_tmpl')
331 context.clone_uri_ssh_tmpl = rc_config.get('rhodecode_clone_uri_ssh_tmpl')
330
332
331 # INI stored
333 # INI stored
332 context.labs_active = str2bool(
334 context.labs_active = str2bool(
333 config.get('labs_settings_active', 'false'))
335 config.get('labs_settings_active', 'false'))
334 context.ssh_enabled = str2bool(
336 context.ssh_enabled = str2bool(
335 config.get('ssh.generate_authorized_keyfile', 'false'))
337 config.get('ssh.generate_authorized_keyfile', 'false'))
336
338
337 context.visual.allow_repo_location_change = str2bool(
339 context.visual.allow_repo_location_change = str2bool(
338 config.get('allow_repo_location_change', True))
340 config.get('allow_repo_location_change', True))
339 context.visual.allow_custom_hooks_settings = str2bool(
341 context.visual.allow_custom_hooks_settings = str2bool(
340 config.get('allow_custom_hooks_settings', True))
342 config.get('allow_custom_hooks_settings', True))
341 context.debug_style = str2bool(config.get('debug_style', False))
343 context.debug_style = str2bool(config.get('debug_style', False))
342
344
343 context.rhodecode_instanceid = config.get('instance_id')
345 context.rhodecode_instanceid = config.get('instance_id')
344
346
345 context.visual.cut_off_limit_diff = safe_int(
347 context.visual.cut_off_limit_diff = safe_int(
346 config.get('cut_off_limit_diff'))
348 config.get('cut_off_limit_diff'))
347 context.visual.cut_off_limit_file = safe_int(
349 context.visual.cut_off_limit_file = safe_int(
348 config.get('cut_off_limit_file'))
350 config.get('cut_off_limit_file'))
349
351
350 # AppEnlight
352 # AppEnlight
351 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
353 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
352 context.appenlight_api_public_key = config.get(
354 context.appenlight_api_public_key = config.get(
353 'appenlight.api_public_key', '')
355 'appenlight.api_public_key', '')
354 context.appenlight_server_url = config.get('appenlight.server_url', '')
356 context.appenlight_server_url = config.get('appenlight.server_url', '')
355
357
356 # JS template context
358 # JS template context
357 context.template_context = {
359 context.template_context = {
358 'repo_name': None,
360 'repo_name': None,
359 'repo_type': None,
361 'repo_type': None,
360 'repo_landing_commit': None,
362 'repo_landing_commit': None,
361 'rhodecode_user': {
363 'rhodecode_user': {
362 'username': None,
364 'username': None,
363 'email': None,
365 'email': None,
364 'notification_status': False
366 'notification_status': False
365 },
367 },
366 'visual': {
368 'visual': {
367 'default_renderer': None
369 'default_renderer': None
368 },
370 },
369 'commit_data': {
371 'commit_data': {
370 'commit_id': None
372 'commit_id': None
371 },
373 },
372 'pull_request_data': {'pull_request_id': None},
374 'pull_request_data': {'pull_request_id': None},
373 'timeago': {
375 'timeago': {
374 'refresh_time': 120 * 1000,
376 'refresh_time': 120 * 1000,
375 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
377 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
376 },
378 },
377 'pyramid_dispatch': {
379 'pyramid_dispatch': {
378
380
379 },
381 },
380 'extra': {'plugins': {}}
382 'extra': {'plugins': {}}
381 }
383 }
382 # END CONFIG VARS
384 # END CONFIG VARS
383
385
384 diffmode = 'sideside'
386 diffmode = 'sideside'
385 if request.GET.get('diffmode'):
387 if request.GET.get('diffmode'):
386 if request.GET['diffmode'] == 'unified':
388 if request.GET['diffmode'] == 'unified':
387 diffmode = 'unified'
389 diffmode = 'unified'
388 elif request.session.get('diffmode'):
390 elif request.session.get('diffmode'):
389 diffmode = request.session['diffmode']
391 diffmode = request.session['diffmode']
390
392
391 context.diffmode = diffmode
393 context.diffmode = diffmode
392
394
393 if request.session.get('diffmode') != diffmode:
395 if request.session.get('diffmode') != diffmode:
394 request.session['diffmode'] = diffmode
396 request.session['diffmode'] = diffmode
395
397
396 context.csrf_token = auth.get_csrf_token(session=request.session)
398 context.csrf_token = auth.get_csrf_token(session=request.session)
397 context.backends = rhodecode.BACKENDS.keys()
399 context.backends = rhodecode.BACKENDS.keys()
398 context.backends.sort()
400 context.backends.sort()
399 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id)
401 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id)
400
402
401 # web case
403 # web case
402 if hasattr(request, 'user'):
404 if hasattr(request, 'user'):
403 context.auth_user = request.user
405 context.auth_user = request.user
404 context.rhodecode_user = request.user
406 context.rhodecode_user = request.user
405
407
406 # api case
408 # api case
407 if hasattr(request, 'rpc_user'):
409 if hasattr(request, 'rpc_user'):
408 context.auth_user = request.rpc_user
410 context.auth_user = request.rpc_user
409 context.rhodecode_user = request.rpc_user
411 context.rhodecode_user = request.rpc_user
410
412
411 # attach the whole call context to the request
413 # attach the whole call context to the request
412 request.call_context = context
414 request.call_context = context
413
415
414
416
415 def get_auth_user(request):
417 def get_auth_user(request):
416 environ = request.environ
418 environ = request.environ
417 session = request.session
419 session = request.session
418
420
419 ip_addr = get_ip_addr(environ)
421 ip_addr = get_ip_addr(environ)
420 # make sure that we update permissions each time we call controller
422 # make sure that we update permissions each time we call controller
421 _auth_token = (request.GET.get('auth_token', '') or
423 _auth_token = (request.GET.get('auth_token', '') or
422 request.GET.get('api_key', ''))
424 request.GET.get('api_key', ''))
423
425
424 if _auth_token:
426 if _auth_token:
425 # when using API_KEY we assume user exists, and
427 # when using API_KEY we assume user exists, and
426 # doesn't need auth based on cookies.
428 # doesn't need auth based on cookies.
427 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
429 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
428 authenticated = False
430 authenticated = False
429 else:
431 else:
430 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
432 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
431 try:
433 try:
432 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
434 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
433 ip_addr=ip_addr)
435 ip_addr=ip_addr)
434 except UserCreationError as e:
436 except UserCreationError as e:
435 h.flash(e, 'error')
437 h.flash(e, 'error')
436 # container auth or other auth functions that create users
438 # container auth or other auth functions that create users
437 # on the fly can throw this exception signaling that there's
439 # on the fly can throw this exception signaling that there's
438 # issue with user creation, explanation should be provided
440 # issue with user creation, explanation should be provided
439 # in Exception itself. We then create a simple blank
441 # in Exception itself. We then create a simple blank
440 # AuthUser
442 # AuthUser
441 auth_user = AuthUser(ip_addr=ip_addr)
443 auth_user = AuthUser(ip_addr=ip_addr)
442
444
443 # in case someone changes a password for user it triggers session
445 # in case someone changes a password for user it triggers session
444 # flush and forces a re-login
446 # flush and forces a re-login
445 if password_changed(auth_user, session):
447 if password_changed(auth_user, session):
446 session.invalidate()
448 session.invalidate()
447 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
449 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
448 auth_user = AuthUser(ip_addr=ip_addr)
450 auth_user = AuthUser(ip_addr=ip_addr)
449
451
450 authenticated = cookie_store.get('is_authenticated')
452 authenticated = cookie_store.get('is_authenticated')
451
453
452 if not auth_user.is_authenticated and auth_user.is_user_object:
454 if not auth_user.is_authenticated and auth_user.is_user_object:
453 # user is not authenticated and not empty
455 # user is not authenticated and not empty
454 auth_user.set_authenticated(authenticated)
456 auth_user.set_authenticated(authenticated)
455
457
456 return auth_user
458 return auth_user
457
459
458
460
459 def h_filter(s):
461 def h_filter(s):
460 """
462 """
461 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
463 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
462 we wrap this with additional functionality that converts None to empty
464 we wrap this with additional functionality that converts None to empty
463 strings
465 strings
464 """
466 """
465 if s is None:
467 if s is None:
466 return markupsafe.Markup()
468 return markupsafe.Markup()
467 return markupsafe.escape(s)
469 return markupsafe.escape(s)
468
470
469
471
470 def add_events_routes(config):
472 def add_events_routes(config):
471 """
473 """
472 Adds routing that can be used in events. Because some events are triggered
474 Adds routing that can be used in events. Because some events are triggered
473 outside of pyramid context, we need to bootstrap request with some
475 outside of pyramid context, we need to bootstrap request with some
474 routing registered
476 routing registered
475 """
477 """
476
478
477 from rhodecode.apps._base import ADMIN_PREFIX
479 from rhodecode.apps._base import ADMIN_PREFIX
478
480
479 config.add_route(name='home', pattern='/')
481 config.add_route(name='home', pattern='/')
480
482
481 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
483 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
482 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
484 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
483 config.add_route(name='repo_summary', pattern='/{repo_name}')
485 config.add_route(name='repo_summary', pattern='/{repo_name}')
484 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
486 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
485 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
487 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
486
488
487 config.add_route(name='pullrequest_show',
489 config.add_route(name='pullrequest_show',
488 pattern='/{repo_name}/pull-request/{pull_request_id}')
490 pattern='/{repo_name}/pull-request/{pull_request_id}')
489 config.add_route(name='pull_requests_global',
491 config.add_route(name='pull_requests_global',
490 pattern='/pull-request/{pull_request_id}')
492 pattern='/pull-request/{pull_request_id}')
491 config.add_route(name='repo_commit',
493 config.add_route(name='repo_commit',
492 pattern='/{repo_name}/changeset/{commit_id}')
494 pattern='/{repo_name}/changeset/{commit_id}')
493
495
494 config.add_route(name='repo_files',
496 config.add_route(name='repo_files',
495 pattern='/{repo_name}/files/{commit_id}/{f_path}')
497 pattern='/{repo_name}/files/{commit_id}/{f_path}')
496
498
497
499
498 def bootstrap_config(request):
500 def bootstrap_config(request):
499 import pyramid.testing
501 import pyramid.testing
500 registry = pyramid.testing.Registry('RcTestRegistry')
502 registry = pyramid.testing.Registry('RcTestRegistry')
501
503
502 config = pyramid.testing.setUp(registry=registry, request=request)
504 config = pyramid.testing.setUp(registry=registry, request=request)
503
505
504 # allow pyramid lookup in testing
506 # allow pyramid lookup in testing
505 config.include('pyramid_mako')
507 config.include('pyramid_mako')
506 config.include('pyramid_beaker')
508 config.include('pyramid_beaker')
507 config.include('rhodecode.lib.rc_cache')
509 config.include('rhodecode.lib.rc_cache')
508
510
509 add_events_routes(config)
511 add_events_routes(config)
510
512
511 return config
513 return config
512
514
513
515
514 def bootstrap_request(**kwargs):
516 def bootstrap_request(**kwargs):
515 import pyramid.testing
517 import pyramid.testing
516
518
517 class TestRequest(pyramid.testing.DummyRequest):
519 class TestRequest(pyramid.testing.DummyRequest):
518 application_url = kwargs.pop('application_url', 'http://example.com')
520 application_url = kwargs.pop('application_url', 'http://example.com')
519 host = kwargs.pop('host', 'example.com:80')
521 host = kwargs.pop('host', 'example.com:80')
520 domain = kwargs.pop('domain', 'example.com')
522 domain = kwargs.pop('domain', 'example.com')
521
523
522 def translate(self, msg):
524 def translate(self, msg):
523 return msg
525 return msg
524
526
525 def plularize(self, singular, plural, n):
527 def plularize(self, singular, plural, n):
526 return singular
528 return singular
527
529
528 def get_partial_renderer(self, tmpl_name):
530 def get_partial_renderer(self, tmpl_name):
529
531
530 from rhodecode.lib.partial_renderer import get_partial_renderer
532 from rhodecode.lib.partial_renderer import get_partial_renderer
531 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
533 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
532
534
533 _call_context = {}
535 _call_context = {}
534 @property
536 @property
535 def call_context(self):
537 def call_context(self):
536 return self._call_context
538 return self._call_context
537
539
538 class TestDummySession(pyramid.testing.DummySession):
540 class TestDummySession(pyramid.testing.DummySession):
539 def save(*arg, **kw):
541 def save(*arg, **kw):
540 pass
542 pass
541
543
542 request = TestRequest(**kwargs)
544 request = TestRequest(**kwargs)
543 request.session = TestDummySession()
545 request.session = TestDummySession()
544
546
545 return request
547 return request
546
548
@@ -1,140 +1,155 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Set of custom exceptions used in RhodeCode
22 Set of custom exceptions used in RhodeCode
23 """
23 """
24
24
25 from webob.exc import HTTPClientError
25 from webob.exc import HTTPClientError
26 from pyramid.httpexceptions import HTTPBadGateway
26 from pyramid.httpexceptions import HTTPBadGateway
27
27
28
28
29 class LdapUsernameError(Exception):
29 class LdapUsernameError(Exception):
30 pass
30 pass
31
31
32
32
33 class LdapPasswordError(Exception):
33 class LdapPasswordError(Exception):
34 pass
34 pass
35
35
36
36
37 class LdapConnectionError(Exception):
37 class LdapConnectionError(Exception):
38 pass
38 pass
39
39
40
40
41 class LdapImportError(Exception):
41 class LdapImportError(Exception):
42 pass
42 pass
43
43
44
44
45 class DefaultUserException(Exception):
45 class DefaultUserException(Exception):
46 pass
46 pass
47
47
48
48
49 class UserOwnsReposException(Exception):
49 class UserOwnsReposException(Exception):
50 pass
50 pass
51
51
52
52
53 class UserOwnsRepoGroupsException(Exception):
53 class UserOwnsRepoGroupsException(Exception):
54 pass
54 pass
55
55
56
56
57 class UserOwnsUserGroupsException(Exception):
57 class UserOwnsUserGroupsException(Exception):
58 pass
58 pass
59
59
60
60
61 class UserGroupAssignedException(Exception):
61 class UserGroupAssignedException(Exception):
62 pass
62 pass
63
63
64
64
65 class StatusChangeOnClosedPullRequestError(Exception):
65 class StatusChangeOnClosedPullRequestError(Exception):
66 pass
66 pass
67
67
68
68
69 class AttachedForksError(Exception):
69 class AttachedForksError(Exception):
70 pass
70 pass
71
71
72
72
73 class RepoGroupAssignmentError(Exception):
73 class RepoGroupAssignmentError(Exception):
74 pass
74 pass
75
75
76
76
77 class NonRelativePathError(Exception):
77 class NonRelativePathError(Exception):
78 pass
78 pass
79
79
80
80
81 class HTTPRequirementError(HTTPClientError):
81 class HTTPRequirementError(HTTPClientError):
82 title = explanation = 'Repository Requirement Missing'
82 title = explanation = 'Repository Requirement Missing'
83 reason = None
83 reason = None
84
84
85 def __init__(self, message, *args, **kwargs):
85 def __init__(self, message, *args, **kwargs):
86 self.title = self.explanation = message
86 self.title = self.explanation = message
87 super(HTTPRequirementError, self).__init__(*args, **kwargs)
87 super(HTTPRequirementError, self).__init__(*args, **kwargs)
88 self.args = (message, )
88 self.args = (message, )
89
89
90
90
91 class HTTPLockedRC(HTTPClientError):
91 class HTTPLockedRC(HTTPClientError):
92 """
92 """
93 Special Exception For locked Repos in RhodeCode, the return code can
93 Special Exception For locked Repos in RhodeCode, the return code can
94 be overwritten by _code keyword argument passed into constructors
94 be overwritten by _code keyword argument passed into constructors
95 """
95 """
96 code = 423
96 code = 423
97 title = explanation = 'Repository Locked'
97 title = explanation = 'Repository Locked'
98 reason = None
98 reason = None
99
99
100 def __init__(self, message, *args, **kwargs):
100 def __init__(self, message, *args, **kwargs):
101 from rhodecode import CONFIG
101 from rhodecode import CONFIG
102 from rhodecode.lib.utils2 import safe_int
102 from rhodecode.lib.utils2 import safe_int
103 _code = CONFIG.get('lock_ret_code')
103 _code = CONFIG.get('lock_ret_code')
104 self.code = safe_int(_code, self.code)
104 self.code = safe_int(_code, self.code)
105 self.title = self.explanation = message
105 self.title = self.explanation = message
106 super(HTTPLockedRC, self).__init__(*args, **kwargs)
106 super(HTTPLockedRC, self).__init__(*args, **kwargs)
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
113
128
114 class UserCreationError(Exception):
129 class UserCreationError(Exception):
115 pass
130 pass
116
131
117
132
118 class NotAllowedToCreateUserError(Exception):
133 class NotAllowedToCreateUserError(Exception):
119 pass
134 pass
120
135
121
136
122 class RepositoryCreationError(Exception):
137 class RepositoryCreationError(Exception):
123 pass
138 pass
124
139
125
140
126 class VCSServerUnavailable(HTTPBadGateway):
141 class VCSServerUnavailable(HTTPBadGateway):
127 """ HTTP Exception class for VCS Server errors """
142 """ HTTP Exception class for VCS Server errors """
128 code = 502
143 code = 502
129 title = 'VCS Server Error'
144 title = 'VCS Server Error'
130 causes = [
145 causes = [
131 'VCS Server is not running',
146 'VCS Server is not running',
132 'Incorrect vcs.server=host:port',
147 'Incorrect vcs.server=host:port',
133 'Incorrect vcs.server.protocol',
148 'Incorrect vcs.server.protocol',
134 ]
149 ]
135
150
136 def __init__(self, message=''):
151 def __init__(self, message=''):
137 self.explanation = 'Could not connect to VCS Server'
152 self.explanation = 'Could not connect to VCS Server'
138 if message:
153 if message:
139 self.explanation += ': ' + message
154 self.explanation += ': ' + message
140 super(VCSServerUnavailable, self).__init__()
155 super(VCSServerUnavailable, self).__init__()
@@ -1,425 +1,465 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2013-2018 RhodeCode GmbH
3 # Copyright (C) 2013-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 Set of hooks run by RhodeCode Enterprise
23 Set of hooks run by RhodeCode Enterprise
24 """
24 """
25
25
26 import os
26 import os
27 import collections
27 import collections
28 import logging
28 import logging
29
29
30 import rhodecode
30 import rhodecode
31 from rhodecode import events
31 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 HTTPLockedRC, UserCreationError
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__)
39
40
40
41
41 HookResponse = collections.namedtuple('HookResponse', ('status', 'output'))
42 HookResponse = collections.namedtuple('HookResponse', ('status', 'output'))
42
43
43
44
44 def is_shadow_repo(extras):
45 def is_shadow_repo(extras):
45 """
46 """
46 Returns ``True`` if this is an action executed against a shadow repository.
47 Returns ``True`` if this is an action executed against a shadow repository.
47 """
48 """
48 return extras['is_shadow_repo']
49 return extras['is_shadow_repo']
49
50
50
51
51 def _get_scm_size(alias, root_path):
52 def _get_scm_size(alias, root_path):
52
53
53 if not alias.startswith('.'):
54 if not alias.startswith('.'):
54 alias += '.'
55 alias += '.'
55
56
56 size_scm, size_root = 0, 0
57 size_scm, size_root = 0, 0
57 for path, unused_dirs, files in os.walk(safe_str(root_path)):
58 for path, unused_dirs, files in os.walk(safe_str(root_path)):
58 if path.find(alias) != -1:
59 if path.find(alias) != -1:
59 for f in files:
60 for f in files:
60 try:
61 try:
61 size_scm += os.path.getsize(os.path.join(path, f))
62 size_scm += os.path.getsize(os.path.join(path, f))
62 except OSError:
63 except OSError:
63 pass
64 pass
64 else:
65 else:
65 for f in files:
66 for f in files:
66 try:
67 try:
67 size_root += os.path.getsize(os.path.join(path, f))
68 size_root += os.path.getsize(os.path.join(path, f))
68 except OSError:
69 except OSError:
69 pass
70 pass
70
71
71 size_scm_f = h.format_byte_size_binary(size_scm)
72 size_scm_f = h.format_byte_size_binary(size_scm)
72 size_root_f = h.format_byte_size_binary(size_root)
73 size_root_f = h.format_byte_size_binary(size_root)
73 size_total_f = h.format_byte_size_binary(size_root + size_scm)
74 size_total_f = h.format_byte_size_binary(size_root + size_scm)
74
75
75 return size_scm_f, size_root_f, size_total_f
76 return size_scm_f, size_root_f, size_total_f
76
77
77
78
78 # actual hooks called by Mercurial internally, and GIT by our Python Hooks
79 # actual hooks called by Mercurial internally, and GIT by our Python Hooks
79 def repo_size(extras):
80 def repo_size(extras):
80 """Present size of repository after push."""
81 """Present size of repository after push."""
81 repo = Repository.get_by_repo_name(extras.repository)
82 repo = Repository.get_by_repo_name(extras.repository)
82 vcs_part = safe_str(u'.%s' % repo.repo_type)
83 vcs_part = safe_str(u'.%s' % repo.repo_type)
83 size_vcs, size_root, size_total = _get_scm_size(vcs_part,
84 size_vcs, size_root, size_total = _get_scm_size(vcs_part,
84 repo.repo_full_path)
85 repo.repo_full_path)
85 msg = ('Repository `%s` size summary %s:%s repo:%s total:%s\n'
86 msg = ('Repository `%s` size summary %s:%s repo:%s total:%s\n'
86 % (repo.repo_name, vcs_part, size_vcs, size_root, size_total))
87 % (repo.repo_name, vcs_part, size_vcs, size_root, size_total))
87 return HookResponse(0, msg)
88 return HookResponse(0, msg)
88
89
89
90
90 def pre_push(extras):
91 def pre_push(extras):
91 """
92 """
92 Hook executed before pushing code.
93 Hook executed before pushing code.
93
94
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
103 # on that proper return code is server to client
104 # on that proper return code is server to client
104 _http_ret = HTTPLockedRC(
105 _http_ret = HTTPLockedRC(
105 _locked_by_explanation(extras.repository, locked_by, reason))
106 _locked_by_explanation(extras.repository, locked_by, reason))
106 if str(_http_ret.code).startswith('2'):
107 if str(_http_ret.code).startswith('2'):
107 # 2xx Codes don't raise exceptions
108 # 2xx Codes don't raise exceptions
108 output = _http_ret.title
109 output = _http_ret.title
109 else:
110 else:
110 raise _http_ret
111 raise _http_ret
111
112
112 # Propagate to external components. This is done after checking the
113 # lock, for consistent behavior.
114 if not is_shadow_repo(extras):
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
153 # Propagate to external components. This is done after checking the
154 # lock, for consistent behavior.
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))
118
158
119 return HookResponse(0, output)
159 return HookResponse(0, output)
120
160
121
161
122 def pre_pull(extras):
162 def pre_pull(extras):
123 """
163 """
124 Hook executed before pulling the code.
164 Hook executed before pulling the code.
125
165
126 It bans pulling when the repository is locked.
166 It bans pulling when the repository is locked.
127 """
167 """
128
168
129 output = ''
169 output = ''
130 if extras.locked_by[0]:
170 if extras.locked_by[0]:
131 locked_by = User.get(extras.locked_by[0]).username
171 locked_by = User.get(extras.locked_by[0]).username
132 reason = extras.locked_by[2]
172 reason = extras.locked_by[2]
133 # this exception is interpreted in git/hg middlewares and based
173 # this exception is interpreted in git/hg middlewares and based
134 # on that proper return code is server to client
174 # on that proper return code is server to client
135 _http_ret = HTTPLockedRC(
175 _http_ret = HTTPLockedRC(
136 _locked_by_explanation(extras.repository, locked_by, reason))
176 _locked_by_explanation(extras.repository, locked_by, reason))
137 if str(_http_ret.code).startswith('2'):
177 if str(_http_ret.code).startswith('2'):
138 # 2xx Codes don't raise exceptions
178 # 2xx Codes don't raise exceptions
139 output = _http_ret.title
179 output = _http_ret.title
140 else:
180 else:
141 raise _http_ret
181 raise _http_ret
142
182
143 # Propagate to external components. This is done after checking the
183 # Propagate to external components. This is done after checking the
144 # lock, for consistent behavior.
184 # lock, for consistent behavior.
145 if not is_shadow_repo(extras):
185 if not is_shadow_repo(extras):
146 pre_pull_extension(**extras)
186 pre_pull_extension(**extras)
147 events.trigger(events.RepoPrePullEvent(
187 events.trigger(events.RepoPrePullEvent(
148 repo_name=extras.repository, extras=extras))
188 repo_name=extras.repository, extras=extras))
149
189
150 return HookResponse(0, output)
190 return HookResponse(0, output)
151
191
152
192
153 def post_pull(extras):
193 def post_pull(extras):
154 """Hook executed after client pulls the code."""
194 """Hook executed after client pulls the code."""
155
195
156 audit_user = audit_logger.UserWrap(
196 audit_user = audit_logger.UserWrap(
157 username=extras.username,
197 username=extras.username,
158 ip_addr=extras.ip)
198 ip_addr=extras.ip)
159 repo = audit_logger.RepoWrap(repo_name=extras.repository)
199 repo = audit_logger.RepoWrap(repo_name=extras.repository)
160 audit_logger.store(
200 audit_logger.store(
161 'user.pull', action_data={
201 'user.pull', action_data={
162 'user_agent': extras.user_agent},
202 'user_agent': extras.user_agent},
163 user=audit_user, repo=repo, commit=True)
203 user=audit_user, repo=repo, commit=True)
164
204
165 # Propagate to external components.
205 # Propagate to external components.
166 if not is_shadow_repo(extras):
206 if not is_shadow_repo(extras):
167 post_pull_extension(**extras)
207 post_pull_extension(**extras)
168 events.trigger(events.RepoPullEvent(
208 events.trigger(events.RepoPullEvent(
169 repo_name=extras.repository, extras=extras))
209 repo_name=extras.repository, extras=extras))
170
210
171 output = ''
211 output = ''
172 # make lock is a tri state False, True, None. We only make lock on True
212 # make lock is a tri state False, True, None. We only make lock on True
173 if extras.make_lock is True and not is_shadow_repo(extras):
213 if extras.make_lock is True and not is_shadow_repo(extras):
174 user = User.get_by_username(extras.username)
214 user = User.get_by_username(extras.username)
175 Repository.lock(Repository.get_by_repo_name(extras.repository),
215 Repository.lock(Repository.get_by_repo_name(extras.repository),
176 user.user_id,
216 user.user_id,
177 lock_reason=Repository.LOCK_PULL)
217 lock_reason=Repository.LOCK_PULL)
178 msg = 'Made lock on repo `%s`' % (extras.repository,)
218 msg = 'Made lock on repo `%s`' % (extras.repository,)
179 output += msg
219 output += msg
180
220
181 if extras.locked_by[0]:
221 if extras.locked_by[0]:
182 locked_by = User.get(extras.locked_by[0]).username
222 locked_by = User.get(extras.locked_by[0]).username
183 reason = extras.locked_by[2]
223 reason = extras.locked_by[2]
184 _http_ret = HTTPLockedRC(
224 _http_ret = HTTPLockedRC(
185 _locked_by_explanation(extras.repository, locked_by, reason))
225 _locked_by_explanation(extras.repository, locked_by, reason))
186 if str(_http_ret.code).startswith('2'):
226 if str(_http_ret.code).startswith('2'):
187 # 2xx Codes don't raise exceptions
227 # 2xx Codes don't raise exceptions
188 output += _http_ret.title
228 output += _http_ret.title
189
229
190 return HookResponse(0, output)
230 return HookResponse(0, output)
191
231
192
232
193 def post_push(extras):
233 def post_push(extras):
194 """Hook executed after user pushes to the repository."""
234 """Hook executed after user pushes to the repository."""
195 commit_ids = extras.commit_ids
235 commit_ids = extras.commit_ids
196
236
197 # log the push call
237 # log the push call
198 audit_user = audit_logger.UserWrap(
238 audit_user = audit_logger.UserWrap(
199 username=extras.username, ip_addr=extras.ip)
239 username=extras.username, ip_addr=extras.ip)
200 repo = audit_logger.RepoWrap(repo_name=extras.repository)
240 repo = audit_logger.RepoWrap(repo_name=extras.repository)
201 audit_logger.store(
241 audit_logger.store(
202 'user.push', action_data={
242 'user.push', action_data={
203 'user_agent': extras.user_agent,
243 'user_agent': extras.user_agent,
204 'commit_ids': commit_ids[:400]},
244 'commit_ids': commit_ids[:400]},
205 user=audit_user, repo=repo, commit=True)
245 user=audit_user, repo=repo, commit=True)
206
246
207 # Propagate to external components.
247 # Propagate to external components.
208 if not is_shadow_repo(extras):
248 if not is_shadow_repo(extras):
209 post_push_extension(
249 post_push_extension(
210 repo_store_path=Repository.base_path(),
250 repo_store_path=Repository.base_path(),
211 pushed_revs=commit_ids,
251 pushed_revs=commit_ids,
212 **extras)
252 **extras)
213 events.trigger(events.RepoPushEvent(
253 events.trigger(events.RepoPushEvent(
214 repo_name=extras.repository,
254 repo_name=extras.repository,
215 pushed_commit_ids=commit_ids,
255 pushed_commit_ids=commit_ids,
216 extras=extras))
256 extras=extras))
217
257
218 output = ''
258 output = ''
219 # make lock is a tri state False, True, None. We only release lock on False
259 # make lock is a tri state False, True, None. We only release lock on False
220 if extras.make_lock is False and not is_shadow_repo(extras):
260 if extras.make_lock is False and not is_shadow_repo(extras):
221 Repository.unlock(Repository.get_by_repo_name(extras.repository))
261 Repository.unlock(Repository.get_by_repo_name(extras.repository))
222 msg = 'Released lock on repo `%s`\n' % extras.repository
262 msg = 'Released lock on repo `%s`\n' % extras.repository
223 output += msg
263 output += msg
224
264
225 if extras.locked_by[0]:
265 if extras.locked_by[0]:
226 locked_by = User.get(extras.locked_by[0]).username
266 locked_by = User.get(extras.locked_by[0]).username
227 reason = extras.locked_by[2]
267 reason = extras.locked_by[2]
228 _http_ret = HTTPLockedRC(
268 _http_ret = HTTPLockedRC(
229 _locked_by_explanation(extras.repository, locked_by, reason))
269 _locked_by_explanation(extras.repository, locked_by, reason))
230 # TODO: johbo: if not?
270 # TODO: johbo: if not?
231 if str(_http_ret.code).startswith('2'):
271 if str(_http_ret.code).startswith('2'):
232 # 2xx Codes don't raise exceptions
272 # 2xx Codes don't raise exceptions
233 output += _http_ret.title
273 output += _http_ret.title
234
274
235 if extras.new_refs:
275 if extras.new_refs:
236 tmpl = \
276 tmpl = \
237 extras.server_url + '/' + \
277 extras.server_url + '/' + \
238 extras.repository + \
278 extras.repository + \
239 "/pull-request/new?{ref_type}={ref_name}"
279 "/pull-request/new?{ref_type}={ref_name}"
240 for branch_name in extras.new_refs['branches']:
280 for branch_name in extras.new_refs['branches']:
241 output += 'RhodeCode: open pull request link: {}\n'.format(
281 output += 'RhodeCode: open pull request link: {}\n'.format(
242 tmpl.format(ref_type='branch', ref_name=branch_name))
282 tmpl.format(ref_type='branch', ref_name=branch_name))
243
283
244 for book_name in extras.new_refs['bookmarks']:
284 for book_name in extras.new_refs['bookmarks']:
245 output += 'RhodeCode: open pull request link: {}\n'.format(
285 output += 'RhodeCode: open pull request link: {}\n'.format(
246 tmpl.format(ref_type='bookmark', ref_name=book_name))
286 tmpl.format(ref_type='bookmark', ref_name=book_name))
247
287
248 output += 'RhodeCode: push completed\n'
288 output += 'RhodeCode: push completed\n'
249 return HookResponse(0, output)
289 return HookResponse(0, output)
250
290
251
291
252 def _locked_by_explanation(repo_name, user_name, reason):
292 def _locked_by_explanation(repo_name, user_name, reason):
253 message = (
293 message = (
254 'Repository `%s` locked by user `%s`. Reason:`%s`'
294 'Repository `%s` locked by user `%s`. Reason:`%s`'
255 % (repo_name, user_name, reason))
295 % (repo_name, user_name, reason))
256 return message
296 return message
257
297
258
298
259 def check_allowed_create_user(user_dict, created_by, **kwargs):
299 def check_allowed_create_user(user_dict, created_by, **kwargs):
260 # pre create hooks
300 # pre create hooks
261 if pre_create_user.is_active():
301 if pre_create_user.is_active():
262 allowed, reason = pre_create_user(created_by=created_by, **user_dict)
302 allowed, reason = pre_create_user(created_by=created_by, **user_dict)
263 if not allowed:
303 if not allowed:
264 raise UserCreationError(reason)
304 raise UserCreationError(reason)
265
305
266
306
267 class ExtensionCallback(object):
307 class ExtensionCallback(object):
268 """
308 """
269 Forwards a given call to rcextensions, sanitizes keyword arguments.
309 Forwards a given call to rcextensions, sanitizes keyword arguments.
270
310
271 Does check if there is an extension active for that hook. If it is
311 Does check if there is an extension active for that hook. If it is
272 there, it will forward all `kwargs_keys` keyword arguments to the
312 there, it will forward all `kwargs_keys` keyword arguments to the
273 extension callback.
313 extension callback.
274 """
314 """
275
315
276 def __init__(self, hook_name, kwargs_keys):
316 def __init__(self, hook_name, kwargs_keys):
277 self._hook_name = hook_name
317 self._hook_name = hook_name
278 self._kwargs_keys = set(kwargs_keys)
318 self._kwargs_keys = set(kwargs_keys)
279
319
280 def __call__(self, *args, **kwargs):
320 def __call__(self, *args, **kwargs):
281 log.debug('Calling extension callback for %s', self._hook_name)
321 log.debug('Calling extension callback for %s', self._hook_name)
282
322
283 kwargs_to_pass = dict((key, kwargs[key]) for key in self._kwargs_keys)
323 kwargs_to_pass = dict((key, kwargs[key]) for key in self._kwargs_keys)
284 # backward compat for removed api_key for old hooks. THis was it works
324 # backward compat for removed api_key for old hooks. THis was it works
285 # with older rcextensions that require api_key present
325 # with older rcextensions that require api_key present
286 if self._hook_name in ['CREATE_USER_HOOK', 'DELETE_USER_HOOK']:
326 if self._hook_name in ['CREATE_USER_HOOK', 'DELETE_USER_HOOK']:
287 kwargs_to_pass['api_key'] = '_DEPRECATED_'
327 kwargs_to_pass['api_key'] = '_DEPRECATED_'
288
328
289 callback = self._get_callback()
329 callback = self._get_callback()
290 if callback:
330 if callback:
291 return callback(**kwargs_to_pass)
331 return callback(**kwargs_to_pass)
292 else:
332 else:
293 log.debug('extensions callback not found skipping...')
333 log.debug('extensions callback not found skipping...')
294
334
295 def is_active(self):
335 def is_active(self):
296 return hasattr(rhodecode.EXTENSIONS, self._hook_name)
336 return hasattr(rhodecode.EXTENSIONS, self._hook_name)
297
337
298 def _get_callback(self):
338 def _get_callback(self):
299 return getattr(rhodecode.EXTENSIONS, self._hook_name, None)
339 return getattr(rhodecode.EXTENSIONS, self._hook_name, None)
300
340
301
341
302 pre_pull_extension = ExtensionCallback(
342 pre_pull_extension = ExtensionCallback(
303 hook_name='PRE_PULL_HOOK',
343 hook_name='PRE_PULL_HOOK',
304 kwargs_keys=(
344 kwargs_keys=(
305 'server_url', 'config', 'scm', 'username', 'ip', 'action',
345 'server_url', 'config', 'scm', 'username', 'ip', 'action',
306 'repository'))
346 'repository'))
307
347
308
348
309 post_pull_extension = ExtensionCallback(
349 post_pull_extension = ExtensionCallback(
310 hook_name='PULL_HOOK',
350 hook_name='PULL_HOOK',
311 kwargs_keys=(
351 kwargs_keys=(
312 'server_url', 'config', 'scm', 'username', 'ip', 'action',
352 'server_url', 'config', 'scm', 'username', 'ip', 'action',
313 'repository'))
353 'repository'))
314
354
315
355
316 pre_push_extension = ExtensionCallback(
356 pre_push_extension = ExtensionCallback(
317 hook_name='PRE_PUSH_HOOK',
357 hook_name='PRE_PUSH_HOOK',
318 kwargs_keys=(
358 kwargs_keys=(
319 'server_url', 'config', 'scm', 'username', 'ip', 'action',
359 'server_url', 'config', 'scm', 'username', 'ip', 'action',
320 'repository', 'repo_store_path', 'commit_ids'))
360 'repository', 'repo_store_path', 'commit_ids'))
321
361
322
362
323 post_push_extension = ExtensionCallback(
363 post_push_extension = ExtensionCallback(
324 hook_name='PUSH_HOOK',
364 hook_name='PUSH_HOOK',
325 kwargs_keys=(
365 kwargs_keys=(
326 'server_url', 'config', 'scm', 'username', 'ip', 'action',
366 'server_url', 'config', 'scm', 'username', 'ip', 'action',
327 'repository', 'repo_store_path', 'pushed_revs'))
367 'repository', 'repo_store_path', 'pushed_revs'))
328
368
329
369
330 pre_create_user = ExtensionCallback(
370 pre_create_user = ExtensionCallback(
331 hook_name='PRE_CREATE_USER_HOOK',
371 hook_name='PRE_CREATE_USER_HOOK',
332 kwargs_keys=(
372 kwargs_keys=(
333 'username', 'password', 'email', 'firstname', 'lastname', 'active',
373 'username', 'password', 'email', 'firstname', 'lastname', 'active',
334 'admin', 'created_by'))
374 'admin', 'created_by'))
335
375
336
376
337 log_create_pull_request = ExtensionCallback(
377 log_create_pull_request = ExtensionCallback(
338 hook_name='CREATE_PULL_REQUEST',
378 hook_name='CREATE_PULL_REQUEST',
339 kwargs_keys=(
379 kwargs_keys=(
340 'server_url', 'config', 'scm', 'username', 'ip', 'action',
380 'server_url', 'config', 'scm', 'username', 'ip', 'action',
341 'repository', 'pull_request_id', 'url', 'title', 'description',
381 'repository', 'pull_request_id', 'url', 'title', 'description',
342 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
382 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
343 'mergeable', 'source', 'target', 'author', 'reviewers'))
383 'mergeable', 'source', 'target', 'author', 'reviewers'))
344
384
345
385
346 log_merge_pull_request = ExtensionCallback(
386 log_merge_pull_request = ExtensionCallback(
347 hook_name='MERGE_PULL_REQUEST',
387 hook_name='MERGE_PULL_REQUEST',
348 kwargs_keys=(
388 kwargs_keys=(
349 'server_url', 'config', 'scm', 'username', 'ip', 'action',
389 'server_url', 'config', 'scm', 'username', 'ip', 'action',
350 'repository', 'pull_request_id', 'url', 'title', 'description',
390 'repository', 'pull_request_id', 'url', 'title', 'description',
351 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
391 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
352 'mergeable', 'source', 'target', 'author', 'reviewers'))
392 'mergeable', 'source', 'target', 'author', 'reviewers'))
353
393
354
394
355 log_close_pull_request = ExtensionCallback(
395 log_close_pull_request = ExtensionCallback(
356 hook_name='CLOSE_PULL_REQUEST',
396 hook_name='CLOSE_PULL_REQUEST',
357 kwargs_keys=(
397 kwargs_keys=(
358 'server_url', 'config', 'scm', 'username', 'ip', 'action',
398 'server_url', 'config', 'scm', 'username', 'ip', 'action',
359 'repository', 'pull_request_id', 'url', 'title', 'description',
399 'repository', 'pull_request_id', 'url', 'title', 'description',
360 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
400 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
361 'mergeable', 'source', 'target', 'author', 'reviewers'))
401 'mergeable', 'source', 'target', 'author', 'reviewers'))
362
402
363
403
364 log_review_pull_request = ExtensionCallback(
404 log_review_pull_request = ExtensionCallback(
365 hook_name='REVIEW_PULL_REQUEST',
405 hook_name='REVIEW_PULL_REQUEST',
366 kwargs_keys=(
406 kwargs_keys=(
367 'server_url', 'config', 'scm', 'username', 'ip', 'action',
407 'server_url', 'config', 'scm', 'username', 'ip', 'action',
368 'repository', 'pull_request_id', 'url', 'title', 'description',
408 'repository', 'pull_request_id', 'url', 'title', 'description',
369 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
409 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
370 'mergeable', 'source', 'target', 'author', 'reviewers'))
410 'mergeable', 'source', 'target', 'author', 'reviewers'))
371
411
372
412
373 log_update_pull_request = ExtensionCallback(
413 log_update_pull_request = ExtensionCallback(
374 hook_name='UPDATE_PULL_REQUEST',
414 hook_name='UPDATE_PULL_REQUEST',
375 kwargs_keys=(
415 kwargs_keys=(
376 'server_url', 'config', 'scm', 'username', 'ip', 'action',
416 'server_url', 'config', 'scm', 'username', 'ip', 'action',
377 'repository', 'pull_request_id', 'url', 'title', 'description',
417 'repository', 'pull_request_id', 'url', 'title', 'description',
378 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
418 'status', 'created_on', 'updated_on', 'commit_ids', 'review_status',
379 'mergeable', 'source', 'target', 'author', 'reviewers'))
419 'mergeable', 'source', 'target', 'author', 'reviewers'))
380
420
381
421
382 log_create_user = ExtensionCallback(
422 log_create_user = ExtensionCallback(
383 hook_name='CREATE_USER_HOOK',
423 hook_name='CREATE_USER_HOOK',
384 kwargs_keys=(
424 kwargs_keys=(
385 'username', 'full_name_or_username', 'full_contact', 'user_id',
425 'username', 'full_name_or_username', 'full_contact', 'user_id',
386 'name', 'firstname', 'short_contact', 'admin', 'lastname',
426 'name', 'firstname', 'short_contact', 'admin', 'lastname',
387 'ip_addresses', 'extern_type', 'extern_name',
427 'ip_addresses', 'extern_type', 'extern_name',
388 'email', 'api_keys', 'last_login',
428 'email', 'api_keys', 'last_login',
389 'full_name', 'active', 'password', 'emails',
429 'full_name', 'active', 'password', 'emails',
390 'inherit_default_permissions', 'created_by', 'created_on'))
430 'inherit_default_permissions', 'created_by', 'created_on'))
391
431
392
432
393 log_delete_user = ExtensionCallback(
433 log_delete_user = ExtensionCallback(
394 hook_name='DELETE_USER_HOOK',
434 hook_name='DELETE_USER_HOOK',
395 kwargs_keys=(
435 kwargs_keys=(
396 'username', 'full_name_or_username', 'full_contact', 'user_id',
436 'username', 'full_name_or_username', 'full_contact', 'user_id',
397 'name', 'firstname', 'short_contact', 'admin', 'lastname',
437 'name', 'firstname', 'short_contact', 'admin', 'lastname',
398 'ip_addresses',
438 'ip_addresses',
399 'email', 'last_login',
439 'email', 'last_login',
400 'full_name', 'active', 'password', 'emails',
440 'full_name', 'active', 'password', 'emails',
401 'inherit_default_permissions', 'deleted_by'))
441 'inherit_default_permissions', 'deleted_by'))
402
442
403
443
404 log_create_repository = ExtensionCallback(
444 log_create_repository = ExtensionCallback(
405 hook_name='CREATE_REPO_HOOK',
445 hook_name='CREATE_REPO_HOOK',
406 kwargs_keys=(
446 kwargs_keys=(
407 'repo_name', 'repo_type', 'description', 'private', 'created_on',
447 'repo_name', 'repo_type', 'description', 'private', 'created_on',
408 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
448 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
409 'clone_uri', 'fork_id', 'group_id', 'created_by'))
449 'clone_uri', 'fork_id', 'group_id', 'created_by'))
410
450
411
451
412 log_delete_repository = ExtensionCallback(
452 log_delete_repository = ExtensionCallback(
413 hook_name='DELETE_REPO_HOOK',
453 hook_name='DELETE_REPO_HOOK',
414 kwargs_keys=(
454 kwargs_keys=(
415 'repo_name', 'repo_type', 'description', 'private', 'created_on',
455 'repo_name', 'repo_type', 'description', 'private', 'created_on',
416 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
456 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics',
417 'clone_uri', 'fork_id', 'group_id', 'deleted_by', 'deleted_on'))
457 'clone_uri', 'fork_id', 'group_id', 'deleted_by', 'deleted_on'))
418
458
419
459
420 log_create_repository_group = ExtensionCallback(
460 log_create_repository_group = ExtensionCallback(
421 hook_name='CREATE_REPO_GROUP_HOOK',
461 hook_name='CREATE_REPO_GROUP_HOOK',
422 kwargs_keys=(
462 kwargs_keys=(
423 'group_name', 'group_parent_id', 'group_description',
463 'group_name', 'group_parent_id', 'group_description',
424 'group_id', 'user_id', 'created_by', 'created_on',
464 'group_id', 'user_id', 'created_by', 'created_on',
425 'enable_locking'))
465 'enable_locking'))
@@ -1,312 +1,324 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 import time
22 import time
23 import logging
23 import logging
24 import tempfile
24 import tempfile
25 import traceback
25 import traceback
26 import threading
26 import threading
27
27
28 from BaseHTTPServer import BaseHTTPRequestHandler
28 from BaseHTTPServer import BaseHTTPRequestHandler
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
35 from rhodecode.lib.utils2 import AttributeDict
36 from rhodecode.lib.utils2 import AttributeDict
36 from rhodecode.lib.ext_json import json
37 from rhodecode.lib.ext_json import json
37 from rhodecode.lib import rc_cache
38 from rhodecode.lib import rc_cache
38
39
39 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
40
41
41
42
42 class HooksHttpHandler(BaseHTTPRequestHandler):
43 class HooksHttpHandler(BaseHTTPRequestHandler):
43
44
44 def do_POST(self):
45 def do_POST(self):
45 method, extras = self._read_request()
46 method, extras = self._read_request()
46 txn_id = getattr(self.server, 'txn_id', None)
47 txn_id = getattr(self.server, 'txn_id', None)
47 if txn_id:
48 if txn_id:
48 log.debug('Computing TXN_ID based on `%s`:`%s`',
49 log.debug('Computing TXN_ID based on `%s`:`%s`',
49 extras['repository'], extras['txn_id'])
50 extras['repository'], extras['txn_id'])
50 computed_txn_id = rc_cache.utils.compute_key_from_params(
51 computed_txn_id = rc_cache.utils.compute_key_from_params(
51 extras['repository'], extras['txn_id'])
52 extras['repository'], extras['txn_id'])
52 if txn_id != computed_txn_id:
53 if txn_id != computed_txn_id:
53 raise Exception(
54 raise Exception(
54 'TXN ID fail: expected {} got {} instead'.format(
55 'TXN ID fail: expected {} got {} instead'.format(
55 txn_id, computed_txn_id))
56 txn_id, computed_txn_id))
56
57
57 try:
58 try:
58 result = self._call_hook(method, extras)
59 result = self._call_hook(method, extras)
59 except Exception as e:
60 except Exception as e:
60 exc_tb = traceback.format_exc()
61 exc_tb = traceback.format_exc()
61 result = {
62 result = {
62 'exception': e.__class__.__name__,
63 'exception': e.__class__.__name__,
63 'exception_traceback': exc_tb,
64 'exception_traceback': exc_tb,
64 'exception_args': e.args
65 'exception_args': e.args
65 }
66 }
66 self._write_response(result)
67 self._write_response(result)
67
68
68 def _read_request(self):
69 def _read_request(self):
69 length = int(self.headers['Content-Length'])
70 length = int(self.headers['Content-Length'])
70 body = self.rfile.read(length).decode('utf-8')
71 body = self.rfile.read(length).decode('utf-8')
71 data = json.loads(body)
72 data = json.loads(body)
72 return data['method'], data['extras']
73 return data['method'], data['extras']
73
74
74 def _write_response(self, result):
75 def _write_response(self, result):
75 self.send_response(200)
76 self.send_response(200)
76 self.send_header("Content-type", "text/json")
77 self.send_header("Content-type", "text/json")
77 self.end_headers()
78 self.end_headers()
78 self.wfile.write(json.dumps(result))
79 self.wfile.write(json.dumps(result))
79
80
80 def _call_hook(self, method, extras):
81 def _call_hook(self, method, extras):
81 hooks = Hooks()
82 hooks = Hooks()
82 try:
83 try:
83 result = getattr(hooks, method)(extras)
84 result = getattr(hooks, method)(extras)
84 finally:
85 finally:
85 meta.Session.remove()
86 meta.Session.remove()
86 return result
87 return result
87
88
88 def log_message(self, format, *args):
89 def log_message(self, format, *args):
89 """
90 """
90 This is an overridden method of BaseHTTPRequestHandler which logs using
91 This is an overridden method of BaseHTTPRequestHandler which logs using
91 logging library instead of writing directly to stderr.
92 logging library instead of writing directly to stderr.
92 """
93 """
93
94
94 message = format % args
95 message = format % args
95
96
96 log.debug(
97 log.debug(
97 "%s - - [%s] %s", self.client_address[0],
98 "%s - - [%s] %s", self.client_address[0],
98 self.log_date_time_string(), message)
99 self.log_date_time_string(), message)
99
100
100
101
101 class DummyHooksCallbackDaemon(object):
102 class DummyHooksCallbackDaemon(object):
102 hooks_uri = ''
103 hooks_uri = ''
103
104
104 def __init__(self):
105 def __init__(self):
105 self.hooks_module = Hooks.__module__
106 self.hooks_module = Hooks.__module__
106
107
107 def __enter__(self):
108 def __enter__(self):
108 log.debug('Running dummy hooks callback daemon')
109 log.debug('Running dummy hooks callback daemon')
109 return self
110 return self
110
111
111 def __exit__(self, exc_type, exc_val, exc_tb):
112 def __exit__(self, exc_type, exc_val, exc_tb):
112 log.debug('Exiting dummy hooks callback daemon')
113 log.debug('Exiting dummy hooks callback daemon')
113
114
114
115
115 class ThreadedHookCallbackDaemon(object):
116 class ThreadedHookCallbackDaemon(object):
116
117
117 _callback_thread = None
118 _callback_thread = None
118 _daemon = None
119 _daemon = None
119 _done = False
120 _done = False
120
121
121 def __init__(self, txn_id=None, host=None, port=None):
122 def __init__(self, txn_id=None, host=None, port=None):
122 self._prepare(txn_id=txn_id, host=None, port=port)
123 self._prepare(txn_id=txn_id, host=None, port=port)
123
124
124 def __enter__(self):
125 def __enter__(self):
125 self._run()
126 self._run()
126 return self
127 return self
127
128
128 def __exit__(self, exc_type, exc_val, exc_tb):
129 def __exit__(self, exc_type, exc_val, exc_tb):
129 log.debug('Callback daemon exiting now...')
130 log.debug('Callback daemon exiting now...')
130 self._stop()
131 self._stop()
131
132
132 def _prepare(self, txn_id=None, host=None, port=None):
133 def _prepare(self, txn_id=None, host=None, port=None):
133 raise NotImplementedError()
134 raise NotImplementedError()
134
135
135 def _run(self):
136 def _run(self):
136 raise NotImplementedError()
137 raise NotImplementedError()
137
138
138 def _stop(self):
139 def _stop(self):
139 raise NotImplementedError()
140 raise NotImplementedError()
140
141
141
142
142 class HttpHooksCallbackDaemon(ThreadedHookCallbackDaemon):
143 class HttpHooksCallbackDaemon(ThreadedHookCallbackDaemon):
143 """
144 """
144 Context manager which will run a callback daemon in a background thread.
145 Context manager which will run a callback daemon in a background thread.
145 """
146 """
146
147
147 hooks_uri = None
148 hooks_uri = None
148
149
149 # From Python docs: Polling reduces our responsiveness to a shutdown
150 # From Python docs: Polling reduces our responsiveness to a shutdown
150 # request and wastes cpu at all other times.
151 # request and wastes cpu at all other times.
151 POLL_INTERVAL = 0.01
152 POLL_INTERVAL = 0.01
152
153
153 def _prepare(self, txn_id=None, host=None, port=None):
154 def _prepare(self, txn_id=None, host=None, port=None):
154 host = host or '127.0.0.1'
155 host = host or '127.0.0.1'
155 self._done = False
156 self._done = False
156 self._daemon = TCPServer((host, port or 0), HooksHttpHandler)
157 self._daemon = TCPServer((host, port or 0), HooksHttpHandler)
157 _, port = self._daemon.server_address
158 _, port = self._daemon.server_address
158 self.hooks_uri = '{}:{}'.format(host, port)
159 self.hooks_uri = '{}:{}'.format(host, port)
159 self.txn_id = txn_id
160 self.txn_id = txn_id
160 # inject transaction_id for later verification
161 # inject transaction_id for later verification
161 self._daemon.txn_id = self.txn_id
162 self._daemon.txn_id = self.txn_id
162
163
163 log.debug(
164 log.debug(
164 "Preparing HTTP callback daemon at `%s` and registering hook object",
165 "Preparing HTTP callback daemon at `%s` and registering hook object",
165 self.hooks_uri)
166 self.hooks_uri)
166
167
167 def _run(self):
168 def _run(self):
168 log.debug("Running event loop of callback daemon in background thread")
169 log.debug("Running event loop of callback daemon in background thread")
169 callback_thread = threading.Thread(
170 callback_thread = threading.Thread(
170 target=self._daemon.serve_forever,
171 target=self._daemon.serve_forever,
171 kwargs={'poll_interval': self.POLL_INTERVAL})
172 kwargs={'poll_interval': self.POLL_INTERVAL})
172 callback_thread.daemon = True
173 callback_thread.daemon = True
173 callback_thread.start()
174 callback_thread.start()
174 self._callback_thread = callback_thread
175 self._callback_thread = callback_thread
175
176
176 def _stop(self):
177 def _stop(self):
177 log.debug("Waiting for background thread to finish.")
178 log.debug("Waiting for background thread to finish.")
178 self._daemon.shutdown()
179 self._daemon.shutdown()
179 self._callback_thread.join()
180 self._callback_thread.join()
180 self._daemon = None
181 self._daemon = None
181 self._callback_thread = None
182 self._callback_thread = None
182 if self.txn_id:
183 if self.txn_id:
183 txn_id_file = get_txn_id_data_path(self.txn_id)
184 txn_id_file = get_txn_id_data_path(self.txn_id)
184 log.debug('Cleaning up TXN ID %s', txn_id_file)
185 log.debug('Cleaning up TXN ID %s', txn_id_file)
185 if os.path.isfile(txn_id_file):
186 if os.path.isfile(txn_id_file):
186 os.remove(txn_id_file)
187 os.remove(txn_id_file)
187
188
188 log.debug("Background thread done.")
189 log.debug("Background thread done.")
189
190
190
191
191 def get_txn_id_data_path(txn_id):
192 def get_txn_id_data_path(txn_id):
192 root = tempfile.gettempdir()
193 root = tempfile.gettempdir()
193 return os.path.join(root, 'rc_txn_id_{}'.format(txn_id))
194 return os.path.join(root, 'rc_txn_id_{}'.format(txn_id))
194
195
195
196
196 def store_txn_id_data(txn_id, data_dict):
197 def store_txn_id_data(txn_id, data_dict):
197 if not txn_id:
198 if not txn_id:
198 log.warning('Cannot store txn_id because it is empty')
199 log.warning('Cannot store txn_id because it is empty')
199 return
200 return
200
201
201 path = get_txn_id_data_path(txn_id)
202 path = get_txn_id_data_path(txn_id)
202 try:
203 try:
203 with open(path, 'wb') as f:
204 with open(path, 'wb') as f:
204 f.write(json.dumps(data_dict))
205 f.write(json.dumps(data_dict))
205 except Exception:
206 except Exception:
206 log.exception('Failed to write txn_id metadata')
207 log.exception('Failed to write txn_id metadata')
207
208
208
209
209 def get_txn_id_from_store(txn_id):
210 def get_txn_id_from_store(txn_id):
210 """
211 """
211 Reads txn_id from store and if present returns the data for callback manager
212 Reads txn_id from store and if present returns the data for callback manager
212 """
213 """
213 path = get_txn_id_data_path(txn_id)
214 path = get_txn_id_data_path(txn_id)
214 try:
215 try:
215 with open(path, 'rb') as f:
216 with open(path, 'rb') as f:
216 return json.loads(f.read())
217 return json.loads(f.read())
217 except Exception:
218 except Exception:
218 return {}
219 return {}
219
220
220
221
221 def prepare_callback_daemon(extras, protocol, host, use_direct_calls, txn_id=None):
222 def prepare_callback_daemon(extras, protocol, host, use_direct_calls, txn_id=None):
222 txn_details = get_txn_id_from_store(txn_id)
223 txn_details = get_txn_id_from_store(txn_id)
223 port = txn_details.get('port', 0)
224 port = txn_details.get('port', 0)
224 if use_direct_calls:
225 if use_direct_calls:
225 callback_daemon = DummyHooksCallbackDaemon()
226 callback_daemon = DummyHooksCallbackDaemon()
226 extras['hooks_module'] = callback_daemon.hooks_module
227 extras['hooks_module'] = callback_daemon.hooks_module
227 else:
228 else:
228 if protocol == 'http':
229 if protocol == 'http':
229 callback_daemon = HttpHooksCallbackDaemon(
230 callback_daemon = HttpHooksCallbackDaemon(
230 txn_id=txn_id, host=host, port=port)
231 txn_id=txn_id, host=host, port=port)
231 else:
232 else:
232 log.error('Unsupported callback daemon protocol "%s"', protocol)
233 log.error('Unsupported callback daemon protocol "%s"', protocol)
233 raise Exception('Unsupported callback daemon protocol.')
234 raise Exception('Unsupported callback daemon protocol.')
234
235
235 extras['hooks_uri'] = callback_daemon.hooks_uri
236 extras['hooks_uri'] = callback_daemon.hooks_uri
236 extras['hooks_protocol'] = protocol
237 extras['hooks_protocol'] = protocol
237 extras['time'] = time.time()
238 extras['time'] = time.time()
238
239
239 # register txn_id
240 # register txn_id
240 extras['txn_id'] = txn_id
241 extras['txn_id'] = txn_id
241
242
242 log.debug('Prepared a callback daemon: %s at url `%s`',
243 log.debug('Prepared a callback daemon: %s at url `%s`',
243 callback_daemon.__class__.__name__, callback_daemon.hooks_uri)
244 callback_daemon.__class__.__name__, callback_daemon.hooks_uri)
244 return callback_daemon, extras
245 return callback_daemon, extras
245
246
246
247
247 class Hooks(object):
248 class Hooks(object):
248 """
249 """
249 Exposes the hooks for remote call backs
250 Exposes the hooks for remote call backs
250 """
251 """
251
252
252 def repo_size(self, extras):
253 def repo_size(self, extras):
253 log.debug("Called repo_size of %s object", self)
254 log.debug("Called repo_size of %s object", self)
254 return self._call_hook(hooks_base.repo_size, extras)
255 return self._call_hook(hooks_base.repo_size, extras)
255
256
256 def pre_pull(self, extras):
257 def pre_pull(self, extras):
257 log.debug("Called pre_pull of %s object", self)
258 log.debug("Called pre_pull of %s object", self)
258 return self._call_hook(hooks_base.pre_pull, extras)
259 return self._call_hook(hooks_base.pre_pull, extras)
259
260
260 def post_pull(self, extras):
261 def post_pull(self, extras):
261 log.debug("Called post_pull of %s object", self)
262 log.debug("Called post_pull of %s object", self)
262 return self._call_hook(hooks_base.post_pull, extras)
263 return self._call_hook(hooks_base.post_pull, extras)
263
264
264 def pre_push(self, extras):
265 def pre_push(self, extras):
265 log.debug("Called pre_push of %s object", self)
266 log.debug("Called pre_push of %s object", self)
266 return self._call_hook(hooks_base.pre_push, extras)
267 return self._call_hook(hooks_base.pre_push, extras)
267
268
268 def post_push(self, extras):
269 def post_push(self, extras):
269 log.debug("Called post_push of %s object", self)
270 log.debug("Called post_push of %s object", self)
270 return self._call_hook(hooks_base.post_push, extras)
271 return self._call_hook(hooks_base.post_push, extras)
271
272
272 def _call_hook(self, hook, extras):
273 def _call_hook(self, hook, extras):
273 extras = AttributeDict(extras)
274 extras = AttributeDict(extras)
274 server_url = extras['server_url']
275 server_url = extras['server_url']
275 request = bootstrap_request(application_url=server_url)
276 request = bootstrap_request(application_url=server_url)
276
277
277 bootstrap_config(request) # inject routes and other interfaces
278 bootstrap_config(request) # inject routes and other interfaces
278
279
279 # inject the user for usage in hooks
280 # inject the user for usage in hooks
280 request.user = AttributeDict({'username': extras.username,
281 request.user = AttributeDict({'username': extras.username,
281 'ip_addr': extras.ip,
282 'ip_addr': extras.ip,
282 'user_id': extras.user_id})
283 'user_id': extras.user_id})
283
284
284 extras.request = request
285 extras.request = request
285
286
286 try:
287 try:
287 result = hook(extras)
288 result = hook(extras)
288 except Exception as error:
289 except HTTPBranchProtected as handled_error:
289 exc_tb = traceback.format_exc()
290 # Those special cases doesn't need error reporting. It's a case of
290 log.exception('Exception when handling hook %s', hook)
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):
301 exc_tb = traceback.format_exc()
302 log.exception('Exception when handling hook %s', hook)
291 error_args = error.args
303 error_args = error.args
292 return {
304 return {
293 'status': 128,
305 'status': 128,
294 'output': '',
306 'output': '',
295 'exception': type(error).__name__,
307 'exception': type(error).__name__,
296 'exception_traceback': exc_tb,
308 'exception_traceback': exc_tb,
297 'exception_args': error_args,
309 'exception_args': error_args,
298 }
310 }
299 finally:
311 finally:
300 meta.Session.remove()
312 meta.Session.remove()
301
313
302 log.debug('Got hook call response %s', result)
314 log.debug('Got hook call response %s', result)
303 return {
315 return {
304 'status': result.status,
316 'status': result.status,
305 'output': result.output,
317 'output': result.output,
306 }
318 }
307
319
308 def __enter__(self):
320 def __enter__(self):
309 return self
321 return self
310
322
311 def __exit__(self, exc_type, exc_val, exc_tb):
323 def __exit__(self, exc_type, exc_val, exc_tb):
312 pass
324 pass
@@ -1,661 +1,673 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2014-2018 RhodeCode GmbH
3 # Copyright (C) 2014-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 SimpleVCS middleware for handling protocol request (push/clone etc.)
22 SimpleVCS middleware for handling protocol request (push/clone etc.)
23 It's implemented with basic auth function
23 It's implemented with basic auth function
24 """
24 """
25
25
26 import os
26 import os
27 import re
27 import re
28 import logging
28 import logging
29 import importlib
29 import importlib
30 from functools import wraps
30 from functools import wraps
31 from StringIO import StringIO
31 from StringIO import StringIO
32 from lxml import etree
32 from lxml import etree
33
33
34 import time
34 import time
35 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
35 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
36
36
37 from pyramid.httpexceptions import (
37 from pyramid.httpexceptions import (
38 HTTPNotFound, HTTPForbidden, HTTPNotAcceptable, HTTPInternalServerError)
38 HTTPNotFound, HTTPForbidden, HTTPNotAcceptable, HTTPInternalServerError)
39 from zope.cachedescriptors.property import Lazy as LazyProperty
39 from zope.cachedescriptors.property import Lazy as LazyProperty
40
40
41 import rhodecode
41 import rhodecode
42 from rhodecode.authentication.base import authenticate, VCS_TYPE, loadplugin
42 from rhodecode.authentication.base import authenticate, VCS_TYPE, loadplugin
43 from rhodecode.lib import rc_cache
43 from rhodecode.lib import rc_cache
44 from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware
44 from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware
45 from rhodecode.lib.base import (
45 from rhodecode.lib.base import (
46 BasicAuth, get_ip_addr, get_user_agent, vcs_operation_context)
46 BasicAuth, get_ip_addr, get_user_agent, vcs_operation_context)
47 from rhodecode.lib.exceptions import (UserCreationError, NotAllowedToCreateUserError)
47 from rhodecode.lib.exceptions import (UserCreationError, NotAllowedToCreateUserError)
48 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
48 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
49 from rhodecode.lib.middleware import appenlight
49 from rhodecode.lib.middleware import appenlight
50 from rhodecode.lib.middleware.utils import scm_app_http
50 from rhodecode.lib.middleware.utils import scm_app_http
51 from rhodecode.lib.utils import is_valid_repo, SLUG_RE
51 from rhodecode.lib.utils import is_valid_repo, SLUG_RE
52 from rhodecode.lib.utils2 import safe_str, fix_PATH, str2bool, safe_unicode
52 from rhodecode.lib.utils2 import safe_str, fix_PATH, str2bool, safe_unicode
53 from rhodecode.lib.vcs.conf import settings as vcs_settings
53 from rhodecode.lib.vcs.conf import settings as vcs_settings
54 from rhodecode.lib.vcs.backends import base
54 from rhodecode.lib.vcs.backends import base
55
55
56 from rhodecode.model import meta
56 from rhodecode.model import meta
57 from rhodecode.model.db import User, Repository, PullRequest
57 from rhodecode.model.db import User, Repository, PullRequest
58 from rhodecode.model.scm import ScmModel
58 from rhodecode.model.scm import ScmModel
59 from rhodecode.model.pull_request import PullRequestModel
59 from rhodecode.model.pull_request import PullRequestModel
60 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
60 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
61
61
62 log = logging.getLogger(__name__)
62 log = logging.getLogger(__name__)
63
63
64
64
65 def extract_svn_txn_id(acl_repo_name, data):
65 def extract_svn_txn_id(acl_repo_name, data):
66 """
66 """
67 Helper method for extraction of svn txn_id from submitted XML data during
67 Helper method for extraction of svn txn_id from submitted XML data during
68 POST operations
68 POST operations
69 """
69 """
70 try:
70 try:
71 root = etree.fromstring(data)
71 root = etree.fromstring(data)
72 pat = re.compile(r'/txn/(?P<txn_id>.*)')
72 pat = re.compile(r'/txn/(?P<txn_id>.*)')
73 for el in root:
73 for el in root:
74 if el.tag == '{DAV:}source':
74 if el.tag == '{DAV:}source':
75 for sub_el in el:
75 for sub_el in el:
76 if sub_el.tag == '{DAV:}href':
76 if sub_el.tag == '{DAV:}href':
77 match = pat.search(sub_el.text)
77 match = pat.search(sub_el.text)
78 if match:
78 if match:
79 svn_tx_id = match.groupdict()['txn_id']
79 svn_tx_id = match.groupdict()['txn_id']
80 txn_id = rc_cache.utils.compute_key_from_params(
80 txn_id = rc_cache.utils.compute_key_from_params(
81 acl_repo_name, svn_tx_id)
81 acl_repo_name, svn_tx_id)
82 return txn_id
82 return txn_id
83 except Exception:
83 except Exception:
84 log.exception('Failed to extract txn_id')
84 log.exception('Failed to extract txn_id')
85
85
86
86
87 def initialize_generator(factory):
87 def initialize_generator(factory):
88 """
88 """
89 Initializes the returned generator by draining its first element.
89 Initializes the returned generator by draining its first element.
90
90
91 This can be used to give a generator an initializer, which is the code
91 This can be used to give a generator an initializer, which is the code
92 up to the first yield statement. This decorator enforces that the first
92 up to the first yield statement. This decorator enforces that the first
93 produced element has the value ``"__init__"`` to make its special
93 produced element has the value ``"__init__"`` to make its special
94 purpose very explicit in the using code.
94 purpose very explicit in the using code.
95 """
95 """
96
96
97 @wraps(factory)
97 @wraps(factory)
98 def wrapper(*args, **kwargs):
98 def wrapper(*args, **kwargs):
99 gen = factory(*args, **kwargs)
99 gen = factory(*args, **kwargs)
100 try:
100 try:
101 init = gen.next()
101 init = gen.next()
102 except StopIteration:
102 except StopIteration:
103 raise ValueError('Generator must yield at least one element.')
103 raise ValueError('Generator must yield at least one element.')
104 if init != "__init__":
104 if init != "__init__":
105 raise ValueError('First yielded element must be "__init__".')
105 raise ValueError('First yielded element must be "__init__".')
106 return gen
106 return gen
107 return wrapper
107 return wrapper
108
108
109
109
110 class SimpleVCS(object):
110 class SimpleVCS(object):
111 """Common functionality for SCM HTTP handlers."""
111 """Common functionality for SCM HTTP handlers."""
112
112
113 SCM = 'unknown'
113 SCM = 'unknown'
114
114
115 acl_repo_name = None
115 acl_repo_name = None
116 url_repo_name = None
116 url_repo_name = None
117 vcs_repo_name = None
117 vcs_repo_name = None
118 rc_extras = {}
118 rc_extras = {}
119
119
120 # We have to handle requests to shadow repositories different than requests
120 # We have to handle requests to shadow repositories different than requests
121 # to normal repositories. Therefore we have to distinguish them. To do this
121 # to normal repositories. Therefore we have to distinguish them. To do this
122 # we use this regex which will match only on URLs pointing to shadow
122 # we use this regex which will match only on URLs pointing to shadow
123 # repositories.
123 # repositories.
124 shadow_repo_re = re.compile(
124 shadow_repo_re = re.compile(
125 '(?P<groups>(?:{slug_pat}/)*)' # repo groups
125 '(?P<groups>(?:{slug_pat}/)*)' # repo groups
126 '(?P<target>{slug_pat})/' # target repo
126 '(?P<target>{slug_pat})/' # target repo
127 'pull-request/(?P<pr_id>\d+)/' # pull request
127 'pull-request/(?P<pr_id>\d+)/' # pull request
128 'repository$' # shadow repo
128 'repository$' # shadow repo
129 .format(slug_pat=SLUG_RE.pattern))
129 .format(slug_pat=SLUG_RE.pattern))
130
130
131 def __init__(self, config, registry):
131 def __init__(self, config, registry):
132 self.registry = registry
132 self.registry = registry
133 self.config = config
133 self.config = config
134 # re-populated by specialized middleware
134 # re-populated by specialized middleware
135 self.repo_vcs_config = base.Config()
135 self.repo_vcs_config = base.Config()
136 self.rhodecode_settings = SettingsModel().get_all_settings(cache=True)
136 self.rhodecode_settings = SettingsModel().get_all_settings(cache=True)
137
137
138 registry.rhodecode_settings = self.rhodecode_settings
138 registry.rhodecode_settings = self.rhodecode_settings
139 # authenticate this VCS request using authfunc
139 # authenticate this VCS request using authfunc
140 auth_ret_code_detection = \
140 auth_ret_code_detection = \
141 str2bool(self.config.get('auth_ret_code_detection', False))
141 str2bool(self.config.get('auth_ret_code_detection', False))
142 self.authenticate = BasicAuth(
142 self.authenticate = BasicAuth(
143 '', authenticate, registry, config.get('auth_ret_code'),
143 '', authenticate, registry, config.get('auth_ret_code'),
144 auth_ret_code_detection)
144 auth_ret_code_detection)
145 self.ip_addr = '0.0.0.0'
145 self.ip_addr = '0.0.0.0'
146
146
147 @LazyProperty
147 @LazyProperty
148 def global_vcs_config(self):
148 def global_vcs_config(self):
149 try:
149 try:
150 return VcsSettingsModel().get_ui_settings_as_config_obj()
150 return VcsSettingsModel().get_ui_settings_as_config_obj()
151 except Exception:
151 except Exception:
152 return base.Config()
152 return base.Config()
153
153
154 @property
154 @property
155 def base_path(self):
155 def base_path(self):
156 settings_path = self.repo_vcs_config.get(
156 settings_path = self.repo_vcs_config.get(
157 *VcsSettingsModel.PATH_SETTING)
157 *VcsSettingsModel.PATH_SETTING)
158
158
159 if not settings_path:
159 if not settings_path:
160 settings_path = self.global_vcs_config.get(
160 settings_path = self.global_vcs_config.get(
161 *VcsSettingsModel.PATH_SETTING)
161 *VcsSettingsModel.PATH_SETTING)
162
162
163 if not settings_path:
163 if not settings_path:
164 # try, maybe we passed in explicitly as config option
164 # try, maybe we passed in explicitly as config option
165 settings_path = self.config.get('base_path')
165 settings_path = self.config.get('base_path')
166
166
167 if not settings_path:
167 if not settings_path:
168 raise ValueError('FATAL: base_path is empty')
168 raise ValueError('FATAL: base_path is empty')
169 return settings_path
169 return settings_path
170
170
171 def set_repo_names(self, environ):
171 def set_repo_names(self, environ):
172 """
172 """
173 This will populate the attributes acl_repo_name, url_repo_name,
173 This will populate the attributes acl_repo_name, url_repo_name,
174 vcs_repo_name and is_shadow_repo. In case of requests to normal (non
174 vcs_repo_name and is_shadow_repo. In case of requests to normal (non
175 shadow) repositories all names are equal. In case of requests to a
175 shadow) repositories all names are equal. In case of requests to a
176 shadow repository the acl-name points to the target repo of the pull
176 shadow repository the acl-name points to the target repo of the pull
177 request and the vcs-name points to the shadow repo file system path.
177 request and the vcs-name points to the shadow repo file system path.
178 The url-name is always the URL used by the vcs client program.
178 The url-name is always the URL used by the vcs client program.
179
179
180 Example in case of a shadow repo:
180 Example in case of a shadow repo:
181 acl_repo_name = RepoGroup/MyRepo
181 acl_repo_name = RepoGroup/MyRepo
182 url_repo_name = RepoGroup/MyRepo/pull-request/3/repository
182 url_repo_name = RepoGroup/MyRepo/pull-request/3/repository
183 vcs_repo_name = /repo/base/path/RepoGroup/.__shadow_MyRepo_pr-3'
183 vcs_repo_name = /repo/base/path/RepoGroup/.__shadow_MyRepo_pr-3'
184 """
184 """
185 # First we set the repo name from URL for all attributes. This is the
185 # First we set the repo name from URL for all attributes. This is the
186 # default if handling normal (non shadow) repo requests.
186 # default if handling normal (non shadow) repo requests.
187 self.url_repo_name = self._get_repository_name(environ)
187 self.url_repo_name = self._get_repository_name(environ)
188 self.acl_repo_name = self.vcs_repo_name = self.url_repo_name
188 self.acl_repo_name = self.vcs_repo_name = self.url_repo_name
189 self.is_shadow_repo = False
189 self.is_shadow_repo = False
190
190
191 # Check if this is a request to a shadow repository.
191 # Check if this is a request to a shadow repository.
192 match = self.shadow_repo_re.match(self.url_repo_name)
192 match = self.shadow_repo_re.match(self.url_repo_name)
193 if match:
193 if match:
194 match_dict = match.groupdict()
194 match_dict = match.groupdict()
195
195
196 # Build acl repo name from regex match.
196 # Build acl repo name from regex match.
197 acl_repo_name = safe_unicode('{groups}{target}'.format(
197 acl_repo_name = safe_unicode('{groups}{target}'.format(
198 groups=match_dict['groups'] or '',
198 groups=match_dict['groups'] or '',
199 target=match_dict['target']))
199 target=match_dict['target']))
200
200
201 # Retrieve pull request instance by ID from regex match.
201 # Retrieve pull request instance by ID from regex match.
202 pull_request = PullRequest.get(match_dict['pr_id'])
202 pull_request = PullRequest.get(match_dict['pr_id'])
203
203
204 # Only proceed if we got a pull request and if acl repo name from
204 # Only proceed if we got a pull request and if acl repo name from
205 # URL equals the target repo name of the pull request.
205 # URL equals the target repo name of the pull request.
206 if pull_request and \
206 if pull_request and \
207 (acl_repo_name == pull_request.target_repo.repo_name):
207 (acl_repo_name == pull_request.target_repo.repo_name):
208 repo_id = pull_request.target_repo.repo_id
208 repo_id = pull_request.target_repo.repo_id
209 # Get file system path to shadow repository.
209 # Get file system path to shadow repository.
210 workspace_id = PullRequestModel()._workspace_id(pull_request)
210 workspace_id = PullRequestModel()._workspace_id(pull_request)
211 target_vcs = pull_request.target_repo.scm_instance()
211 target_vcs = pull_request.target_repo.scm_instance()
212 vcs_repo_name = target_vcs._get_shadow_repository_path(
212 vcs_repo_name = target_vcs._get_shadow_repository_path(
213 repo_id, workspace_id)
213 repo_id, workspace_id)
214
214
215 # Store names for later usage.
215 # Store names for later usage.
216 self.vcs_repo_name = vcs_repo_name
216 self.vcs_repo_name = vcs_repo_name
217 self.acl_repo_name = acl_repo_name
217 self.acl_repo_name = acl_repo_name
218 self.is_shadow_repo = True
218 self.is_shadow_repo = True
219
219
220 log.debug('Setting all VCS repository names: %s', {
220 log.debug('Setting all VCS repository names: %s', {
221 'acl_repo_name': self.acl_repo_name,
221 'acl_repo_name': self.acl_repo_name,
222 'url_repo_name': self.url_repo_name,
222 'url_repo_name': self.url_repo_name,
223 'vcs_repo_name': self.vcs_repo_name,
223 'vcs_repo_name': self.vcs_repo_name,
224 })
224 })
225
225
226 @property
226 @property
227 def scm_app(self):
227 def scm_app(self):
228 custom_implementation = self.config['vcs.scm_app_implementation']
228 custom_implementation = self.config['vcs.scm_app_implementation']
229 if custom_implementation == 'http':
229 if custom_implementation == 'http':
230 log.info('Using HTTP implementation of scm app.')
230 log.info('Using HTTP implementation of scm app.')
231 scm_app_impl = scm_app_http
231 scm_app_impl = scm_app_http
232 else:
232 else:
233 log.info('Using custom implementation of scm_app: "{}"'.format(
233 log.info('Using custom implementation of scm_app: "{}"'.format(
234 custom_implementation))
234 custom_implementation))
235 scm_app_impl = importlib.import_module(custom_implementation)
235 scm_app_impl = importlib.import_module(custom_implementation)
236 return scm_app_impl
236 return scm_app_impl
237
237
238 def _get_by_id(self, repo_name):
238 def _get_by_id(self, repo_name):
239 """
239 """
240 Gets a special pattern _<ID> from clone url and tries to replace it
240 Gets a special pattern _<ID> from clone url and tries to replace it
241 with a repository_name for support of _<ID> non changeable urls
241 with a repository_name for support of _<ID> non changeable urls
242 """
242 """
243
243
244 data = repo_name.split('/')
244 data = repo_name.split('/')
245 if len(data) >= 2:
245 if len(data) >= 2:
246 from rhodecode.model.repo import RepoModel
246 from rhodecode.model.repo import RepoModel
247 by_id_match = RepoModel().get_repo_by_id(repo_name)
247 by_id_match = RepoModel().get_repo_by_id(repo_name)
248 if by_id_match:
248 if by_id_match:
249 data[1] = by_id_match.repo_name
249 data[1] = by_id_match.repo_name
250
250
251 return safe_str('/'.join(data))
251 return safe_str('/'.join(data))
252
252
253 def _invalidate_cache(self, repo_name):
253 def _invalidate_cache(self, repo_name):
254 """
254 """
255 Set's cache for this repository for invalidation on next access
255 Set's cache for this repository for invalidation on next access
256
256
257 :param repo_name: full repo name, also a cache key
257 :param repo_name: full repo name, also a cache key
258 """
258 """
259 ScmModel().mark_for_invalidation(repo_name)
259 ScmModel().mark_for_invalidation(repo_name)
260
260
261 def is_valid_and_existing_repo(self, repo_name, base_path, scm_type):
261 def is_valid_and_existing_repo(self, repo_name, base_path, scm_type):
262 db_repo = Repository.get_by_repo_name(repo_name)
262 db_repo = Repository.get_by_repo_name(repo_name)
263 if not db_repo:
263 if not db_repo:
264 log.debug('Repository `%s` not found inside the database.',
264 log.debug('Repository `%s` not found inside the database.',
265 repo_name)
265 repo_name)
266 return False
266 return False
267
267
268 if db_repo.repo_type != scm_type:
268 if db_repo.repo_type != scm_type:
269 log.warning(
269 log.warning(
270 'Repository `%s` have incorrect scm_type, expected %s got %s',
270 'Repository `%s` have incorrect scm_type, expected %s got %s',
271 repo_name, db_repo.repo_type, scm_type)
271 repo_name, db_repo.repo_type, scm_type)
272 return False
272 return False
273
273
274 config = db_repo._config
274 config = db_repo._config
275 config.set('extensions', 'largefiles', '')
275 config.set('extensions', 'largefiles', '')
276 return is_valid_repo(
276 return is_valid_repo(
277 repo_name, base_path,
277 repo_name, base_path,
278 explicit_scm=scm_type, expect_scm=scm_type, config=config)
278 explicit_scm=scm_type, expect_scm=scm_type, config=config)
279
279
280 def valid_and_active_user(self, user):
280 def valid_and_active_user(self, user):
281 """
281 """
282 Checks if that user is not empty, and if it's actually object it checks
282 Checks if that user is not empty, and if it's actually object it checks
283 if he's active.
283 if he's active.
284
284
285 :param user: user object or None
285 :param user: user object or None
286 :return: boolean
286 :return: boolean
287 """
287 """
288 if user is None:
288 if user is None:
289 return False
289 return False
290
290
291 elif user.active:
291 elif user.active:
292 return True
292 return True
293
293
294 return False
294 return False
295
295
296 @property
296 @property
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
304 name. If plugin_cache and ttl is set it will use the plugin which
304 name. If plugin_cache and ttl is set it will use the plugin which
305 authenticated the user to store the cached permissions result for N
305 authenticated the user to store the cached permissions result for N
306 amount of seconds as in cache_ttl
306 amount of seconds as in cache_ttl
307
307
308 :param action: push or pull action
308 :param action: push or pull action
309 :param user: user instance
309 :param user: user instance
310 :param repo_name: repository name
310 :param repo_name: repository name
311 """
311 """
312
312
313 log.debug('AUTH_CACHE_TTL for permissions `%s` active: %s (TTL: %s)',
313 log.debug('AUTH_CACHE_TTL for permissions `%s` active: %s (TTL: %s)',
314 plugin_id, plugin_cache_active, cache_ttl)
314 plugin_id, plugin_cache_active, cache_ttl)
315
315
316 user_id = user.user_id
316 user_id = user.user_id
317 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
317 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
318 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
318 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
319
319
320 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
320 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
321 expiration_time=cache_ttl,
321 expiration_time=cache_ttl,
322 condition=plugin_cache_active)
322 condition=plugin_cache_active)
323 def compute_perm_vcs(
323 def compute_perm_vcs(
324 cache_name, plugin_id, action, user_id, repo_name, ip_addr):
324 cache_name, plugin_id, action, user_id, repo_name, ip_addr):
325
325
326 log.debug('auth: calculating permission access now...')
326 log.debug('auth: calculating permission access now...')
327 # check IP
327 # check IP
328 inherit = user.inherit_default_permissions
328 inherit = user.inherit_default_permissions
329 ip_allowed = AuthUser.check_ip_allowed(
329 ip_allowed = AuthUser.check_ip_allowed(
330 user_id, ip_addr, inherit_from_default=inherit)
330 user_id, ip_addr, inherit_from_default=inherit)
331 if ip_allowed:
331 if ip_allowed:
332 log.info('Access for IP:%s allowed', ip_addr)
332 log.info('Access for IP:%s allowed', ip_addr)
333 else:
333 else:
334 return False
334 return False
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
349
349
350 start = time.time()
350 start = time.time()
351 log.debug('Running plugin `%s` permissions check', plugin_id)
351 log.debug('Running plugin `%s` permissions check', plugin_id)
352
352
353 # for environ based auth, password can be empty, but then the validation is
353 # for environ based auth, password can be empty, but then the validation is
354 # on the server that fills in the env data needed for authentication
354 # on the server that fills in the env data needed for authentication
355 perm_result = compute_perm_vcs(
355 perm_result = compute_perm_vcs(
356 'vcs_permissions', plugin_id, action, user.user_id, repo_name, ip_addr)
356 'vcs_permissions', plugin_id, action, user.user_id, repo_name, ip_addr)
357
357
358 auth_time = time.time() - start
358 auth_time = time.time() - start
359 log.debug('Permissions for plugin `%s` completed in %.3fs, '
359 log.debug('Permissions for plugin `%s` completed in %.3fs, '
360 'expiration time of fetched cache %.1fs.',
360 'expiration time of fetched cache %.1fs.',
361 plugin_id, auth_time, cache_ttl)
361 plugin_id, auth_time, cache_ttl)
362
362
363 return perm_result
363 return perm_result
364
364
365 def _check_ssl(self, environ, start_response):
365 def _check_ssl(self, environ, start_response):
366 """
366 """
367 Checks the SSL check flag and returns False if SSL is not present
367 Checks the SSL check flag and returns False if SSL is not present
368 and required True otherwise
368 and required True otherwise
369 """
369 """
370 org_proto = environ['wsgi._org_proto']
370 org_proto = environ['wsgi._org_proto']
371 # check if we have SSL required ! if not it's a bad request !
371 # check if we have SSL required ! if not it's a bad request !
372 require_ssl = str2bool(self.repo_vcs_config.get('web', 'push_ssl'))
372 require_ssl = str2bool(self.repo_vcs_config.get('web', 'push_ssl'))
373 if require_ssl and org_proto == 'http':
373 if require_ssl and org_proto == 'http':
374 log.debug(
374 log.debug(
375 'Bad request: detected protocol is `%s` and '
375 'Bad request: detected protocol is `%s` and '
376 'SSL/HTTPS is required.', org_proto)
376 'SSL/HTTPS is required.', org_proto)
377 return False
377 return False
378 return True
378 return True
379
379
380 def _get_default_cache_ttl(self):
380 def _get_default_cache_ttl(self):
381 # take AUTH_CACHE_TTL from the `rhodecode` auth plugin
381 # take AUTH_CACHE_TTL from the `rhodecode` auth plugin
382 plugin = loadplugin('egg:rhodecode-enterprise-ce#rhodecode')
382 plugin = loadplugin('egg:rhodecode-enterprise-ce#rhodecode')
383 plugin_settings = plugin.get_settings()
383 plugin_settings = plugin.get_settings()
384 plugin_cache_active, cache_ttl = plugin.get_ttl_cache(
384 plugin_cache_active, cache_ttl = plugin.get_ttl_cache(
385 plugin_settings) or (False, 0)
385 plugin_settings) or (False, 0)
386 return plugin_cache_active, cache_ttl
386 return plugin_cache_active, cache_ttl
387
387
388 def __call__(self, environ, start_response):
388 def __call__(self, environ, start_response):
389 try:
389 try:
390 return self._handle_request(environ, start_response)
390 return self._handle_request(environ, start_response)
391 except Exception:
391 except Exception:
392 log.exception("Exception while handling request")
392 log.exception("Exception while handling request")
393 appenlight.track_exception(environ)
393 appenlight.track_exception(environ)
394 return HTTPInternalServerError()(environ, start_response)
394 return HTTPInternalServerError()(environ, start_response)
395 finally:
395 finally:
396 meta.Session.remove()
396 meta.Session.remove()
397
397
398 def _handle_request(self, environ, start_response):
398 def _handle_request(self, environ, start_response):
399
399
400 if not self._check_ssl(environ, start_response):
400 if not self._check_ssl(environ, start_response):
401 reason = ('SSL required, while RhodeCode was unable '
401 reason = ('SSL required, while RhodeCode was unable '
402 'to detect this as SSL request')
402 'to detect this as SSL request')
403 log.debug('User not allowed to proceed, %s', reason)
403 log.debug('User not allowed to proceed, %s', reason)
404 return HTTPNotAcceptable(reason)(environ, start_response)
404 return HTTPNotAcceptable(reason)(environ, start_response)
405
405
406 if not self.url_repo_name:
406 if not self.url_repo_name:
407 log.warning('Repository name is empty: %s', self.url_repo_name)
407 log.warning('Repository name is empty: %s', self.url_repo_name)
408 # failed to get repo name, we fail now
408 # failed to get repo name, we fail now
409 return HTTPNotFound()(environ, start_response)
409 return HTTPNotFound()(environ, start_response)
410 log.debug('Extracted repo name is %s', self.url_repo_name)
410 log.debug('Extracted repo name is %s', self.url_repo_name)
411
411
412 ip_addr = get_ip_addr(environ)
412 ip_addr = get_ip_addr(environ)
413 user_agent = get_user_agent(environ)
413 user_agent = get_user_agent(environ)
414 username = None
414 username = None
415
415
416 # skip passing error to error controller
416 # skip passing error to error controller
417 environ['pylons.status_code_redirect'] = True
417 environ['pylons.status_code_redirect'] = True
418
418
419 # ======================================================================
419 # ======================================================================
420 # GET ACTION PULL or PUSH
420 # GET ACTION PULL or PUSH
421 # ======================================================================
421 # ======================================================================
422 action = self._get_action(environ)
422 action = self._get_action(environ)
423
423
424 # ======================================================================
424 # ======================================================================
425 # Check if this is a request to a shadow repository of a pull request.
425 # Check if this is a request to a shadow repository of a pull request.
426 # In this case only pull action is allowed.
426 # In this case only pull action is allowed.
427 # ======================================================================
427 # ======================================================================
428 if self.is_shadow_repo and action != 'pull':
428 if self.is_shadow_repo and action != 'pull':
429 reason = 'Only pull action is allowed for shadow repositories.'
429 reason = 'Only pull action is allowed for shadow repositories.'
430 log.debug('User not allowed to proceed, %s', reason)
430 log.debug('User not allowed to proceed, %s', reason)
431 return HTTPNotAcceptable(reason)(environ, start_response)
431 return HTTPNotAcceptable(reason)(environ, start_response)
432
432
433 # Check if the shadow repo actually exists, in case someone refers
433 # Check if the shadow repo actually exists, in case someone refers
434 # to it, and it has been deleted because of successful merge.
434 # to it, and it has been deleted because of successful merge.
435 if self.is_shadow_repo and not self.is_shadow_repo_dir:
435 if self.is_shadow_repo and not self.is_shadow_repo_dir:
436 log.debug(
436 log.debug(
437 'Shadow repo detected, and shadow repo dir `%s` is missing',
437 'Shadow repo detected, and shadow repo dir `%s` is missing',
438 self.is_shadow_repo_dir)
438 self.is_shadow_repo_dir)
439 return HTTPNotFound()(environ, start_response)
439 return HTTPNotFound()(environ, start_response)
440
440
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,
455 )
458 )
456 else:
459 else:
457 anonymous_perm = False
460 anonymous_perm = False
458
461
459 if not anonymous_user.active or not anonymous_perm:
462 if not anonymous_user.active or not anonymous_perm:
460 if not anonymous_user.active:
463 if not anonymous_user.active:
461 log.debug('Anonymous access is disabled, running '
464 log.debug('Anonymous access is disabled, running '
462 'authentication')
465 'authentication')
463
466
464 if not anonymous_perm:
467 if not anonymous_perm:
465 log.debug('Not enough credentials to access this '
468 log.debug('Not enough credentials to access this '
466 'repository as anonymous user')
469 'repository as anonymous user')
467
470
468 username = None
471 username = None
469 # ==============================================================
472 # ==============================================================
470 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
473 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
471 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
474 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
472 # ==============================================================
475 # ==============================================================
473
476
474 # try to auth based on environ, container auth methods
477 # try to auth based on environ, container auth methods
475 log.debug('Running PRE-AUTH for container based authentication')
478 log.debug('Running PRE-AUTH for container based authentication')
476 pre_auth = authenticate(
479 pre_auth = authenticate(
477 '', '', environ, VCS_TYPE, registry=self.registry,
480 '', '', environ, VCS_TYPE, registry=self.registry,
478 acl_repo_name=self.acl_repo_name)
481 acl_repo_name=self.acl_repo_name)
479 if pre_auth and pre_auth.get('username'):
482 if pre_auth and pre_auth.get('username'):
480 username = pre_auth['username']
483 username = pre_auth['username']
481 log.debug('PRE-AUTH got %s as username', username)
484 log.debug('PRE-AUTH got %s as username', username)
482 if pre_auth:
485 if pre_auth:
483 log.debug('PRE-AUTH successful from %s',
486 log.debug('PRE-AUTH successful from %s',
484 pre_auth.get('auth_data', {}).get('_plugin'))
487 pre_auth.get('auth_data', {}).get('_plugin'))
485
488
486 # If not authenticated by the container, running basic auth
489 # If not authenticated by the container, running basic auth
487 # before inject the calling repo_name for special scope checks
490 # before inject the calling repo_name for special scope checks
488 self.authenticate.acl_repo_name = self.acl_repo_name
491 self.authenticate.acl_repo_name = self.acl_repo_name
489
492
490 plugin_cache_active, cache_ttl = False, 0
493 plugin_cache_active, cache_ttl = False, 0
491 plugin = None
494 plugin = None
492 if not username:
495 if not username:
493 self.authenticate.realm = self.authenticate.get_rc_realm()
496 self.authenticate.realm = self.authenticate.get_rc_realm()
494
497
495 try:
498 try:
496 auth_result = self.authenticate(environ)
499 auth_result = self.authenticate(environ)
497 except (UserCreationError, NotAllowedToCreateUserError) as e:
500 except (UserCreationError, NotAllowedToCreateUserError) as e:
498 log.error(e)
501 log.error(e)
499 reason = safe_str(e)
502 reason = safe_str(e)
500 return HTTPNotAcceptable(reason)(environ, start_response)
503 return HTTPNotAcceptable(reason)(environ, start_response)
501
504
502 if isinstance(auth_result, dict):
505 if isinstance(auth_result, dict):
503 AUTH_TYPE.update(environ, 'basic')
506 AUTH_TYPE.update(environ, 'basic')
504 REMOTE_USER.update(environ, auth_result['username'])
507 REMOTE_USER.update(environ, auth_result['username'])
505 username = auth_result['username']
508 username = auth_result['username']
506 plugin = auth_result.get('auth_data', {}).get('_plugin')
509 plugin = auth_result.get('auth_data', {}).get('_plugin')
507 log.info(
510 log.info(
508 'MAIN-AUTH successful for user `%s` from %s plugin',
511 'MAIN-AUTH successful for user `%s` from %s plugin',
509 username, plugin)
512 username, plugin)
510
513
511 plugin_cache_active, cache_ttl = auth_result.get(
514 plugin_cache_active, cache_ttl = auth_result.get(
512 'auth_data', {}).get('_ttl_cache') or (False, 0)
515 'auth_data', {}).get('_ttl_cache') or (False, 0)
513 else:
516 else:
514 return auth_result.wsgi_application(
517 return auth_result.wsgi_application(
515 environ, start_response)
518 environ, start_response)
516
519
517 # ==============================================================
520 # ==============================================================
518 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
521 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
519 # ==============================================================
522 # ==============================================================
520 user = User.get_by_username(username)
523 user = User.get_by_username(username)
521 if not self.valid_and_active_user(user):
524 if not self.valid_and_active_user(user):
522 return HTTPForbidden()(environ, start_response)
525 return HTTPForbidden()(environ, start_response)
523 username = user.username
526 username = user.username
524 user_id = user.user_id
527 user_id = user.user_id
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'
531 log.debug('User not allowed to authenticate, %s', reason)
535 log.debug('User not allowed to authenticate, %s', reason)
532 return HTTPNotAcceptable(reason)(environ, start_response)
536 return HTTPNotAcceptable(reason)(environ, start_response)
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 # ======================================================================
552 # REQUEST HANDLING
564 # REQUEST HANDLING
553 # ======================================================================
565 # ======================================================================
554 repo_path = os.path.join(
566 repo_path = os.path.join(
555 safe_str(self.base_path), safe_str(self.vcs_repo_name))
567 safe_str(self.base_path), safe_str(self.vcs_repo_name))
556 log.debug('Repository path is %s', repo_path)
568 log.debug('Repository path is %s', repo_path)
557
569
558 fix_PATH()
570 fix_PATH()
559
571
560 log.info(
572 log.info(
561 '%s action on %s repo "%s" by "%s" from %s %s',
573 '%s action on %s repo "%s" by "%s" from %s %s',
562 action, self.SCM, safe_str(self.url_repo_name),
574 action, self.SCM, safe_str(self.url_repo_name),
563 safe_str(username), ip_addr, user_agent)
575 safe_str(username), ip_addr, user_agent)
564
576
565 return self._generate_vcs_response(
577 return self._generate_vcs_response(
566 environ, start_response, repo_path, extras, action)
578 environ, start_response, repo_path, extras, action)
567
579
568 @initialize_generator
580 @initialize_generator
569 def _generate_vcs_response(
581 def _generate_vcs_response(
570 self, environ, start_response, repo_path, extras, action):
582 self, environ, start_response, repo_path, extras, action):
571 """
583 """
572 Returns a generator for the response content.
584 Returns a generator for the response content.
573
585
574 This method is implemented as a generator, so that it can trigger
586 This method is implemented as a generator, so that it can trigger
575 the cache validation after all content sent back to the client. It
587 the cache validation after all content sent back to the client. It
576 also handles the locking exceptions which will be triggered when
588 also handles the locking exceptions which will be triggered when
577 the first chunk is produced by the underlying WSGI application.
589 the first chunk is produced by the underlying WSGI application.
578 """
590 """
579 txn_id = ''
591 txn_id = ''
580 if 'CONTENT_LENGTH' in environ and environ['REQUEST_METHOD'] == 'MERGE':
592 if 'CONTENT_LENGTH' in environ and environ['REQUEST_METHOD'] == 'MERGE':
581 # case for SVN, we want to re-use the callback daemon port
593 # case for SVN, we want to re-use the callback daemon port
582 # so we use the txn_id, for this we peek the body, and still save
594 # so we use the txn_id, for this we peek the body, and still save
583 # it as wsgi.input
595 # it as wsgi.input
584 data = environ['wsgi.input'].read()
596 data = environ['wsgi.input'].read()
585 environ['wsgi.input'] = StringIO(data)
597 environ['wsgi.input'] = StringIO(data)
586 txn_id = extract_svn_txn_id(self.acl_repo_name, data)
598 txn_id = extract_svn_txn_id(self.acl_repo_name, data)
587
599
588 callback_daemon, extras = self._prepare_callback_daemon(
600 callback_daemon, extras = self._prepare_callback_daemon(
589 extras, environ, action, txn_id=txn_id)
601 extras, environ, action, txn_id=txn_id)
590 log.debug('HOOKS extras is %s', extras)
602 log.debug('HOOKS extras is %s', extras)
591
603
592 config = self._create_config(extras, self.acl_repo_name)
604 config = self._create_config(extras, self.acl_repo_name)
593 app = self._create_wsgi_app(repo_path, self.url_repo_name, config)
605 app = self._create_wsgi_app(repo_path, self.url_repo_name, config)
594 with callback_daemon:
606 with callback_daemon:
595 app.rc_extras = extras
607 app.rc_extras = extras
596
608
597 try:
609 try:
598 response = app(environ, start_response)
610 response = app(environ, start_response)
599 finally:
611 finally:
600 # This statement works together with the decorator
612 # This statement works together with the decorator
601 # "initialize_generator" above. The decorator ensures that
613 # "initialize_generator" above. The decorator ensures that
602 # we hit the first yield statement before the generator is
614 # we hit the first yield statement before the generator is
603 # returned back to the WSGI server. This is needed to
615 # returned back to the WSGI server. This is needed to
604 # ensure that the call to "app" above triggers the
616 # ensure that the call to "app" above triggers the
605 # needed callback to "start_response" before the
617 # needed callback to "start_response" before the
606 # generator is actually used.
618 # generator is actually used.
607 yield "__init__"
619 yield "__init__"
608
620
609 # iter content
621 # iter content
610 for chunk in response:
622 for chunk in response:
611 yield chunk
623 yield chunk
612
624
613 try:
625 try:
614 # invalidate cache on push
626 # invalidate cache on push
615 if action == 'push':
627 if action == 'push':
616 self._invalidate_cache(self.url_repo_name)
628 self._invalidate_cache(self.url_repo_name)
617 finally:
629 finally:
618 meta.Session.remove()
630 meta.Session.remove()
619
631
620 def _get_repository_name(self, environ):
632 def _get_repository_name(self, environ):
621 """Get repository name out of the environmnent
633 """Get repository name out of the environmnent
622
634
623 :param environ: WSGI environment
635 :param environ: WSGI environment
624 """
636 """
625 raise NotImplementedError()
637 raise NotImplementedError()
626
638
627 def _get_action(self, environ):
639 def _get_action(self, environ):
628 """Map request commands into a pull or push command.
640 """Map request commands into a pull or push command.
629
641
630 :param environ: WSGI environment
642 :param environ: WSGI environment
631 """
643 """
632 raise NotImplementedError()
644 raise NotImplementedError()
633
645
634 def _create_wsgi_app(self, repo_path, repo_name, config):
646 def _create_wsgi_app(self, repo_path, repo_name, config):
635 """Return the WSGI app that will finally handle the request."""
647 """Return the WSGI app that will finally handle the request."""
636 raise NotImplementedError()
648 raise NotImplementedError()
637
649
638 def _create_config(self, extras, repo_name):
650 def _create_config(self, extras, repo_name):
639 """Create a safe config representation."""
651 """Create a safe config representation."""
640 raise NotImplementedError()
652 raise NotImplementedError()
641
653
642 def _should_use_callback_daemon(self, extras, environ, action):
654 def _should_use_callback_daemon(self, extras, environ, action):
643 return True
655 return True
644
656
645 def _prepare_callback_daemon(self, extras, environ, action, txn_id=None):
657 def _prepare_callback_daemon(self, extras, environ, action, txn_id=None):
646 direct_calls = vcs_settings.HOOKS_DIRECT_CALLS
658 direct_calls = vcs_settings.HOOKS_DIRECT_CALLS
647 if not self._should_use_callback_daemon(extras, environ, action):
659 if not self._should_use_callback_daemon(extras, environ, action):
648 # disable callback daemon for actions that don't require it
660 # disable callback daemon for actions that don't require it
649 direct_calls = True
661 direct_calls = True
650
662
651 return prepare_callback_daemon(
663 return prepare_callback_daemon(
652 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
664 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
653 host=vcs_settings.HOOKS_HOST, use_direct_calls=direct_calls, txn_id=txn_id)
665 host=vcs_settings.HOOKS_HOST, use_direct_calls=direct_calls, txn_id=txn_id)
654
666
655
667
656 def _should_check_locking(query_string):
668 def _should_check_locking(query_string):
657 # this is kind of hacky, but due to how mercurial handles client-server
669 # this is kind of hacky, but due to how mercurial handles client-server
658 # server see all operation on commit; bookmarks, phases and
670 # server see all operation on commit; bookmarks, phases and
659 # obsolescence marker in different transaction, we don't want to check
671 # obsolescence marker in different transaction, we don't want to check
660 # locking on those
672 # locking on those
661 return query_string not in ['cmd=listkeys']
673 return query_string not in ['cmd=listkeys']
@@ -1,617 +1,617 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 from hashlib import sha1
22 from hashlib import sha1
23
23
24 import pytest
24 import pytest
25 from mock import patch
25 from mock import patch
26
26
27 from rhodecode.lib import auth
27 from rhodecode.lib import auth
28 from rhodecode.lib.utils2 import md5
28 from rhodecode.lib.utils2 import md5
29 from rhodecode.model.auth_token import AuthTokenModel
29 from rhodecode.model.auth_token import AuthTokenModel
30 from rhodecode.model.db import User
30 from rhodecode.model.db import User
31 from rhodecode.model.repo import RepoModel
31 from rhodecode.model.repo import RepoModel
32 from rhodecode.model.user import UserModel
32 from rhodecode.model.user import UserModel
33 from rhodecode.model.user_group import UserGroupModel
33 from rhodecode.model.user_group import UserGroupModel
34
34
35
35
36 def test_perm_origin_dict():
36 def test_perm_origin_dict():
37 pod = auth.PermOriginDict()
37 pod = auth.PermOriginDict()
38 pod['thing'] = 'read', 'default'
38 pod['thing'] = 'read', 'default'
39 assert pod['thing'] == 'read'
39 assert pod['thing'] == 'read'
40
40
41 assert pod.perm_origin_stack == {
41 assert pod.perm_origin_stack == {
42 'thing': [('read', 'default')]}
42 'thing': [('read', 'default')]}
43
43
44 pod['thing'] = 'write', 'admin'
44 pod['thing'] = 'write', 'admin'
45 assert pod['thing'] == 'write'
45 assert pod['thing'] == 'write'
46
46
47 assert pod.perm_origin_stack == {
47 assert pod.perm_origin_stack == {
48 'thing': [('read', 'default'), ('write', 'admin')]}
48 'thing': [('read', 'default'), ('write', 'admin')]}
49
49
50 pod['other'] = 'write', 'default'
50 pod['other'] = 'write', 'default'
51
51
52 assert pod.perm_origin_stack == {
52 assert pod.perm_origin_stack == {
53 'other': [('write', 'default')],
53 'other': [('write', 'default')],
54 'thing': [('read', 'default'), ('write', 'admin')]}
54 'thing': [('read', 'default'), ('write', 'admin')]}
55
55
56 pod['other'] = 'none', 'override'
56 pod['other'] = 'none', 'override'
57
57
58 assert pod.perm_origin_stack == {
58 assert pod.perm_origin_stack == {
59 'other': [('write', 'default'), ('none', 'override')],
59 'other': [('write', 'default'), ('none', 'override')],
60 'thing': [('read', 'default'), ('write', 'admin')]}
60 'thing': [('read', 'default'), ('write', 'admin')]}
61
61
62 with pytest.raises(ValueError):
62 with pytest.raises(ValueError):
63 pod['thing'] = 'read'
63 pod['thing'] = 'read'
64
64
65
65
66 def test_cached_perms_data(user_regular, backend_random):
66 def test_cached_perms_data(user_regular, backend_random):
67 permissions = get_permissions(user_regular)
67 permissions = get_permissions(user_regular)
68 repo_name = backend_random.repo.repo_name
68 repo_name = backend_random.repo.repo_name
69 expected_global_permissions = {
69 expected_global_permissions = {
70 'repository.read', 'group.read', 'usergroup.read'}
70 'repository.read', 'group.read', 'usergroup.read'}
71 assert expected_global_permissions.issubset(permissions['global'])
71 assert expected_global_permissions.issubset(permissions['global'])
72 assert permissions['repositories'][repo_name] == 'repository.read'
72 assert permissions['repositories'][repo_name] == 'repository.read'
73
73
74
74
75 def test_cached_perms_data_with_admin_user(user_regular, backend_random):
75 def test_cached_perms_data_with_admin_user(user_regular, backend_random):
76 permissions = get_permissions(user_regular, user_is_admin=True)
76 permissions = get_permissions(user_regular, user_is_admin=True)
77 repo_name = backend_random.repo.repo_name
77 repo_name = backend_random.repo.repo_name
78 assert 'hg.admin' in permissions['global']
78 assert 'hg.admin' in permissions['global']
79 assert permissions['repositories'][repo_name] == 'repository.admin'
79 assert permissions['repositories'][repo_name] == 'repository.admin'
80
80
81
81
82 def test_cached_perms_data_with_admin_user_extended_calculation(user_regular, backend_random):
82 def test_cached_perms_data_with_admin_user_extended_calculation(user_regular, backend_random):
83 permissions = get_permissions(user_regular, user_is_admin=True,
83 permissions = get_permissions(user_regular, user_is_admin=True,
84 calculate_super_admin=True)
84 calculate_super_admin=True)
85 repo_name = backend_random.repo.repo_name
85 repo_name = backend_random.repo.repo_name
86 assert 'hg.admin' in permissions['global']
86 assert 'hg.admin' in permissions['global']
87 assert permissions['repositories'][repo_name] == 'repository.admin'
87 assert permissions['repositories'][repo_name] == 'repository.admin'
88
88
89
89
90 def test_cached_perms_data_user_group_global_permissions(user_util):
90 def test_cached_perms_data_user_group_global_permissions(user_util):
91 user, user_group = user_util.create_user_with_group()
91 user, user_group = user_util.create_user_with_group()
92 user_group.inherit_default_permissions = False
92 user_group.inherit_default_permissions = False
93
93
94 granted_permission = 'repository.write'
94 granted_permission = 'repository.write'
95 UserGroupModel().grant_perm(user_group, granted_permission)
95 UserGroupModel().grant_perm(user_group, granted_permission)
96
96
97 permissions = get_permissions(user)
97 permissions = get_permissions(user)
98 assert granted_permission in permissions['global']
98 assert granted_permission in permissions['global']
99
99
100
100
101 @pytest.mark.xfail(reason="Not implemented, see TODO note")
101 @pytest.mark.xfail(reason="Not implemented, see TODO note")
102 def test_cached_perms_data_user_group_global_permissions_(user_util):
102 def test_cached_perms_data_user_group_global_permissions_(user_util):
103 user, user_group = user_util.create_user_with_group()
103 user, user_group = user_util.create_user_with_group()
104
104
105 granted_permission = 'repository.write'
105 granted_permission = 'repository.write'
106 UserGroupModel().grant_perm(user_group, granted_permission)
106 UserGroupModel().grant_perm(user_group, granted_permission)
107
107
108 permissions = get_permissions(user)
108 permissions = get_permissions(user)
109 assert granted_permission in permissions['global']
109 assert granted_permission in permissions['global']
110
110
111
111
112 def test_cached_perms_data_user_global_permissions(user_util):
112 def test_cached_perms_data_user_global_permissions(user_util):
113 user = user_util.create_user()
113 user = user_util.create_user()
114 UserModel().grant_perm(user, 'repository.none')
114 UserModel().grant_perm(user, 'repository.none')
115
115
116 permissions = get_permissions(user, user_inherit_default_permissions=True)
116 permissions = get_permissions(user, user_inherit_default_permissions=True)
117 assert 'repository.read' in permissions['global']
117 assert 'repository.read' in permissions['global']
118
118
119
119
120 def test_cached_perms_data_repository_permissions_on_private_repository(
120 def test_cached_perms_data_repository_permissions_on_private_repository(
121 backend_random, user_util):
121 backend_random, user_util):
122 user, user_group = user_util.create_user_with_group()
122 user, user_group = user_util.create_user_with_group()
123
123
124 repo = backend_random.create_repo()
124 repo = backend_random.create_repo()
125 repo.private = True
125 repo.private = True
126
126
127 granted_permission = 'repository.write'
127 granted_permission = 'repository.write'
128 RepoModel().grant_user_group_permission(
128 RepoModel().grant_user_group_permission(
129 repo, user_group.users_group_name, granted_permission)
129 repo, user_group.users_group_name, granted_permission)
130
130
131 permissions = get_permissions(user)
131 permissions = get_permissions(user)
132 assert permissions['repositories'][repo.repo_name] == granted_permission
132 assert permissions['repositories'][repo.repo_name] == granted_permission
133
133
134
134
135 def test_cached_perms_data_repository_permissions_for_owner(
135 def test_cached_perms_data_repository_permissions_for_owner(
136 backend_random, user_util):
136 backend_random, user_util):
137 user = user_util.create_user()
137 user = user_util.create_user()
138
138
139 repo = backend_random.create_repo()
139 repo = backend_random.create_repo()
140 repo.user_id = user.user_id
140 repo.user_id = user.user_id
141
141
142 permissions = get_permissions(user)
142 permissions = get_permissions(user)
143 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
143 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
144
144
145 # TODO: johbo: Make cleanup in UserUtility smarter, then remove this hack
145 # TODO: johbo: Make cleanup in UserUtility smarter, then remove this hack
146 repo.user_id = User.get_default_user().user_id
146 repo.user_id = User.get_default_user().user_id
147
147
148
148
149 def test_cached_perms_data_repository_permissions_not_inheriting_defaults(
149 def test_cached_perms_data_repository_permissions_not_inheriting_defaults(
150 backend_random, user_util):
150 backend_random, user_util):
151 user = user_util.create_user()
151 user = user_util.create_user()
152 repo = backend_random.create_repo()
152 repo = backend_random.create_repo()
153
153
154 # Don't inherit default object permissions
154 # Don't inherit default object permissions
155 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
155 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
156
156
157 permissions = get_permissions(user)
157 permissions = get_permissions(user)
158 assert permissions['repositories'][repo.repo_name] == 'repository.none'
158 assert permissions['repositories'][repo.repo_name] == 'repository.none'
159
159
160
160
161 def test_cached_perms_data_default_permissions_on_repository_group(user_util):
161 def test_cached_perms_data_default_permissions_on_repository_group(user_util):
162 # Have a repository group with default permissions set
162 # Have a repository group with default permissions set
163 repo_group = user_util.create_repo_group()
163 repo_group = user_util.create_repo_group()
164 default_user = User.get_default_user()
164 default_user = User.get_default_user()
165 user_util.grant_user_permission_to_repo_group(
165 user_util.grant_user_permission_to_repo_group(
166 repo_group, default_user, 'repository.write')
166 repo_group, default_user, 'repository.write')
167 user = user_util.create_user()
167 user = user_util.create_user()
168
168
169 permissions = get_permissions(user)
169 permissions = get_permissions(user)
170 assert permissions['repositories_groups'][repo_group.group_name] == \
170 assert permissions['repositories_groups'][repo_group.group_name] == \
171 'repository.write'
171 'repository.write'
172
172
173
173
174 def test_cached_perms_data_default_permissions_on_repository_group_owner(
174 def test_cached_perms_data_default_permissions_on_repository_group_owner(
175 user_util):
175 user_util):
176 # Have a repository group
176 # Have a repository group
177 repo_group = user_util.create_repo_group()
177 repo_group = user_util.create_repo_group()
178 default_user = User.get_default_user()
178 default_user = User.get_default_user()
179
179
180 # Add a permission for the default user to hit the code path
180 # Add a permission for the default user to hit the code path
181 user_util.grant_user_permission_to_repo_group(
181 user_util.grant_user_permission_to_repo_group(
182 repo_group, default_user, 'repository.write')
182 repo_group, default_user, 'repository.write')
183
183
184 # Have an owner of the group
184 # Have an owner of the group
185 user = user_util.create_user()
185 user = user_util.create_user()
186 repo_group.user_id = user.user_id
186 repo_group.user_id = user.user_id
187
187
188 permissions = get_permissions(user)
188 permissions = get_permissions(user)
189 assert permissions['repositories_groups'][repo_group.group_name] == \
189 assert permissions['repositories_groups'][repo_group.group_name] == \
190 'group.admin'
190 'group.admin'
191
191
192
192
193 def test_cached_perms_data_default_permissions_on_repository_group_no_inherit(
193 def test_cached_perms_data_default_permissions_on_repository_group_no_inherit(
194 user_util):
194 user_util):
195 # Have a repository group
195 # Have a repository group
196 repo_group = user_util.create_repo_group()
196 repo_group = user_util.create_repo_group()
197 default_user = User.get_default_user()
197 default_user = User.get_default_user()
198
198
199 # Add a permission for the default user to hit the code path
199 # Add a permission for the default user to hit the code path
200 user_util.grant_user_permission_to_repo_group(
200 user_util.grant_user_permission_to_repo_group(
201 repo_group, default_user, 'repository.write')
201 repo_group, default_user, 'repository.write')
202
202
203 # Don't inherit default object permissions
203 # Don't inherit default object permissions
204 user = user_util.create_user()
204 user = user_util.create_user()
205 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
205 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
206
206
207 permissions = get_permissions(user)
207 permissions = get_permissions(user)
208 assert permissions['repositories_groups'][repo_group.group_name] == \
208 assert permissions['repositories_groups'][repo_group.group_name] == \
209 'group.none'
209 'group.none'
210
210
211
211
212 def test_cached_perms_data_repository_permissions_from_user_group(
212 def test_cached_perms_data_repository_permissions_from_user_group(
213 user_util, backend_random):
213 user_util, backend_random):
214 user, user_group = user_util.create_user_with_group()
214 user, user_group = user_util.create_user_with_group()
215
215
216 # Needs a second user group to make sure that we select the right
216 # Needs a second user group to make sure that we select the right
217 # permissions.
217 # permissions.
218 user_group2 = user_util.create_user_group()
218 user_group2 = user_util.create_user_group()
219 UserGroupModel().add_user_to_group(user_group2, user)
219 UserGroupModel().add_user_to_group(user_group2, user)
220
220
221 repo = backend_random.create_repo()
221 repo = backend_random.create_repo()
222
222
223 RepoModel().grant_user_group_permission(
223 RepoModel().grant_user_group_permission(
224 repo, user_group.users_group_name, 'repository.read')
224 repo, user_group.users_group_name, 'repository.read')
225 RepoModel().grant_user_group_permission(
225 RepoModel().grant_user_group_permission(
226 repo, user_group2.users_group_name, 'repository.write')
226 repo, user_group2.users_group_name, 'repository.write')
227
227
228 permissions = get_permissions(user)
228 permissions = get_permissions(user)
229 assert permissions['repositories'][repo.repo_name] == 'repository.write'
229 assert permissions['repositories'][repo.repo_name] == 'repository.write'
230
230
231
231
232 def test_cached_perms_data_repository_permissions_from_user_group_owner(
232 def test_cached_perms_data_repository_permissions_from_user_group_owner(
233 user_util, backend_random):
233 user_util, backend_random):
234 user, user_group = user_util.create_user_with_group()
234 user, user_group = user_util.create_user_with_group()
235
235
236 repo = backend_random.create_repo()
236 repo = backend_random.create_repo()
237 repo.user_id = user.user_id
237 repo.user_id = user.user_id
238
238
239 RepoModel().grant_user_group_permission(
239 RepoModel().grant_user_group_permission(
240 repo, user_group.users_group_name, 'repository.write')
240 repo, user_group.users_group_name, 'repository.write')
241
241
242 permissions = get_permissions(user)
242 permissions = get_permissions(user)
243 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
243 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
244
244
245
245
246 def test_cached_perms_data_user_repository_permissions(
246 def test_cached_perms_data_user_repository_permissions(
247 user_util, backend_random):
247 user_util, backend_random):
248 user = user_util.create_user()
248 user = user_util.create_user()
249 repo = backend_random.create_repo()
249 repo = backend_random.create_repo()
250 granted_permission = 'repository.write'
250 granted_permission = 'repository.write'
251 RepoModel().grant_user_permission(repo, user, granted_permission)
251 RepoModel().grant_user_permission(repo, user, granted_permission)
252
252
253 permissions = get_permissions(user)
253 permissions = get_permissions(user)
254 assert permissions['repositories'][repo.repo_name] == granted_permission
254 assert permissions['repositories'][repo.repo_name] == granted_permission
255
255
256
256
257 def test_cached_perms_data_user_repository_permissions_explicit(
257 def test_cached_perms_data_user_repository_permissions_explicit(
258 user_util, backend_random):
258 user_util, backend_random):
259 user = user_util.create_user()
259 user = user_util.create_user()
260 repo = backend_random.create_repo()
260 repo = backend_random.create_repo()
261 granted_permission = 'repository.none'
261 granted_permission = 'repository.none'
262 RepoModel().grant_user_permission(repo, user, granted_permission)
262 RepoModel().grant_user_permission(repo, user, granted_permission)
263
263
264 permissions = get_permissions(user, explicit=True)
264 permissions = get_permissions(user, explicit=True)
265 assert permissions['repositories'][repo.repo_name] == granted_permission
265 assert permissions['repositories'][repo.repo_name] == granted_permission
266
266
267
267
268 def test_cached_perms_data_user_repository_permissions_owner(
268 def test_cached_perms_data_user_repository_permissions_owner(
269 user_util, backend_random):
269 user_util, backend_random):
270 user = user_util.create_user()
270 user = user_util.create_user()
271 repo = backend_random.create_repo()
271 repo = backend_random.create_repo()
272 repo.user_id = user.user_id
272 repo.user_id = user.user_id
273 RepoModel().grant_user_permission(repo, user, 'repository.write')
273 RepoModel().grant_user_permission(repo, user, 'repository.write')
274
274
275 permissions = get_permissions(user)
275 permissions = get_permissions(user)
276 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
276 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
277
277
278
278
279 def test_cached_perms_data_repository_groups_permissions_inherited(
279 def test_cached_perms_data_repository_groups_permissions_inherited(
280 user_util, backend_random):
280 user_util, backend_random):
281 user, user_group = user_util.create_user_with_group()
281 user, user_group = user_util.create_user_with_group()
282
282
283 # Needs a second group to hit the last condition
283 # Needs a second group to hit the last condition
284 user_group2 = user_util.create_user_group()
284 user_group2 = user_util.create_user_group()
285 UserGroupModel().add_user_to_group(user_group2, user)
285 UserGroupModel().add_user_to_group(user_group2, user)
286
286
287 repo_group = user_util.create_repo_group()
287 repo_group = user_util.create_repo_group()
288
288
289 user_util.grant_user_group_permission_to_repo_group(
289 user_util.grant_user_group_permission_to_repo_group(
290 repo_group, user_group, 'group.read')
290 repo_group, user_group, 'group.read')
291 user_util.grant_user_group_permission_to_repo_group(
291 user_util.grant_user_group_permission_to_repo_group(
292 repo_group, user_group2, 'group.write')
292 repo_group, user_group2, 'group.write')
293
293
294 permissions = get_permissions(user)
294 permissions = get_permissions(user)
295 assert permissions['repositories_groups'][repo_group.group_name] == \
295 assert permissions['repositories_groups'][repo_group.group_name] == \
296 'group.write'
296 'group.write'
297
297
298
298
299 def test_cached_perms_data_repository_groups_permissions_inherited_owner(
299 def test_cached_perms_data_repository_groups_permissions_inherited_owner(
300 user_util, backend_random):
300 user_util, backend_random):
301 user, user_group = user_util.create_user_with_group()
301 user, user_group = user_util.create_user_with_group()
302 repo_group = user_util.create_repo_group()
302 repo_group = user_util.create_repo_group()
303 repo_group.user_id = user.user_id
303 repo_group.user_id = user.user_id
304
304
305 granted_permission = 'group.write'
305 granted_permission = 'group.write'
306 user_util.grant_user_group_permission_to_repo_group(
306 user_util.grant_user_group_permission_to_repo_group(
307 repo_group, user_group, granted_permission)
307 repo_group, user_group, granted_permission)
308
308
309 permissions = get_permissions(user)
309 permissions = get_permissions(user)
310 assert permissions['repositories_groups'][repo_group.group_name] == \
310 assert permissions['repositories_groups'][repo_group.group_name] == \
311 'group.admin'
311 'group.admin'
312
312
313
313
314 def test_cached_perms_data_repository_groups_permissions(
314 def test_cached_perms_data_repository_groups_permissions(
315 user_util, backend_random):
315 user_util, backend_random):
316 user = user_util.create_user()
316 user = user_util.create_user()
317
317
318 repo_group = user_util.create_repo_group()
318 repo_group = user_util.create_repo_group()
319
319
320 granted_permission = 'group.write'
320 granted_permission = 'group.write'
321 user_util.grant_user_permission_to_repo_group(
321 user_util.grant_user_permission_to_repo_group(
322 repo_group, user, granted_permission)
322 repo_group, user, granted_permission)
323
323
324 permissions = get_permissions(user)
324 permissions = get_permissions(user)
325 assert permissions['repositories_groups'][repo_group.group_name] == \
325 assert permissions['repositories_groups'][repo_group.group_name] == \
326 'group.write'
326 'group.write'
327
327
328
328
329 def test_cached_perms_data_repository_groups_permissions_explicit(
329 def test_cached_perms_data_repository_groups_permissions_explicit(
330 user_util, backend_random):
330 user_util, backend_random):
331 user = user_util.create_user()
331 user = user_util.create_user()
332
332
333 repo_group = user_util.create_repo_group()
333 repo_group = user_util.create_repo_group()
334
334
335 granted_permission = 'group.none'
335 granted_permission = 'group.none'
336 user_util.grant_user_permission_to_repo_group(
336 user_util.grant_user_permission_to_repo_group(
337 repo_group, user, granted_permission)
337 repo_group, user, granted_permission)
338
338
339 permissions = get_permissions(user, explicit=True)
339 permissions = get_permissions(user, explicit=True)
340 assert permissions['repositories_groups'][repo_group.group_name] == \
340 assert permissions['repositories_groups'][repo_group.group_name] == \
341 'group.none'
341 'group.none'
342
342
343
343
344 def test_cached_perms_data_repository_groups_permissions_owner(
344 def test_cached_perms_data_repository_groups_permissions_owner(
345 user_util, backend_random):
345 user_util, backend_random):
346 user = user_util.create_user()
346 user = user_util.create_user()
347
347
348 repo_group = user_util.create_repo_group()
348 repo_group = user_util.create_repo_group()
349 repo_group.user_id = user.user_id
349 repo_group.user_id = user.user_id
350
350
351 granted_permission = 'group.write'
351 granted_permission = 'group.write'
352 user_util.grant_user_permission_to_repo_group(
352 user_util.grant_user_permission_to_repo_group(
353 repo_group, user, granted_permission)
353 repo_group, user, granted_permission)
354
354
355 permissions = get_permissions(user)
355 permissions = get_permissions(user)
356 assert permissions['repositories_groups'][repo_group.group_name] == \
356 assert permissions['repositories_groups'][repo_group.group_name] == \
357 'group.admin'
357 'group.admin'
358
358
359
359
360 def test_cached_perms_data_user_group_permissions_inherited(
360 def test_cached_perms_data_user_group_permissions_inherited(
361 user_util, backend_random):
361 user_util, backend_random):
362 user, user_group = user_util.create_user_with_group()
362 user, user_group = user_util.create_user_with_group()
363 user_group2 = user_util.create_user_group()
363 user_group2 = user_util.create_user_group()
364 UserGroupModel().add_user_to_group(user_group2, user)
364 UserGroupModel().add_user_to_group(user_group2, user)
365
365
366 target_user_group = user_util.create_user_group()
366 target_user_group = user_util.create_user_group()
367
367
368 user_util.grant_user_group_permission_to_user_group(
368 user_util.grant_user_group_permission_to_user_group(
369 target_user_group, user_group, 'usergroup.read')
369 target_user_group, user_group, 'usergroup.read')
370 user_util.grant_user_group_permission_to_user_group(
370 user_util.grant_user_group_permission_to_user_group(
371 target_user_group, user_group2, 'usergroup.write')
371 target_user_group, user_group2, 'usergroup.write')
372
372
373 permissions = get_permissions(user)
373 permissions = get_permissions(user)
374 assert permissions['user_groups'][target_user_group.users_group_name] == \
374 assert permissions['user_groups'][target_user_group.users_group_name] == \
375 'usergroup.write'
375 'usergroup.write'
376
376
377
377
378 def test_cached_perms_data_user_group_permissions(
378 def test_cached_perms_data_user_group_permissions(
379 user_util, backend_random):
379 user_util, backend_random):
380 user = user_util.create_user()
380 user = user_util.create_user()
381 user_group = user_util.create_user_group()
381 user_group = user_util.create_user_group()
382 UserGroupModel().grant_user_permission(user_group, user, 'usergroup.write')
382 UserGroupModel().grant_user_permission(user_group, user, 'usergroup.write')
383
383
384 permissions = get_permissions(user)
384 permissions = get_permissions(user)
385 assert permissions['user_groups'][user_group.users_group_name] == \
385 assert permissions['user_groups'][user_group.users_group_name] == \
386 'usergroup.write'
386 'usergroup.write'
387
387
388
388
389 def test_cached_perms_data_user_group_permissions_explicit(
389 def test_cached_perms_data_user_group_permissions_explicit(
390 user_util, backend_random):
390 user_util, backend_random):
391 user = user_util.create_user()
391 user = user_util.create_user()
392 user_group = user_util.create_user_group()
392 user_group = user_util.create_user_group()
393 UserGroupModel().grant_user_permission(user_group, user, 'usergroup.none')
393 UserGroupModel().grant_user_permission(user_group, user, 'usergroup.none')
394
394
395 permissions = get_permissions(user, explicit=True)
395 permissions = get_permissions(user, explicit=True)
396 assert permissions['user_groups'][user_group.users_group_name] == \
396 assert permissions['user_groups'][user_group.users_group_name] == \
397 'usergroup.none'
397 'usergroup.none'
398
398
399
399
400 def test_cached_perms_data_user_group_permissions_not_inheriting_defaults(
400 def test_cached_perms_data_user_group_permissions_not_inheriting_defaults(
401 user_util, backend_random):
401 user_util, backend_random):
402 user = user_util.create_user()
402 user = user_util.create_user()
403 user_group = user_util.create_user_group()
403 user_group = user_util.create_user_group()
404
404
405 # Don't inherit default object permissions
405 # Don't inherit default object permissions
406 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
406 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
407
407
408 permissions = get_permissions(user)
408 permissions = get_permissions(user)
409 assert permissions['user_groups'][user_group.users_group_name] == \
409 assert permissions['user_groups'][user_group.users_group_name] == \
410 'usergroup.none'
410 'usergroup.none'
411
411
412
412
413 def test_permission_calculator_admin_permissions(
413 def test_permission_calculator_admin_permissions(
414 user_util, backend_random):
414 user_util, backend_random):
415 user = user_util.create_user()
415 user = user_util.create_user()
416 user_group = user_util.create_user_group()
416 user_group = user_util.create_user_group()
417 repo = backend_random.repo
417 repo = backend_random.repo
418 repo_group = user_util.create_repo_group()
418 repo_group = user_util.create_repo_group()
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'
426 assert permissions['user_groups'][user_group.users_group_name] == \
426 assert permissions['user_groups'][user_group.users_group_name] == \
427 'usergroup.admin'
427 'usergroup.admin'
428 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
428 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
429 assert 'hg.admin' in permissions['global']
429 assert 'hg.admin' in permissions['global']
430
430
431
431
432 def test_permission_calculator_repository_permissions_robustness_from_group(
432 def test_permission_calculator_repository_permissions_robustness_from_group(
433 user_util, backend_random):
433 user_util, backend_random):
434 user, user_group = user_util.create_user_with_group()
434 user, user_group = user_util.create_user_with_group()
435
435
436 RepoModel().grant_user_group_permission(
436 RepoModel().grant_user_group_permission(
437 backend_random.repo, user_group.users_group_name, 'repository.write')
437 backend_random.repo, user_group.users_group_name, 'repository.write')
438
438
439 calculator = auth.PermissionCalculator(
439 calculator = auth.PermissionCalculator(
440 user.user_id, {}, False, False, False, 'higherwin')
440 user.user_id, {}, False, False, False, 'higherwin')
441 calculator._calculate_repository_permissions()
441 calculator._calculate_repository_permissions()
442
442
443
443
444 def test_permission_calculator_repository_permissions_robustness_from_user(
444 def test_permission_calculator_repository_permissions_robustness_from_user(
445 user_util, backend_random):
445 user_util, backend_random):
446 user = user_util.create_user()
446 user = user_util.create_user()
447
447
448 RepoModel().grant_user_permission(
448 RepoModel().grant_user_permission(
449 backend_random.repo, user, 'repository.write')
449 backend_random.repo, user, 'repository.write')
450
450
451 calculator = auth.PermissionCalculator(
451 calculator = auth.PermissionCalculator(
452 user.user_id, {}, False, False, False, 'higherwin')
452 user.user_id, {}, False, False, False, 'higherwin')
453 calculator._calculate_repository_permissions()
453 calculator._calculate_repository_permissions()
454
454
455
455
456 def test_permission_calculator_repo_group_permissions_robustness_from_group(
456 def test_permission_calculator_repo_group_permissions_robustness_from_group(
457 user_util, backend_random):
457 user_util, backend_random):
458 user, user_group = user_util.create_user_with_group()
458 user, user_group = user_util.create_user_with_group()
459 repo_group = user_util.create_repo_group()
459 repo_group = user_util.create_repo_group()
460
460
461 user_util.grant_user_group_permission_to_repo_group(
461 user_util.grant_user_group_permission_to_repo_group(
462 repo_group, user_group, 'group.write')
462 repo_group, user_group, 'group.write')
463
463
464 calculator = auth.PermissionCalculator(
464 calculator = auth.PermissionCalculator(
465 user.user_id, {}, False, False, False, 'higherwin')
465 user.user_id, {}, False, False, False, 'higherwin')
466 calculator._calculate_repository_group_permissions()
466 calculator._calculate_repository_group_permissions()
467
467
468
468
469 def test_permission_calculator_repo_group_permissions_robustness_from_user(
469 def test_permission_calculator_repo_group_permissions_robustness_from_user(
470 user_util, backend_random):
470 user_util, backend_random):
471 user = user_util.create_user()
471 user = user_util.create_user()
472 repo_group = user_util.create_repo_group()
472 repo_group = user_util.create_repo_group()
473
473
474 user_util.grant_user_permission_to_repo_group(
474 user_util.grant_user_permission_to_repo_group(
475 repo_group, user, 'group.write')
475 repo_group, user, 'group.write')
476
476
477 calculator = auth.PermissionCalculator(
477 calculator = auth.PermissionCalculator(
478 user.user_id, {}, False, False, False, 'higherwin')
478 user.user_id, {}, False, False, False, 'higherwin')
479 calculator._calculate_repository_group_permissions()
479 calculator._calculate_repository_group_permissions()
480
480
481
481
482 def test_permission_calculator_user_group_permissions_robustness_from_group(
482 def test_permission_calculator_user_group_permissions_robustness_from_group(
483 user_util, backend_random):
483 user_util, backend_random):
484 user, user_group = user_util.create_user_with_group()
484 user, user_group = user_util.create_user_with_group()
485 target_user_group = user_util.create_user_group()
485 target_user_group = user_util.create_user_group()
486
486
487 user_util.grant_user_group_permission_to_user_group(
487 user_util.grant_user_group_permission_to_user_group(
488 target_user_group, user_group, 'usergroup.write')
488 target_user_group, user_group, 'usergroup.write')
489
489
490 calculator = auth.PermissionCalculator(
490 calculator = auth.PermissionCalculator(
491 user.user_id, {}, False, False, False, 'higherwin')
491 user.user_id, {}, False, False, False, 'higherwin')
492 calculator._calculate_user_group_permissions()
492 calculator._calculate_user_group_permissions()
493
493
494
494
495 def test_permission_calculator_user_group_permissions_robustness_from_user(
495 def test_permission_calculator_user_group_permissions_robustness_from_user(
496 user_util, backend_random):
496 user_util, backend_random):
497 user = user_util.create_user()
497 user = user_util.create_user()
498 target_user_group = user_util.create_user_group()
498 target_user_group = user_util.create_user_group()
499
499
500 user_util.grant_user_permission_to_user_group(
500 user_util.grant_user_permission_to_user_group(
501 target_user_group, user, 'usergroup.write')
501 target_user_group, user, 'usergroup.write')
502
502
503 calculator = auth.PermissionCalculator(
503 calculator = auth.PermissionCalculator(
504 user.user_id, {}, False, False, False, 'higherwin')
504 user.user_id, {}, False, False, False, 'higherwin')
505 calculator._calculate_user_group_permissions()
505 calculator._calculate_user_group_permissions()
506
506
507
507
508 @pytest.mark.parametrize("algo, new_permission, old_permission, expected", [
508 @pytest.mark.parametrize("algo, new_permission, old_permission, expected", [
509 ('higherwin', 'repository.none', 'repository.none', 'repository.none'),
509 ('higherwin', 'repository.none', 'repository.none', 'repository.none'),
510 ('higherwin', 'repository.read', 'repository.none', 'repository.read'),
510 ('higherwin', 'repository.read', 'repository.none', 'repository.read'),
511 ('lowerwin', 'repository.write', 'repository.write', 'repository.write'),
511 ('lowerwin', 'repository.write', 'repository.write', 'repository.write'),
512 ('lowerwin', 'repository.read', 'repository.write', 'repository.read'),
512 ('lowerwin', 'repository.read', 'repository.write', 'repository.read'),
513 ])
513 ])
514 def test_permission_calculator_choose_permission(
514 def test_permission_calculator_choose_permission(
515 user_regular, algo, new_permission, old_permission, expected):
515 user_regular, algo, new_permission, old_permission, expected):
516 calculator = auth.PermissionCalculator(
516 calculator = auth.PermissionCalculator(
517 user_regular.user_id, {}, False, False, False, algo)
517 user_regular.user_id, {}, False, False, False, algo)
518 result = calculator._choose_permission(new_permission, old_permission)
518 result = calculator._choose_permission(new_permission, old_permission)
519 assert result == expected
519 assert result == expected
520
520
521
521
522 def test_permission_calculator_choose_permission_raises_on_wrong_algo(
522 def test_permission_calculator_choose_permission_raises_on_wrong_algo(
523 user_regular):
523 user_regular):
524 calculator = auth.PermissionCalculator(
524 calculator = auth.PermissionCalculator(
525 user_regular.user_id, {}, False, False, False, 'invalid')
525 user_regular.user_id, {}, False, False, False, 'invalid')
526 result = calculator._choose_permission(
526 result = calculator._choose_permission(
527 'repository.read', 'repository.read')
527 'repository.read', 'repository.read')
528 # TODO: johbo: This documents the existing behavior. Think of an
528 # TODO: johbo: This documents the existing behavior. Think of an
529 # improvement.
529 # improvement.
530 assert result is None
530 assert result is None
531
531
532
532
533 def test_auth_user_get_cookie_store_for_normal_user(user_util):
533 def test_auth_user_get_cookie_store_for_normal_user(user_util):
534 user = user_util.create_user()
534 user = user_util.create_user()
535 auth_user = auth.AuthUser(user_id=user.user_id)
535 auth_user = auth.AuthUser(user_id=user.user_id)
536 expected_data = {
536 expected_data = {
537 'username': user.username,
537 'username': user.username,
538 'user_id': user.user_id,
538 'user_id': user.user_id,
539 'password': md5(user.password),
539 'password': md5(user.password),
540 'is_authenticated': False
540 'is_authenticated': False
541 }
541 }
542 assert auth_user.get_cookie_store() == expected_data
542 assert auth_user.get_cookie_store() == expected_data
543
543
544
544
545 def test_auth_user_get_cookie_store_for_default_user():
545 def test_auth_user_get_cookie_store_for_default_user():
546 default_user = User.get_default_user()
546 default_user = User.get_default_user()
547 auth_user = auth.AuthUser()
547 auth_user = auth.AuthUser()
548 expected_data = {
548 expected_data = {
549 'username': User.DEFAULT_USER,
549 'username': User.DEFAULT_USER,
550 'user_id': default_user.user_id,
550 'user_id': default_user.user_id,
551 'password': md5(default_user.password),
551 'password': md5(default_user.password),
552 'is_authenticated': True
552 'is_authenticated': True
553 }
553 }
554 assert auth_user.get_cookie_store() == expected_data
554 assert auth_user.get_cookie_store() == expected_data
555
555
556
556
557 def get_permissions(user, **kwargs):
557 def get_permissions(user, **kwargs):
558 """
558 """
559 Utility filling in useful defaults into the call to `_cached_perms_data`.
559 Utility filling in useful defaults into the call to `_cached_perms_data`.
560
560
561 Fill in `**kwargs` if specific values are needed for a test.
561 Fill in `**kwargs` if specific values are needed for a test.
562 """
562 """
563 call_args = {
563 call_args = {
564 'user_id': user.user_id,
564 'user_id': user.user_id,
565 'scope': {},
565 'scope': {},
566 'user_is_admin': False,
566 'user_is_admin': False,
567 'user_inherit_default_permissions': False,
567 'user_inherit_default_permissions': False,
568 'explicit': False,
568 'explicit': False,
569 'algo': 'higherwin',
569 'algo': 'higherwin',
570 'calculate_super_admin': False,
570 'calculate_super_admin': False,
571 }
571 }
572 call_args.update(kwargs)
572 call_args.update(kwargs)
573 permissions = auth._cached_perms_data(**call_args)
573 permissions = auth._cached_perms_data(**call_args)
574 return permissions
574 return permissions
575
575
576
576
577 class TestGenerateAuthToken(object):
577 class TestGenerateAuthToken(object):
578 def test_salt_is_used_when_specified(self):
578 def test_salt_is_used_when_specified(self):
579 salt = 'abcde'
579 salt = 'abcde'
580 user_name = 'test_user'
580 user_name = 'test_user'
581 result = auth.generate_auth_token(user_name, salt)
581 result = auth.generate_auth_token(user_name, salt)
582 expected_result = sha1(user_name + salt).hexdigest()
582 expected_result = sha1(user_name + salt).hexdigest()
583 assert result == expected_result
583 assert result == expected_result
584
584
585 def test_salt_is_geneated_when_not_specified(self):
585 def test_salt_is_geneated_when_not_specified(self):
586 user_name = 'test_user'
586 user_name = 'test_user'
587 random_salt = os.urandom(16)
587 random_salt = os.urandom(16)
588 with patch.object(auth, 'os') as os_mock:
588 with patch.object(auth, 'os') as os_mock:
589 os_mock.urandom.return_value = random_salt
589 os_mock.urandom.return_value = random_salt
590 result = auth.generate_auth_token(user_name)
590 result = auth.generate_auth_token(user_name)
591 expected_result = sha1(user_name + random_salt).hexdigest()
591 expected_result = sha1(user_name + random_salt).hexdigest()
592 assert result == expected_result
592 assert result == expected_result
593
593
594
594
595 @pytest.mark.parametrize("test_token, test_roles, auth_result, expected_tokens", [
595 @pytest.mark.parametrize("test_token, test_roles, auth_result, expected_tokens", [
596 ('', None, False,
596 ('', None, False,
597 []),
597 []),
598 ('wrongtoken', None, False,
598 ('wrongtoken', None, False,
599 []),
599 []),
600 ('abracadabra_vcs', [AuthTokenModel.cls.ROLE_API], False,
600 ('abracadabra_vcs', [AuthTokenModel.cls.ROLE_API], False,
601 [('abracadabra_api', AuthTokenModel.cls.ROLE_API, -1)]),
601 [('abracadabra_api', AuthTokenModel.cls.ROLE_API, -1)]),
602 ('abracadabra_api', [AuthTokenModel.cls.ROLE_API], True,
602 ('abracadabra_api', [AuthTokenModel.cls.ROLE_API], True,
603 [('abracadabra_api', AuthTokenModel.cls.ROLE_API, -1)]),
603 [('abracadabra_api', AuthTokenModel.cls.ROLE_API, -1)]),
604 ('abracadabra_api', [AuthTokenModel.cls.ROLE_API], True,
604 ('abracadabra_api', [AuthTokenModel.cls.ROLE_API], True,
605 [('abracadabra_api', AuthTokenModel.cls.ROLE_API, -1),
605 [('abracadabra_api', AuthTokenModel.cls.ROLE_API, -1),
606 ('abracadabra_http', AuthTokenModel.cls.ROLE_HTTP, -1)]),
606 ('abracadabra_http', AuthTokenModel.cls.ROLE_HTTP, -1)]),
607 ])
607 ])
608 def test_auth_by_token(test_token, test_roles, auth_result, expected_tokens,
608 def test_auth_by_token(test_token, test_roles, auth_result, expected_tokens,
609 user_util):
609 user_util):
610 user = user_util.create_user()
610 user = user_util.create_user()
611 user_id = user.user_id
611 user_id = user.user_id
612 for token, role, expires in expected_tokens:
612 for token, role, expires in expected_tokens:
613 new_token = AuthTokenModel().create(user_id, 'test-token', expires, role)
613 new_token = AuthTokenModel().create(user_id, 'test-token', expires, role)
614 new_token.api_key = token # inject known name for testing...
614 new_token.api_key = token # inject known name for testing...
615
615
616 assert auth_result == user.authenticate_by_token(
616 assert auth_result == user.authenticate_by_token(
617 test_token, roles=test_roles)
617 test_token, roles=test_roles)
@@ -1,172 +1,193 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Base for test suite for making push/pull operations.
22 Base for test suite for making push/pull operations.
23
23
24 .. important::
24 .. important::
25
25
26 You must have git >= 1.8.5 for tests to work fine. With 68b939b git started
26 You must have git >= 1.8.5 for tests to work fine. With 68b939b git started
27 to redirect things to stderr instead of stdout.
27 to redirect things to stderr instead of stdout.
28 """
28 """
29
29
30 from os.path import join as jn
30 from os.path import join as jn
31 from subprocess32 import Popen, PIPE
31 from subprocess32 import Popen, PIPE
32 import logging
32 import logging
33 import os
33 import os
34 import tempfile
34 import tempfile
35
35
36 from rhodecode.tests import GIT_REPO, HG_REPO
36 from rhodecode.tests import GIT_REPO, HG_REPO
37
37
38 DEBUG = True
38 DEBUG = True
39 RC_LOG = os.path.join(tempfile.gettempdir(), 'rc.log')
39 RC_LOG = os.path.join(tempfile.gettempdir(), 'rc.log')
40 REPO_GROUP = 'a_repo_group'
40 REPO_GROUP = 'a_repo_group'
41 HG_REPO_WITH_GROUP = '%s/%s' % (REPO_GROUP, HG_REPO)
41 HG_REPO_WITH_GROUP = '%s/%s' % (REPO_GROUP, HG_REPO)
42 GIT_REPO_WITH_GROUP = '%s/%s' % (REPO_GROUP, GIT_REPO)
42 GIT_REPO_WITH_GROUP = '%s/%s' % (REPO_GROUP, GIT_REPO)
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46
46
47 class Command(object):
47 class Command(object):
48
48
49 def __init__(self, cwd):
49 def __init__(self, cwd):
50 self.cwd = cwd
50 self.cwd = cwd
51 self.process = None
51 self.process = None
52
52
53 def execute(self, cmd, *args):
53 def execute(self, cmd, *args):
54 """
54 """
55 Runs command on the system with given ``args``.
55 Runs command on the system with given ``args``.
56 """
56 """
57
57
58 command = cmd + ' ' + ' '.join(args)
58 command = cmd + ' ' + ' '.join(args)
59 if DEBUG:
59 if DEBUG:
60 log.debug('*** CMD %s ***' % (command,))
60 log.debug('*** CMD %s ***' % (command,))
61
61
62 env = dict(os.environ)
62 env = dict(os.environ)
63 # Delete coverage variables, as they make the test fail for Mercurial
63 # Delete coverage variables, as they make the test fail for Mercurial
64 for key in env.keys():
64 for key in env.keys():
65 if key.startswith('COV_CORE_'):
65 if key.startswith('COV_CORE_'):
66 del env[key]
66 del env[key]
67
67
68 self.process = Popen(command, shell=True, stdout=PIPE, stderr=PIPE,
68 self.process = Popen(command, shell=True, stdout=PIPE, stderr=PIPE,
69 cwd=self.cwd, env=env)
69 cwd=self.cwd, env=env)
70 stdout, stderr = self.process.communicate()
70 stdout, stderr = self.process.communicate()
71 if DEBUG:
71 if DEBUG:
72 log.debug('STDOUT:%s' % (stdout,))
72 log.debug('STDOUT:%s' % (stdout,))
73 log.debug('STDERR:%s' % (stderr,))
73 log.debug('STDERR:%s' % (stderr,))
74 return stdout, stderr
74 return stdout, stderr
75
75
76 def assert_returncode_success(self):
76 def assert_returncode_success(self):
77 assert self.process.returncode == 0
77 assert self.process.returncode == 0
78
78
79
79
80 def _add_files_and_push(vcs, dest, clone_url=None, tags=None, **kwargs):
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)
99 if vcs == 'hg':
95 if vcs == 'hg':
100 cmd = """hg commit -m 'commited new %s' -u '%s' %s """ % (
96 cmd = """hg commit -m 'commited new %s' -u '%s' %s """ % (
101 i, author_str, added_file
97 i, author_str, added_file
102 )
98 )
103 elif vcs == 'git':
99 elif vcs == 'git':
104 cmd = """%s && git commit -m 'commited new %s' %s""" % (
100 cmd = """%s && git commit -m 'commited new %s' %s""" % (
105 git_ident, i, added_file)
101 git_ident, i, added_file)
106 Command(cwd).execute(cmd)
102 Command(cwd).execute(cmd)
107
103
108 for tag in tags:
104 for tag in tags:
109 if vcs == 'hg':
105 if vcs == 'hg':
110 stdout, stderr = Command(cwd).execute(
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 """%s &&
152 """{} &&
132 git push --verbose --tags %s master""" % (
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
137
158
138 def _check_proper_git_push(
159 def _check_proper_git_push(
139 stdout, stderr, branch='master', should_set_default_branch=False):
160 stdout, stderr, branch='master', should_set_default_branch=False):
140 # Note: Git is writing most information to stderr intentionally
161 # Note: Git is writing most information to stderr intentionally
141 assert 'fatal' not in stderr
162 assert 'fatal' not in stderr
142 assert 'rejected' not in stderr
163 assert 'rejected' not in stderr
143 assert 'Pushing to' in stderr
164 assert 'Pushing to' in stderr
144 assert '%s -> %s' % (branch, branch) in stderr
165 assert '%s -> %s' % (branch, branch) in stderr
145
166
146 if should_set_default_branch:
167 if should_set_default_branch:
147 assert "Setting default branch to %s" % branch in stderr
168 assert "Setting default branch to %s" % branch in stderr
148 else:
169 else:
149 assert "Setting default branch" not in stderr
170 assert "Setting default branch" not in stderr
150
171
151
172
152 def _check_proper_hg_push(stdout, stderr, branch='default'):
173 def _check_proper_hg_push(stdout, stderr, branch='default'):
153 assert 'pushing to' in stdout
174 assert 'pushing to' in stdout
154 assert 'searching for changes' in stdout
175 assert 'searching for changes' in stdout
155
176
156 assert 'abort:' not in stderr
177 assert 'abort:' not in stderr
157
178
158
179
159 def _check_proper_clone(stdout, stderr, vcs):
180 def _check_proper_clone(stdout, stderr, vcs):
160 if vcs == 'hg':
181 if vcs == 'hg':
161 assert 'requesting all changes' in stdout
182 assert 'requesting all changes' in stdout
162 assert 'adding changesets' in stdout
183 assert 'adding changesets' in stdout
163 assert 'adding manifests' in stdout
184 assert 'adding manifests' in stdout
164 assert 'adding file changes' in stdout
185 assert 'adding file changes' in stdout
165
186
166 assert stderr == ''
187 assert stderr == ''
167
188
168 if vcs == 'git':
189 if vcs == 'git':
169 assert '' == stdout
190 assert '' == stdout
170 assert 'Cloning into' in stderr
191 assert 'Cloning into' in stderr
171 assert 'abort:' not in stderr
192 assert 'abort:' not in stderr
172 assert 'fatal:' not in stderr
193 assert 'fatal:' not in stderr
@@ -1,269 +1,341 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 py.test config for test suite for making push/pull operations.
22 py.test config for test suite for making push/pull operations.
23
23
24 .. important::
24 .. important::
25
25
26 You must have git >= 1.8.5 for tests to work fine. With 68b939b git started
26 You must have git >= 1.8.5 for tests to work fine. With 68b939b git started
27 to redirect things to stderr instead of stdout.
27 to redirect things to stderr instead of stdout.
28 """
28 """
29
29
30 import os
30 import os
31 import tempfile
31 import tempfile
32 import textwrap
32 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
40 from rhodecode.model.settings import SettingsModel
41 from rhodecode.model.settings import SettingsModel
41 from rhodecode.integrations.types.webhook import WebhookIntegrationType
42 from rhodecode.integrations.types.webhook import WebhookIntegrationType
42
43
43 from rhodecode.tests import GIT_REPO, HG_REPO
44 from rhodecode.tests import GIT_REPO, HG_REPO
44 from rhodecode.tests.fixture import Fixture
45 from rhodecode.tests.fixture import Fixture
45 from rhodecode.tests.server_utils import RcWebServer
46 from rhodecode.tests.server_utils import RcWebServer
46
47
47 REPO_GROUP = 'a_repo_group'
48 REPO_GROUP = 'a_repo_group'
48 HG_REPO_WITH_GROUP = '%s/%s' % (REPO_GROUP, HG_REPO)
49 HG_REPO_WITH_GROUP = '%s/%s' % (REPO_GROUP, HG_REPO)
49 GIT_REPO_WITH_GROUP = '%s/%s' % (REPO_GROUP, GIT_REPO)
50 GIT_REPO_WITH_GROUP = '%s/%s' % (REPO_GROUP, GIT_REPO)
50
51
51
52
52 @pytest.fixture(scope="module")
53 @pytest.fixture(scope="module")
53 def rcextensions(request, db_connection, tmpdir_factory):
54 def rcextensions(request, db_connection, tmpdir_factory):
54 """
55 """
55 Installs a testing rcextensions pack to ensure they work as expected.
56 Installs a testing rcextensions pack to ensure they work as expected.
56 """
57 """
57 init_content = textwrap.dedent("""
58 init_content = textwrap.dedent("""
58 # Forward import the example rcextensions to make it
59 # Forward import the example rcextensions to make it
59 # active for our tests.
60 # active for our tests.
60 from rhodecode.tests.other.example_rcextensions import *
61 from rhodecode.tests.other.example_rcextensions import *
61 """)
62 """)
62
63
63 # Note: rcextensions are looked up based on the path of the ini file
64 # Note: rcextensions are looked up based on the path of the ini file
64 root_path = tmpdir_factory.getbasetemp()
65 root_path = tmpdir_factory.getbasetemp()
65 rcextensions_path = root_path.join('rcextensions')
66 rcextensions_path = root_path.join('rcextensions')
66 init_path = rcextensions_path.join('__init__.py')
67 init_path = rcextensions_path.join('__init__.py')
67
68
68 if rcextensions_path.check():
69 if rcextensions_path.check():
69 pytest.fail(
70 pytest.fail(
70 "Path for rcextensions already exists, please clean up before "
71 "Path for rcextensions already exists, please clean up before "
71 "test run this path: %s" % (rcextensions_path, ))
72 "test run this path: %s" % (rcextensions_path, ))
72 return
73 return
73
74
74 request.addfinalizer(rcextensions_path.remove)
75 request.addfinalizer(rcextensions_path.remove)
75 init_path.write_binary(init_content, ensure=True)
76 init_path.write_binary(init_content, ensure=True)
76
77
77
78
78 @pytest.fixture(scope="module")
79 @pytest.fixture(scope="module")
79 def repos(request, db_connection):
80 def repos(request, db_connection):
80 """Create a copy of each test repo in a repo group."""
81 """Create a copy of each test repo in a repo group."""
81 fixture = Fixture()
82 fixture = Fixture()
82 repo_group = fixture.create_repo_group(REPO_GROUP)
83 repo_group = fixture.create_repo_group(REPO_GROUP)
83 repo_group_id = repo_group.group_id
84 repo_group_id = repo_group.group_id
84 fixture.create_fork(HG_REPO, HG_REPO,
85 fixture.create_fork(HG_REPO, HG_REPO,
85 repo_name_full=HG_REPO_WITH_GROUP,
86 repo_name_full=HG_REPO_WITH_GROUP,
86 repo_group=repo_group_id)
87 repo_group=repo_group_id)
87 fixture.create_fork(GIT_REPO, GIT_REPO,
88 fixture.create_fork(GIT_REPO, GIT_REPO,
88 repo_name_full=GIT_REPO_WITH_GROUP,
89 repo_name_full=GIT_REPO_WITH_GROUP,
89 repo_group=repo_group_id)
90 repo_group=repo_group_id)
90
91
91 @request.addfinalizer
92 @request.addfinalizer
92 def cleanup():
93 def cleanup():
93 fixture.destroy_repo(HG_REPO_WITH_GROUP)
94 fixture.destroy_repo(HG_REPO_WITH_GROUP)
94 fixture.destroy_repo(GIT_REPO_WITH_GROUP)
95 fixture.destroy_repo(GIT_REPO_WITH_GROUP)
95 fixture.destroy_repo_group(repo_group_id)
96 fixture.destroy_repo_group(repo_group_id)
96
97
97
98
98 @pytest.fixture(scope="module")
99 @pytest.fixture(scope="module")
99 def rc_web_server_config_modification():
100 def rc_web_server_config_modification():
100 return []
101 return []
101
102
102
103
103 @pytest.fixture(scope="module")
104 @pytest.fixture(scope="module")
104 def rc_web_server_config_factory(testini_factory, rc_web_server_config_modification):
105 def rc_web_server_config_factory(testini_factory, rc_web_server_config_modification):
105 """
106 """
106 Configuration file used for the fixture `rc_web_server`.
107 Configuration file used for the fixture `rc_web_server`.
107 """
108 """
108
109
109 def factory(rcweb_port, vcsserver_port):
110 def factory(rcweb_port, vcsserver_port):
110 custom_params = [
111 custom_params = [
111 {'handler_console': {'level': 'DEBUG'}},
112 {'handler_console': {'level': 'DEBUG'}},
112 {'server:main': {'port': rcweb_port}},
113 {'server:main': {'port': rcweb_port}},
113 {'app:main': {'vcs.server': 'localhost:%s' % vcsserver_port}}
114 {'app:main': {'vcs.server': 'localhost:%s' % vcsserver_port}}
114 ]
115 ]
115 custom_params.extend(rc_web_server_config_modification)
116 custom_params.extend(rc_web_server_config_modification)
116 return testini_factory(custom_params)
117 return testini_factory(custom_params)
117 return factory
118 return factory
118
119
119
120
120 @pytest.fixture(scope="module")
121 @pytest.fixture(scope="module")
121 def rc_web_server(
122 def rc_web_server(
122 request, vcsserver_factory, available_port_factory,
123 request, vcsserver_factory, available_port_factory,
123 rc_web_server_config_factory, repos, rcextensions):
124 rc_web_server_config_factory, repos, rcextensions):
124 """
125 """
125 Run the web server as a subprocess. with it's own instance of vcsserver
126 Run the web server as a subprocess. with it's own instance of vcsserver
126 """
127 """
127 rcweb_port = available_port_factory()
128 rcweb_port = available_port_factory()
128 print('Using rcweb ops test port {}'.format(rcweb_port))
129 print('Using rcweb ops test port {}'.format(rcweb_port))
129
130
130 vcsserver_port = available_port_factory()
131 vcsserver_port = available_port_factory()
131 print('Using vcsserver ops test port {}'.format(vcsserver_port))
132 print('Using vcsserver ops test port {}'.format(vcsserver_port))
132
133
133 vcs_log = os.path.join(tempfile.gettempdir(), 'rc_op_vcs.log')
134 vcs_log = os.path.join(tempfile.gettempdir(), 'rc_op_vcs.log')
134 vcsserver_factory(
135 vcsserver_factory(
135 request, vcsserver_port=vcsserver_port,
136 request, vcsserver_port=vcsserver_port,
136 log_file=vcs_log,
137 log_file=vcs_log,
137 overrides=(
138 overrides=(
138 {'server:main': {'workers': 2}},
139 {'server:main': {'workers': 2}},
139 {'server:main': {'graceful_timeout': 10}},
140 {'server:main': {'graceful_timeout': 10}},
140 ))
141 ))
141
142
142 rc_log = os.path.join(tempfile.gettempdir(), 'rc_op_web.log')
143 rc_log = os.path.join(tempfile.gettempdir(), 'rc_op_web.log')
143 rc_web_server_config = rc_web_server_config_factory(
144 rc_web_server_config = rc_web_server_config_factory(
144 rcweb_port=rcweb_port,
145 rcweb_port=rcweb_port,
145 vcsserver_port=vcsserver_port)
146 vcsserver_port=vcsserver_port)
146 server = RcWebServer(rc_web_server_config, log_file=rc_log)
147 server = RcWebServer(rc_web_server_config, log_file=rc_log)
147 server.start()
148 server.start()
148
149
149 @request.addfinalizer
150 @request.addfinalizer
150 def cleanup():
151 def cleanup():
151 server.shutdown()
152 server.shutdown()
152
153
153 server.wait_until_ready()
154 server.wait_until_ready()
154 return server
155 return server
155
156
156
157
157 @pytest.fixture
158 @pytest.fixture
158 def disable_locking(baseapp):
159 def disable_locking(baseapp):
159 r = Repository.get_by_repo_name(GIT_REPO)
160 r = Repository.get_by_repo_name(GIT_REPO)
160 Repository.unlock(r)
161 Repository.unlock(r)
161 r.enable_locking = False
162 r.enable_locking = False
162 Session().add(r)
163 Session().add(r)
163 Session().commit()
164 Session().commit()
164
165
165 r = Repository.get_by_repo_name(HG_REPO)
166 r = Repository.get_by_repo_name(HG_REPO)
166 Repository.unlock(r)
167 Repository.unlock(r)
167 r.enable_locking = False
168 r.enable_locking = False
168 Session().add(r)
169 Session().add(r)
169 Session().commit()
170 Session().commit()
170
171
171
172
172 @pytest.fixture
173 @pytest.fixture
173 def enable_auth_plugins(request, baseapp, csrf_token):
174 def enable_auth_plugins(request, baseapp, csrf_token):
174 """
175 """
175 Return a factory object that when called, allows to control which
176 Return a factory object that when called, allows to control which
176 authentication plugins are enabled.
177 authentication plugins are enabled.
177 """
178 """
178 def _enable_plugins(plugins_list, override=None):
179 def _enable_plugins(plugins_list, override=None):
179 override = override or {}
180 override = override or {}
180 params = {
181 params = {
181 'auth_plugins': ','.join(plugins_list),
182 'auth_plugins': ','.join(plugins_list),
182 }
183 }
183
184
184 # helper translate some names to others
185 # helper translate some names to others
185 name_map = {
186 name_map = {
186 'token': 'authtoken'
187 'token': 'authtoken'
187 }
188 }
188
189
189 for module in plugins_list:
190 for module in plugins_list:
190 plugin_name = module.partition('#')[-1]
191 plugin_name = module.partition('#')[-1]
191 if plugin_name in name_map:
192 if plugin_name in name_map:
192 plugin_name = name_map[plugin_name]
193 plugin_name = name_map[plugin_name]
193 enabled_plugin = 'auth_%s_enabled' % plugin_name
194 enabled_plugin = 'auth_%s_enabled' % plugin_name
194 cache_ttl = 'auth_%s_cache_ttl' % plugin_name
195 cache_ttl = 'auth_%s_cache_ttl' % plugin_name
195
196
196 # default params that are needed for each plugin,
197 # default params that are needed for each plugin,
197 # `enabled` and `cache_ttl`
198 # `enabled` and `cache_ttl`
198 params.update({
199 params.update({
199 enabled_plugin: True,
200 enabled_plugin: True,
200 cache_ttl: 0
201 cache_ttl: 0
201 })
202 })
202 if override.get:
203 if override.get:
203 params.update(override.get(module, {}))
204 params.update(override.get(module, {}))
204
205
205 validated_params = params
206 validated_params = params
206 for k, v in validated_params.items():
207 for k, v in validated_params.items():
207 setting = SettingsModel().create_or_update_setting(k, v)
208 setting = SettingsModel().create_or_update_setting(k, v)
208 Session().add(setting)
209 Session().add(setting)
209 Session().commit()
210 Session().commit()
210
211
211 def cleanup():
212 def cleanup():
212 _enable_plugins(['egg:rhodecode-enterprise-ce#rhodecode'])
213 _enable_plugins(['egg:rhodecode-enterprise-ce#rhodecode'])
213
214
214 request.addfinalizer(cleanup)
215 request.addfinalizer(cleanup)
215
216
216 return _enable_plugins
217 return _enable_plugins
217
218
218
219
219 @pytest.fixture
220 @pytest.fixture
220 def fs_repo_only(request, rhodecode_fixtures):
221 def fs_repo_only(request, rhodecode_fixtures):
221 def fs_repo_fabric(repo_name, repo_type):
222 def fs_repo_fabric(repo_name, repo_type):
222 rhodecode_fixtures.create_repo(repo_name, repo_type=repo_type)
223 rhodecode_fixtures.create_repo(repo_name, repo_type=repo_type)
223 rhodecode_fixtures.destroy_repo(repo_name, fs_remove=False)
224 rhodecode_fixtures.destroy_repo(repo_name, fs_remove=False)
224
225
225 def cleanup():
226 def cleanup():
226 rhodecode_fixtures.destroy_repo(repo_name, fs_remove=True)
227 rhodecode_fixtures.destroy_repo(repo_name, fs_remove=True)
227 rhodecode_fixtures.destroy_repo_on_filesystem(repo_name)
228 rhodecode_fixtures.destroy_repo_on_filesystem(repo_name)
228
229
229 request.addfinalizer(cleanup)
230 request.addfinalizer(cleanup)
230
231
231 return fs_repo_fabric
232 return fs_repo_fabric
232
233
233
234
234 @pytest.fixture
235 @pytest.fixture
235 def enable_webhook_push_integration(request):
236 def enable_webhook_push_integration(request):
236 integration = Integration()
237 integration = Integration()
237 integration.integration_type = WebhookIntegrationType.key
238 integration.integration_type = WebhookIntegrationType.key
238 Session().add(integration)
239 Session().add(integration)
239
240
240 settings = dict(
241 settings = dict(
241 url='http://httpbin.org/post',
242 url='http://httpbin.org/post',
242 secret_token='secret',
243 secret_token='secret',
243 username=None,
244 username=None,
244 password=None,
245 password=None,
245 custom_header_key=None,
246 custom_header_key=None,
246 custom_header_val=None,
247 custom_header_val=None,
247 method_type='post',
248 method_type='post',
248 events=[events.RepoPushEvent.name],
249 events=[events.RepoPushEvent.name],
249 log_data=True
250 log_data=True
250 )
251 )
251
252
252 IntegrationModel().update_integration(
253 IntegrationModel().update_integration(
253 integration,
254 integration,
254 name='IntegrationWebhookTest',
255 name='IntegrationWebhookTest',
255 enabled=True,
256 enabled=True,
256 settings=settings,
257 settings=settings,
257 repo=None,
258 repo=None,
258 repo_group=None,
259 repo_group=None,
259 child_repos_only=False,
260 child_repos_only=False,
260 )
261 )
261 Session().commit()
262 Session().commit()
262 integration_id = integration.integration_id
263 integration_id = integration.integration_id
263
264
264 @request.addfinalizer
265 @request.addfinalizer
265 def cleanup():
266 def cleanup():
266 integration = Integration.get(integration_id)
267 integration = Integration.get(integration_id)
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