##// END OF EJS Templates
tests: stabilize tests for mysql/postgres.
marcink -
r3981:39e93a55 default
parent child Browse files
Show More
@@ -1,202 +1,202 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 pytest
21 import pytest
22
22
23 from rhodecode.tests import assert_session_flash
23 from rhodecode.tests import assert_session_flash
24 from rhodecode.tests.utils import AssertResponse
24 from rhodecode.tests.utils import AssertResponse
25 from rhodecode.model.db import Session
25 from rhodecode.model.db import Session
26 from rhodecode.model.settings import SettingsModel
26 from rhodecode.model.settings import SettingsModel
27
27
28
28
29 def assert_auth_settings_updated(response):
29 def assert_auth_settings_updated(response):
30 assert response.status_int == 302, 'Expected response HTTP Found 302'
30 assert response.status_int == 302, 'Expected response HTTP Found 302'
31 assert_session_flash(response, 'Auth settings updated successfully')
31 assert_session_flash(response, 'Auth settings updated successfully')
32
32
33
33
34 @pytest.mark.usefixtures("autologin_user", "app")
34 @pytest.mark.usefixtures("autologin_user", "app")
35 class TestAuthSettingsView(object):
35 class TestAuthSettingsView(object):
36
36
37 def _enable_plugins(self, plugins_list, csrf_token, override=None,
37 def _enable_plugins(self, plugins_list, csrf_token, override=None,
38 verify_response=False):
38 verify_response=False):
39 test_url = '/_admin/auth'
39 test_url = '/_admin/auth'
40 params = {
40 params = {
41 'auth_plugins': plugins_list,
41 'auth_plugins': plugins_list,
42 'csrf_token': csrf_token,
42 'csrf_token': csrf_token,
43 }
43 }
44 if override:
44 if override:
45 params.update(override)
45 params.update(override)
46 _enabled_plugins = []
46 _enabled_plugins = []
47 for plugin in plugins_list.split(','):
47 for plugin in plugins_list.split(','):
48 plugin_name = plugin.partition('#')[-1]
48 plugin_name = plugin.partition('#')[-1]
49 enabled_plugin = '%s_enabled' % plugin_name
49 enabled_plugin = '%s_enabled' % plugin_name
50 cache_ttl = '%s_cache_ttl' % plugin_name
50 cache_ttl = '%s_cache_ttl' % plugin_name
51
51
52 # default params that are needed for each plugin,
52 # default params that are needed for each plugin,
53 # `enabled` and `cache_ttl`
53 # `enabled` and `cache_ttl`
54 params.update({
54 params.update({
55 enabled_plugin: True,
55 enabled_plugin: True,
56 cache_ttl: 0
56 cache_ttl: 0
57 })
57 })
58 _enabled_plugins.append(enabled_plugin)
58 _enabled_plugins.append(enabled_plugin)
59
59
60 # we need to clean any enabled plugin before, since they require
60 # we need to clean any enabled plugin before, since they require
61 # form params to be present
61 # form params to be present
62 db_plugin = SettingsModel().get_setting_by_name('auth_plugins')
62 db_plugin = SettingsModel().get_setting_by_name('auth_plugins')
63 db_plugin.app_settings_value = \
63 db_plugin.app_settings_value = \
64 'egg:rhodecode-enterprise-ce#rhodecode'
64 'egg:rhodecode-enterprise-ce#rhodecode'
65 Session().add(db_plugin)
65 Session().add(db_plugin)
66 Session().commit()
66 Session().commit()
67 for _plugin in _enabled_plugins:
67 for _plugin in _enabled_plugins:
68 db_plugin = SettingsModel().get_setting_by_name(_plugin)
68 db_plugin = SettingsModel().get_setting_by_name(_plugin)
69 if db_plugin:
69 if db_plugin:
70 Session().delete(db_plugin)
70 Session().delete(db_plugin)
71 Session().commit()
71 Session().commit()
72
72
73 response = self.app.post(url=test_url, params=params)
73 response = self.app.post(url=test_url, params=params)
74
74
75 if verify_response:
75 if verify_response:
76 assert_auth_settings_updated(response)
76 assert_auth_settings_updated(response)
77 return params
77 return params
78
78
79 def _post_ldap_settings(self, params, override=None, force=False):
79 def _post_ldap_settings(self, params, override=None, force=False):
80
80
81 params.update({
81 params.update({
82 'filter': 'user',
82 'filter': 'user',
83 'user_member_of': '',
83 'user_member_of': '',
84 'user_search_base': '',
84 'user_search_base': '',
85 'user_search_filter': 'test_filter',
85 'user_search_filter': 'test_filter',
86
86
87 'host': 'dc.example.com',
87 'host': 'dc.example.com',
88 'port': '999',
88 'port': '999',
89 'timeout': 3600,
89 'timeout': 3600,
90 'tls_kind': 'PLAIN',
90 'tls_kind': 'PLAIN',
91 'tls_reqcert': 'NEVER',
91 'tls_reqcert': 'NEVER',
92 'tls_cert_dir':'/etc/openldap/cacerts',
92 'tls_cert_dir':'/etc/openldap/cacerts',
93 'dn_user': 'test_user',
93 'dn_user': 'test_user',
94 'dn_pass': 'test_pass',
94 'dn_pass': 'test_pass',
95 'base_dn': 'test_base_dn',
95 'base_dn': 'test_base_dn',
96 'search_scope': 'BASE',
96 'search_scope': 'BASE',
97 'attr_login': 'test_attr_login',
97 'attr_login': 'test_attr_login',
98 'attr_firstname': 'ima',
98 'attr_firstname': 'ima',
99 'attr_lastname': 'tester',
99 'attr_lastname': 'tester',
100 'attr_email': 'test@example.com',
100 'attr_email': 'test@example.com',
101 'cache_ttl': '0',
101 'cache_ttl': '0',
102 })
102 })
103 if force:
103 if force:
104 params = {}
104 params = {}
105 params.update(override or {})
105 params.update(override or {})
106
106
107 test_url = '/_admin/auth/ldap/'
107 test_url = '/_admin/auth/ldap/'
108
108
109 response = self.app.post(url=test_url, params=params)
109 response = self.app.post(url=test_url, params=params)
110 return response
110 return response
111
111
112 def test_index(self):
112 def test_index(self):
113 response = self.app.get('/_admin/auth')
113 response = self.app.get('/_admin/auth')
114 response.mustcontain('Authentication Plugins')
114 response.mustcontain('Authentication Plugins')
115
115
116 @pytest.mark.parametrize("disable_plugin, needs_import", [
116 @pytest.mark.parametrize("disable_plugin, needs_import", [
117 ('egg:rhodecode-enterprise-ce#headers', None),
117 ('egg:rhodecode-enterprise-ce#headers', None),
118 ('egg:rhodecode-enterprise-ce#crowd', None),
118 ('egg:rhodecode-enterprise-ce#crowd', None),
119 ('egg:rhodecode-enterprise-ce#jasig_cas', None),
119 ('egg:rhodecode-enterprise-ce#jasig_cas', None),
120 ('egg:rhodecode-enterprise-ce#ldap', None),
120 ('egg:rhodecode-enterprise-ce#ldap', None),
121 ('egg:rhodecode-enterprise-ce#pam', "pam"),
121 ('egg:rhodecode-enterprise-ce#pam', "pam"),
122 ])
122 ])
123 def test_disable_plugin(self, csrf_token, disable_plugin, needs_import):
123 def test_disable_plugin(self, csrf_token, disable_plugin, needs_import):
124 # TODO: johbo: "pam" is currently not available on darwin,
124 # TODO: johbo: "pam" is currently not available on darwin,
125 # although the docs state that it should work on darwin.
125 # although the docs state that it should work on darwin.
126 if needs_import:
126 if needs_import:
127 pytest.importorskip(needs_import)
127 pytest.importorskip(needs_import)
128
128
129 self._enable_plugins(
129 self._enable_plugins(
130 'egg:rhodecode-enterprise-ce#rhodecode,' + disable_plugin,
130 'egg:rhodecode-enterprise-ce#rhodecode,' + disable_plugin,
131 csrf_token, verify_response=True)
131 csrf_token, verify_response=True)
132
132
133 self._enable_plugins(
133 self._enable_plugins(
134 'egg:rhodecode-enterprise-ce#rhodecode', csrf_token,
134 'egg:rhodecode-enterprise-ce#rhodecode', csrf_token,
135 verify_response=True)
135 verify_response=True)
136
136
137 def test_ldap_save_settings(self, csrf_token):
137 def test_ldap_save_settings(self, csrf_token):
138 params = self._enable_plugins(
138 params = self._enable_plugins(
139 'egg:rhodecode-enterprise-ce#rhodecode,'
139 'egg:rhodecode-enterprise-ce#rhodecode,'
140 'egg:rhodecode-enterprise-ce#ldap',
140 'egg:rhodecode-enterprise-ce#ldap',
141 csrf_token)
141 csrf_token)
142 response = self._post_ldap_settings(params)
142 response = self._post_ldap_settings(params)
143 assert_auth_settings_updated(response)
143 assert_auth_settings_updated(response)
144
144
145 new_settings = SettingsModel().get_auth_settings()
145 new_settings = SettingsModel().get_auth_settings()
146 assert new_settings['auth_ldap_host'] == u'dc.example.com', \
146 assert new_settings['auth_ldap_host'] == u'dc.example.com', \
147 'fail db write compare'
147 'fail db write compare'
148
148
149 def test_ldap_error_form_wrong_port_number(self, csrf_token):
149 def test_ldap_error_form_wrong_port_number(self, csrf_token):
150 params = self._enable_plugins(
150 params = self._enable_plugins(
151 'egg:rhodecode-enterprise-ce#rhodecode,'
151 'egg:rhodecode-enterprise-ce#rhodecode,'
152 'egg:rhodecode-enterprise-ce#ldap',
152 'egg:rhodecode-enterprise-ce#ldap',
153 csrf_token)
153 csrf_token)
154 invalid_port_value = 'invalid-port-number'
154 invalid_port_value = 'invalid-port-number'
155 response = self._post_ldap_settings(params, override={
155 response = self._post_ldap_settings(params, override={
156 'port': invalid_port_value,
156 'port': invalid_port_value,
157 })
157 })
158 assertr = AssertResponse(response)
158 assertr = response.assert_response()
159 assertr.element_contains(
159 assertr.element_contains(
160 '.form .field #port ~ .error-message',
160 '.form .field #port ~ .error-message',
161 invalid_port_value)
161 invalid_port_value)
162
162
163 def test_ldap_error_form(self, csrf_token):
163 def test_ldap_error_form(self, csrf_token):
164 params = self._enable_plugins(
164 params = self._enable_plugins(
165 'egg:rhodecode-enterprise-ce#rhodecode,'
165 'egg:rhodecode-enterprise-ce#rhodecode,'
166 'egg:rhodecode-enterprise-ce#ldap',
166 'egg:rhodecode-enterprise-ce#ldap',
167 csrf_token)
167 csrf_token)
168 response = self._post_ldap_settings(params, override={
168 response = self._post_ldap_settings(params, override={
169 'attr_login': '',
169 'attr_login': '',
170 })
170 })
171 response.mustcontain("""<span class="error-message">The LDAP Login"""
171 response.mustcontain("""<span class="error-message">The LDAP Login"""
172 """ attribute of the CN must be specified""")
172 """ attribute of the CN must be specified""")
173
173
174 def test_post_ldap_group_settings(self, csrf_token):
174 def test_post_ldap_group_settings(self, csrf_token):
175 params = self._enable_plugins(
175 params = self._enable_plugins(
176 'egg:rhodecode-enterprise-ce#rhodecode,'
176 'egg:rhodecode-enterprise-ce#rhodecode,'
177 'egg:rhodecode-enterprise-ce#ldap',
177 'egg:rhodecode-enterprise-ce#ldap',
178 csrf_token)
178 csrf_token)
179
179
180 response = self._post_ldap_settings(params, override={
180 response = self._post_ldap_settings(params, override={
181 'host': 'dc-legacy.example.com',
181 'host': 'dc-legacy.example.com',
182 'port': '999',
182 'port': '999',
183 'tls_kind': 'PLAIN',
183 'tls_kind': 'PLAIN',
184 'tls_reqcert': 'NEVER',
184 'tls_reqcert': 'NEVER',
185 'dn_user': 'test_user',
185 'dn_user': 'test_user',
186 'dn_pass': 'test_pass',
186 'dn_pass': 'test_pass',
187 'base_dn': 'test_base_dn',
187 'base_dn': 'test_base_dn',
188 'filter': 'test_filter',
188 'filter': 'test_filter',
189 'search_scope': 'BASE',
189 'search_scope': 'BASE',
190 'attr_login': 'test_attr_login',
190 'attr_login': 'test_attr_login',
191 'attr_firstname': 'ima',
191 'attr_firstname': 'ima',
192 'attr_lastname': 'tester',
192 'attr_lastname': 'tester',
193 'attr_email': 'test@example.com',
193 'attr_email': 'test@example.com',
194 'cache_ttl': '60',
194 'cache_ttl': '60',
195 'csrf_token': csrf_token,
195 'csrf_token': csrf_token,
196 }
196 }
197 )
197 )
198 assert_auth_settings_updated(response)
198 assert_auth_settings_updated(response)
199
199
200 new_settings = SettingsModel().get_auth_settings()
200 new_settings = SettingsModel().get_auth_settings()
201 assert new_settings['auth_ldap_host'] == u'dc-legacy.example.com', \
201 assert new_settings['auth_ldap_host'] == u'dc-legacy.example.com', \
202 'fail db write compare'
202 'fail db write compare'
@@ -1,512 +1,512 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 urllib
21 import urllib
22
22
23 import mock
23 import mock
24 import pytest
24 import pytest
25
25
26 from rhodecode.apps._base import ADMIN_PREFIX
26 from rhodecode.apps._base import ADMIN_PREFIX
27 from rhodecode.lib import auth
27 from rhodecode.lib import auth
28 from rhodecode.lib.utils2 import safe_str
28 from rhodecode.lib.utils2 import safe_str
29 from rhodecode.lib import helpers as h
29 from rhodecode.lib import helpers as h
30 from rhodecode.model.db import (
30 from rhodecode.model.db import (
31 Repository, RepoGroup, UserRepoToPerm, User, Permission)
31 Repository, RepoGroup, UserRepoToPerm, User, Permission)
32 from rhodecode.model.meta import Session
32 from rhodecode.model.meta import Session
33 from rhodecode.model.repo import RepoModel
33 from rhodecode.model.repo import RepoModel
34 from rhodecode.model.repo_group import RepoGroupModel
34 from rhodecode.model.repo_group import RepoGroupModel
35 from rhodecode.model.user import UserModel
35 from rhodecode.model.user import UserModel
36 from rhodecode.tests import (
36 from rhodecode.tests import (
37 login_user_session, assert_session_flash, TEST_USER_ADMIN_LOGIN,
37 login_user_session, assert_session_flash, TEST_USER_ADMIN_LOGIN,
38 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
38 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
39 from rhodecode.tests.fixture import Fixture, error_function
39 from rhodecode.tests.fixture import Fixture, error_function
40 from rhodecode.tests.utils import AssertResponse, repo_on_filesystem
40 from rhodecode.tests.utils import AssertResponse, repo_on_filesystem
41
41
42 fixture = Fixture()
42 fixture = Fixture()
43
43
44
44
45 def route_path(name, params=None, **kwargs):
45 def route_path(name, params=None, **kwargs):
46 import urllib
46 import urllib
47
47
48 base_url = {
48 base_url = {
49 'repos': ADMIN_PREFIX + '/repos',
49 'repos': ADMIN_PREFIX + '/repos',
50 'repo_new': ADMIN_PREFIX + '/repos/new',
50 'repo_new': ADMIN_PREFIX + '/repos/new',
51 'repo_create': ADMIN_PREFIX + '/repos/create',
51 'repo_create': ADMIN_PREFIX + '/repos/create',
52
52
53 'repo_creating_check': '/{repo_name}/repo_creating_check',
53 'repo_creating_check': '/{repo_name}/repo_creating_check',
54 }[name].format(**kwargs)
54 }[name].format(**kwargs)
55
55
56 if params:
56 if params:
57 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
57 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
58 return base_url
58 return base_url
59
59
60
60
61 def _get_permission_for_user(user, repo):
61 def _get_permission_for_user(user, repo):
62 perm = UserRepoToPerm.query()\
62 perm = UserRepoToPerm.query()\
63 .filter(UserRepoToPerm.repository ==
63 .filter(UserRepoToPerm.repository ==
64 Repository.get_by_repo_name(repo))\
64 Repository.get_by_repo_name(repo))\
65 .filter(UserRepoToPerm.user == User.get_by_username(user))\
65 .filter(UserRepoToPerm.user == User.get_by_username(user))\
66 .all()
66 .all()
67 return perm
67 return perm
68
68
69
69
70 @pytest.mark.usefixtures("app")
70 @pytest.mark.usefixtures("app")
71 class TestAdminRepos(object):
71 class TestAdminRepos(object):
72
72
73 def test_repo_list(self, autologin_user, user_util):
73 def test_repo_list(self, autologin_user, user_util):
74 repo = user_util.create_repo()
74 repo = user_util.create_repo()
75 repo_name = repo.repo_name
75 repo_name = repo.repo_name
76 response = self.app.get(
76 response = self.app.get(
77 route_path('repos'), status=200)
77 route_path('repos'), status=200)
78
78
79 response.mustcontain(repo_name)
79 response.mustcontain(repo_name)
80
80
81 def test_create_page_restricted_to_single_backend(self, autologin_user, backend):
81 def test_create_page_restricted_to_single_backend(self, autologin_user, backend):
82 with mock.patch('rhodecode.BACKENDS', {'git': 'git'}):
82 with mock.patch('rhodecode.BACKENDS', {'git': 'git'}):
83 response = self.app.get(route_path('repo_new'), status=200)
83 response = self.app.get(route_path('repo_new'), status=200)
84 assert_response = AssertResponse(response)
84 assert_response = response.assert_response()
85 element = assert_response.get_element('#repo_type')
85 element = assert_response.get_element('#repo_type')
86 assert element.text_content() == '\ngit\n'
86 assert element.text_content() == '\ngit\n'
87
87
88 def test_create_page_non_restricted_backends(self, autologin_user, backend):
88 def test_create_page_non_restricted_backends(self, autologin_user, backend):
89 response = self.app.get(route_path('repo_new'), status=200)
89 response = self.app.get(route_path('repo_new'), status=200)
90 assert_response = AssertResponse(response)
90 assert_response = response.assert_response()
91 assert_response.element_contains('#repo_type', 'git')
91 assert_response.element_contains('#repo_type', 'git')
92 assert_response.element_contains('#repo_type', 'svn')
92 assert_response.element_contains('#repo_type', 'svn')
93 assert_response.element_contains('#repo_type', 'hg')
93 assert_response.element_contains('#repo_type', 'hg')
94
94
95 @pytest.mark.parametrize(
95 @pytest.mark.parametrize(
96 "suffix", [u'', u'xxa'], ids=['', 'non-ascii'])
96 "suffix", [u'', u'xxa'], ids=['', 'non-ascii'])
97 def test_create(self, autologin_user, backend, suffix, csrf_token):
97 def test_create(self, autologin_user, backend, suffix, csrf_token):
98 repo_name_unicode = backend.new_repo_name(suffix=suffix)
98 repo_name_unicode = backend.new_repo_name(suffix=suffix)
99 repo_name = repo_name_unicode.encode('utf8')
99 repo_name = repo_name_unicode.encode('utf8')
100 description_unicode = u'description for newly created repo' + suffix
100 description_unicode = u'description for newly created repo' + suffix
101 description = description_unicode.encode('utf8')
101 description = description_unicode.encode('utf8')
102 response = self.app.post(
102 response = self.app.post(
103 route_path('repo_create'),
103 route_path('repo_create'),
104 fixture._get_repo_create_params(
104 fixture._get_repo_create_params(
105 repo_private=False,
105 repo_private=False,
106 repo_name=repo_name,
106 repo_name=repo_name,
107 repo_type=backend.alias,
107 repo_type=backend.alias,
108 repo_description=description,
108 repo_description=description,
109 csrf_token=csrf_token),
109 csrf_token=csrf_token),
110 status=302)
110 status=302)
111
111
112 self.assert_repository_is_created_correctly(
112 self.assert_repository_is_created_correctly(
113 repo_name, description, backend)
113 repo_name, description, backend)
114
114
115 def test_create_numeric_name(self, autologin_user, backend, csrf_token):
115 def test_create_numeric_name(self, autologin_user, backend, csrf_token):
116 numeric_repo = '1234'
116 numeric_repo = '1234'
117 repo_name = numeric_repo
117 repo_name = numeric_repo
118 description = 'description for newly created repo' + numeric_repo
118 description = 'description for newly created repo' + numeric_repo
119 self.app.post(
119 self.app.post(
120 route_path('repo_create'),
120 route_path('repo_create'),
121 fixture._get_repo_create_params(
121 fixture._get_repo_create_params(
122 repo_private=False,
122 repo_private=False,
123 repo_name=repo_name,
123 repo_name=repo_name,
124 repo_type=backend.alias,
124 repo_type=backend.alias,
125 repo_description=description,
125 repo_description=description,
126 csrf_token=csrf_token))
126 csrf_token=csrf_token))
127
127
128 self.assert_repository_is_created_correctly(
128 self.assert_repository_is_created_correctly(
129 repo_name, description, backend)
129 repo_name, description, backend)
130
130
131 @pytest.mark.parametrize("suffix", [u'', u'Δ…Δ‡Δ™'], ids=['', 'non-ascii'])
131 @pytest.mark.parametrize("suffix", [u'', u'Δ…Δ‡Δ™'], ids=['', 'non-ascii'])
132 def test_create_in_group(
132 def test_create_in_group(
133 self, autologin_user, backend, suffix, csrf_token):
133 self, autologin_user, backend, suffix, csrf_token):
134 # create GROUP
134 # create GROUP
135 group_name = 'sometest_%s' % backend.alias
135 group_name = 'sometest_%s' % backend.alias
136 gr = RepoGroupModel().create(group_name=group_name,
136 gr = RepoGroupModel().create(group_name=group_name,
137 group_description='test',
137 group_description='test',
138 owner=TEST_USER_ADMIN_LOGIN)
138 owner=TEST_USER_ADMIN_LOGIN)
139 Session().commit()
139 Session().commit()
140
140
141 repo_name = u'ingroup' + suffix
141 repo_name = u'ingroup' + suffix
142 repo_name_full = RepoGroup.url_sep().join(
142 repo_name_full = RepoGroup.url_sep().join(
143 [group_name, repo_name])
143 [group_name, repo_name])
144 description = u'description for newly created repo'
144 description = u'description for newly created repo'
145 self.app.post(
145 self.app.post(
146 route_path('repo_create'),
146 route_path('repo_create'),
147 fixture._get_repo_create_params(
147 fixture._get_repo_create_params(
148 repo_private=False,
148 repo_private=False,
149 repo_name=safe_str(repo_name),
149 repo_name=safe_str(repo_name),
150 repo_type=backend.alias,
150 repo_type=backend.alias,
151 repo_description=description,
151 repo_description=description,
152 repo_group=gr.group_id,
152 repo_group=gr.group_id,
153 csrf_token=csrf_token))
153 csrf_token=csrf_token))
154
154
155 # TODO: johbo: Cleanup work to fixture
155 # TODO: johbo: Cleanup work to fixture
156 try:
156 try:
157 self.assert_repository_is_created_correctly(
157 self.assert_repository_is_created_correctly(
158 repo_name_full, description, backend)
158 repo_name_full, description, backend)
159
159
160 new_repo = RepoModel().get_by_repo_name(repo_name_full)
160 new_repo = RepoModel().get_by_repo_name(repo_name_full)
161 inherited_perms = UserRepoToPerm.query().filter(
161 inherited_perms = UserRepoToPerm.query().filter(
162 UserRepoToPerm.repository_id == new_repo.repo_id).all()
162 UserRepoToPerm.repository_id == new_repo.repo_id).all()
163 assert len(inherited_perms) == 1
163 assert len(inherited_perms) == 1
164 finally:
164 finally:
165 RepoModel().delete(repo_name_full)
165 RepoModel().delete(repo_name_full)
166 RepoGroupModel().delete(group_name)
166 RepoGroupModel().delete(group_name)
167 Session().commit()
167 Session().commit()
168
168
169 def test_create_in_group_numeric_name(
169 def test_create_in_group_numeric_name(
170 self, autologin_user, backend, csrf_token):
170 self, autologin_user, backend, csrf_token):
171 # create GROUP
171 # create GROUP
172 group_name = 'sometest_%s' % backend.alias
172 group_name = 'sometest_%s' % backend.alias
173 gr = RepoGroupModel().create(group_name=group_name,
173 gr = RepoGroupModel().create(group_name=group_name,
174 group_description='test',
174 group_description='test',
175 owner=TEST_USER_ADMIN_LOGIN)
175 owner=TEST_USER_ADMIN_LOGIN)
176 Session().commit()
176 Session().commit()
177
177
178 repo_name = '12345'
178 repo_name = '12345'
179 repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
179 repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
180 description = 'description for newly created repo'
180 description = 'description for newly created repo'
181 self.app.post(
181 self.app.post(
182 route_path('repo_create'),
182 route_path('repo_create'),
183 fixture._get_repo_create_params(
183 fixture._get_repo_create_params(
184 repo_private=False,
184 repo_private=False,
185 repo_name=repo_name,
185 repo_name=repo_name,
186 repo_type=backend.alias,
186 repo_type=backend.alias,
187 repo_description=description,
187 repo_description=description,
188 repo_group=gr.group_id,
188 repo_group=gr.group_id,
189 csrf_token=csrf_token))
189 csrf_token=csrf_token))
190
190
191 # TODO: johbo: Cleanup work to fixture
191 # TODO: johbo: Cleanup work to fixture
192 try:
192 try:
193 self.assert_repository_is_created_correctly(
193 self.assert_repository_is_created_correctly(
194 repo_name_full, description, backend)
194 repo_name_full, description, backend)
195
195
196 new_repo = RepoModel().get_by_repo_name(repo_name_full)
196 new_repo = RepoModel().get_by_repo_name(repo_name_full)
197 inherited_perms = UserRepoToPerm.query()\
197 inherited_perms = UserRepoToPerm.query()\
198 .filter(UserRepoToPerm.repository_id == new_repo.repo_id).all()
198 .filter(UserRepoToPerm.repository_id == new_repo.repo_id).all()
199 assert len(inherited_perms) == 1
199 assert len(inherited_perms) == 1
200 finally:
200 finally:
201 RepoModel().delete(repo_name_full)
201 RepoModel().delete(repo_name_full)
202 RepoGroupModel().delete(group_name)
202 RepoGroupModel().delete(group_name)
203 Session().commit()
203 Session().commit()
204
204
205 def test_create_in_group_without_needed_permissions(self, backend):
205 def test_create_in_group_without_needed_permissions(self, backend):
206 session = login_user_session(
206 session = login_user_session(
207 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
207 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
208 csrf_token = auth.get_csrf_token(session)
208 csrf_token = auth.get_csrf_token(session)
209 # revoke
209 # revoke
210 user_model = UserModel()
210 user_model = UserModel()
211 # disable fork and create on default user
211 # disable fork and create on default user
212 user_model.revoke_perm(User.DEFAULT_USER, 'hg.create.repository')
212 user_model.revoke_perm(User.DEFAULT_USER, 'hg.create.repository')
213 user_model.grant_perm(User.DEFAULT_USER, 'hg.create.none')
213 user_model.grant_perm(User.DEFAULT_USER, 'hg.create.none')
214 user_model.revoke_perm(User.DEFAULT_USER, 'hg.fork.repository')
214 user_model.revoke_perm(User.DEFAULT_USER, 'hg.fork.repository')
215 user_model.grant_perm(User.DEFAULT_USER, 'hg.fork.none')
215 user_model.grant_perm(User.DEFAULT_USER, 'hg.fork.none')
216
216
217 # disable on regular user
217 # disable on regular user
218 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.repository')
218 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.repository')
219 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.none')
219 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.none')
220 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.repository')
220 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.repository')
221 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.none')
221 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.none')
222 Session().commit()
222 Session().commit()
223
223
224 # create GROUP
224 # create GROUP
225 group_name = 'reg_sometest_%s' % backend.alias
225 group_name = 'reg_sometest_%s' % backend.alias
226 gr = RepoGroupModel().create(group_name=group_name,
226 gr = RepoGroupModel().create(group_name=group_name,
227 group_description='test',
227 group_description='test',
228 owner=TEST_USER_ADMIN_LOGIN)
228 owner=TEST_USER_ADMIN_LOGIN)
229 Session().commit()
229 Session().commit()
230 repo_group_id = gr.group_id
230 repo_group_id = gr.group_id
231
231
232 group_name_allowed = 'reg_sometest_allowed_%s' % backend.alias
232 group_name_allowed = 'reg_sometest_allowed_%s' % backend.alias
233 gr_allowed = RepoGroupModel().create(
233 gr_allowed = RepoGroupModel().create(
234 group_name=group_name_allowed,
234 group_name=group_name_allowed,
235 group_description='test',
235 group_description='test',
236 owner=TEST_USER_REGULAR_LOGIN)
236 owner=TEST_USER_REGULAR_LOGIN)
237 allowed_repo_group_id = gr_allowed.group_id
237 allowed_repo_group_id = gr_allowed.group_id
238 Session().commit()
238 Session().commit()
239
239
240 repo_name = 'ingroup'
240 repo_name = 'ingroup'
241 description = 'description for newly created repo'
241 description = 'description for newly created repo'
242 response = self.app.post(
242 response = self.app.post(
243 route_path('repo_create'),
243 route_path('repo_create'),
244 fixture._get_repo_create_params(
244 fixture._get_repo_create_params(
245 repo_private=False,
245 repo_private=False,
246 repo_name=repo_name,
246 repo_name=repo_name,
247 repo_type=backend.alias,
247 repo_type=backend.alias,
248 repo_description=description,
248 repo_description=description,
249 repo_group=repo_group_id,
249 repo_group=repo_group_id,
250 csrf_token=csrf_token))
250 csrf_token=csrf_token))
251
251
252 response.mustcontain('Invalid value')
252 response.mustcontain('Invalid value')
253
253
254 # user is allowed to create in this group
254 # user is allowed to create in this group
255 repo_name = 'ingroup'
255 repo_name = 'ingroup'
256 repo_name_full = RepoGroup.url_sep().join(
256 repo_name_full = RepoGroup.url_sep().join(
257 [group_name_allowed, repo_name])
257 [group_name_allowed, repo_name])
258 description = 'description for newly created repo'
258 description = 'description for newly created repo'
259 response = self.app.post(
259 response = self.app.post(
260 route_path('repo_create'),
260 route_path('repo_create'),
261 fixture._get_repo_create_params(
261 fixture._get_repo_create_params(
262 repo_private=False,
262 repo_private=False,
263 repo_name=repo_name,
263 repo_name=repo_name,
264 repo_type=backend.alias,
264 repo_type=backend.alias,
265 repo_description=description,
265 repo_description=description,
266 repo_group=allowed_repo_group_id,
266 repo_group=allowed_repo_group_id,
267 csrf_token=csrf_token))
267 csrf_token=csrf_token))
268
268
269 # TODO: johbo: Cleanup in pytest fixture
269 # TODO: johbo: Cleanup in pytest fixture
270 try:
270 try:
271 self.assert_repository_is_created_correctly(
271 self.assert_repository_is_created_correctly(
272 repo_name_full, description, backend)
272 repo_name_full, description, backend)
273
273
274 new_repo = RepoModel().get_by_repo_name(repo_name_full)
274 new_repo = RepoModel().get_by_repo_name(repo_name_full)
275 inherited_perms = UserRepoToPerm.query().filter(
275 inherited_perms = UserRepoToPerm.query().filter(
276 UserRepoToPerm.repository_id == new_repo.repo_id).all()
276 UserRepoToPerm.repository_id == new_repo.repo_id).all()
277 assert len(inherited_perms) == 1
277 assert len(inherited_perms) == 1
278
278
279 assert repo_on_filesystem(repo_name_full)
279 assert repo_on_filesystem(repo_name_full)
280 finally:
280 finally:
281 RepoModel().delete(repo_name_full)
281 RepoModel().delete(repo_name_full)
282 RepoGroupModel().delete(group_name)
282 RepoGroupModel().delete(group_name)
283 RepoGroupModel().delete(group_name_allowed)
283 RepoGroupModel().delete(group_name_allowed)
284 Session().commit()
284 Session().commit()
285
285
286 def test_create_in_group_inherit_permissions(self, autologin_user, backend,
286 def test_create_in_group_inherit_permissions(self, autologin_user, backend,
287 csrf_token):
287 csrf_token):
288 # create GROUP
288 # create GROUP
289 group_name = 'sometest_%s' % backend.alias
289 group_name = 'sometest_%s' % backend.alias
290 gr = RepoGroupModel().create(group_name=group_name,
290 gr = RepoGroupModel().create(group_name=group_name,
291 group_description='test',
291 group_description='test',
292 owner=TEST_USER_ADMIN_LOGIN)
292 owner=TEST_USER_ADMIN_LOGIN)
293 perm = Permission.get_by_key('repository.write')
293 perm = Permission.get_by_key('repository.write')
294 RepoGroupModel().grant_user_permission(
294 RepoGroupModel().grant_user_permission(
295 gr, TEST_USER_REGULAR_LOGIN, perm)
295 gr, TEST_USER_REGULAR_LOGIN, perm)
296
296
297 # add repo permissions
297 # add repo permissions
298 Session().commit()
298 Session().commit()
299 repo_group_id = gr.group_id
299 repo_group_id = gr.group_id
300 repo_name = 'ingroup_inherited_%s' % backend.alias
300 repo_name = 'ingroup_inherited_%s' % backend.alias
301 repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
301 repo_name_full = RepoGroup.url_sep().join([group_name, repo_name])
302 description = 'description for newly created repo'
302 description = 'description for newly created repo'
303 self.app.post(
303 self.app.post(
304 route_path('repo_create'),
304 route_path('repo_create'),
305 fixture._get_repo_create_params(
305 fixture._get_repo_create_params(
306 repo_private=False,
306 repo_private=False,
307 repo_name=repo_name,
307 repo_name=repo_name,
308 repo_type=backend.alias,
308 repo_type=backend.alias,
309 repo_description=description,
309 repo_description=description,
310 repo_group=repo_group_id,
310 repo_group=repo_group_id,
311 repo_copy_permissions=True,
311 repo_copy_permissions=True,
312 csrf_token=csrf_token))
312 csrf_token=csrf_token))
313
313
314 # TODO: johbo: Cleanup to pytest fixture
314 # TODO: johbo: Cleanup to pytest fixture
315 try:
315 try:
316 self.assert_repository_is_created_correctly(
316 self.assert_repository_is_created_correctly(
317 repo_name_full, description, backend)
317 repo_name_full, description, backend)
318 except Exception:
318 except Exception:
319 RepoGroupModel().delete(group_name)
319 RepoGroupModel().delete(group_name)
320 Session().commit()
320 Session().commit()
321 raise
321 raise
322
322
323 # check if inherited permissions are applied
323 # check if inherited permissions are applied
324 new_repo = RepoModel().get_by_repo_name(repo_name_full)
324 new_repo = RepoModel().get_by_repo_name(repo_name_full)
325 inherited_perms = UserRepoToPerm.query().filter(
325 inherited_perms = UserRepoToPerm.query().filter(
326 UserRepoToPerm.repository_id == new_repo.repo_id).all()
326 UserRepoToPerm.repository_id == new_repo.repo_id).all()
327 assert len(inherited_perms) == 2
327 assert len(inherited_perms) == 2
328
328
329 assert TEST_USER_REGULAR_LOGIN in [
329 assert TEST_USER_REGULAR_LOGIN in [
330 x.user.username for x in inherited_perms]
330 x.user.username for x in inherited_perms]
331 assert 'repository.write' in [
331 assert 'repository.write' in [
332 x.permission.permission_name for x in inherited_perms]
332 x.permission.permission_name for x in inherited_perms]
333
333
334 RepoModel().delete(repo_name_full)
334 RepoModel().delete(repo_name_full)
335 RepoGroupModel().delete(group_name)
335 RepoGroupModel().delete(group_name)
336 Session().commit()
336 Session().commit()
337
337
338 @pytest.mark.xfail_backends(
338 @pytest.mark.xfail_backends(
339 "git", "hg", reason="Missing reposerver support")
339 "git", "hg", reason="Missing reposerver support")
340 def test_create_with_clone_uri(self, autologin_user, backend, reposerver,
340 def test_create_with_clone_uri(self, autologin_user, backend, reposerver,
341 csrf_token):
341 csrf_token):
342 source_repo = backend.create_repo(number_of_commits=2)
342 source_repo = backend.create_repo(number_of_commits=2)
343 source_repo_name = source_repo.repo_name
343 source_repo_name = source_repo.repo_name
344 reposerver.serve(source_repo.scm_instance())
344 reposerver.serve(source_repo.scm_instance())
345
345
346 repo_name = backend.new_repo_name()
346 repo_name = backend.new_repo_name()
347 response = self.app.post(
347 response = self.app.post(
348 route_path('repo_create'),
348 route_path('repo_create'),
349 fixture._get_repo_create_params(
349 fixture._get_repo_create_params(
350 repo_private=False,
350 repo_private=False,
351 repo_name=repo_name,
351 repo_name=repo_name,
352 repo_type=backend.alias,
352 repo_type=backend.alias,
353 repo_description='',
353 repo_description='',
354 clone_uri=reposerver.url,
354 clone_uri=reposerver.url,
355 csrf_token=csrf_token),
355 csrf_token=csrf_token),
356 status=302)
356 status=302)
357
357
358 # Should be redirected to the creating page
358 # Should be redirected to the creating page
359 response.mustcontain('repo_creating')
359 response.mustcontain('repo_creating')
360
360
361 # Expecting that both repositories have same history
361 # Expecting that both repositories have same history
362 source_repo = RepoModel().get_by_repo_name(source_repo_name)
362 source_repo = RepoModel().get_by_repo_name(source_repo_name)
363 source_vcs = source_repo.scm_instance()
363 source_vcs = source_repo.scm_instance()
364 repo = RepoModel().get_by_repo_name(repo_name)
364 repo = RepoModel().get_by_repo_name(repo_name)
365 repo_vcs = repo.scm_instance()
365 repo_vcs = repo.scm_instance()
366 assert source_vcs[0].message == repo_vcs[0].message
366 assert source_vcs[0].message == repo_vcs[0].message
367 assert source_vcs.count() == repo_vcs.count()
367 assert source_vcs.count() == repo_vcs.count()
368 assert source_vcs.commit_ids == repo_vcs.commit_ids
368 assert source_vcs.commit_ids == repo_vcs.commit_ids
369
369
370 @pytest.mark.xfail_backends("svn", reason="Depends on import support")
370 @pytest.mark.xfail_backends("svn", reason="Depends on import support")
371 def test_create_remote_repo_wrong_clone_uri(self, autologin_user, backend,
371 def test_create_remote_repo_wrong_clone_uri(self, autologin_user, backend,
372 csrf_token):
372 csrf_token):
373 repo_name = backend.new_repo_name()
373 repo_name = backend.new_repo_name()
374 description = 'description for newly created repo'
374 description = 'description for newly created repo'
375 response = self.app.post(
375 response = self.app.post(
376 route_path('repo_create'),
376 route_path('repo_create'),
377 fixture._get_repo_create_params(
377 fixture._get_repo_create_params(
378 repo_private=False,
378 repo_private=False,
379 repo_name=repo_name,
379 repo_name=repo_name,
380 repo_type=backend.alias,
380 repo_type=backend.alias,
381 repo_description=description,
381 repo_description=description,
382 clone_uri='http://repo.invalid/repo',
382 clone_uri='http://repo.invalid/repo',
383 csrf_token=csrf_token))
383 csrf_token=csrf_token))
384 response.mustcontain('invalid clone url')
384 response.mustcontain('invalid clone url')
385
385
386 @pytest.mark.xfail_backends("svn", reason="Depends on import support")
386 @pytest.mark.xfail_backends("svn", reason="Depends on import support")
387 def test_create_remote_repo_wrong_clone_uri_hg_svn(
387 def test_create_remote_repo_wrong_clone_uri_hg_svn(
388 self, autologin_user, backend, csrf_token):
388 self, autologin_user, backend, csrf_token):
389 repo_name = backend.new_repo_name()
389 repo_name = backend.new_repo_name()
390 description = 'description for newly created repo'
390 description = 'description for newly created repo'
391 response = self.app.post(
391 response = self.app.post(
392 route_path('repo_create'),
392 route_path('repo_create'),
393 fixture._get_repo_create_params(
393 fixture._get_repo_create_params(
394 repo_private=False,
394 repo_private=False,
395 repo_name=repo_name,
395 repo_name=repo_name,
396 repo_type=backend.alias,
396 repo_type=backend.alias,
397 repo_description=description,
397 repo_description=description,
398 clone_uri='svn+http://svn.invalid/repo',
398 clone_uri='svn+http://svn.invalid/repo',
399 csrf_token=csrf_token))
399 csrf_token=csrf_token))
400 response.mustcontain('invalid clone url')
400 response.mustcontain('invalid clone url')
401
401
402 def test_create_with_git_suffix(
402 def test_create_with_git_suffix(
403 self, autologin_user, backend, csrf_token):
403 self, autologin_user, backend, csrf_token):
404 repo_name = backend.new_repo_name() + ".git"
404 repo_name = backend.new_repo_name() + ".git"
405 description = 'description for newly created repo'
405 description = 'description for newly created repo'
406 response = self.app.post(
406 response = self.app.post(
407 route_path('repo_create'),
407 route_path('repo_create'),
408 fixture._get_repo_create_params(
408 fixture._get_repo_create_params(
409 repo_private=False,
409 repo_private=False,
410 repo_name=repo_name,
410 repo_name=repo_name,
411 repo_type=backend.alias,
411 repo_type=backend.alias,
412 repo_description=description,
412 repo_description=description,
413 csrf_token=csrf_token))
413 csrf_token=csrf_token))
414 response.mustcontain('Repository name cannot end with .git')
414 response.mustcontain('Repository name cannot end with .git')
415
415
416 def test_default_user_cannot_access_private_repo_in_a_group(
416 def test_default_user_cannot_access_private_repo_in_a_group(
417 self, autologin_user, user_util, backend):
417 self, autologin_user, user_util, backend):
418
418
419 group = user_util.create_repo_group()
419 group = user_util.create_repo_group()
420
420
421 repo = backend.create_repo(
421 repo = backend.create_repo(
422 repo_private=True, repo_group=group, repo_copy_permissions=True)
422 repo_private=True, repo_group=group, repo_copy_permissions=True)
423
423
424 permissions = _get_permission_for_user(
424 permissions = _get_permission_for_user(
425 user='default', repo=repo.repo_name)
425 user='default', repo=repo.repo_name)
426 assert len(permissions) == 1
426 assert len(permissions) == 1
427 assert permissions[0].permission.permission_name == 'repository.none'
427 assert permissions[0].permission.permission_name == 'repository.none'
428 assert permissions[0].repository.private is True
428 assert permissions[0].repository.private is True
429
429
430 def test_create_on_top_level_without_permissions(self, backend):
430 def test_create_on_top_level_without_permissions(self, backend):
431 session = login_user_session(
431 session = login_user_session(
432 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
432 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
433 csrf_token = auth.get_csrf_token(session)
433 csrf_token = auth.get_csrf_token(session)
434
434
435 # revoke
435 # revoke
436 user_model = UserModel()
436 user_model = UserModel()
437 # disable fork and create on default user
437 # disable fork and create on default user
438 user_model.revoke_perm(User.DEFAULT_USER, 'hg.create.repository')
438 user_model.revoke_perm(User.DEFAULT_USER, 'hg.create.repository')
439 user_model.grant_perm(User.DEFAULT_USER, 'hg.create.none')
439 user_model.grant_perm(User.DEFAULT_USER, 'hg.create.none')
440 user_model.revoke_perm(User.DEFAULT_USER, 'hg.fork.repository')
440 user_model.revoke_perm(User.DEFAULT_USER, 'hg.fork.repository')
441 user_model.grant_perm(User.DEFAULT_USER, 'hg.fork.none')
441 user_model.grant_perm(User.DEFAULT_USER, 'hg.fork.none')
442
442
443 # disable on regular user
443 # disable on regular user
444 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.repository')
444 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.repository')
445 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.none')
445 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.create.none')
446 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.repository')
446 user_model.revoke_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.repository')
447 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.none')
447 user_model.grant_perm(TEST_USER_REGULAR_LOGIN, 'hg.fork.none')
448 Session().commit()
448 Session().commit()
449
449
450 repo_name = backend.new_repo_name()
450 repo_name = backend.new_repo_name()
451 description = 'description for newly created repo'
451 description = 'description for newly created repo'
452 response = self.app.post(
452 response = self.app.post(
453 route_path('repo_create'),
453 route_path('repo_create'),
454 fixture._get_repo_create_params(
454 fixture._get_repo_create_params(
455 repo_private=False,
455 repo_private=False,
456 repo_name=repo_name,
456 repo_name=repo_name,
457 repo_type=backend.alias,
457 repo_type=backend.alias,
458 repo_description=description,
458 repo_description=description,
459 csrf_token=csrf_token))
459 csrf_token=csrf_token))
460
460
461 response.mustcontain(
461 response.mustcontain(
462 u"You do not have the permission to store repositories in "
462 u"You do not have the permission to store repositories in "
463 u"the root location.")
463 u"the root location.")
464
464
465 @mock.patch.object(RepoModel, '_create_filesystem_repo', error_function)
465 @mock.patch.object(RepoModel, '_create_filesystem_repo', error_function)
466 def test_create_repo_when_filesystem_op_fails(
466 def test_create_repo_when_filesystem_op_fails(
467 self, autologin_user, backend, csrf_token):
467 self, autologin_user, backend, csrf_token):
468 repo_name = backend.new_repo_name()
468 repo_name = backend.new_repo_name()
469 description = 'description for newly created repo'
469 description = 'description for newly created repo'
470
470
471 response = self.app.post(
471 response = self.app.post(
472 route_path('repo_create'),
472 route_path('repo_create'),
473 fixture._get_repo_create_params(
473 fixture._get_repo_create_params(
474 repo_private=False,
474 repo_private=False,
475 repo_name=repo_name,
475 repo_name=repo_name,
476 repo_type=backend.alias,
476 repo_type=backend.alias,
477 repo_description=description,
477 repo_description=description,
478 csrf_token=csrf_token))
478 csrf_token=csrf_token))
479
479
480 assert_session_flash(
480 assert_session_flash(
481 response, 'Error creating repository %s' % repo_name)
481 response, 'Error creating repository %s' % repo_name)
482 # repo must not be in db
482 # repo must not be in db
483 assert backend.repo is None
483 assert backend.repo is None
484 # repo must not be in filesystem !
484 # repo must not be in filesystem !
485 assert not repo_on_filesystem(repo_name)
485 assert not repo_on_filesystem(repo_name)
486
486
487 def assert_repository_is_created_correctly(
487 def assert_repository_is_created_correctly(
488 self, repo_name, description, backend):
488 self, repo_name, description, backend):
489 repo_name_utf8 = safe_str(repo_name)
489 repo_name_utf8 = safe_str(repo_name)
490
490
491 # run the check page that triggers the flash message
491 # run the check page that triggers the flash message
492 response = self.app.get(
492 response = self.app.get(
493 route_path('repo_creating_check', repo_name=safe_str(repo_name)))
493 route_path('repo_creating_check', repo_name=safe_str(repo_name)))
494 assert response.json == {u'result': True}
494 assert response.json == {u'result': True}
495
495
496 flash_msg = u'Created repository <a href="/{}">{}</a>'.format(
496 flash_msg = u'Created repository <a href="/{}">{}</a>'.format(
497 urllib.quote(repo_name_utf8), repo_name)
497 urllib.quote(repo_name_utf8), repo_name)
498 assert_session_flash(response, flash_msg)
498 assert_session_flash(response, flash_msg)
499
499
500 # test if the repo was created in the database
500 # test if the repo was created in the database
501 new_repo = RepoModel().get_by_repo_name(repo_name)
501 new_repo = RepoModel().get_by_repo_name(repo_name)
502
502
503 assert new_repo.repo_name == repo_name
503 assert new_repo.repo_name == repo_name
504 assert new_repo.description == description
504 assert new_repo.description == description
505
505
506 # test if the repository is visible in the list ?
506 # test if the repository is visible in the list ?
507 response = self.app.get(
507 response = self.app.get(
508 h.route_path('repo_summary', repo_name=safe_str(repo_name)))
508 h.route_path('repo_summary', repo_name=safe_str(repo_name)))
509 response.mustcontain(repo_name)
509 response.mustcontain(repo_name)
510 response.mustcontain(backend.alias)
510 response.mustcontain(backend.alias)
511
511
512 assert repo_on_filesystem(repo_name)
512 assert repo_on_filesystem(repo_name)
@@ -1,743 +1,743 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 mock
21 import mock
22 import pytest
22 import pytest
23
23
24 import rhodecode
24 import rhodecode
25 from rhodecode.apps._base import ADMIN_PREFIX
25 from rhodecode.apps._base import ADMIN_PREFIX
26 from rhodecode.lib.utils2 import md5
26 from rhodecode.lib.utils2 import md5
27 from rhodecode.model.db import RhodeCodeUi
27 from rhodecode.model.db import RhodeCodeUi
28 from rhodecode.model.meta import Session
28 from rhodecode.model.meta import Session
29 from rhodecode.model.settings import SettingsModel, IssueTrackerSettingsModel
29 from rhodecode.model.settings import SettingsModel, IssueTrackerSettingsModel
30 from rhodecode.tests import assert_session_flash
30 from rhodecode.tests import assert_session_flash
31 from rhodecode.tests.utils import AssertResponse
31 from rhodecode.tests.utils import AssertResponse
32
32
33
33
34 UPDATE_DATA_QUALNAME = 'rhodecode.model.update.UpdateModel.get_update_data'
34 UPDATE_DATA_QUALNAME = 'rhodecode.model.update.UpdateModel.get_update_data'
35
35
36
36
37 def route_path(name, params=None, **kwargs):
37 def route_path(name, params=None, **kwargs):
38 import urllib
38 import urllib
39 from rhodecode.apps._base import ADMIN_PREFIX
39 from rhodecode.apps._base import ADMIN_PREFIX
40
40
41 base_url = {
41 base_url = {
42
42
43 'admin_settings':
43 'admin_settings':
44 ADMIN_PREFIX +'/settings',
44 ADMIN_PREFIX +'/settings',
45 'admin_settings_update':
45 'admin_settings_update':
46 ADMIN_PREFIX + '/settings/update',
46 ADMIN_PREFIX + '/settings/update',
47 'admin_settings_global':
47 'admin_settings_global':
48 ADMIN_PREFIX + '/settings/global',
48 ADMIN_PREFIX + '/settings/global',
49 'admin_settings_global_update':
49 'admin_settings_global_update':
50 ADMIN_PREFIX + '/settings/global/update',
50 ADMIN_PREFIX + '/settings/global/update',
51 'admin_settings_vcs':
51 'admin_settings_vcs':
52 ADMIN_PREFIX + '/settings/vcs',
52 ADMIN_PREFIX + '/settings/vcs',
53 'admin_settings_vcs_update':
53 'admin_settings_vcs_update':
54 ADMIN_PREFIX + '/settings/vcs/update',
54 ADMIN_PREFIX + '/settings/vcs/update',
55 'admin_settings_vcs_svn_pattern_delete':
55 'admin_settings_vcs_svn_pattern_delete':
56 ADMIN_PREFIX + '/settings/vcs/svn_pattern_delete',
56 ADMIN_PREFIX + '/settings/vcs/svn_pattern_delete',
57 'admin_settings_mapping':
57 'admin_settings_mapping':
58 ADMIN_PREFIX + '/settings/mapping',
58 ADMIN_PREFIX + '/settings/mapping',
59 'admin_settings_mapping_update':
59 'admin_settings_mapping_update':
60 ADMIN_PREFIX + '/settings/mapping/update',
60 ADMIN_PREFIX + '/settings/mapping/update',
61 'admin_settings_visual':
61 'admin_settings_visual':
62 ADMIN_PREFIX + '/settings/visual',
62 ADMIN_PREFIX + '/settings/visual',
63 'admin_settings_visual_update':
63 'admin_settings_visual_update':
64 ADMIN_PREFIX + '/settings/visual/update',
64 ADMIN_PREFIX + '/settings/visual/update',
65 'admin_settings_issuetracker':
65 'admin_settings_issuetracker':
66 ADMIN_PREFIX + '/settings/issue-tracker',
66 ADMIN_PREFIX + '/settings/issue-tracker',
67 'admin_settings_issuetracker_update':
67 'admin_settings_issuetracker_update':
68 ADMIN_PREFIX + '/settings/issue-tracker/update',
68 ADMIN_PREFIX + '/settings/issue-tracker/update',
69 'admin_settings_issuetracker_test':
69 'admin_settings_issuetracker_test':
70 ADMIN_PREFIX + '/settings/issue-tracker/test',
70 ADMIN_PREFIX + '/settings/issue-tracker/test',
71 'admin_settings_issuetracker_delete':
71 'admin_settings_issuetracker_delete':
72 ADMIN_PREFIX + '/settings/issue-tracker/delete',
72 ADMIN_PREFIX + '/settings/issue-tracker/delete',
73 'admin_settings_email':
73 'admin_settings_email':
74 ADMIN_PREFIX + '/settings/email',
74 ADMIN_PREFIX + '/settings/email',
75 'admin_settings_email_update':
75 'admin_settings_email_update':
76 ADMIN_PREFIX + '/settings/email/update',
76 ADMIN_PREFIX + '/settings/email/update',
77 'admin_settings_hooks':
77 'admin_settings_hooks':
78 ADMIN_PREFIX + '/settings/hooks',
78 ADMIN_PREFIX + '/settings/hooks',
79 'admin_settings_hooks_update':
79 'admin_settings_hooks_update':
80 ADMIN_PREFIX + '/settings/hooks/update',
80 ADMIN_PREFIX + '/settings/hooks/update',
81 'admin_settings_hooks_delete':
81 'admin_settings_hooks_delete':
82 ADMIN_PREFIX + '/settings/hooks/delete',
82 ADMIN_PREFIX + '/settings/hooks/delete',
83 'admin_settings_search':
83 'admin_settings_search':
84 ADMIN_PREFIX + '/settings/search',
84 ADMIN_PREFIX + '/settings/search',
85 'admin_settings_labs':
85 'admin_settings_labs':
86 ADMIN_PREFIX + '/settings/labs',
86 ADMIN_PREFIX + '/settings/labs',
87 'admin_settings_labs_update':
87 'admin_settings_labs_update':
88 ADMIN_PREFIX + '/settings/labs/update',
88 ADMIN_PREFIX + '/settings/labs/update',
89
89
90 'admin_settings_sessions':
90 'admin_settings_sessions':
91 ADMIN_PREFIX + '/settings/sessions',
91 ADMIN_PREFIX + '/settings/sessions',
92 'admin_settings_sessions_cleanup':
92 'admin_settings_sessions_cleanup':
93 ADMIN_PREFIX + '/settings/sessions/cleanup',
93 ADMIN_PREFIX + '/settings/sessions/cleanup',
94 'admin_settings_system':
94 'admin_settings_system':
95 ADMIN_PREFIX + '/settings/system',
95 ADMIN_PREFIX + '/settings/system',
96 'admin_settings_system_update':
96 'admin_settings_system_update':
97 ADMIN_PREFIX + '/settings/system/updates',
97 ADMIN_PREFIX + '/settings/system/updates',
98 'admin_settings_open_source':
98 'admin_settings_open_source':
99 ADMIN_PREFIX + '/settings/open_source',
99 ADMIN_PREFIX + '/settings/open_source',
100
100
101
101
102 }[name].format(**kwargs)
102 }[name].format(**kwargs)
103
103
104 if params:
104 if params:
105 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
105 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
106 return base_url
106 return base_url
107
107
108
108
109 @pytest.mark.usefixtures('autologin_user', 'app')
109 @pytest.mark.usefixtures('autologin_user', 'app')
110 class TestAdminSettingsController(object):
110 class TestAdminSettingsController(object):
111
111
112 @pytest.mark.parametrize('urlname', [
112 @pytest.mark.parametrize('urlname', [
113 'admin_settings_vcs',
113 'admin_settings_vcs',
114 'admin_settings_mapping',
114 'admin_settings_mapping',
115 'admin_settings_global',
115 'admin_settings_global',
116 'admin_settings_visual',
116 'admin_settings_visual',
117 'admin_settings_email',
117 'admin_settings_email',
118 'admin_settings_hooks',
118 'admin_settings_hooks',
119 'admin_settings_search',
119 'admin_settings_search',
120 ])
120 ])
121 def test_simple_get(self, urlname):
121 def test_simple_get(self, urlname):
122 self.app.get(route_path(urlname))
122 self.app.get(route_path(urlname))
123
123
124 def test_create_custom_hook(self, csrf_token):
124 def test_create_custom_hook(self, csrf_token):
125 response = self.app.post(
125 response = self.app.post(
126 route_path('admin_settings_hooks_update'),
126 route_path('admin_settings_hooks_update'),
127 params={
127 params={
128 'new_hook_ui_key': 'test_hooks_1',
128 'new_hook_ui_key': 'test_hooks_1',
129 'new_hook_ui_value': 'cd /tmp',
129 'new_hook_ui_value': 'cd /tmp',
130 'csrf_token': csrf_token})
130 'csrf_token': csrf_token})
131
131
132 response = response.follow()
132 response = response.follow()
133 response.mustcontain('test_hooks_1')
133 response.mustcontain('test_hooks_1')
134 response.mustcontain('cd /tmp')
134 response.mustcontain('cd /tmp')
135
135
136 def test_create_custom_hook_delete(self, csrf_token):
136 def test_create_custom_hook_delete(self, csrf_token):
137 response = self.app.post(
137 response = self.app.post(
138 route_path('admin_settings_hooks_update'),
138 route_path('admin_settings_hooks_update'),
139 params={
139 params={
140 'new_hook_ui_key': 'test_hooks_2',
140 'new_hook_ui_key': 'test_hooks_2',
141 'new_hook_ui_value': 'cd /tmp2',
141 'new_hook_ui_value': 'cd /tmp2',
142 'csrf_token': csrf_token})
142 'csrf_token': csrf_token})
143
143
144 response = response.follow()
144 response = response.follow()
145 response.mustcontain('test_hooks_2')
145 response.mustcontain('test_hooks_2')
146 response.mustcontain('cd /tmp2')
146 response.mustcontain('cd /tmp2')
147
147
148 hook_id = SettingsModel().get_ui_by_key('test_hooks_2').ui_id
148 hook_id = SettingsModel().get_ui_by_key('test_hooks_2').ui_id
149
149
150 # delete
150 # delete
151 self.app.post(
151 self.app.post(
152 route_path('admin_settings_hooks_delete'),
152 route_path('admin_settings_hooks_delete'),
153 params={'hook_id': hook_id, 'csrf_token': csrf_token})
153 params={'hook_id': hook_id, 'csrf_token': csrf_token})
154 response = self.app.get(route_path('admin_settings_hooks'))
154 response = self.app.get(route_path('admin_settings_hooks'))
155 response.mustcontain(no=['test_hooks_2'])
155 response.mustcontain(no=['test_hooks_2'])
156 response.mustcontain(no=['cd /tmp2'])
156 response.mustcontain(no=['cd /tmp2'])
157
157
158
158
159 @pytest.mark.usefixtures('autologin_user', 'app')
159 @pytest.mark.usefixtures('autologin_user', 'app')
160 class TestAdminSettingsGlobal(object):
160 class TestAdminSettingsGlobal(object):
161
161
162 def test_pre_post_code_code_active(self, csrf_token):
162 def test_pre_post_code_code_active(self, csrf_token):
163 pre_code = 'rc-pre-code-187652122'
163 pre_code = 'rc-pre-code-187652122'
164 post_code = 'rc-postcode-98165231'
164 post_code = 'rc-postcode-98165231'
165
165
166 response = self.post_and_verify_settings({
166 response = self.post_and_verify_settings({
167 'rhodecode_pre_code': pre_code,
167 'rhodecode_pre_code': pre_code,
168 'rhodecode_post_code': post_code,
168 'rhodecode_post_code': post_code,
169 'csrf_token': csrf_token,
169 'csrf_token': csrf_token,
170 })
170 })
171
171
172 response = response.follow()
172 response = response.follow()
173 response.mustcontain(pre_code, post_code)
173 response.mustcontain(pre_code, post_code)
174
174
175 def test_pre_post_code_code_inactive(self, csrf_token):
175 def test_pre_post_code_code_inactive(self, csrf_token):
176 pre_code = 'rc-pre-code-187652122'
176 pre_code = 'rc-pre-code-187652122'
177 post_code = 'rc-postcode-98165231'
177 post_code = 'rc-postcode-98165231'
178 response = self.post_and_verify_settings({
178 response = self.post_and_verify_settings({
179 'rhodecode_pre_code': '',
179 'rhodecode_pre_code': '',
180 'rhodecode_post_code': '',
180 'rhodecode_post_code': '',
181 'csrf_token': csrf_token,
181 'csrf_token': csrf_token,
182 })
182 })
183
183
184 response = response.follow()
184 response = response.follow()
185 response.mustcontain(no=[pre_code, post_code])
185 response.mustcontain(no=[pre_code, post_code])
186
186
187 def test_captcha_activate(self, csrf_token):
187 def test_captcha_activate(self, csrf_token):
188 self.post_and_verify_settings({
188 self.post_and_verify_settings({
189 'rhodecode_captcha_private_key': '1234567890',
189 'rhodecode_captcha_private_key': '1234567890',
190 'rhodecode_captcha_public_key': '1234567890',
190 'rhodecode_captcha_public_key': '1234567890',
191 'csrf_token': csrf_token,
191 'csrf_token': csrf_token,
192 })
192 })
193
193
194 response = self.app.get(ADMIN_PREFIX + '/register')
194 response = self.app.get(ADMIN_PREFIX + '/register')
195 response.mustcontain('captcha')
195 response.mustcontain('captcha')
196
196
197 def test_captcha_deactivate(self, csrf_token):
197 def test_captcha_deactivate(self, csrf_token):
198 self.post_and_verify_settings({
198 self.post_and_verify_settings({
199 'rhodecode_captcha_private_key': '',
199 'rhodecode_captcha_private_key': '',
200 'rhodecode_captcha_public_key': '1234567890',
200 'rhodecode_captcha_public_key': '1234567890',
201 'csrf_token': csrf_token,
201 'csrf_token': csrf_token,
202 })
202 })
203
203
204 response = self.app.get(ADMIN_PREFIX + '/register')
204 response = self.app.get(ADMIN_PREFIX + '/register')
205 response.mustcontain(no=['captcha'])
205 response.mustcontain(no=['captcha'])
206
206
207 def test_title_change(self, csrf_token):
207 def test_title_change(self, csrf_token):
208 old_title = 'RhodeCode'
208 old_title = 'RhodeCode'
209
209
210 for new_title in ['Changed', 'Ε»Γ³Ε‚wik', old_title]:
210 for new_title in ['Changed', 'Ε»Γ³Ε‚wik', old_title]:
211 response = self.post_and_verify_settings({
211 response = self.post_and_verify_settings({
212 'rhodecode_title': new_title,
212 'rhodecode_title': new_title,
213 'csrf_token': csrf_token,
213 'csrf_token': csrf_token,
214 })
214 })
215
215
216 response = response.follow()
216 response = response.follow()
217 response.mustcontain(new_title)
217 response.mustcontain(new_title)
218
218
219 def post_and_verify_settings(self, settings):
219 def post_and_verify_settings(self, settings):
220 old_title = 'RhodeCode'
220 old_title = 'RhodeCode'
221 old_realm = 'RhodeCode authentication'
221 old_realm = 'RhodeCode authentication'
222 params = {
222 params = {
223 'rhodecode_title': old_title,
223 'rhodecode_title': old_title,
224 'rhodecode_realm': old_realm,
224 'rhodecode_realm': old_realm,
225 'rhodecode_pre_code': '',
225 'rhodecode_pre_code': '',
226 'rhodecode_post_code': '',
226 'rhodecode_post_code': '',
227 'rhodecode_captcha_private_key': '',
227 'rhodecode_captcha_private_key': '',
228 'rhodecode_captcha_public_key': '',
228 'rhodecode_captcha_public_key': '',
229 'rhodecode_create_personal_repo_group': False,
229 'rhodecode_create_personal_repo_group': False,
230 'rhodecode_personal_repo_group_pattern': '${username}',
230 'rhodecode_personal_repo_group_pattern': '${username}',
231 }
231 }
232 params.update(settings)
232 params.update(settings)
233 response = self.app.post(
233 response = self.app.post(
234 route_path('admin_settings_global_update'), params=params)
234 route_path('admin_settings_global_update'), params=params)
235
235
236 assert_session_flash(response, 'Updated application settings')
236 assert_session_flash(response, 'Updated application settings')
237 app_settings = SettingsModel().get_all_settings()
237 app_settings = SettingsModel().get_all_settings()
238 del settings['csrf_token']
238 del settings['csrf_token']
239 for key, value in settings.iteritems():
239 for key, value in settings.iteritems():
240 assert app_settings[key] == value.decode('utf-8')
240 assert app_settings[key] == value.decode('utf-8')
241
241
242 return response
242 return response
243
243
244
244
245 @pytest.mark.usefixtures('autologin_user', 'app')
245 @pytest.mark.usefixtures('autologin_user', 'app')
246 class TestAdminSettingsVcs(object):
246 class TestAdminSettingsVcs(object):
247
247
248 def test_contains_svn_default_patterns(self):
248 def test_contains_svn_default_patterns(self):
249 response = self.app.get(route_path('admin_settings_vcs'))
249 response = self.app.get(route_path('admin_settings_vcs'))
250 expected_patterns = [
250 expected_patterns = [
251 '/trunk',
251 '/trunk',
252 '/branches/*',
252 '/branches/*',
253 '/tags/*',
253 '/tags/*',
254 ]
254 ]
255 for pattern in expected_patterns:
255 for pattern in expected_patterns:
256 response.mustcontain(pattern)
256 response.mustcontain(pattern)
257
257
258 def test_add_new_svn_branch_and_tag_pattern(
258 def test_add_new_svn_branch_and_tag_pattern(
259 self, backend_svn, form_defaults, disable_sql_cache,
259 self, backend_svn, form_defaults, disable_sql_cache,
260 csrf_token):
260 csrf_token):
261 form_defaults.update({
261 form_defaults.update({
262 'new_svn_branch': '/exp/branches/*',
262 'new_svn_branch': '/exp/branches/*',
263 'new_svn_tag': '/important_tags/*',
263 'new_svn_tag': '/important_tags/*',
264 'csrf_token': csrf_token,
264 'csrf_token': csrf_token,
265 })
265 })
266
266
267 response = self.app.post(
267 response = self.app.post(
268 route_path('admin_settings_vcs_update'),
268 route_path('admin_settings_vcs_update'),
269 params=form_defaults, status=302)
269 params=form_defaults, status=302)
270 response = response.follow()
270 response = response.follow()
271
271
272 # Expect to find the new values on the page
272 # Expect to find the new values on the page
273 response.mustcontain('/exp/branches/*')
273 response.mustcontain('/exp/branches/*')
274 response.mustcontain('/important_tags/*')
274 response.mustcontain('/important_tags/*')
275
275
276 # Expect that those patterns are used to match branches and tags now
276 # Expect that those patterns are used to match branches and tags now
277 repo = backend_svn['svn-simple-layout'].scm_instance()
277 repo = backend_svn['svn-simple-layout'].scm_instance()
278 assert 'exp/branches/exp-sphinx-docs' in repo.branches
278 assert 'exp/branches/exp-sphinx-docs' in repo.branches
279 assert 'important_tags/v0.5' in repo.tags
279 assert 'important_tags/v0.5' in repo.tags
280
280
281 def test_add_same_svn_value_twice_shows_an_error_message(
281 def test_add_same_svn_value_twice_shows_an_error_message(
282 self, form_defaults, csrf_token, settings_util):
282 self, form_defaults, csrf_token, settings_util):
283 settings_util.create_rhodecode_ui('vcs_svn_branch', '/test')
283 settings_util.create_rhodecode_ui('vcs_svn_branch', '/test')
284 settings_util.create_rhodecode_ui('vcs_svn_tag', '/test')
284 settings_util.create_rhodecode_ui('vcs_svn_tag', '/test')
285
285
286 response = self.app.post(
286 response = self.app.post(
287 route_path('admin_settings_vcs_update'),
287 route_path('admin_settings_vcs_update'),
288 params={
288 params={
289 'paths_root_path': form_defaults['paths_root_path'],
289 'paths_root_path': form_defaults['paths_root_path'],
290 'new_svn_branch': '/test',
290 'new_svn_branch': '/test',
291 'new_svn_tag': '/test',
291 'new_svn_tag': '/test',
292 'csrf_token': csrf_token,
292 'csrf_token': csrf_token,
293 },
293 },
294 status=200)
294 status=200)
295
295
296 response.mustcontain("Pattern already exists")
296 response.mustcontain("Pattern already exists")
297 response.mustcontain("Some form inputs contain invalid data.")
297 response.mustcontain("Some form inputs contain invalid data.")
298
298
299 @pytest.mark.parametrize('section', [
299 @pytest.mark.parametrize('section', [
300 'vcs_svn_branch',
300 'vcs_svn_branch',
301 'vcs_svn_tag',
301 'vcs_svn_tag',
302 ])
302 ])
303 def test_delete_svn_patterns(
303 def test_delete_svn_patterns(
304 self, section, csrf_token, settings_util):
304 self, section, csrf_token, settings_util):
305 setting = settings_util.create_rhodecode_ui(
305 setting = settings_util.create_rhodecode_ui(
306 section, '/test_delete', cleanup=False)
306 section, '/test_delete', cleanup=False)
307
307
308 self.app.post(
308 self.app.post(
309 route_path('admin_settings_vcs_svn_pattern_delete'),
309 route_path('admin_settings_vcs_svn_pattern_delete'),
310 params={
310 params={
311 'delete_svn_pattern': setting.ui_id,
311 'delete_svn_pattern': setting.ui_id,
312 'csrf_token': csrf_token},
312 'csrf_token': csrf_token},
313 headers={'X-REQUESTED-WITH': 'XMLHttpRequest'})
313 headers={'X-REQUESTED-WITH': 'XMLHttpRequest'})
314
314
315 @pytest.mark.parametrize('section', [
315 @pytest.mark.parametrize('section', [
316 'vcs_svn_branch',
316 'vcs_svn_branch',
317 'vcs_svn_tag',
317 'vcs_svn_tag',
318 ])
318 ])
319 def test_delete_svn_patterns_raises_404_when_no_xhr(
319 def test_delete_svn_patterns_raises_404_when_no_xhr(
320 self, section, csrf_token, settings_util):
320 self, section, csrf_token, settings_util):
321 setting = settings_util.create_rhodecode_ui(section, '/test_delete')
321 setting = settings_util.create_rhodecode_ui(section, '/test_delete')
322
322
323 self.app.post(
323 self.app.post(
324 route_path('admin_settings_vcs_svn_pattern_delete'),
324 route_path('admin_settings_vcs_svn_pattern_delete'),
325 params={
325 params={
326 'delete_svn_pattern': setting.ui_id,
326 'delete_svn_pattern': setting.ui_id,
327 'csrf_token': csrf_token},
327 'csrf_token': csrf_token},
328 status=404)
328 status=404)
329
329
330 def test_extensions_hgsubversion(self, form_defaults, csrf_token):
330 def test_extensions_hgsubversion(self, form_defaults, csrf_token):
331 form_defaults.update({
331 form_defaults.update({
332 'csrf_token': csrf_token,
332 'csrf_token': csrf_token,
333 'extensions_hgsubversion': 'True',
333 'extensions_hgsubversion': 'True',
334 })
334 })
335 response = self.app.post(
335 response = self.app.post(
336 route_path('admin_settings_vcs_update'),
336 route_path('admin_settings_vcs_update'),
337 params=form_defaults,
337 params=form_defaults,
338 status=302)
338 status=302)
339
339
340 response = response.follow()
340 response = response.follow()
341 extensions_input = (
341 extensions_input = (
342 '<input id="extensions_hgsubversion" '
342 '<input id="extensions_hgsubversion" '
343 'name="extensions_hgsubversion" type="checkbox" '
343 'name="extensions_hgsubversion" type="checkbox" '
344 'value="True" checked="checked" />')
344 'value="True" checked="checked" />')
345 response.mustcontain(extensions_input)
345 response.mustcontain(extensions_input)
346
346
347 def test_extensions_hgevolve(self, form_defaults, csrf_token):
347 def test_extensions_hgevolve(self, form_defaults, csrf_token):
348 form_defaults.update({
348 form_defaults.update({
349 'csrf_token': csrf_token,
349 'csrf_token': csrf_token,
350 'extensions_evolve': 'True',
350 'extensions_evolve': 'True',
351 })
351 })
352 response = self.app.post(
352 response = self.app.post(
353 route_path('admin_settings_vcs_update'),
353 route_path('admin_settings_vcs_update'),
354 params=form_defaults,
354 params=form_defaults,
355 status=302)
355 status=302)
356
356
357 response = response.follow()
357 response = response.follow()
358 extensions_input = (
358 extensions_input = (
359 '<input id="extensions_evolve" '
359 '<input id="extensions_evolve" '
360 'name="extensions_evolve" type="checkbox" '
360 'name="extensions_evolve" type="checkbox" '
361 'value="True" checked="checked" />')
361 'value="True" checked="checked" />')
362 response.mustcontain(extensions_input)
362 response.mustcontain(extensions_input)
363
363
364 def test_has_a_section_for_pull_request_settings(self):
364 def test_has_a_section_for_pull_request_settings(self):
365 response = self.app.get(route_path('admin_settings_vcs'))
365 response = self.app.get(route_path('admin_settings_vcs'))
366 response.mustcontain('Pull Request Settings')
366 response.mustcontain('Pull Request Settings')
367
367
368 def test_has_an_input_for_invalidation_of_inline_comments(self):
368 def test_has_an_input_for_invalidation_of_inline_comments(self):
369 response = self.app.get(route_path('admin_settings_vcs'))
369 response = self.app.get(route_path('admin_settings_vcs'))
370 assert_response = AssertResponse(response)
370 assert_response = response.assert_response()
371 assert_response.one_element_exists(
371 assert_response.one_element_exists(
372 '[name=rhodecode_use_outdated_comments]')
372 '[name=rhodecode_use_outdated_comments]')
373
373
374 @pytest.mark.parametrize('new_value', [True, False])
374 @pytest.mark.parametrize('new_value', [True, False])
375 def test_allows_to_change_invalidation_of_inline_comments(
375 def test_allows_to_change_invalidation_of_inline_comments(
376 self, form_defaults, csrf_token, new_value):
376 self, form_defaults, csrf_token, new_value):
377 setting_key = 'use_outdated_comments'
377 setting_key = 'use_outdated_comments'
378 setting = SettingsModel().create_or_update_setting(
378 setting = SettingsModel().create_or_update_setting(
379 setting_key, not new_value, 'bool')
379 setting_key, not new_value, 'bool')
380 Session().add(setting)
380 Session().add(setting)
381 Session().commit()
381 Session().commit()
382
382
383 form_defaults.update({
383 form_defaults.update({
384 'csrf_token': csrf_token,
384 'csrf_token': csrf_token,
385 'rhodecode_use_outdated_comments': str(new_value),
385 'rhodecode_use_outdated_comments': str(new_value),
386 })
386 })
387 response = self.app.post(
387 response = self.app.post(
388 route_path('admin_settings_vcs_update'),
388 route_path('admin_settings_vcs_update'),
389 params=form_defaults,
389 params=form_defaults,
390 status=302)
390 status=302)
391 response = response.follow()
391 response = response.follow()
392 setting = SettingsModel().get_setting_by_name(setting_key)
392 setting = SettingsModel().get_setting_by_name(setting_key)
393 assert setting.app_settings_value is new_value
393 assert setting.app_settings_value is new_value
394
394
395 @pytest.mark.parametrize('new_value', [True, False])
395 @pytest.mark.parametrize('new_value', [True, False])
396 def test_allows_to_change_hg_rebase_merge_strategy(
396 def test_allows_to_change_hg_rebase_merge_strategy(
397 self, form_defaults, csrf_token, new_value):
397 self, form_defaults, csrf_token, new_value):
398 setting_key = 'hg_use_rebase_for_merging'
398 setting_key = 'hg_use_rebase_for_merging'
399
399
400 form_defaults.update({
400 form_defaults.update({
401 'csrf_token': csrf_token,
401 'csrf_token': csrf_token,
402 'rhodecode_' + setting_key: str(new_value),
402 'rhodecode_' + setting_key: str(new_value),
403 })
403 })
404
404
405 with mock.patch.dict(
405 with mock.patch.dict(
406 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
406 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
407 self.app.post(
407 self.app.post(
408 route_path('admin_settings_vcs_update'),
408 route_path('admin_settings_vcs_update'),
409 params=form_defaults,
409 params=form_defaults,
410 status=302)
410 status=302)
411
411
412 setting = SettingsModel().get_setting_by_name(setting_key)
412 setting = SettingsModel().get_setting_by_name(setting_key)
413 assert setting.app_settings_value is new_value
413 assert setting.app_settings_value is new_value
414
414
415 @pytest.fixture()
415 @pytest.fixture()
416 def disable_sql_cache(self, request):
416 def disable_sql_cache(self, request):
417 patcher = mock.patch(
417 patcher = mock.patch(
418 'rhodecode.lib.caching_query.FromCache.process_query')
418 'rhodecode.lib.caching_query.FromCache.process_query')
419 request.addfinalizer(patcher.stop)
419 request.addfinalizer(patcher.stop)
420 patcher.start()
420 patcher.start()
421
421
422 @pytest.fixture()
422 @pytest.fixture()
423 def form_defaults(self):
423 def form_defaults(self):
424 from rhodecode.apps.admin.views.settings import AdminSettingsView
424 from rhodecode.apps.admin.views.settings import AdminSettingsView
425 return AdminSettingsView._form_defaults()
425 return AdminSettingsView._form_defaults()
426
426
427 # TODO: johbo: What we really want is to checkpoint before a test run and
427 # TODO: johbo: What we really want is to checkpoint before a test run and
428 # reset the session afterwards.
428 # reset the session afterwards.
429 @pytest.fixture(scope='class', autouse=True)
429 @pytest.fixture(scope='class', autouse=True)
430 def cleanup_settings(self, request, baseapp):
430 def cleanup_settings(self, request, baseapp):
431 ui_id = RhodeCodeUi.ui_id
431 ui_id = RhodeCodeUi.ui_id
432 original_ids = list(
432 original_ids = list(
433 r.ui_id for r in RhodeCodeUi.query().values(ui_id))
433 r.ui_id for r in RhodeCodeUi.query().values(ui_id))
434
434
435 @request.addfinalizer
435 @request.addfinalizer
436 def cleanup():
436 def cleanup():
437 RhodeCodeUi.query().filter(
437 RhodeCodeUi.query().filter(
438 ui_id.notin_(original_ids)).delete(False)
438 ui_id.notin_(original_ids)).delete(False)
439
439
440
440
441 @pytest.mark.usefixtures('autologin_user', 'app')
441 @pytest.mark.usefixtures('autologin_user', 'app')
442 class TestLabsSettings(object):
442 class TestLabsSettings(object):
443 def test_get_settings_page_disabled(self):
443 def test_get_settings_page_disabled(self):
444 with mock.patch.dict(
444 with mock.patch.dict(
445 rhodecode.CONFIG, {'labs_settings_active': 'false'}):
445 rhodecode.CONFIG, {'labs_settings_active': 'false'}):
446
446
447 response = self.app.get(
447 response = self.app.get(
448 route_path('admin_settings_labs'), status=302)
448 route_path('admin_settings_labs'), status=302)
449
449
450 assert response.location.endswith(route_path('admin_settings'))
450 assert response.location.endswith(route_path('admin_settings'))
451
451
452 def test_get_settings_page_enabled(self):
452 def test_get_settings_page_enabled(self):
453 from rhodecode.apps.admin.views import settings
453 from rhodecode.apps.admin.views import settings
454 lab_settings = [
454 lab_settings = [
455 settings.LabSetting(
455 settings.LabSetting(
456 key='rhodecode_bool',
456 key='rhodecode_bool',
457 type='bool',
457 type='bool',
458 group='bool group',
458 group='bool group',
459 label='bool label',
459 label='bool label',
460 help='bool help'
460 help='bool help'
461 ),
461 ),
462 settings.LabSetting(
462 settings.LabSetting(
463 key='rhodecode_text',
463 key='rhodecode_text',
464 type='unicode',
464 type='unicode',
465 group='text group',
465 group='text group',
466 label='text label',
466 label='text label',
467 help='text help'
467 help='text help'
468 ),
468 ),
469 ]
469 ]
470 with mock.patch.dict(rhodecode.CONFIG,
470 with mock.patch.dict(rhodecode.CONFIG,
471 {'labs_settings_active': 'true'}):
471 {'labs_settings_active': 'true'}):
472 with mock.patch.object(settings, '_LAB_SETTINGS', lab_settings):
472 with mock.patch.object(settings, '_LAB_SETTINGS', lab_settings):
473 response = self.app.get(route_path('admin_settings_labs'))
473 response = self.app.get(route_path('admin_settings_labs'))
474
474
475 assert '<label>bool group:</label>' in response
475 assert '<label>bool group:</label>' in response
476 assert '<label for="rhodecode_bool">bool label</label>' in response
476 assert '<label for="rhodecode_bool">bool label</label>' in response
477 assert '<p class="help-block">bool help</p>' in response
477 assert '<p class="help-block">bool help</p>' in response
478 assert 'name="rhodecode_bool" type="checkbox"' in response
478 assert 'name="rhodecode_bool" type="checkbox"' in response
479
479
480 assert '<label>text group:</label>' in response
480 assert '<label>text group:</label>' in response
481 assert '<label for="rhodecode_text">text label</label>' in response
481 assert '<label for="rhodecode_text">text label</label>' in response
482 assert '<p class="help-block">text help</p>' in response
482 assert '<p class="help-block">text help</p>' in response
483 assert 'name="rhodecode_text" size="60" type="text"' in response
483 assert 'name="rhodecode_text" size="60" type="text"' in response
484
484
485
485
486 @pytest.mark.usefixtures('app')
486 @pytest.mark.usefixtures('app')
487 class TestOpenSourceLicenses(object):
487 class TestOpenSourceLicenses(object):
488
488
489 def test_records_are_displayed(self, autologin_user):
489 def test_records_are_displayed(self, autologin_user):
490 sample_licenses = [
490 sample_licenses = [
491 {
491 {
492 "license": [
492 "license": [
493 {
493 {
494 "fullName": "BSD 4-clause \"Original\" or \"Old\" License",
494 "fullName": "BSD 4-clause \"Original\" or \"Old\" License",
495 "shortName": "bsdOriginal",
495 "shortName": "bsdOriginal",
496 "spdxId": "BSD-4-Clause",
496 "spdxId": "BSD-4-Clause",
497 "url": "http://spdx.org/licenses/BSD-4-Clause.html"
497 "url": "http://spdx.org/licenses/BSD-4-Clause.html"
498 }
498 }
499 ],
499 ],
500 "name": "python2.7-coverage-3.7.1"
500 "name": "python2.7-coverage-3.7.1"
501 },
501 },
502 {
502 {
503 "license": [
503 "license": [
504 {
504 {
505 "fullName": "MIT License",
505 "fullName": "MIT License",
506 "shortName": "mit",
506 "shortName": "mit",
507 "spdxId": "MIT",
507 "spdxId": "MIT",
508 "url": "http://spdx.org/licenses/MIT.html"
508 "url": "http://spdx.org/licenses/MIT.html"
509 }
509 }
510 ],
510 ],
511 "name": "python2.7-bootstrapped-pip-9.0.1"
511 "name": "python2.7-bootstrapped-pip-9.0.1"
512 },
512 },
513 ]
513 ]
514 read_licenses_patch = mock.patch(
514 read_licenses_patch = mock.patch(
515 'rhodecode.apps.admin.views.open_source_licenses.read_opensource_licenses',
515 'rhodecode.apps.admin.views.open_source_licenses.read_opensource_licenses',
516 return_value=sample_licenses)
516 return_value=sample_licenses)
517 with read_licenses_patch:
517 with read_licenses_patch:
518 response = self.app.get(
518 response = self.app.get(
519 route_path('admin_settings_open_source'), status=200)
519 route_path('admin_settings_open_source'), status=200)
520
520
521 assert_response = AssertResponse(response)
521 assert_response = response.assert_response()
522 assert_response.element_contains(
522 assert_response.element_contains(
523 '.panel-heading', 'Licenses of Third Party Packages')
523 '.panel-heading', 'Licenses of Third Party Packages')
524 for license_data in sample_licenses:
524 for license_data in sample_licenses:
525 response.mustcontain(license_data["license"][0]["spdxId"])
525 response.mustcontain(license_data["license"][0]["spdxId"])
526 assert_response.element_contains('.panel-body', license_data["name"])
526 assert_response.element_contains('.panel-body', license_data["name"])
527
527
528 def test_records_can_be_read(self, autologin_user):
528 def test_records_can_be_read(self, autologin_user):
529 response = self.app.get(
529 response = self.app.get(
530 route_path('admin_settings_open_source'), status=200)
530 route_path('admin_settings_open_source'), status=200)
531 assert_response = AssertResponse(response)
531 assert_response = response.assert_response()
532 assert_response.element_contains(
532 assert_response.element_contains(
533 '.panel-heading', 'Licenses of Third Party Packages')
533 '.panel-heading', 'Licenses of Third Party Packages')
534
534
535 def test_forbidden_when_normal_user(self, autologin_regular_user):
535 def test_forbidden_when_normal_user(self, autologin_regular_user):
536 self.app.get(
536 self.app.get(
537 route_path('admin_settings_open_source'), status=404)
537 route_path('admin_settings_open_source'), status=404)
538
538
539
539
540 @pytest.mark.usefixtures('app')
540 @pytest.mark.usefixtures('app')
541 class TestUserSessions(object):
541 class TestUserSessions(object):
542
542
543 def test_forbidden_when_normal_user(self, autologin_regular_user):
543 def test_forbidden_when_normal_user(self, autologin_regular_user):
544 self.app.get(route_path('admin_settings_sessions'), status=404)
544 self.app.get(route_path('admin_settings_sessions'), status=404)
545
545
546 def test_show_sessions_page(self, autologin_user):
546 def test_show_sessions_page(self, autologin_user):
547 response = self.app.get(route_path('admin_settings_sessions'), status=200)
547 response = self.app.get(route_path('admin_settings_sessions'), status=200)
548 response.mustcontain('file')
548 response.mustcontain('file')
549
549
550 def test_cleanup_old_sessions(self, autologin_user, csrf_token):
550 def test_cleanup_old_sessions(self, autologin_user, csrf_token):
551
551
552 post_data = {
552 post_data = {
553 'csrf_token': csrf_token,
553 'csrf_token': csrf_token,
554 'expire_days': '60'
554 'expire_days': '60'
555 }
555 }
556 response = self.app.post(
556 response = self.app.post(
557 route_path('admin_settings_sessions_cleanup'), params=post_data,
557 route_path('admin_settings_sessions_cleanup'), params=post_data,
558 status=302)
558 status=302)
559 assert_session_flash(response, 'Cleaned up old sessions')
559 assert_session_flash(response, 'Cleaned up old sessions')
560
560
561
561
562 @pytest.mark.usefixtures('app')
562 @pytest.mark.usefixtures('app')
563 class TestAdminSystemInfo(object):
563 class TestAdminSystemInfo(object):
564
564
565 def test_forbidden_when_normal_user(self, autologin_regular_user):
565 def test_forbidden_when_normal_user(self, autologin_regular_user):
566 self.app.get(route_path('admin_settings_system'), status=404)
566 self.app.get(route_path('admin_settings_system'), status=404)
567
567
568 def test_system_info_page(self, autologin_user):
568 def test_system_info_page(self, autologin_user):
569 response = self.app.get(route_path('admin_settings_system'))
569 response = self.app.get(route_path('admin_settings_system'))
570 response.mustcontain('RhodeCode Community Edition, version {}'.format(
570 response.mustcontain('RhodeCode Community Edition, version {}'.format(
571 rhodecode.__version__))
571 rhodecode.__version__))
572
572
573 def test_system_update_new_version(self, autologin_user):
573 def test_system_update_new_version(self, autologin_user):
574 update_data = {
574 update_data = {
575 'versions': [
575 'versions': [
576 {
576 {
577 'version': '100.3.1415926535',
577 'version': '100.3.1415926535',
578 'general': 'The latest version we are ever going to ship'
578 'general': 'The latest version we are ever going to ship'
579 },
579 },
580 {
580 {
581 'version': '0.0.0',
581 'version': '0.0.0',
582 'general': 'The first version we ever shipped'
582 'general': 'The first version we ever shipped'
583 }
583 }
584 ]
584 ]
585 }
585 }
586 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
586 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
587 response = self.app.get(route_path('admin_settings_system_update'))
587 response = self.app.get(route_path('admin_settings_system_update'))
588 response.mustcontain('A <b>new version</b> is available')
588 response.mustcontain('A <b>new version</b> is available')
589
589
590 def test_system_update_nothing_new(self, autologin_user):
590 def test_system_update_nothing_new(self, autologin_user):
591 update_data = {
591 update_data = {
592 'versions': [
592 'versions': [
593 {
593 {
594 'version': '0.0.0',
594 'version': '0.0.0',
595 'general': 'The first version we ever shipped'
595 'general': 'The first version we ever shipped'
596 }
596 }
597 ]
597 ]
598 }
598 }
599 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
599 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
600 response = self.app.get(route_path('admin_settings_system_update'))
600 response = self.app.get(route_path('admin_settings_system_update'))
601 response.mustcontain(
601 response.mustcontain(
602 'This instance is already running the <b>latest</b> stable version')
602 'This instance is already running the <b>latest</b> stable version')
603
603
604 def test_system_update_bad_response(self, autologin_user):
604 def test_system_update_bad_response(self, autologin_user):
605 with mock.patch(UPDATE_DATA_QUALNAME, side_effect=ValueError('foo')):
605 with mock.patch(UPDATE_DATA_QUALNAME, side_effect=ValueError('foo')):
606 response = self.app.get(route_path('admin_settings_system_update'))
606 response = self.app.get(route_path('admin_settings_system_update'))
607 response.mustcontain(
607 response.mustcontain(
608 'Bad data sent from update server')
608 'Bad data sent from update server')
609
609
610
610
611 @pytest.mark.usefixtures("app")
611 @pytest.mark.usefixtures("app")
612 class TestAdminSettingsIssueTracker(object):
612 class TestAdminSettingsIssueTracker(object):
613 RC_PREFIX = 'rhodecode_'
613 RC_PREFIX = 'rhodecode_'
614 SHORT_PATTERN_KEY = 'issuetracker_pat_'
614 SHORT_PATTERN_KEY = 'issuetracker_pat_'
615 PATTERN_KEY = RC_PREFIX + SHORT_PATTERN_KEY
615 PATTERN_KEY = RC_PREFIX + SHORT_PATTERN_KEY
616
616
617 def test_issuetracker_index(self, autologin_user):
617 def test_issuetracker_index(self, autologin_user):
618 response = self.app.get(route_path('admin_settings_issuetracker'))
618 response = self.app.get(route_path('admin_settings_issuetracker'))
619 assert response.status_code == 200
619 assert response.status_code == 200
620
620
621 def test_add_empty_issuetracker_pattern(
621 def test_add_empty_issuetracker_pattern(
622 self, request, autologin_user, csrf_token):
622 self, request, autologin_user, csrf_token):
623 post_url = route_path('admin_settings_issuetracker_update')
623 post_url = route_path('admin_settings_issuetracker_update')
624 post_data = {
624 post_data = {
625 'csrf_token': csrf_token
625 'csrf_token': csrf_token
626 }
626 }
627 self.app.post(post_url, post_data, status=302)
627 self.app.post(post_url, post_data, status=302)
628
628
629 def test_add_issuetracker_pattern(
629 def test_add_issuetracker_pattern(
630 self, request, autologin_user, csrf_token):
630 self, request, autologin_user, csrf_token):
631 pattern = 'issuetracker_pat'
631 pattern = 'issuetracker_pat'
632 another_pattern = pattern+'1'
632 another_pattern = pattern+'1'
633 post_url = route_path('admin_settings_issuetracker_update')
633 post_url = route_path('admin_settings_issuetracker_update')
634 post_data = {
634 post_data = {
635 'new_pattern_pattern_0': pattern,
635 'new_pattern_pattern_0': pattern,
636 'new_pattern_url_0': 'http://url',
636 'new_pattern_url_0': 'http://url',
637 'new_pattern_prefix_0': 'prefix',
637 'new_pattern_prefix_0': 'prefix',
638 'new_pattern_description_0': 'description',
638 'new_pattern_description_0': 'description',
639 'new_pattern_pattern_1': another_pattern,
639 'new_pattern_pattern_1': another_pattern,
640 'new_pattern_url_1': 'https://url1',
640 'new_pattern_url_1': 'https://url1',
641 'new_pattern_prefix_1': 'prefix1',
641 'new_pattern_prefix_1': 'prefix1',
642 'new_pattern_description_1': 'description1',
642 'new_pattern_description_1': 'description1',
643 'csrf_token': csrf_token
643 'csrf_token': csrf_token
644 }
644 }
645 self.app.post(post_url, post_data, status=302)
645 self.app.post(post_url, post_data, status=302)
646 settings = SettingsModel().get_all_settings()
646 settings = SettingsModel().get_all_settings()
647 self.uid = md5(pattern)
647 self.uid = md5(pattern)
648 assert settings[self.PATTERN_KEY+self.uid] == pattern
648 assert settings[self.PATTERN_KEY+self.uid] == pattern
649 self.another_uid = md5(another_pattern)
649 self.another_uid = md5(another_pattern)
650 assert settings[self.PATTERN_KEY+self.another_uid] == another_pattern
650 assert settings[self.PATTERN_KEY+self.another_uid] == another_pattern
651
651
652 @request.addfinalizer
652 @request.addfinalizer
653 def cleanup():
653 def cleanup():
654 defaults = SettingsModel().get_all_settings()
654 defaults = SettingsModel().get_all_settings()
655
655
656 entries = [name for name in defaults if (
656 entries = [name for name in defaults if (
657 (self.uid in name) or (self.another_uid) in name)]
657 (self.uid in name) or (self.another_uid) in name)]
658 start = len(self.RC_PREFIX)
658 start = len(self.RC_PREFIX)
659 for del_key in entries:
659 for del_key in entries:
660 # TODO: anderson: get_by_name needs name without prefix
660 # TODO: anderson: get_by_name needs name without prefix
661 entry = SettingsModel().get_setting_by_name(del_key[start:])
661 entry = SettingsModel().get_setting_by_name(del_key[start:])
662 Session().delete(entry)
662 Session().delete(entry)
663
663
664 Session().commit()
664 Session().commit()
665
665
666 def test_edit_issuetracker_pattern(
666 def test_edit_issuetracker_pattern(
667 self, autologin_user, backend, csrf_token, request):
667 self, autologin_user, backend, csrf_token, request):
668 old_pattern = 'issuetracker_pat'
668 old_pattern = 'issuetracker_pat'
669 old_uid = md5(old_pattern)
669 old_uid = md5(old_pattern)
670 pattern = 'issuetracker_pat_new'
670 pattern = 'issuetracker_pat_new'
671 self.new_uid = md5(pattern)
671 self.new_uid = md5(pattern)
672
672
673 SettingsModel().create_or_update_setting(
673 SettingsModel().create_or_update_setting(
674 self.SHORT_PATTERN_KEY+old_uid, old_pattern, 'unicode')
674 self.SHORT_PATTERN_KEY+old_uid, old_pattern, 'unicode')
675
675
676 post_url = route_path('admin_settings_issuetracker_update')
676 post_url = route_path('admin_settings_issuetracker_update')
677 post_data = {
677 post_data = {
678 'new_pattern_pattern_0': pattern,
678 'new_pattern_pattern_0': pattern,
679 'new_pattern_url_0': 'https://url',
679 'new_pattern_url_0': 'https://url',
680 'new_pattern_prefix_0': 'prefix',
680 'new_pattern_prefix_0': 'prefix',
681 'new_pattern_description_0': 'description',
681 'new_pattern_description_0': 'description',
682 'uid': old_uid,
682 'uid': old_uid,
683 'csrf_token': csrf_token
683 'csrf_token': csrf_token
684 }
684 }
685 self.app.post(post_url, post_data, status=302)
685 self.app.post(post_url, post_data, status=302)
686 settings = SettingsModel().get_all_settings()
686 settings = SettingsModel().get_all_settings()
687 assert settings[self.PATTERN_KEY+self.new_uid] == pattern
687 assert settings[self.PATTERN_KEY+self.new_uid] == pattern
688 assert self.PATTERN_KEY+old_uid not in settings
688 assert self.PATTERN_KEY+old_uid not in settings
689
689
690 @request.addfinalizer
690 @request.addfinalizer
691 def cleanup():
691 def cleanup():
692 IssueTrackerSettingsModel().delete_entries(self.new_uid)
692 IssueTrackerSettingsModel().delete_entries(self.new_uid)
693
693
694 def test_replace_issuetracker_pattern_description(
694 def test_replace_issuetracker_pattern_description(
695 self, autologin_user, csrf_token, request, settings_util):
695 self, autologin_user, csrf_token, request, settings_util):
696 prefix = 'issuetracker'
696 prefix = 'issuetracker'
697 pattern = 'issuetracker_pat'
697 pattern = 'issuetracker_pat'
698 self.uid = md5(pattern)
698 self.uid = md5(pattern)
699 pattern_key = '_'.join([prefix, 'pat', self.uid])
699 pattern_key = '_'.join([prefix, 'pat', self.uid])
700 rc_pattern_key = '_'.join(['rhodecode', pattern_key])
700 rc_pattern_key = '_'.join(['rhodecode', pattern_key])
701 desc_key = '_'.join([prefix, 'desc', self.uid])
701 desc_key = '_'.join([prefix, 'desc', self.uid])
702 rc_desc_key = '_'.join(['rhodecode', desc_key])
702 rc_desc_key = '_'.join(['rhodecode', desc_key])
703 new_description = 'new_description'
703 new_description = 'new_description'
704
704
705 settings_util.create_rhodecode_setting(
705 settings_util.create_rhodecode_setting(
706 pattern_key, pattern, 'unicode', cleanup=False)
706 pattern_key, pattern, 'unicode', cleanup=False)
707 settings_util.create_rhodecode_setting(
707 settings_util.create_rhodecode_setting(
708 desc_key, 'old description', 'unicode', cleanup=False)
708 desc_key, 'old description', 'unicode', cleanup=False)
709
709
710 post_url = route_path('admin_settings_issuetracker_update')
710 post_url = route_path('admin_settings_issuetracker_update')
711 post_data = {
711 post_data = {
712 'new_pattern_pattern_0': pattern,
712 'new_pattern_pattern_0': pattern,
713 'new_pattern_url_0': 'https://url',
713 'new_pattern_url_0': 'https://url',
714 'new_pattern_prefix_0': 'prefix',
714 'new_pattern_prefix_0': 'prefix',
715 'new_pattern_description_0': new_description,
715 'new_pattern_description_0': new_description,
716 'uid': self.uid,
716 'uid': self.uid,
717 'csrf_token': csrf_token
717 'csrf_token': csrf_token
718 }
718 }
719 self.app.post(post_url, post_data, status=302)
719 self.app.post(post_url, post_data, status=302)
720 settings = SettingsModel().get_all_settings()
720 settings = SettingsModel().get_all_settings()
721 assert settings[rc_pattern_key] == pattern
721 assert settings[rc_pattern_key] == pattern
722 assert settings[rc_desc_key] == new_description
722 assert settings[rc_desc_key] == new_description
723
723
724 @request.addfinalizer
724 @request.addfinalizer
725 def cleanup():
725 def cleanup():
726 IssueTrackerSettingsModel().delete_entries(self.uid)
726 IssueTrackerSettingsModel().delete_entries(self.uid)
727
727
728 def test_delete_issuetracker_pattern(
728 def test_delete_issuetracker_pattern(
729 self, autologin_user, backend, csrf_token, settings_util):
729 self, autologin_user, backend, csrf_token, settings_util):
730 pattern = 'issuetracker_pat'
730 pattern = 'issuetracker_pat'
731 uid = md5(pattern)
731 uid = md5(pattern)
732 settings_util.create_rhodecode_setting(
732 settings_util.create_rhodecode_setting(
733 self.SHORT_PATTERN_KEY+uid, pattern, 'unicode', cleanup=False)
733 self.SHORT_PATTERN_KEY+uid, pattern, 'unicode', cleanup=False)
734
734
735 post_url = route_path('admin_settings_issuetracker_delete')
735 post_url = route_path('admin_settings_issuetracker_delete')
736 post_data = {
736 post_data = {
737 '_method': 'delete',
737 '_method': 'delete',
738 'uid': uid,
738 'uid': uid,
739 'csrf_token': csrf_token
739 'csrf_token': csrf_token
740 }
740 }
741 self.app.post(post_url, post_data, status=302)
741 self.app.post(post_url, post_data, status=302)
742 settings = SettingsModel().get_all_settings()
742 settings = SettingsModel().get_all_settings()
743 assert 'rhodecode_%s%s' % (self.SHORT_PATTERN_KEY, uid) not in settings
743 assert 'rhodecode_%s%s' % (self.SHORT_PATTERN_KEY, uid) not in settings
@@ -1,118 +1,118 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 pytest
21 import pytest
22
22
23 from rhodecode.lib import helpers as h
23 from rhodecode.lib import helpers as h
24 from rhodecode.tests import (
24 from rhodecode.tests import (
25 TestController, clear_cache_regions,
25 TestController, clear_cache_regions,
26 TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
26 TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
27 from rhodecode.tests.fixture import Fixture
27 from rhodecode.tests.fixture import Fixture
28 from rhodecode.tests.utils import AssertResponse
28 from rhodecode.tests.utils import AssertResponse
29
29
30 fixture = Fixture()
30 fixture = Fixture()
31
31
32
32
33 def route_path(name, params=None, **kwargs):
33 def route_path(name, params=None, **kwargs):
34 import urllib
34 import urllib
35 from rhodecode.apps._base import ADMIN_PREFIX
35 from rhodecode.apps._base import ADMIN_PREFIX
36
36
37 base_url = {
37 base_url = {
38 'login': ADMIN_PREFIX + '/login',
38 'login': ADMIN_PREFIX + '/login',
39 'logout': ADMIN_PREFIX + '/logout',
39 'logout': ADMIN_PREFIX + '/logout',
40 'register': ADMIN_PREFIX + '/register',
40 'register': ADMIN_PREFIX + '/register',
41 'reset_password':
41 'reset_password':
42 ADMIN_PREFIX + '/password_reset',
42 ADMIN_PREFIX + '/password_reset',
43 'reset_password_confirmation':
43 'reset_password_confirmation':
44 ADMIN_PREFIX + '/password_reset_confirmation',
44 ADMIN_PREFIX + '/password_reset_confirmation',
45
45
46 'admin_permissions_application':
46 'admin_permissions_application':
47 ADMIN_PREFIX + '/permissions/application',
47 ADMIN_PREFIX + '/permissions/application',
48 'admin_permissions_application_update':
48 'admin_permissions_application_update':
49 ADMIN_PREFIX + '/permissions/application/update',
49 ADMIN_PREFIX + '/permissions/application/update',
50 }[name].format(**kwargs)
50 }[name].format(**kwargs)
51
51
52 if params:
52 if params:
53 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
53 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
54 return base_url
54 return base_url
55
55
56
56
57 class TestPasswordReset(TestController):
57 class TestPasswordReset(TestController):
58
58
59 @pytest.mark.parametrize(
59 @pytest.mark.parametrize(
60 'pwd_reset_setting, show_link, show_reset', [
60 'pwd_reset_setting, show_link, show_reset', [
61 ('hg.password_reset.enabled', True, True),
61 ('hg.password_reset.enabled', True, True),
62 ('hg.password_reset.hidden', False, True),
62 ('hg.password_reset.hidden', False, True),
63 ('hg.password_reset.disabled', False, False),
63 ('hg.password_reset.disabled', False, False),
64 ])
64 ])
65 def test_password_reset_settings(
65 def test_password_reset_settings(
66 self, pwd_reset_setting, show_link, show_reset):
66 self, pwd_reset_setting, show_link, show_reset):
67 clear_cache_regions()
67 clear_cache_regions()
68 self.log_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
68 self.log_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
69 params = {
69 params = {
70 'csrf_token': self.csrf_token,
70 'csrf_token': self.csrf_token,
71 'anonymous': 'True',
71 'anonymous': 'True',
72 'default_register': 'hg.register.auto_activate',
72 'default_register': 'hg.register.auto_activate',
73 'default_register_message': '',
73 'default_register_message': '',
74 'default_password_reset': pwd_reset_setting,
74 'default_password_reset': pwd_reset_setting,
75 'default_extern_activate': 'hg.extern_activate.auto',
75 'default_extern_activate': 'hg.extern_activate.auto',
76 }
76 }
77 resp = self.app.post(
77 resp = self.app.post(
78 route_path('admin_permissions_application_update'), params=params)
78 route_path('admin_permissions_application_update'), params=params)
79 self.logout_user()
79 self.logout_user()
80
80
81 login_page = self.app.get(route_path('login'))
81 login_page = self.app.get(route_path('login'))
82 asr_login = AssertResponse(login_page)
82 asr_login = AssertResponse(login_page)
83
83
84 if show_link:
84 if show_link:
85 asr_login.one_element_exists('a.pwd_reset')
85 asr_login.one_element_exists('a.pwd_reset')
86 else:
86 else:
87 asr_login.no_element_exists('a.pwd_reset')
87 asr_login.no_element_exists('a.pwd_reset')
88
88
89 response = self.app.get(route_path('reset_password'))
89 response = self.app.get(route_path('reset_password'))
90
90
91 assert_response = AssertResponse(response)
91 assert_response = response.assert_response()
92 if show_reset:
92 if show_reset:
93 response.mustcontain('Send password reset email')
93 response.mustcontain('Send password reset email')
94 assert_response.one_element_exists('#email')
94 assert_response.one_element_exists('#email')
95 assert_response.one_element_exists('#send')
95 assert_response.one_element_exists('#send')
96 else:
96 else:
97 response.mustcontain('Password reset is disabled.')
97 response.mustcontain('Password reset is disabled.')
98 assert_response.no_element_exists('#email')
98 assert_response.no_element_exists('#email')
99 assert_response.no_element_exists('#send')
99 assert_response.no_element_exists('#send')
100
100
101 def test_password_form_disabled(self):
101 def test_password_form_disabled(self):
102 self.log_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
102 self.log_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
103 params = {
103 params = {
104 'csrf_token': self.csrf_token,
104 'csrf_token': self.csrf_token,
105 'anonymous': 'True',
105 'anonymous': 'True',
106 'default_register': 'hg.register.auto_activate',
106 'default_register': 'hg.register.auto_activate',
107 'default_register_message': '',
107 'default_register_message': '',
108 'default_password_reset': 'hg.password_reset.disabled',
108 'default_password_reset': 'hg.password_reset.disabled',
109 'default_extern_activate': 'hg.extern_activate.auto',
109 'default_extern_activate': 'hg.extern_activate.auto',
110 }
110 }
111 self.app.post(route_path('admin_permissions_application_update'), params=params)
111 self.app.post(route_path('admin_permissions_application_update'), params=params)
112 self.logout_user()
112 self.logout_user()
113
113
114 response = self.app.post(
114 response = self.app.post(
115 route_path('reset_password'), {'email': 'lisa@rhodecode.com',}
115 route_path('reset_password'), {'email': 'lisa@rhodecode.com',}
116 )
116 )
117 response = response.follow()
117 response = response.follow()
118 response.mustcontain('Password reset is disabled.')
118 response.mustcontain('Password reset is disabled.')
@@ -1,133 +1,133 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 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 import mock
22 import mock
23 import pytest
23 import pytest
24
24
25 from rhodecode.apps._base import ADMIN_PREFIX
25 from rhodecode.apps._base import ADMIN_PREFIX
26 from rhodecode.apps.login.views import LoginView, CaptchaData
26 from rhodecode.apps.login.views import LoginView, CaptchaData
27 from rhodecode.model.settings import SettingsModel
27 from rhodecode.model.settings import SettingsModel
28 from rhodecode.lib.utils2 import AttributeDict
28 from rhodecode.lib.utils2 import AttributeDict
29 from rhodecode.tests.utils import AssertResponse
29 from rhodecode.tests.utils import AssertResponse
30
30
31
31
32 class RhodeCodeSetting(object):
32 class RhodeCodeSetting(object):
33 def __init__(self, name, value):
33 def __init__(self, name, value):
34 self.name = name
34 self.name = name
35 self.value = value
35 self.value = value
36
36
37 def __enter__(self):
37 def __enter__(self):
38 from rhodecode.model.settings import SettingsModel
38 from rhodecode.model.settings import SettingsModel
39 model = SettingsModel()
39 model = SettingsModel()
40 self.old_setting = model.get_setting_by_name(self.name)
40 self.old_setting = model.get_setting_by_name(self.name)
41 model.create_or_update_setting(name=self.name, val=self.value)
41 model.create_or_update_setting(name=self.name, val=self.value)
42 return self
42 return self
43
43
44 def __exit__(self, exc_type, exc_val, exc_tb):
44 def __exit__(self, exc_type, exc_val, exc_tb):
45 model = SettingsModel()
45 model = SettingsModel()
46 if self.old_setting:
46 if self.old_setting:
47 model.create_or_update_setting(
47 model.create_or_update_setting(
48 name=self.name, val=self.old_setting.app_settings_value)
48 name=self.name, val=self.old_setting.app_settings_value)
49 else:
49 else:
50 model.create_or_update_setting(name=self.name)
50 model.create_or_update_setting(name=self.name)
51
51
52
52
53 class TestRegisterCaptcha(object):
53 class TestRegisterCaptcha(object):
54
54
55 @pytest.mark.parametrize('private_key, public_key, expected', [
55 @pytest.mark.parametrize('private_key, public_key, expected', [
56 ('', '', CaptchaData(False, '', '')),
56 ('', '', CaptchaData(False, '', '')),
57 ('', 'pubkey', CaptchaData(False, '', 'pubkey')),
57 ('', 'pubkey', CaptchaData(False, '', 'pubkey')),
58 ('privkey', '', CaptchaData(True, 'privkey', '')),
58 ('privkey', '', CaptchaData(True, 'privkey', '')),
59 ('privkey', 'pubkey', CaptchaData(True, 'privkey', 'pubkey')),
59 ('privkey', 'pubkey', CaptchaData(True, 'privkey', 'pubkey')),
60 ])
60 ])
61 def test_get_captcha_data(self, private_key, public_key, expected,
61 def test_get_captcha_data(self, private_key, public_key, expected,
62 request_stub, user_util):
62 request_stub, user_util):
63 request_stub.user = user_util.create_user().AuthUser()
63 request_stub.user = user_util.create_user().AuthUser()
64 request_stub.matched_route = AttributeDict({'name': 'login'})
64 request_stub.matched_route = AttributeDict({'name': 'login'})
65 login_view = LoginView(mock.Mock(), request_stub)
65 login_view = LoginView(mock.Mock(), request_stub)
66
66
67 with RhodeCodeSetting('captcha_private_key', private_key):
67 with RhodeCodeSetting('captcha_private_key', private_key):
68 with RhodeCodeSetting('captcha_public_key', public_key):
68 with RhodeCodeSetting('captcha_public_key', public_key):
69 captcha = login_view._get_captcha_data()
69 captcha = login_view._get_captcha_data()
70 assert captcha == expected
70 assert captcha == expected
71
71
72 @pytest.mark.parametrize('active', [False, True])
72 @pytest.mark.parametrize('active', [False, True])
73 @mock.patch.object(LoginView, '_get_captcha_data')
73 @mock.patch.object(LoginView, '_get_captcha_data')
74 def test_private_key_does_not_leak_to_html(
74 def test_private_key_does_not_leak_to_html(
75 self, m_get_captcha_data, active, app):
75 self, m_get_captcha_data, active, app):
76 captcha = CaptchaData(
76 captcha = CaptchaData(
77 active=active, private_key='PRIVATE_KEY', public_key='PUBLIC_KEY')
77 active=active, private_key='PRIVATE_KEY', public_key='PUBLIC_KEY')
78 m_get_captcha_data.return_value = captcha
78 m_get_captcha_data.return_value = captcha
79
79
80 response = app.get(ADMIN_PREFIX + '/register')
80 response = app.get(ADMIN_PREFIX + '/register')
81 assert 'PRIVATE_KEY' not in response
81 assert 'PRIVATE_KEY' not in response
82
82
83 @pytest.mark.parametrize('active', [False, True])
83 @pytest.mark.parametrize('active', [False, True])
84 @mock.patch.object(LoginView, '_get_captcha_data')
84 @mock.patch.object(LoginView, '_get_captcha_data')
85 def test_register_view_renders_captcha(
85 def test_register_view_renders_captcha(
86 self, m_get_captcha_data, active, app):
86 self, m_get_captcha_data, active, app):
87 captcha = CaptchaData(
87 captcha = CaptchaData(
88 active=active, private_key='PRIVATE_KEY', public_key='PUBLIC_KEY')
88 active=active, private_key='PRIVATE_KEY', public_key='PUBLIC_KEY')
89 m_get_captcha_data.return_value = captcha
89 m_get_captcha_data.return_value = captcha
90
90
91 response = app.get(ADMIN_PREFIX + '/register')
91 response = app.get(ADMIN_PREFIX + '/register')
92
92
93 assertr = AssertResponse(response)
93 assertr = response.assert_response()
94 if active:
94 if active:
95 assertr.one_element_exists('#recaptcha_field')
95 assertr.one_element_exists('#recaptcha_field')
96 else:
96 else:
97 assertr.no_element_exists('#recaptcha_field')
97 assertr.no_element_exists('#recaptcha_field')
98
98
99 @pytest.mark.parametrize('valid', [False, True])
99 @pytest.mark.parametrize('valid', [False, True])
100 @mock.patch.object(LoginView, 'validate_captcha')
100 @mock.patch.object(LoginView, 'validate_captcha')
101 @mock.patch.object(LoginView, '_get_captcha_data')
101 @mock.patch.object(LoginView, '_get_captcha_data')
102 def test_register_with_active_captcha(
102 def test_register_with_active_captcha(
103 self, m_get_captcha_data, m_validate_captcha, valid, app, csrf_token):
103 self, m_get_captcha_data, m_validate_captcha, valid, app, csrf_token):
104 captcha = CaptchaData(
104 captcha = CaptchaData(
105 active=True, private_key='PRIVATE_KEY', public_key='PUBLIC_KEY')
105 active=True, private_key='PRIVATE_KEY', public_key='PUBLIC_KEY')
106 m_get_captcha_data.return_value = captcha
106 m_get_captcha_data.return_value = captcha
107 m_response = mock.Mock()
107 m_response = mock.Mock()
108 m_response.is_valid = valid
108 m_response.is_valid = valid
109 m_validate_captcha.return_value = valid, 'ok'
109 m_validate_captcha.return_value = valid, 'ok'
110
110
111 params = {
111 params = {
112 'csrf_token': csrf_token,
112 'csrf_token': csrf_token,
113 'email': 'pytest@example.com',
113 'email': 'pytest@example.com',
114 'firstname': 'pytest-firstname',
114 'firstname': 'pytest-firstname',
115 'lastname': 'pytest-lastname',
115 'lastname': 'pytest-lastname',
116 'password': 'secret',
116 'password': 'secret',
117 'password_confirmation': 'secret',
117 'password_confirmation': 'secret',
118 'username': 'pytest',
118 'username': 'pytest',
119 }
119 }
120 response = app.post(ADMIN_PREFIX + '/register', params=params)
120 response = app.post(ADMIN_PREFIX + '/register', params=params)
121
121
122 if valid:
122 if valid:
123 # If we provided a valid captcha input we expect a successful
123 # If we provided a valid captcha input we expect a successful
124 # registration and redirect to the login page.
124 # registration and redirect to the login page.
125 assert response.status_int == 302
125 assert response.status_int == 302
126 assert 'location' in response.headers
126 assert 'location' in response.headers
127 assert ADMIN_PREFIX + '/login' in response.headers['location']
127 assert ADMIN_PREFIX + '/login' in response.headers['location']
128 else:
128 else:
129 # If captche input is invalid we expect to stay on the registration
129 # If captche input is invalid we expect to stay on the registration
130 # page with an error message displayed.
130 # page with an error message displayed.
131 assertr = AssertResponse(response)
131 assertr = response.assert_response()
132 assert response.status_int == 200
132 assert response.status_int == 200
133 assertr.one_element_exists('#recaptcha_field ~ span.error-message')
133 assertr.one_element_exists('#recaptcha_field ~ span.error-message')
@@ -1,137 +1,137 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 pytest
21 import pytest
22 from rhodecode.model.auth_token import AuthTokenModel
22 from rhodecode.model.auth_token import AuthTokenModel
23 from rhodecode.tests import TestController
23 from rhodecode.tests import TestController
24
24
25
25
26 def route_path(name, params=None, **kwargs):
26 def route_path(name, params=None, **kwargs):
27 import urllib
27 import urllib
28
28
29 base_url = {
29 base_url = {
30 'rss_feed_home': '/{repo_name}/feed-rss',
30 'rss_feed_home': '/{repo_name}/feed-rss',
31 'atom_feed_home': '/{repo_name}/feed-atom',
31 'atom_feed_home': '/{repo_name}/feed-atom',
32 'rss_feed_home_old': '/{repo_name}/feed/rss',
32 'rss_feed_home_old': '/{repo_name}/feed/rss',
33 'atom_feed_home_old': '/{repo_name}/feed/atom',
33 'atom_feed_home_old': '/{repo_name}/feed/atom',
34 }[name].format(**kwargs)
34 }[name].format(**kwargs)
35
35
36 if params:
36 if params:
37 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
37 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
38 return base_url
38 return base_url
39
39
40
40
41 class TestFeedView(TestController):
41 class TestFeedView(TestController):
42
42
43 @pytest.mark.parametrize("feed_type,response_types,content_type",[
43 @pytest.mark.parametrize("feed_type,response_types,content_type",[
44 ('rss', ['<rss version="2.0">'],
44 ('rss', ['<rss version="2.0">'],
45 "application/rss+xml"),
45 "application/rss+xml"),
46 ('atom', ['xmlns="http://www.w3.org/2005/Atom"', 'xml:lang="en-us"'],
46 ('atom', ['xmlns="http://www.w3.org/2005/Atom"', 'xml:lang="en-us"'],
47 "application/atom+xml"),
47 "application/atom+xml"),
48 ])
48 ])
49 def test_feed(self, backend, feed_type, response_types, content_type):
49 def test_feed(self, backend, feed_type, response_types, content_type):
50 self.log_user()
50 self.log_user()
51 response = self.app.get(
51 response = self.app.get(
52 route_path('{}_feed_home'.format(feed_type),
52 route_path('{}_feed_home'.format(feed_type),
53 repo_name=backend.repo_name))
53 repo_name=backend.repo_name))
54
54
55 for content in response_types:
55 for content in response_types:
56 response.mustcontain(content)
56 response.mustcontain(content)
57
57
58 assert response.content_type == content_type
58 assert response.content_type == content_type
59
59
60 @pytest.mark.parametrize("feed_type, content_type", [
60 @pytest.mark.parametrize("feed_type, content_type", [
61 ('rss', "application/rss+xml"),
61 ('rss', "application/rss+xml"),
62 ('atom', "application/atom+xml")
62 ('atom', "application/atom+xml")
63 ])
63 ])
64 def test_feed_with_auth_token(
64 def test_feed_with_auth_token(
65 self, backend, user_admin, feed_type, content_type):
65 self, backend, user_admin, feed_type, content_type):
66 auth_token = user_admin.feed_token
66 auth_token = user_admin.feed_token
67 assert auth_token != ''
67 assert auth_token != ''
68
68
69 response = self.app.get(
69 response = self.app.get(
70 route_path(
70 route_path(
71 '{}_feed_home'.format(feed_type),
71 '{}_feed_home'.format(feed_type),
72 repo_name=backend.repo_name,
72 repo_name=backend.repo_name,
73 params=dict(auth_token=auth_token)),
73 params=dict(auth_token=auth_token)),
74 status=200)
74 status=200)
75
75
76 assert response.content_type == content_type
76 assert response.content_type == content_type
77
77
78 @pytest.mark.parametrize("feed_type, content_type", [
78 @pytest.mark.parametrize("feed_type, content_type", [
79 ('rss', "application/rss+xml"),
79 ('rss', "application/rss+xml"),
80 ('atom', "application/atom+xml")
80 ('atom', "application/atom+xml")
81 ])
81 ])
82 def test_feed_with_auth_token_by_uid(
82 def test_feed_with_auth_token_by_uid(
83 self, backend, user_admin, feed_type, content_type):
83 self, backend, user_admin, feed_type, content_type):
84 auth_token = user_admin.feed_token
84 auth_token = user_admin.feed_token
85 assert auth_token != ''
85 assert auth_token != ''
86
86
87 response = self.app.get(
87 response = self.app.get(
88 route_path(
88 route_path(
89 '{}_feed_home'.format(feed_type),
89 '{}_feed_home'.format(feed_type),
90 repo_name='_{}'.format(backend.repo.repo_id),
90 repo_name='_{}'.format(backend.repo.repo_id),
91 params=dict(auth_token=auth_token)),
91 params=dict(auth_token=auth_token)),
92 status=200)
92 status=200)
93
93
94 assert response.content_type == content_type
94 assert response.content_type == content_type
95
95
96 @pytest.mark.parametrize("feed_type, content_type", [
96 @pytest.mark.parametrize("feed_type, content_type", [
97 ('rss', "application/rss+xml"),
97 ('rss', "application/rss+xml"),
98 ('atom', "application/atom+xml")
98 ('atom', "application/atom+xml")
99 ])
99 ])
100 def test_feed_old_urls_with_auth_token(
100 def test_feed_old_urls_with_auth_token(
101 self, backend, user_admin, feed_type, content_type):
101 self, backend, user_admin, feed_type, content_type):
102 auth_token = user_admin.feed_token
102 auth_token = user_admin.feed_token
103 assert auth_token != ''
103 assert auth_token != ''
104
104
105 response = self.app.get(
105 response = self.app.get(
106 route_path(
106 route_path(
107 '{}_feed_home_old'.format(feed_type),
107 '{}_feed_home_old'.format(feed_type),
108 repo_name=backend.repo_name,
108 repo_name=backend.repo_name,
109 params=dict(auth_token=auth_token)),
109 params=dict(auth_token=auth_token)),
110 status=200)
110 status=200)
111
111
112 assert response.content_type == content_type
112 assert response.content_type == content_type
113
113
114 @pytest.mark.parametrize("feed_type", ['rss', 'atom'])
114 @pytest.mark.parametrize("feed_type", ['rss', 'atom'])
115 def test_feed_with_auth_token_of_wrong_type(
115 def test_feed_with_auth_token_of_wrong_type(
116 self, backend, user_util, feed_type):
116 self, backend, user_util, feed_type):
117 user = user_util.create_user()
117 user = user_util.create_user()
118 auth_token = AuthTokenModel().create(
118 auth_token = AuthTokenModel().create(
119 user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_API)
119 user.user_id, u'test-token', -1, AuthTokenModel.cls.ROLE_API)
120 auth_token = auth_token.api_key
120 auth_token = auth_token.api_key
121
121
122 self.app.get(
122 self.app.get(
123 route_path(
123 route_path(
124 '{}_feed_home'.format(feed_type),
124 '{}_feed_home'.format(feed_type),
125 repo_name=backend.repo_name,
125 repo_name=backend.repo_name,
126 params=dict(auth_token=auth_token)),
126 params=dict(auth_token=auth_token)),
127 status=302)
127 status=302)
128
128
129 auth_token = AuthTokenModel().create(
129 auth_token = AuthTokenModel().create(
130 user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_FEED)
130 user.user_id, u'test-token', -1, AuthTokenModel.cls.ROLE_FEED)
131 auth_token = auth_token.api_key
131 auth_token = auth_token.api_key
132 self.app.get(
132 self.app.get(
133 route_path(
133 route_path(
134 '{}_feed_home'.format(feed_type),
134 '{}_feed_home'.format(feed_type),
135 repo_name=backend.repo_name,
135 repo_name=backend.repo_name,
136 params=dict(auth_token=auth_token)),
136 params=dict(auth_token=auth_token)),
137 status=200)
137 status=200)
@@ -1,524 +1,524 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 re
21 import re
22
22
23 import mock
23 import mock
24 import pytest
24 import pytest
25
25
26 from rhodecode.apps.repository.views.repo_summary import RepoSummaryView
26 from rhodecode.apps.repository.views.repo_summary import RepoSummaryView
27 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib.compat import OrderedDict
28 from rhodecode.lib.compat import OrderedDict
29 from rhodecode.lib.utils2 import AttributeDict, safe_str
29 from rhodecode.lib.utils2 import AttributeDict, safe_str
30 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
30 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
31 from rhodecode.model.db import Repository
31 from rhodecode.model.db import Repository
32 from rhodecode.model.meta import Session
32 from rhodecode.model.meta import Session
33 from rhodecode.model.repo import RepoModel
33 from rhodecode.model.repo import RepoModel
34 from rhodecode.model.scm import ScmModel
34 from rhodecode.model.scm import ScmModel
35 from rhodecode.tests import assert_session_flash
35 from rhodecode.tests import assert_session_flash
36 from rhodecode.tests.fixture import Fixture
36 from rhodecode.tests.fixture import Fixture
37 from rhodecode.tests.utils import AssertResponse, repo_on_filesystem
37 from rhodecode.tests.utils import AssertResponse, repo_on_filesystem
38
38
39
39
40 fixture = Fixture()
40 fixture = Fixture()
41
41
42
42
43 def route_path(name, params=None, **kwargs):
43 def route_path(name, params=None, **kwargs):
44 import urllib
44 import urllib
45
45
46 base_url = {
46 base_url = {
47 'repo_summary': '/{repo_name}',
47 'repo_summary': '/{repo_name}',
48 'repo_stats': '/{repo_name}/repo_stats/{commit_id}',
48 'repo_stats': '/{repo_name}/repo_stats/{commit_id}',
49 'repo_refs_data': '/{repo_name}/refs-data',
49 'repo_refs_data': '/{repo_name}/refs-data',
50 'repo_refs_changelog_data': '/{repo_name}/refs-data-changelog',
50 'repo_refs_changelog_data': '/{repo_name}/refs-data-changelog',
51 'repo_creating_check': '/{repo_name}/repo_creating_check',
51 'repo_creating_check': '/{repo_name}/repo_creating_check',
52 }[name].format(**kwargs)
52 }[name].format(**kwargs)
53
53
54 if params:
54 if params:
55 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
55 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
56 return base_url
56 return base_url
57
57
58
58
59 def assert_clone_url(response, server, repo, disabled=False):
59 def assert_clone_url(response, server, repo, disabled=False):
60
60
61 response.mustcontain(
61 response.mustcontain(
62 '<input type="text" class="input-monospace clone_url_input" '
62 '<input type="text" class="input-monospace clone_url_input" '
63 '{disabled}readonly="readonly" '
63 '{disabled}readonly="readonly" '
64 'value="http://test_admin@{server}/{repo}"/>'.format(
64 'value="http://test_admin@{server}/{repo}"/>'.format(
65 server=server, repo=repo, disabled='disabled ' if disabled else ' ')
65 server=server, repo=repo, disabled='disabled ' if disabled else ' ')
66 )
66 )
67
67
68
68
69 @pytest.mark.usefixtures('app')
69 @pytest.mark.usefixtures('app')
70 class TestSummaryView(object):
70 class TestSummaryView(object):
71 def test_index(self, autologin_user, backend, http_host_only_stub):
71 def test_index(self, autologin_user, backend, http_host_only_stub):
72 repo_id = backend.repo.repo_id
72 repo_id = backend.repo.repo_id
73 repo_name = backend.repo_name
73 repo_name = backend.repo_name
74 with mock.patch('rhodecode.lib.helpers.is_svn_without_proxy',
74 with mock.patch('rhodecode.lib.helpers.is_svn_without_proxy',
75 return_value=False):
75 return_value=False):
76 response = self.app.get(
76 response = self.app.get(
77 route_path('repo_summary', repo_name=repo_name))
77 route_path('repo_summary', repo_name=repo_name))
78
78
79 # repo type
79 # repo type
80 response.mustcontain(
80 response.mustcontain(
81 '<i class="icon-%s">' % (backend.alias, )
81 '<i class="icon-%s">' % (backend.alias, )
82 )
82 )
83 # public/private
83 # public/private
84 response.mustcontain(
84 response.mustcontain(
85 """<i class="icon-unlock-alt">"""
85 """<i class="icon-unlock-alt">"""
86 )
86 )
87
87
88 # clone url...
88 # clone url...
89 assert_clone_url(response, http_host_only_stub, repo_name)
89 assert_clone_url(response, http_host_only_stub, repo_name)
90 assert_clone_url(response, http_host_only_stub, '_{}'.format(repo_id))
90 assert_clone_url(response, http_host_only_stub, '_{}'.format(repo_id))
91
91
92 def test_index_svn_without_proxy(
92 def test_index_svn_without_proxy(
93 self, autologin_user, backend_svn, http_host_only_stub):
93 self, autologin_user, backend_svn, http_host_only_stub):
94 repo_id = backend_svn.repo.repo_id
94 repo_id = backend_svn.repo.repo_id
95 repo_name = backend_svn.repo_name
95 repo_name = backend_svn.repo_name
96 response = self.app.get(route_path('repo_summary', repo_name=repo_name))
96 response = self.app.get(route_path('repo_summary', repo_name=repo_name))
97 # clone url...
97 # clone url...
98
98
99 assert_clone_url(response, http_host_only_stub, repo_name, disabled=True)
99 assert_clone_url(response, http_host_only_stub, repo_name, disabled=True)
100 assert_clone_url(response, http_host_only_stub, '_{}'.format(repo_id), disabled=True)
100 assert_clone_url(response, http_host_only_stub, '_{}'.format(repo_id), disabled=True)
101
101
102 def test_index_with_trailing_slash(
102 def test_index_with_trailing_slash(
103 self, autologin_user, backend, http_host_only_stub):
103 self, autologin_user, backend, http_host_only_stub):
104
104
105 repo_id = backend.repo.repo_id
105 repo_id = backend.repo.repo_id
106 repo_name = backend.repo_name
106 repo_name = backend.repo_name
107 with mock.patch('rhodecode.lib.helpers.is_svn_without_proxy',
107 with mock.patch('rhodecode.lib.helpers.is_svn_without_proxy',
108 return_value=False):
108 return_value=False):
109 response = self.app.get(
109 response = self.app.get(
110 route_path('repo_summary', repo_name=repo_name) + '/',
110 route_path('repo_summary', repo_name=repo_name) + '/',
111 status=200)
111 status=200)
112
112
113 # clone url...
113 # clone url...
114 assert_clone_url(response, http_host_only_stub, repo_name)
114 assert_clone_url(response, http_host_only_stub, repo_name)
115 assert_clone_url(response, http_host_only_stub, '_{}'.format(repo_id))
115 assert_clone_url(response, http_host_only_stub, '_{}'.format(repo_id))
116
116
117 def test_index_by_id(self, autologin_user, backend):
117 def test_index_by_id(self, autologin_user, backend):
118 repo_id = backend.repo.repo_id
118 repo_id = backend.repo.repo_id
119 response = self.app.get(
119 response = self.app.get(
120 route_path('repo_summary', repo_name='_%s' % (repo_id,)))
120 route_path('repo_summary', repo_name='_%s' % (repo_id,)))
121
121
122 # repo type
122 # repo type
123 response.mustcontain(
123 response.mustcontain(
124 '<i class="icon-%s">' % (backend.alias, )
124 '<i class="icon-%s">' % (backend.alias, )
125 )
125 )
126 # public/private
126 # public/private
127 response.mustcontain(
127 response.mustcontain(
128 """<i class="icon-unlock-alt">"""
128 """<i class="icon-unlock-alt">"""
129 )
129 )
130
130
131 def test_index_by_repo_having_id_path_in_name_hg(self, autologin_user):
131 def test_index_by_repo_having_id_path_in_name_hg(self, autologin_user):
132 fixture.create_repo(name='repo_1')
132 fixture.create_repo(name='repo_1')
133 response = self.app.get(route_path('repo_summary', repo_name='repo_1'))
133 response = self.app.get(route_path('repo_summary', repo_name='repo_1'))
134
134
135 try:
135 try:
136 response.mustcontain("repo_1")
136 response.mustcontain("repo_1")
137 finally:
137 finally:
138 RepoModel().delete(Repository.get_by_repo_name('repo_1'))
138 RepoModel().delete(Repository.get_by_repo_name('repo_1'))
139 Session().commit()
139 Session().commit()
140
140
141 def test_index_with_anonymous_access_disabled(
141 def test_index_with_anonymous_access_disabled(
142 self, backend, disable_anonymous_user):
142 self, backend, disable_anonymous_user):
143 response = self.app.get(
143 response = self.app.get(
144 route_path('repo_summary', repo_name=backend.repo_name), status=302)
144 route_path('repo_summary', repo_name=backend.repo_name), status=302)
145 assert 'login' in response.location
145 assert 'login' in response.location
146
146
147 def _enable_stats(self, repo):
147 def _enable_stats(self, repo):
148 r = Repository.get_by_repo_name(repo)
148 r = Repository.get_by_repo_name(repo)
149 r.enable_statistics = True
149 r.enable_statistics = True
150 Session().add(r)
150 Session().add(r)
151 Session().commit()
151 Session().commit()
152
152
153 expected_trending = {
153 expected_trending = {
154 'hg': {
154 'hg': {
155 "py": {"count": 68, "desc": ["Python"]},
155 "py": {"count": 68, "desc": ["Python"]},
156 "rst": {"count": 16, "desc": ["Rst"]},
156 "rst": {"count": 16, "desc": ["Rst"]},
157 "css": {"count": 2, "desc": ["Css"]},
157 "css": {"count": 2, "desc": ["Css"]},
158 "sh": {"count": 2, "desc": ["Bash"]},
158 "sh": {"count": 2, "desc": ["Bash"]},
159 "bat": {"count": 1, "desc": ["Batch"]},
159 "bat": {"count": 1, "desc": ["Batch"]},
160 "cfg": {"count": 1, "desc": ["Ini"]},
160 "cfg": {"count": 1, "desc": ["Ini"]},
161 "html": {"count": 1, "desc": ["EvoqueHtml", "Html"]},
161 "html": {"count": 1, "desc": ["EvoqueHtml", "Html"]},
162 "ini": {"count": 1, "desc": ["Ini"]},
162 "ini": {"count": 1, "desc": ["Ini"]},
163 "js": {"count": 1, "desc": ["Javascript"]},
163 "js": {"count": 1, "desc": ["Javascript"]},
164 "makefile": {"count": 1, "desc": ["Makefile", "Makefile"]}
164 "makefile": {"count": 1, "desc": ["Makefile", "Makefile"]}
165 },
165 },
166 'git': {
166 'git': {
167 "py": {"count": 68, "desc": ["Python"]},
167 "py": {"count": 68, "desc": ["Python"]},
168 "rst": {"count": 16, "desc": ["Rst"]},
168 "rst": {"count": 16, "desc": ["Rst"]},
169 "css": {"count": 2, "desc": ["Css"]},
169 "css": {"count": 2, "desc": ["Css"]},
170 "sh": {"count": 2, "desc": ["Bash"]},
170 "sh": {"count": 2, "desc": ["Bash"]},
171 "bat": {"count": 1, "desc": ["Batch"]},
171 "bat": {"count": 1, "desc": ["Batch"]},
172 "cfg": {"count": 1, "desc": ["Ini"]},
172 "cfg": {"count": 1, "desc": ["Ini"]},
173 "html": {"count": 1, "desc": ["EvoqueHtml", "Html"]},
173 "html": {"count": 1, "desc": ["EvoqueHtml", "Html"]},
174 "ini": {"count": 1, "desc": ["Ini"]},
174 "ini": {"count": 1, "desc": ["Ini"]},
175 "js": {"count": 1, "desc": ["Javascript"]},
175 "js": {"count": 1, "desc": ["Javascript"]},
176 "makefile": {"count": 1, "desc": ["Makefile", "Makefile"]}
176 "makefile": {"count": 1, "desc": ["Makefile", "Makefile"]}
177 },
177 },
178 'svn': {
178 'svn': {
179 "py": {"count": 75, "desc": ["Python"]},
179 "py": {"count": 75, "desc": ["Python"]},
180 "rst": {"count": 16, "desc": ["Rst"]},
180 "rst": {"count": 16, "desc": ["Rst"]},
181 "html": {"count": 11, "desc": ["EvoqueHtml", "Html"]},
181 "html": {"count": 11, "desc": ["EvoqueHtml", "Html"]},
182 "css": {"count": 2, "desc": ["Css"]},
182 "css": {"count": 2, "desc": ["Css"]},
183 "bat": {"count": 1, "desc": ["Batch"]},
183 "bat": {"count": 1, "desc": ["Batch"]},
184 "cfg": {"count": 1, "desc": ["Ini"]},
184 "cfg": {"count": 1, "desc": ["Ini"]},
185 "ini": {"count": 1, "desc": ["Ini"]},
185 "ini": {"count": 1, "desc": ["Ini"]},
186 "js": {"count": 1, "desc": ["Javascript"]},
186 "js": {"count": 1, "desc": ["Javascript"]},
187 "makefile": {"count": 1, "desc": ["Makefile", "Makefile"]},
187 "makefile": {"count": 1, "desc": ["Makefile", "Makefile"]},
188 "sh": {"count": 1, "desc": ["Bash"]}
188 "sh": {"count": 1, "desc": ["Bash"]}
189 },
189 },
190 }
190 }
191
191
192 def test_repo_stats(self, autologin_user, backend, xhr_header):
192 def test_repo_stats(self, autologin_user, backend, xhr_header):
193 response = self.app.get(
193 response = self.app.get(
194 route_path(
194 route_path(
195 'repo_stats', repo_name=backend.repo_name, commit_id='tip'),
195 'repo_stats', repo_name=backend.repo_name, commit_id='tip'),
196 extra_environ=xhr_header,
196 extra_environ=xhr_header,
197 status=200)
197 status=200)
198 assert re.match(r'6[\d\.]+ KiB', response.json['size'])
198 assert re.match(r'6[\d\.]+ KiB', response.json['size'])
199
199
200 def test_repo_stats_code_stats_enabled(self, autologin_user, backend, xhr_header):
200 def test_repo_stats_code_stats_enabled(self, autologin_user, backend, xhr_header):
201 repo_name = backend.repo_name
201 repo_name = backend.repo_name
202
202
203 # codes stats
203 # codes stats
204 self._enable_stats(repo_name)
204 self._enable_stats(repo_name)
205 ScmModel().mark_for_invalidation(repo_name)
205 ScmModel().mark_for_invalidation(repo_name)
206
206
207 response = self.app.get(
207 response = self.app.get(
208 route_path(
208 route_path(
209 'repo_stats', repo_name=backend.repo_name, commit_id='tip'),
209 'repo_stats', repo_name=backend.repo_name, commit_id='tip'),
210 extra_environ=xhr_header,
210 extra_environ=xhr_header,
211 status=200)
211 status=200)
212
212
213 expected_data = self.expected_trending[backend.alias]
213 expected_data = self.expected_trending[backend.alias]
214 returned_stats = response.json['code_stats']
214 returned_stats = response.json['code_stats']
215 for k, v in expected_data.items():
215 for k, v in expected_data.items():
216 assert v == returned_stats[k]
216 assert v == returned_stats[k]
217
217
218 def test_repo_refs_data(self, backend):
218 def test_repo_refs_data(self, backend):
219 response = self.app.get(
219 response = self.app.get(
220 route_path('repo_refs_data', repo_name=backend.repo_name),
220 route_path('repo_refs_data', repo_name=backend.repo_name),
221 status=200)
221 status=200)
222
222
223 # Ensure that there is the correct amount of items in the result
223 # Ensure that there is the correct amount of items in the result
224 repo = backend.repo.scm_instance()
224 repo = backend.repo.scm_instance()
225 data = response.json['results']
225 data = response.json['results']
226 items = sum(len(section['children']) for section in data)
226 items = sum(len(section['children']) for section in data)
227 repo_refs = len(repo.branches) + len(repo.tags) + len(repo.bookmarks)
227 repo_refs = len(repo.branches) + len(repo.tags) + len(repo.bookmarks)
228 assert items == repo_refs
228 assert items == repo_refs
229
229
230 def test_index_shows_missing_requirements_message(
230 def test_index_shows_missing_requirements_message(
231 self, backend, autologin_user):
231 self, backend, autologin_user):
232 repo_name = backend.repo_name
232 repo_name = backend.repo_name
233 scm_patcher = mock.patch.object(
233 scm_patcher = mock.patch.object(
234 Repository, 'scm_instance', side_effect=RepositoryRequirementError)
234 Repository, 'scm_instance', side_effect=RepositoryRequirementError)
235
235
236 with scm_patcher:
236 with scm_patcher:
237 response = self.app.get(
237 response = self.app.get(
238 route_path('repo_summary', repo_name=repo_name))
238 route_path('repo_summary', repo_name=repo_name))
239 assert_response = AssertResponse(response)
239 assert_response = response.assert_response()
240 assert_response.element_contains(
240 assert_response.element_contains(
241 '.main .alert-warning strong', 'Missing requirements')
241 '.main .alert-warning strong', 'Missing requirements')
242 assert_response.element_contains(
242 assert_response.element_contains(
243 '.main .alert-warning',
243 '.main .alert-warning',
244 'Commits cannot be displayed, because this repository '
244 'Commits cannot be displayed, because this repository '
245 'uses one or more extensions, which was not enabled.')
245 'uses one or more extensions, which was not enabled.')
246
246
247 def test_missing_requirements_page_does_not_contains_switch_to(
247 def test_missing_requirements_page_does_not_contains_switch_to(
248 self, autologin_user, backend):
248 self, autologin_user, backend):
249 repo_name = backend.repo_name
249 repo_name = backend.repo_name
250 scm_patcher = mock.patch.object(
250 scm_patcher = mock.patch.object(
251 Repository, 'scm_instance', side_effect=RepositoryRequirementError)
251 Repository, 'scm_instance', side_effect=RepositoryRequirementError)
252
252
253 with scm_patcher:
253 with scm_patcher:
254 response = self.app.get(route_path('repo_summary', repo_name=repo_name))
254 response = self.app.get(route_path('repo_summary', repo_name=repo_name))
255 response.mustcontain(no='Switch To')
255 response.mustcontain(no='Switch To')
256
256
257
257
258 @pytest.mark.usefixtures('app')
258 @pytest.mark.usefixtures('app')
259 class TestRepoLocation(object):
259 class TestRepoLocation(object):
260
260
261 @pytest.mark.parametrize("suffix", [u'', u'Δ…Δ™Ε‚'], ids=['', 'non-ascii'])
261 @pytest.mark.parametrize("suffix", [u'', u'Δ…Δ™Ε‚'], ids=['', 'non-ascii'])
262 def test_missing_filesystem_repo(
262 def test_missing_filesystem_repo(
263 self, autologin_user, backend, suffix, csrf_token):
263 self, autologin_user, backend, suffix, csrf_token):
264 repo = backend.create_repo(name_suffix=suffix)
264 repo = backend.create_repo(name_suffix=suffix)
265 repo_name = repo.repo_name
265 repo_name = repo.repo_name
266
266
267 # delete from file system
267 # delete from file system
268 RepoModel()._delete_filesystem_repo(repo)
268 RepoModel()._delete_filesystem_repo(repo)
269
269
270 # test if the repo is still in the database
270 # test if the repo is still in the database
271 new_repo = RepoModel().get_by_repo_name(repo_name)
271 new_repo = RepoModel().get_by_repo_name(repo_name)
272 assert new_repo.repo_name == repo_name
272 assert new_repo.repo_name == repo_name
273
273
274 # check if repo is not in the filesystem
274 # check if repo is not in the filesystem
275 assert not repo_on_filesystem(repo_name)
275 assert not repo_on_filesystem(repo_name)
276
276
277 response = self.app.get(
277 response = self.app.get(
278 route_path('repo_summary', repo_name=safe_str(repo_name)), status=302)
278 route_path('repo_summary', repo_name=safe_str(repo_name)), status=302)
279
279
280 msg = 'The repository `%s` cannot be loaded in filesystem. ' \
280 msg = 'The repository `%s` cannot be loaded in filesystem. ' \
281 'Please check if it exist, or is not damaged.' % repo_name
281 'Please check if it exist, or is not damaged.' % repo_name
282 assert_session_flash(response, msg)
282 assert_session_flash(response, msg)
283
283
284 @pytest.mark.parametrize("suffix", [u'', u'Δ…Δ™Ε‚'], ids=['', 'non-ascii'])
284 @pytest.mark.parametrize("suffix", [u'', u'Δ…Δ™Ε‚'], ids=['', 'non-ascii'])
285 def test_missing_filesystem_repo_on_repo_check(
285 def test_missing_filesystem_repo_on_repo_check(
286 self, autologin_user, backend, suffix, csrf_token):
286 self, autologin_user, backend, suffix, csrf_token):
287 repo = backend.create_repo(name_suffix=suffix)
287 repo = backend.create_repo(name_suffix=suffix)
288 repo_name = repo.repo_name
288 repo_name = repo.repo_name
289
289
290 # delete from file system
290 # delete from file system
291 RepoModel()._delete_filesystem_repo(repo)
291 RepoModel()._delete_filesystem_repo(repo)
292
292
293 # test if the repo is still in the database
293 # test if the repo is still in the database
294 new_repo = RepoModel().get_by_repo_name(repo_name)
294 new_repo = RepoModel().get_by_repo_name(repo_name)
295 assert new_repo.repo_name == repo_name
295 assert new_repo.repo_name == repo_name
296
296
297 # check if repo is not in the filesystem
297 # check if repo is not in the filesystem
298 assert not repo_on_filesystem(repo_name)
298 assert not repo_on_filesystem(repo_name)
299
299
300 # flush the session
300 # flush the session
301 self.app.get(
301 self.app.get(
302 route_path('repo_summary', repo_name=safe_str(repo_name)),
302 route_path('repo_summary', repo_name=safe_str(repo_name)),
303 status=302)
303 status=302)
304
304
305 response = self.app.get(
305 response = self.app.get(
306 route_path('repo_creating_check', repo_name=safe_str(repo_name)),
306 route_path('repo_creating_check', repo_name=safe_str(repo_name)),
307 status=200)
307 status=200)
308 msg = 'The repository `%s` cannot be loaded in filesystem. ' \
308 msg = 'The repository `%s` cannot be loaded in filesystem. ' \
309 'Please check if it exist, or is not damaged.' % repo_name
309 'Please check if it exist, or is not damaged.' % repo_name
310 assert_session_flash(response, msg )
310 assert_session_flash(response, msg )
311
311
312
312
313 @pytest.fixture()
313 @pytest.fixture()
314 def summary_view(context_stub, request_stub, user_util):
314 def summary_view(context_stub, request_stub, user_util):
315 """
315 """
316 Bootstrap view to test the view functions
316 Bootstrap view to test the view functions
317 """
317 """
318 request_stub.matched_route = AttributeDict(name='test_view')
318 request_stub.matched_route = AttributeDict(name='test_view')
319
319
320 request_stub.user = user_util.create_user().AuthUser()
320 request_stub.user = user_util.create_user().AuthUser()
321 request_stub.db_repo = user_util.create_repo()
321 request_stub.db_repo = user_util.create_repo()
322
322
323 view = RepoSummaryView(context=context_stub, request=request_stub)
323 view = RepoSummaryView(context=context_stub, request=request_stub)
324 return view
324 return view
325
325
326
326
327 @pytest.mark.usefixtures('app')
327 @pytest.mark.usefixtures('app')
328 class TestCreateReferenceData(object):
328 class TestCreateReferenceData(object):
329
329
330 @pytest.fixture()
330 @pytest.fixture()
331 def example_refs(self):
331 def example_refs(self):
332 section_1_refs = OrderedDict((('a', 'a_id'), ('b', 'b_id')))
332 section_1_refs = OrderedDict((('a', 'a_id'), ('b', 'b_id')))
333 example_refs = [
333 example_refs = [
334 ('section_1', section_1_refs, 't1'),
334 ('section_1', section_1_refs, 't1'),
335 ('section_2', {'c': 'c_id'}, 't2'),
335 ('section_2', {'c': 'c_id'}, 't2'),
336 ]
336 ]
337 return example_refs
337 return example_refs
338
338
339 def test_generates_refs_based_on_commit_ids(self, example_refs, summary_view):
339 def test_generates_refs_based_on_commit_ids(self, example_refs, summary_view):
340 repo = mock.Mock()
340 repo = mock.Mock()
341 repo.name = 'test-repo'
341 repo.name = 'test-repo'
342 repo.alias = 'git'
342 repo.alias = 'git'
343 full_repo_name = 'pytest-repo-group/' + repo.name
343 full_repo_name = 'pytest-repo-group/' + repo.name
344
344
345 result = summary_view._create_reference_data(
345 result = summary_view._create_reference_data(
346 repo, full_repo_name, example_refs)
346 repo, full_repo_name, example_refs)
347
347
348 expected_files_url = '/{}/files/'.format(full_repo_name)
348 expected_files_url = '/{}/files/'.format(full_repo_name)
349 expected_result = [
349 expected_result = [
350 {
350 {
351 'children': [
351 'children': [
352 {
352 {
353 'id': 'a', 'idx': 0, 'raw_id': 'a_id', 'text': 'a', 'type': 't1',
353 'id': 'a', 'idx': 0, 'raw_id': 'a_id', 'text': 'a', 'type': 't1',
354 'files_url': expected_files_url + 'a/?at=a',
354 'files_url': expected_files_url + 'a/?at=a',
355 },
355 },
356 {
356 {
357 'id': 'b', 'idx': 0, 'raw_id': 'b_id', 'text': 'b', 'type': 't1',
357 'id': 'b', 'idx': 0, 'raw_id': 'b_id', 'text': 'b', 'type': 't1',
358 'files_url': expected_files_url + 'b/?at=b',
358 'files_url': expected_files_url + 'b/?at=b',
359 }
359 }
360 ],
360 ],
361 'text': 'section_1'
361 'text': 'section_1'
362 },
362 },
363 {
363 {
364 'children': [
364 'children': [
365 {
365 {
366 'id': 'c', 'idx': 0, 'raw_id': 'c_id', 'text': 'c', 'type': 't2',
366 'id': 'c', 'idx': 0, 'raw_id': 'c_id', 'text': 'c', 'type': 't2',
367 'files_url': expected_files_url + 'c/?at=c',
367 'files_url': expected_files_url + 'c/?at=c',
368 }
368 }
369 ],
369 ],
370 'text': 'section_2'
370 'text': 'section_2'
371 }]
371 }]
372 assert result == expected_result
372 assert result == expected_result
373
373
374 def test_generates_refs_with_path_for_svn(self, example_refs, summary_view):
374 def test_generates_refs_with_path_for_svn(self, example_refs, summary_view):
375 repo = mock.Mock()
375 repo = mock.Mock()
376 repo.name = 'test-repo'
376 repo.name = 'test-repo'
377 repo.alias = 'svn'
377 repo.alias = 'svn'
378 full_repo_name = 'pytest-repo-group/' + repo.name
378 full_repo_name = 'pytest-repo-group/' + repo.name
379
379
380 result = summary_view._create_reference_data(
380 result = summary_view._create_reference_data(
381 repo, full_repo_name, example_refs)
381 repo, full_repo_name, example_refs)
382
382
383 expected_files_url = '/{}/files/'.format(full_repo_name)
383 expected_files_url = '/{}/files/'.format(full_repo_name)
384 expected_result = [
384 expected_result = [
385 {
385 {
386 'children': [
386 'children': [
387 {
387 {
388 'id': 'a@a_id', 'idx': 0, 'raw_id': 'a_id',
388 'id': 'a@a_id', 'idx': 0, 'raw_id': 'a_id',
389 'text': 'a', 'type': 't1',
389 'text': 'a', 'type': 't1',
390 'files_url': expected_files_url + 'a_id/a?at=a',
390 'files_url': expected_files_url + 'a_id/a?at=a',
391 },
391 },
392 {
392 {
393 'id': 'b@b_id', 'idx': 0, 'raw_id': 'b_id',
393 'id': 'b@b_id', 'idx': 0, 'raw_id': 'b_id',
394 'text': 'b', 'type': 't1',
394 'text': 'b', 'type': 't1',
395 'files_url': expected_files_url + 'b_id/b?at=b',
395 'files_url': expected_files_url + 'b_id/b?at=b',
396 }
396 }
397 ],
397 ],
398 'text': 'section_1'
398 'text': 'section_1'
399 },
399 },
400 {
400 {
401 'children': [
401 'children': [
402 {
402 {
403 'id': 'c@c_id', 'idx': 0, 'raw_id': 'c_id',
403 'id': 'c@c_id', 'idx': 0, 'raw_id': 'c_id',
404 'text': 'c', 'type': 't2',
404 'text': 'c', 'type': 't2',
405 'files_url': expected_files_url + 'c_id/c?at=c',
405 'files_url': expected_files_url + 'c_id/c?at=c',
406 }
406 }
407 ],
407 ],
408 'text': 'section_2'
408 'text': 'section_2'
409 }
409 }
410 ]
410 ]
411 assert result == expected_result
411 assert result == expected_result
412
412
413
413
414 class TestCreateFilesUrl(object):
414 class TestCreateFilesUrl(object):
415
415
416 def test_creates_non_svn_url(self, app, summary_view):
416 def test_creates_non_svn_url(self, app, summary_view):
417 repo = mock.Mock()
417 repo = mock.Mock()
418 repo.name = 'abcde'
418 repo.name = 'abcde'
419 full_repo_name = 'test-repo-group/' + repo.name
419 full_repo_name = 'test-repo-group/' + repo.name
420 ref_name = 'branch1'
420 ref_name = 'branch1'
421 raw_id = 'deadbeef0123456789'
421 raw_id = 'deadbeef0123456789'
422 is_svn = False
422 is_svn = False
423
423
424 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
424 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
425 result = summary_view._create_files_url(
425 result = summary_view._create_files_url(
426 repo, full_repo_name, ref_name, raw_id, is_svn)
426 repo, full_repo_name, ref_name, raw_id, is_svn)
427 url_mock.assert_called_once_with(
427 url_mock.assert_called_once_with(
428 'repo_files', repo_name=full_repo_name, commit_id=ref_name,
428 'repo_files', repo_name=full_repo_name, commit_id=ref_name,
429 f_path='', _query=dict(at=ref_name))
429 f_path='', _query=dict(at=ref_name))
430 assert result == url_mock.return_value
430 assert result == url_mock.return_value
431
431
432 def test_creates_svn_url(self, app, summary_view):
432 def test_creates_svn_url(self, app, summary_view):
433 repo = mock.Mock()
433 repo = mock.Mock()
434 repo.name = 'abcde'
434 repo.name = 'abcde'
435 full_repo_name = 'test-repo-group/' + repo.name
435 full_repo_name = 'test-repo-group/' + repo.name
436 ref_name = 'branch1'
436 ref_name = 'branch1'
437 raw_id = 'deadbeef0123456789'
437 raw_id = 'deadbeef0123456789'
438 is_svn = True
438 is_svn = True
439
439
440 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
440 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
441 result = summary_view._create_files_url(
441 result = summary_view._create_files_url(
442 repo, full_repo_name, ref_name, raw_id, is_svn)
442 repo, full_repo_name, ref_name, raw_id, is_svn)
443 url_mock.assert_called_once_with(
443 url_mock.assert_called_once_with(
444 'repo_files', repo_name=full_repo_name, f_path=ref_name,
444 'repo_files', repo_name=full_repo_name, f_path=ref_name,
445 commit_id=raw_id, _query=dict(at=ref_name))
445 commit_id=raw_id, _query=dict(at=ref_name))
446 assert result == url_mock.return_value
446 assert result == url_mock.return_value
447
447
448 def test_name_has_slashes(self, app, summary_view):
448 def test_name_has_slashes(self, app, summary_view):
449 repo = mock.Mock()
449 repo = mock.Mock()
450 repo.name = 'abcde'
450 repo.name = 'abcde'
451 full_repo_name = 'test-repo-group/' + repo.name
451 full_repo_name = 'test-repo-group/' + repo.name
452 ref_name = 'branch1/branch2'
452 ref_name = 'branch1/branch2'
453 raw_id = 'deadbeef0123456789'
453 raw_id = 'deadbeef0123456789'
454 is_svn = False
454 is_svn = False
455
455
456 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
456 with mock.patch('rhodecode.lib.helpers.route_path') as url_mock:
457 result = summary_view._create_files_url(
457 result = summary_view._create_files_url(
458 repo, full_repo_name, ref_name, raw_id, is_svn)
458 repo, full_repo_name, ref_name, raw_id, is_svn)
459 url_mock.assert_called_once_with(
459 url_mock.assert_called_once_with(
460 'repo_files', repo_name=full_repo_name, commit_id=raw_id,
460 'repo_files', repo_name=full_repo_name, commit_id=raw_id,
461 f_path='', _query=dict(at=ref_name))
461 f_path='', _query=dict(at=ref_name))
462 assert result == url_mock.return_value
462 assert result == url_mock.return_value
463
463
464
464
465 class TestReferenceItems(object):
465 class TestReferenceItems(object):
466 repo = mock.Mock()
466 repo = mock.Mock()
467 repo.name = 'pytest-repo'
467 repo.name = 'pytest-repo'
468 repo_full_name = 'pytest-repo-group/' + repo.name
468 repo_full_name = 'pytest-repo-group/' + repo.name
469 ref_type = 'branch'
469 ref_type = 'branch'
470 fake_url = '/abcde/'
470 fake_url = '/abcde/'
471
471
472 @staticmethod
472 @staticmethod
473 def _format_function(name, id_):
473 def _format_function(name, id_):
474 return 'format_function_{}_{}'.format(name, id_)
474 return 'format_function_{}_{}'.format(name, id_)
475
475
476 def test_creates_required_amount_of_items(self, summary_view):
476 def test_creates_required_amount_of_items(self, summary_view):
477 amount = 100
477 amount = 100
478 refs = {
478 refs = {
479 'ref{}'.format(i): '{0:040d}'.format(i)
479 'ref{}'.format(i): '{0:040d}'.format(i)
480 for i in range(amount)
480 for i in range(amount)
481 }
481 }
482
482
483 url_patcher = mock.patch.object(summary_view, '_create_files_url')
483 url_patcher = mock.patch.object(summary_view, '_create_files_url')
484 svn_patcher = mock.patch('rhodecode.lib.helpers.is_svn',
484 svn_patcher = mock.patch('rhodecode.lib.helpers.is_svn',
485 return_value=False)
485 return_value=False)
486
486
487 with url_patcher as url_mock, svn_patcher:
487 with url_patcher as url_mock, svn_patcher:
488 result = summary_view._create_reference_items(
488 result = summary_view._create_reference_items(
489 self.repo, self.repo_full_name, refs, self.ref_type,
489 self.repo, self.repo_full_name, refs, self.ref_type,
490 self._format_function)
490 self._format_function)
491 assert len(result) == amount
491 assert len(result) == amount
492 assert url_mock.call_count == amount
492 assert url_mock.call_count == amount
493
493
494 def test_single_item_details(self, summary_view):
494 def test_single_item_details(self, summary_view):
495 ref_name = 'ref1'
495 ref_name = 'ref1'
496 ref_id = 'deadbeef'
496 ref_id = 'deadbeef'
497 refs = {
497 refs = {
498 ref_name: ref_id
498 ref_name: ref_id
499 }
499 }
500
500
501 svn_patcher = mock.patch('rhodecode.lib.helpers.is_svn',
501 svn_patcher = mock.patch('rhodecode.lib.helpers.is_svn',
502 return_value=False)
502 return_value=False)
503
503
504 url_patcher = mock.patch.object(
504 url_patcher = mock.patch.object(
505 summary_view, '_create_files_url', return_value=self.fake_url)
505 summary_view, '_create_files_url', return_value=self.fake_url)
506
506
507 with url_patcher as url_mock, svn_patcher:
507 with url_patcher as url_mock, svn_patcher:
508 result = summary_view._create_reference_items(
508 result = summary_view._create_reference_items(
509 self.repo, self.repo_full_name, refs, self.ref_type,
509 self.repo, self.repo_full_name, refs, self.ref_type,
510 self._format_function)
510 self._format_function)
511
511
512 url_mock.assert_called_once_with(
512 url_mock.assert_called_once_with(
513 self.repo, self.repo_full_name, ref_name, ref_id, False)
513 self.repo, self.repo_full_name, ref_name, ref_id, False)
514 expected_result = [
514 expected_result = [
515 {
515 {
516 'text': ref_name,
516 'text': ref_name,
517 'id': self._format_function(ref_name, ref_id),
517 'id': self._format_function(ref_name, ref_id),
518 'raw_id': ref_id,
518 'raw_id': ref_id,
519 'idx': 0,
519 'idx': 0,
520 'type': self.ref_type,
520 'type': self.ref_type,
521 'files_url': self.fake_url
521 'files_url': self.fake_url
522 }
522 }
523 ]
523 ]
524 assert result == expected_result
524 assert result == expected_result
@@ -1,685 +1,685 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.lib import auth
24 from rhodecode.lib import auth
25 from rhodecode.lib.utils2 import str2bool
25 from rhodecode.lib.utils2 import str2bool
26 from rhodecode.model.db import (
26 from rhodecode.model.db import (
27 Repository, UserRepoToPerm, User)
27 Repository, UserRepoToPerm, User)
28 from rhodecode.model.meta import Session
28 from rhodecode.model.meta import Session
29 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
29 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
30 from rhodecode.model.user import UserModel
30 from rhodecode.model.user import UserModel
31 from rhodecode.tests import (
31 from rhodecode.tests import (
32 login_user_session, logout_user_session,
32 login_user_session, logout_user_session,
33 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
33 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
34 from rhodecode.tests.fixture import Fixture
34 from rhodecode.tests.fixture import Fixture
35 from rhodecode.tests.utils import AssertResponse
35 from rhodecode.tests.utils import AssertResponse
36
36
37 fixture = Fixture()
37 fixture = Fixture()
38
38
39
39
40 def route_path(name, params=None, **kwargs):
40 def route_path(name, params=None, **kwargs):
41 import urllib
41 import urllib
42
42
43 base_url = {
43 base_url = {
44 'repo_summary': '/{repo_name}',
44 'repo_summary': '/{repo_name}',
45 'repo_creating_check': '/{repo_name}/repo_creating_check',
45 'repo_creating_check': '/{repo_name}/repo_creating_check',
46 'edit_repo': '/{repo_name}/settings',
46 'edit_repo': '/{repo_name}/settings',
47 'edit_repo_vcs': '/{repo_name}/settings/vcs',
47 'edit_repo_vcs': '/{repo_name}/settings/vcs',
48 'edit_repo_vcs_update': '/{repo_name}/settings/vcs/update',
48 'edit_repo_vcs_update': '/{repo_name}/settings/vcs/update',
49 'edit_repo_vcs_svn_pattern_delete': '/{repo_name}/settings/vcs/svn_pattern/delete'
49 'edit_repo_vcs_svn_pattern_delete': '/{repo_name}/settings/vcs/svn_pattern/delete'
50 }[name].format(**kwargs)
50 }[name].format(**kwargs)
51
51
52 if params:
52 if params:
53 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
53 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
54 return base_url
54 return base_url
55
55
56
56
57 @pytest.mark.usefixtures("app")
57 @pytest.mark.usefixtures("app")
58 class TestVcsSettings(object):
58 class TestVcsSettings(object):
59 FORM_DATA = {
59 FORM_DATA = {
60 'inherit_global_settings': False,
60 'inherit_global_settings': False,
61 'hooks_changegroup_repo_size': False,
61 'hooks_changegroup_repo_size': False,
62 'hooks_changegroup_push_logger': False,
62 'hooks_changegroup_push_logger': False,
63 'hooks_outgoing_pull_logger': False,
63 'hooks_outgoing_pull_logger': False,
64 'extensions_largefiles': False,
64 'extensions_largefiles': False,
65 'extensions_evolve': False,
65 'extensions_evolve': False,
66 'phases_publish': 'False',
66 'phases_publish': 'False',
67 'rhodecode_pr_merge_enabled': False,
67 'rhodecode_pr_merge_enabled': False,
68 'rhodecode_use_outdated_comments': False,
68 'rhodecode_use_outdated_comments': False,
69 'new_svn_branch': '',
69 'new_svn_branch': '',
70 'new_svn_tag': ''
70 'new_svn_tag': ''
71 }
71 }
72
72
73 @pytest.mark.skip_backends('svn')
73 @pytest.mark.skip_backends('svn')
74 def test_global_settings_initial_values(self, autologin_user, backend):
74 def test_global_settings_initial_values(self, autologin_user, backend):
75 repo_name = backend.repo_name
75 repo_name = backend.repo_name
76 response = self.app.get(route_path('edit_repo_vcs', repo_name=repo_name))
76 response = self.app.get(route_path('edit_repo_vcs', repo_name=repo_name))
77
77
78 expected_settings = (
78 expected_settings = (
79 'rhodecode_use_outdated_comments', 'rhodecode_pr_merge_enabled',
79 'rhodecode_use_outdated_comments', 'rhodecode_pr_merge_enabled',
80 'hooks_changegroup_repo_size', 'hooks_changegroup_push_logger',
80 'hooks_changegroup_repo_size', 'hooks_changegroup_push_logger',
81 'hooks_outgoing_pull_logger'
81 'hooks_outgoing_pull_logger'
82 )
82 )
83 for setting in expected_settings:
83 for setting in expected_settings:
84 self.assert_repo_value_equals_global_value(response, setting)
84 self.assert_repo_value_equals_global_value(response, setting)
85
85
86 def test_show_settings_requires_repo_admin_permission(
86 def test_show_settings_requires_repo_admin_permission(
87 self, backend, user_util, settings_util):
87 self, backend, user_util, settings_util):
88 repo = backend.create_repo()
88 repo = backend.create_repo()
89 repo_name = repo.repo_name
89 repo_name = repo.repo_name
90 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
90 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
91 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
91 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
92 login_user_session(
92 login_user_session(
93 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
93 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
94 self.app.get(route_path('edit_repo_vcs', repo_name=repo_name), status=200)
94 self.app.get(route_path('edit_repo_vcs', repo_name=repo_name), status=200)
95
95
96 def test_inherit_global_settings_flag_is_true_by_default(
96 def test_inherit_global_settings_flag_is_true_by_default(
97 self, autologin_user, backend):
97 self, autologin_user, backend):
98 repo_name = backend.repo_name
98 repo_name = backend.repo_name
99 response = self.app.get(route_path('edit_repo_vcs', repo_name=repo_name))
99 response = self.app.get(route_path('edit_repo_vcs', repo_name=repo_name))
100
100
101 assert_response = AssertResponse(response)
101 assert_response = response.assert_response()
102 element = assert_response.get_element('#inherit_global_settings')
102 element = assert_response.get_element('#inherit_global_settings')
103 assert element.checked
103 assert element.checked
104
104
105 @pytest.mark.parametrize('checked_value', [True, False])
105 @pytest.mark.parametrize('checked_value', [True, False])
106 def test_inherit_global_settings_value(
106 def test_inherit_global_settings_value(
107 self, autologin_user, backend, checked_value, settings_util):
107 self, autologin_user, backend, checked_value, settings_util):
108 repo = backend.create_repo()
108 repo = backend.create_repo()
109 repo_name = repo.repo_name
109 repo_name = repo.repo_name
110 settings_util.create_repo_rhodecode_setting(
110 settings_util.create_repo_rhodecode_setting(
111 repo, 'inherit_vcs_settings', checked_value, 'bool')
111 repo, 'inherit_vcs_settings', checked_value, 'bool')
112 response = self.app.get(route_path('edit_repo_vcs', repo_name=repo_name))
112 response = self.app.get(route_path('edit_repo_vcs', repo_name=repo_name))
113
113
114 assert_response = AssertResponse(response)
114 assert_response = response.assert_response()
115 element = assert_response.get_element('#inherit_global_settings')
115 element = assert_response.get_element('#inherit_global_settings')
116 assert element.checked == checked_value
116 assert element.checked == checked_value
117
117
118 @pytest.mark.skip_backends('svn')
118 @pytest.mark.skip_backends('svn')
119 def test_hooks_settings_are_created(
119 def test_hooks_settings_are_created(
120 self, autologin_user, backend, csrf_token):
120 self, autologin_user, backend, csrf_token):
121 repo_name = backend.repo_name
121 repo_name = backend.repo_name
122 data = self.FORM_DATA.copy()
122 data = self.FORM_DATA.copy()
123 data['csrf_token'] = csrf_token
123 data['csrf_token'] = csrf_token
124 self.app.post(
124 self.app.post(
125 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
125 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
126 settings = SettingsModel(repo=repo_name)
126 settings = SettingsModel(repo=repo_name)
127 try:
127 try:
128 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
128 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
129 ui = settings.get_ui_by_section_and_key(section, key)
129 ui = settings.get_ui_by_section_and_key(section, key)
130 assert ui.ui_active is False
130 assert ui.ui_active is False
131 finally:
131 finally:
132 self._cleanup_repo_settings(settings)
132 self._cleanup_repo_settings(settings)
133
133
134 def test_hooks_settings_are_not_created_for_svn(
134 def test_hooks_settings_are_not_created_for_svn(
135 self, autologin_user, backend_svn, csrf_token):
135 self, autologin_user, backend_svn, csrf_token):
136 repo_name = backend_svn.repo_name
136 repo_name = backend_svn.repo_name
137 data = self.FORM_DATA.copy()
137 data = self.FORM_DATA.copy()
138 data['csrf_token'] = csrf_token
138 data['csrf_token'] = csrf_token
139 self.app.post(
139 self.app.post(
140 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
140 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
141 settings = SettingsModel(repo=repo_name)
141 settings = SettingsModel(repo=repo_name)
142 try:
142 try:
143 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
143 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
144 ui = settings.get_ui_by_section_and_key(section, key)
144 ui = settings.get_ui_by_section_and_key(section, key)
145 assert ui is None
145 assert ui is None
146 finally:
146 finally:
147 self._cleanup_repo_settings(settings)
147 self._cleanup_repo_settings(settings)
148
148
149 @pytest.mark.skip_backends('svn')
149 @pytest.mark.skip_backends('svn')
150 def test_hooks_settings_are_updated(
150 def test_hooks_settings_are_updated(
151 self, autologin_user, backend, csrf_token):
151 self, autologin_user, backend, csrf_token):
152 repo_name = backend.repo_name
152 repo_name = backend.repo_name
153 settings = SettingsModel(repo=repo_name)
153 settings = SettingsModel(repo=repo_name)
154 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
154 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
155 settings.create_ui_section_value(section, '', key=key, active=True)
155 settings.create_ui_section_value(section, '', key=key, active=True)
156
156
157 data = self.FORM_DATA.copy()
157 data = self.FORM_DATA.copy()
158 data['csrf_token'] = csrf_token
158 data['csrf_token'] = csrf_token
159 self.app.post(
159 self.app.post(
160 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
160 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
161 try:
161 try:
162 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
162 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
163 ui = settings.get_ui_by_section_and_key(section, key)
163 ui = settings.get_ui_by_section_and_key(section, key)
164 assert ui.ui_active is False
164 assert ui.ui_active is False
165 finally:
165 finally:
166 self._cleanup_repo_settings(settings)
166 self._cleanup_repo_settings(settings)
167
167
168 def test_hooks_settings_are_not_updated_for_svn(
168 def test_hooks_settings_are_not_updated_for_svn(
169 self, autologin_user, backend_svn, csrf_token):
169 self, autologin_user, backend_svn, csrf_token):
170 repo_name = backend_svn.repo_name
170 repo_name = backend_svn.repo_name
171 settings = SettingsModel(repo=repo_name)
171 settings = SettingsModel(repo=repo_name)
172 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
172 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
173 settings.create_ui_section_value(section, '', key=key, active=True)
173 settings.create_ui_section_value(section, '', key=key, active=True)
174
174
175 data = self.FORM_DATA.copy()
175 data = self.FORM_DATA.copy()
176 data['csrf_token'] = csrf_token
176 data['csrf_token'] = csrf_token
177 self.app.post(
177 self.app.post(
178 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
178 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
179 try:
179 try:
180 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
180 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
181 ui = settings.get_ui_by_section_and_key(section, key)
181 ui = settings.get_ui_by_section_and_key(section, key)
182 assert ui.ui_active is True
182 assert ui.ui_active is True
183 finally:
183 finally:
184 self._cleanup_repo_settings(settings)
184 self._cleanup_repo_settings(settings)
185
185
186 @pytest.mark.skip_backends('svn')
186 @pytest.mark.skip_backends('svn')
187 def test_pr_settings_are_created(
187 def test_pr_settings_are_created(
188 self, autologin_user, backend, csrf_token):
188 self, autologin_user, backend, csrf_token):
189 repo_name = backend.repo_name
189 repo_name = backend.repo_name
190 data = self.FORM_DATA.copy()
190 data = self.FORM_DATA.copy()
191 data['csrf_token'] = csrf_token
191 data['csrf_token'] = csrf_token
192 self.app.post(
192 self.app.post(
193 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
193 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
194 settings = SettingsModel(repo=repo_name)
194 settings = SettingsModel(repo=repo_name)
195 try:
195 try:
196 for name in VcsSettingsModel.GENERAL_SETTINGS:
196 for name in VcsSettingsModel.GENERAL_SETTINGS:
197 setting = settings.get_setting_by_name(name)
197 setting = settings.get_setting_by_name(name)
198 assert setting.app_settings_value is False
198 assert setting.app_settings_value is False
199 finally:
199 finally:
200 self._cleanup_repo_settings(settings)
200 self._cleanup_repo_settings(settings)
201
201
202 def test_pr_settings_are_not_created_for_svn(
202 def test_pr_settings_are_not_created_for_svn(
203 self, autologin_user, backend_svn, csrf_token):
203 self, autologin_user, backend_svn, csrf_token):
204 repo_name = backend_svn.repo_name
204 repo_name = backend_svn.repo_name
205 data = self.FORM_DATA.copy()
205 data = self.FORM_DATA.copy()
206 data['csrf_token'] = csrf_token
206 data['csrf_token'] = csrf_token
207 self.app.post(
207 self.app.post(
208 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
208 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
209 settings = SettingsModel(repo=repo_name)
209 settings = SettingsModel(repo=repo_name)
210 try:
210 try:
211 for name in VcsSettingsModel.GENERAL_SETTINGS:
211 for name in VcsSettingsModel.GENERAL_SETTINGS:
212 setting = settings.get_setting_by_name(name)
212 setting = settings.get_setting_by_name(name)
213 assert setting is None
213 assert setting is None
214 finally:
214 finally:
215 self._cleanup_repo_settings(settings)
215 self._cleanup_repo_settings(settings)
216
216
217 def test_pr_settings_creation_requires_repo_admin_permission(
217 def test_pr_settings_creation_requires_repo_admin_permission(
218 self, backend, user_util, settings_util, csrf_token):
218 self, backend, user_util, settings_util, csrf_token):
219 repo = backend.create_repo()
219 repo = backend.create_repo()
220 repo_name = repo.repo_name
220 repo_name = repo.repo_name
221
221
222 logout_user_session(self.app, csrf_token)
222 logout_user_session(self.app, csrf_token)
223 session = login_user_session(
223 session = login_user_session(
224 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
224 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
225 new_csrf_token = auth.get_csrf_token(session)
225 new_csrf_token = auth.get_csrf_token(session)
226
226
227 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
227 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
228 repo = Repository.get_by_repo_name(repo_name)
228 repo = Repository.get_by_repo_name(repo_name)
229 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
229 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
230 data = self.FORM_DATA.copy()
230 data = self.FORM_DATA.copy()
231 data['csrf_token'] = new_csrf_token
231 data['csrf_token'] = new_csrf_token
232 settings = SettingsModel(repo=repo_name)
232 settings = SettingsModel(repo=repo_name)
233
233
234 try:
234 try:
235 self.app.post(
235 self.app.post(
236 route_path('edit_repo_vcs_update', repo_name=repo_name), data,
236 route_path('edit_repo_vcs_update', repo_name=repo_name), data,
237 status=302)
237 status=302)
238 finally:
238 finally:
239 self._cleanup_repo_settings(settings)
239 self._cleanup_repo_settings(settings)
240
240
241 @pytest.mark.skip_backends('svn')
241 @pytest.mark.skip_backends('svn')
242 def test_pr_settings_are_updated(
242 def test_pr_settings_are_updated(
243 self, autologin_user, backend, csrf_token):
243 self, autologin_user, backend, csrf_token):
244 repo_name = backend.repo_name
244 repo_name = backend.repo_name
245 settings = SettingsModel(repo=repo_name)
245 settings = SettingsModel(repo=repo_name)
246 for name in VcsSettingsModel.GENERAL_SETTINGS:
246 for name in VcsSettingsModel.GENERAL_SETTINGS:
247 settings.create_or_update_setting(name, True, 'bool')
247 settings.create_or_update_setting(name, True, 'bool')
248
248
249 data = self.FORM_DATA.copy()
249 data = self.FORM_DATA.copy()
250 data['csrf_token'] = csrf_token
250 data['csrf_token'] = csrf_token
251 self.app.post(
251 self.app.post(
252 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
252 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
253 try:
253 try:
254 for name in VcsSettingsModel.GENERAL_SETTINGS:
254 for name in VcsSettingsModel.GENERAL_SETTINGS:
255 setting = settings.get_setting_by_name(name)
255 setting = settings.get_setting_by_name(name)
256 assert setting.app_settings_value is False
256 assert setting.app_settings_value is False
257 finally:
257 finally:
258 self._cleanup_repo_settings(settings)
258 self._cleanup_repo_settings(settings)
259
259
260 def test_pr_settings_are_not_updated_for_svn(
260 def test_pr_settings_are_not_updated_for_svn(
261 self, autologin_user, backend_svn, csrf_token):
261 self, autologin_user, backend_svn, csrf_token):
262 repo_name = backend_svn.repo_name
262 repo_name = backend_svn.repo_name
263 settings = SettingsModel(repo=repo_name)
263 settings = SettingsModel(repo=repo_name)
264 for name in VcsSettingsModel.GENERAL_SETTINGS:
264 for name in VcsSettingsModel.GENERAL_SETTINGS:
265 settings.create_or_update_setting(name, True, 'bool')
265 settings.create_or_update_setting(name, True, 'bool')
266
266
267 data = self.FORM_DATA.copy()
267 data = self.FORM_DATA.copy()
268 data['csrf_token'] = csrf_token
268 data['csrf_token'] = csrf_token
269 self.app.post(
269 self.app.post(
270 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
270 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
271 try:
271 try:
272 for name in VcsSettingsModel.GENERAL_SETTINGS:
272 for name in VcsSettingsModel.GENERAL_SETTINGS:
273 setting = settings.get_setting_by_name(name)
273 setting = settings.get_setting_by_name(name)
274 assert setting.app_settings_value is True
274 assert setting.app_settings_value is True
275 finally:
275 finally:
276 self._cleanup_repo_settings(settings)
276 self._cleanup_repo_settings(settings)
277
277
278 def test_svn_settings_are_created(
278 def test_svn_settings_are_created(
279 self, autologin_user, backend_svn, csrf_token, settings_util):
279 self, autologin_user, backend_svn, csrf_token, settings_util):
280 repo_name = backend_svn.repo_name
280 repo_name = backend_svn.repo_name
281 data = self.FORM_DATA.copy()
281 data = self.FORM_DATA.copy()
282 data['new_svn_tag'] = 'svn-tag'
282 data['new_svn_tag'] = 'svn-tag'
283 data['new_svn_branch'] = 'svn-branch'
283 data['new_svn_branch'] = 'svn-branch'
284 data['csrf_token'] = csrf_token
284 data['csrf_token'] = csrf_token
285
285
286 # Create few global settings to make sure that uniqueness validators
286 # Create few global settings to make sure that uniqueness validators
287 # are not triggered
287 # are not triggered
288 settings_util.create_rhodecode_ui(
288 settings_util.create_rhodecode_ui(
289 VcsSettingsModel.SVN_BRANCH_SECTION, 'svn-branch')
289 VcsSettingsModel.SVN_BRANCH_SECTION, 'svn-branch')
290 settings_util.create_rhodecode_ui(
290 settings_util.create_rhodecode_ui(
291 VcsSettingsModel.SVN_TAG_SECTION, 'svn-tag')
291 VcsSettingsModel.SVN_TAG_SECTION, 'svn-tag')
292
292
293 self.app.post(
293 self.app.post(
294 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
294 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
295 settings = SettingsModel(repo=repo_name)
295 settings = SettingsModel(repo=repo_name)
296 try:
296 try:
297 svn_branches = settings.get_ui_by_section(
297 svn_branches = settings.get_ui_by_section(
298 VcsSettingsModel.SVN_BRANCH_SECTION)
298 VcsSettingsModel.SVN_BRANCH_SECTION)
299 svn_branch_names = [b.ui_value for b in svn_branches]
299 svn_branch_names = [b.ui_value for b in svn_branches]
300 svn_tags = settings.get_ui_by_section(
300 svn_tags = settings.get_ui_by_section(
301 VcsSettingsModel.SVN_TAG_SECTION)
301 VcsSettingsModel.SVN_TAG_SECTION)
302 svn_tag_names = [b.ui_value for b in svn_tags]
302 svn_tag_names = [b.ui_value for b in svn_tags]
303 assert 'svn-branch' in svn_branch_names
303 assert 'svn-branch' in svn_branch_names
304 assert 'svn-tag' in svn_tag_names
304 assert 'svn-tag' in svn_tag_names
305 finally:
305 finally:
306 self._cleanup_repo_settings(settings)
306 self._cleanup_repo_settings(settings)
307
307
308 def test_svn_settings_are_unique(
308 def test_svn_settings_are_unique(
309 self, autologin_user, backend_svn, csrf_token, settings_util):
309 self, autologin_user, backend_svn, csrf_token, settings_util):
310 repo = backend_svn.repo
310 repo = backend_svn.repo
311 repo_name = repo.repo_name
311 repo_name = repo.repo_name
312 data = self.FORM_DATA.copy()
312 data = self.FORM_DATA.copy()
313 data['new_svn_tag'] = 'test_tag'
313 data['new_svn_tag'] = 'test_tag'
314 data['new_svn_branch'] = 'test_branch'
314 data['new_svn_branch'] = 'test_branch'
315 data['csrf_token'] = csrf_token
315 data['csrf_token'] = csrf_token
316 settings_util.create_repo_rhodecode_ui(
316 settings_util.create_repo_rhodecode_ui(
317 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch')
317 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch')
318 settings_util.create_repo_rhodecode_ui(
318 settings_util.create_repo_rhodecode_ui(
319 repo, VcsSettingsModel.SVN_TAG_SECTION, 'test_tag')
319 repo, VcsSettingsModel.SVN_TAG_SECTION, 'test_tag')
320
320
321 response = self.app.post(
321 response = self.app.post(
322 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=200)
322 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=200)
323 response.mustcontain('Pattern already exists')
323 response.mustcontain('Pattern already exists')
324
324
325 def test_svn_settings_with_empty_values_are_not_created(
325 def test_svn_settings_with_empty_values_are_not_created(
326 self, autologin_user, backend_svn, csrf_token):
326 self, autologin_user, backend_svn, csrf_token):
327 repo_name = backend_svn.repo_name
327 repo_name = backend_svn.repo_name
328 data = self.FORM_DATA.copy()
328 data = self.FORM_DATA.copy()
329 data['csrf_token'] = csrf_token
329 data['csrf_token'] = csrf_token
330 self.app.post(
330 self.app.post(
331 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
331 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
332 settings = SettingsModel(repo=repo_name)
332 settings = SettingsModel(repo=repo_name)
333 try:
333 try:
334 svn_branches = settings.get_ui_by_section(
334 svn_branches = settings.get_ui_by_section(
335 VcsSettingsModel.SVN_BRANCH_SECTION)
335 VcsSettingsModel.SVN_BRANCH_SECTION)
336 svn_tags = settings.get_ui_by_section(
336 svn_tags = settings.get_ui_by_section(
337 VcsSettingsModel.SVN_TAG_SECTION)
337 VcsSettingsModel.SVN_TAG_SECTION)
338 assert len(svn_branches) == 0
338 assert len(svn_branches) == 0
339 assert len(svn_tags) == 0
339 assert len(svn_tags) == 0
340 finally:
340 finally:
341 self._cleanup_repo_settings(settings)
341 self._cleanup_repo_settings(settings)
342
342
343 def test_svn_settings_are_shown_for_svn_repository(
343 def test_svn_settings_are_shown_for_svn_repository(
344 self, autologin_user, backend_svn, csrf_token):
344 self, autologin_user, backend_svn, csrf_token):
345 repo_name = backend_svn.repo_name
345 repo_name = backend_svn.repo_name
346 response = self.app.get(
346 response = self.app.get(
347 route_path('edit_repo_vcs', repo_name=repo_name), status=200)
347 route_path('edit_repo_vcs', repo_name=repo_name), status=200)
348 response.mustcontain('Subversion Settings')
348 response.mustcontain('Subversion Settings')
349
349
350 @pytest.mark.skip_backends('svn')
350 @pytest.mark.skip_backends('svn')
351 def test_svn_settings_are_not_created_for_not_svn_repository(
351 def test_svn_settings_are_not_created_for_not_svn_repository(
352 self, autologin_user, backend, csrf_token):
352 self, autologin_user, backend, csrf_token):
353 repo_name = backend.repo_name
353 repo_name = backend.repo_name
354 data = self.FORM_DATA.copy()
354 data = self.FORM_DATA.copy()
355 data['csrf_token'] = csrf_token
355 data['csrf_token'] = csrf_token
356 self.app.post(
356 self.app.post(
357 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
357 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
358 settings = SettingsModel(repo=repo_name)
358 settings = SettingsModel(repo=repo_name)
359 try:
359 try:
360 svn_branches = settings.get_ui_by_section(
360 svn_branches = settings.get_ui_by_section(
361 VcsSettingsModel.SVN_BRANCH_SECTION)
361 VcsSettingsModel.SVN_BRANCH_SECTION)
362 svn_tags = settings.get_ui_by_section(
362 svn_tags = settings.get_ui_by_section(
363 VcsSettingsModel.SVN_TAG_SECTION)
363 VcsSettingsModel.SVN_TAG_SECTION)
364 assert len(svn_branches) == 0
364 assert len(svn_branches) == 0
365 assert len(svn_tags) == 0
365 assert len(svn_tags) == 0
366 finally:
366 finally:
367 self._cleanup_repo_settings(settings)
367 self._cleanup_repo_settings(settings)
368
368
369 @pytest.mark.skip_backends('svn')
369 @pytest.mark.skip_backends('svn')
370 def test_svn_settings_are_shown_only_for_svn_repository(
370 def test_svn_settings_are_shown_only_for_svn_repository(
371 self, autologin_user, backend, csrf_token):
371 self, autologin_user, backend, csrf_token):
372 repo_name = backend.repo_name
372 repo_name = backend.repo_name
373 response = self.app.get(
373 response = self.app.get(
374 route_path('edit_repo_vcs', repo_name=repo_name), status=200)
374 route_path('edit_repo_vcs', repo_name=repo_name), status=200)
375 response.mustcontain(no='Subversion Settings')
375 response.mustcontain(no='Subversion Settings')
376
376
377 def test_hg_settings_are_created(
377 def test_hg_settings_are_created(
378 self, autologin_user, backend_hg, csrf_token):
378 self, autologin_user, backend_hg, csrf_token):
379 repo_name = backend_hg.repo_name
379 repo_name = backend_hg.repo_name
380 data = self.FORM_DATA.copy()
380 data = self.FORM_DATA.copy()
381 data['new_svn_tag'] = 'svn-tag'
381 data['new_svn_tag'] = 'svn-tag'
382 data['new_svn_branch'] = 'svn-branch'
382 data['new_svn_branch'] = 'svn-branch'
383 data['csrf_token'] = csrf_token
383 data['csrf_token'] = csrf_token
384 self.app.post(
384 self.app.post(
385 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
385 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
386 settings = SettingsModel(repo=repo_name)
386 settings = SettingsModel(repo=repo_name)
387 try:
387 try:
388 largefiles_ui = settings.get_ui_by_section_and_key(
388 largefiles_ui = settings.get_ui_by_section_and_key(
389 'extensions', 'largefiles')
389 'extensions', 'largefiles')
390 assert largefiles_ui.ui_active is False
390 assert largefiles_ui.ui_active is False
391 phases_ui = settings.get_ui_by_section_and_key(
391 phases_ui = settings.get_ui_by_section_and_key(
392 'phases', 'publish')
392 'phases', 'publish')
393 assert str2bool(phases_ui.ui_value) is False
393 assert str2bool(phases_ui.ui_value) is False
394 finally:
394 finally:
395 self._cleanup_repo_settings(settings)
395 self._cleanup_repo_settings(settings)
396
396
397 def test_hg_settings_are_updated(
397 def test_hg_settings_are_updated(
398 self, autologin_user, backend_hg, csrf_token):
398 self, autologin_user, backend_hg, csrf_token):
399 repo_name = backend_hg.repo_name
399 repo_name = backend_hg.repo_name
400 settings = SettingsModel(repo=repo_name)
400 settings = SettingsModel(repo=repo_name)
401 settings.create_ui_section_value(
401 settings.create_ui_section_value(
402 'extensions', '', key='largefiles', active=True)
402 'extensions', '', key='largefiles', active=True)
403 settings.create_ui_section_value(
403 settings.create_ui_section_value(
404 'phases', '1', key='publish', active=True)
404 'phases', '1', key='publish', active=True)
405
405
406 data = self.FORM_DATA.copy()
406 data = self.FORM_DATA.copy()
407 data['csrf_token'] = csrf_token
407 data['csrf_token'] = csrf_token
408 self.app.post(
408 self.app.post(
409 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
409 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
410 try:
410 try:
411 largefiles_ui = settings.get_ui_by_section_and_key(
411 largefiles_ui = settings.get_ui_by_section_and_key(
412 'extensions', 'largefiles')
412 'extensions', 'largefiles')
413 assert largefiles_ui.ui_active is False
413 assert largefiles_ui.ui_active is False
414 phases_ui = settings.get_ui_by_section_and_key(
414 phases_ui = settings.get_ui_by_section_and_key(
415 'phases', 'publish')
415 'phases', 'publish')
416 assert str2bool(phases_ui.ui_value) is False
416 assert str2bool(phases_ui.ui_value) is False
417 finally:
417 finally:
418 self._cleanup_repo_settings(settings)
418 self._cleanup_repo_settings(settings)
419
419
420 def test_hg_settings_are_shown_for_hg_repository(
420 def test_hg_settings_are_shown_for_hg_repository(
421 self, autologin_user, backend_hg, csrf_token):
421 self, autologin_user, backend_hg, csrf_token):
422 repo_name = backend_hg.repo_name
422 repo_name = backend_hg.repo_name
423 response = self.app.get(
423 response = self.app.get(
424 route_path('edit_repo_vcs', repo_name=repo_name), status=200)
424 route_path('edit_repo_vcs', repo_name=repo_name), status=200)
425 response.mustcontain('Mercurial Settings')
425 response.mustcontain('Mercurial Settings')
426
426
427 @pytest.mark.skip_backends('hg')
427 @pytest.mark.skip_backends('hg')
428 def test_hg_settings_are_created_only_for_hg_repository(
428 def test_hg_settings_are_created_only_for_hg_repository(
429 self, autologin_user, backend, csrf_token):
429 self, autologin_user, backend, csrf_token):
430 repo_name = backend.repo_name
430 repo_name = backend.repo_name
431 data = self.FORM_DATA.copy()
431 data = self.FORM_DATA.copy()
432 data['csrf_token'] = csrf_token
432 data['csrf_token'] = csrf_token
433 self.app.post(
433 self.app.post(
434 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
434 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
435 settings = SettingsModel(repo=repo_name)
435 settings = SettingsModel(repo=repo_name)
436 try:
436 try:
437 largefiles_ui = settings.get_ui_by_section_and_key(
437 largefiles_ui = settings.get_ui_by_section_and_key(
438 'extensions', 'largefiles')
438 'extensions', 'largefiles')
439 assert largefiles_ui is None
439 assert largefiles_ui is None
440 phases_ui = settings.get_ui_by_section_and_key(
440 phases_ui = settings.get_ui_by_section_and_key(
441 'phases', 'publish')
441 'phases', 'publish')
442 assert phases_ui is None
442 assert phases_ui is None
443 finally:
443 finally:
444 self._cleanup_repo_settings(settings)
444 self._cleanup_repo_settings(settings)
445
445
446 @pytest.mark.skip_backends('hg')
446 @pytest.mark.skip_backends('hg')
447 def test_hg_settings_are_shown_only_for_hg_repository(
447 def test_hg_settings_are_shown_only_for_hg_repository(
448 self, autologin_user, backend, csrf_token):
448 self, autologin_user, backend, csrf_token):
449 repo_name = backend.repo_name
449 repo_name = backend.repo_name
450 response = self.app.get(
450 response = self.app.get(
451 route_path('edit_repo_vcs', repo_name=repo_name), status=200)
451 route_path('edit_repo_vcs', repo_name=repo_name), status=200)
452 response.mustcontain(no='Mercurial Settings')
452 response.mustcontain(no='Mercurial Settings')
453
453
454 @pytest.mark.skip_backends('hg')
454 @pytest.mark.skip_backends('hg')
455 def test_hg_settings_are_updated_only_for_hg_repository(
455 def test_hg_settings_are_updated_only_for_hg_repository(
456 self, autologin_user, backend, csrf_token):
456 self, autologin_user, backend, csrf_token):
457 repo_name = backend.repo_name
457 repo_name = backend.repo_name
458 settings = SettingsModel(repo=repo_name)
458 settings = SettingsModel(repo=repo_name)
459 settings.create_ui_section_value(
459 settings.create_ui_section_value(
460 'extensions', '', key='largefiles', active=True)
460 'extensions', '', key='largefiles', active=True)
461 settings.create_ui_section_value(
461 settings.create_ui_section_value(
462 'phases', '1', key='publish', active=True)
462 'phases', '1', key='publish', active=True)
463
463
464 data = self.FORM_DATA.copy()
464 data = self.FORM_DATA.copy()
465 data['csrf_token'] = csrf_token
465 data['csrf_token'] = csrf_token
466 self.app.post(
466 self.app.post(
467 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
467 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
468 try:
468 try:
469 largefiles_ui = settings.get_ui_by_section_and_key(
469 largefiles_ui = settings.get_ui_by_section_and_key(
470 'extensions', 'largefiles')
470 'extensions', 'largefiles')
471 assert largefiles_ui.ui_active is True
471 assert largefiles_ui.ui_active is True
472 phases_ui = settings.get_ui_by_section_and_key(
472 phases_ui = settings.get_ui_by_section_and_key(
473 'phases', 'publish')
473 'phases', 'publish')
474 assert phases_ui.ui_value == '1'
474 assert phases_ui.ui_value == '1'
475 finally:
475 finally:
476 self._cleanup_repo_settings(settings)
476 self._cleanup_repo_settings(settings)
477
477
478 def test_per_repo_svn_settings_are_displayed(
478 def test_per_repo_svn_settings_are_displayed(
479 self, autologin_user, backend_svn, settings_util):
479 self, autologin_user, backend_svn, settings_util):
480 repo = backend_svn.create_repo()
480 repo = backend_svn.create_repo()
481 repo_name = repo.repo_name
481 repo_name = repo.repo_name
482 branches = [
482 branches = [
483 settings_util.create_repo_rhodecode_ui(
483 settings_util.create_repo_rhodecode_ui(
484 repo, VcsSettingsModel.SVN_BRANCH_SECTION,
484 repo, VcsSettingsModel.SVN_BRANCH_SECTION,
485 'branch_{}'.format(i))
485 'branch_{}'.format(i))
486 for i in range(10)]
486 for i in range(10)]
487 tags = [
487 tags = [
488 settings_util.create_repo_rhodecode_ui(
488 settings_util.create_repo_rhodecode_ui(
489 repo, VcsSettingsModel.SVN_TAG_SECTION, 'tag_{}'.format(i))
489 repo, VcsSettingsModel.SVN_TAG_SECTION, 'tag_{}'.format(i))
490 for i in range(10)]
490 for i in range(10)]
491
491
492 response = self.app.get(
492 response = self.app.get(
493 route_path('edit_repo_vcs', repo_name=repo_name), status=200)
493 route_path('edit_repo_vcs', repo_name=repo_name), status=200)
494 assert_response = AssertResponse(response)
494 assert_response = response.assert_response()
495 for branch in branches:
495 for branch in branches:
496 css_selector = '[name=branch_value_{}]'.format(branch.ui_id)
496 css_selector = '[name=branch_value_{}]'.format(branch.ui_id)
497 element = assert_response.get_element(css_selector)
497 element = assert_response.get_element(css_selector)
498 assert element.value == branch.ui_value
498 assert element.value == branch.ui_value
499 for tag in tags:
499 for tag in tags:
500 css_selector = '[name=tag_ui_value_new_{}]'.format(tag.ui_id)
500 css_selector = '[name=tag_ui_value_new_{}]'.format(tag.ui_id)
501 element = assert_response.get_element(css_selector)
501 element = assert_response.get_element(css_selector)
502 assert element.value == tag.ui_value
502 assert element.value == tag.ui_value
503
503
504 def test_per_repo_hg_and_pr_settings_are_not_displayed_for_svn(
504 def test_per_repo_hg_and_pr_settings_are_not_displayed_for_svn(
505 self, autologin_user, backend_svn, settings_util):
505 self, autologin_user, backend_svn, settings_util):
506 repo = backend_svn.create_repo()
506 repo = backend_svn.create_repo()
507 repo_name = repo.repo_name
507 repo_name = repo.repo_name
508 response = self.app.get(
508 response = self.app.get(
509 route_path('edit_repo_vcs', repo_name=repo_name), status=200)
509 route_path('edit_repo_vcs', repo_name=repo_name), status=200)
510 response.mustcontain(no='<label>Hooks:</label>')
510 response.mustcontain(no='<label>Hooks:</label>')
511 response.mustcontain(no='<label>Pull Request Settings:</label>')
511 response.mustcontain(no='<label>Pull Request Settings:</label>')
512
512
513 def test_inherit_global_settings_value_is_saved(
513 def test_inherit_global_settings_value_is_saved(
514 self, autologin_user, backend, csrf_token):
514 self, autologin_user, backend, csrf_token):
515 repo_name = backend.repo_name
515 repo_name = backend.repo_name
516 data = self.FORM_DATA.copy()
516 data = self.FORM_DATA.copy()
517 data['csrf_token'] = csrf_token
517 data['csrf_token'] = csrf_token
518 data['inherit_global_settings'] = True
518 data['inherit_global_settings'] = True
519 self.app.post(
519 self.app.post(
520 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
520 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
521
521
522 settings = SettingsModel(repo=repo_name)
522 settings = SettingsModel(repo=repo_name)
523 vcs_settings = VcsSettingsModel(repo=repo_name)
523 vcs_settings = VcsSettingsModel(repo=repo_name)
524 try:
524 try:
525 assert vcs_settings.inherit_global_settings is True
525 assert vcs_settings.inherit_global_settings is True
526 finally:
526 finally:
527 self._cleanup_repo_settings(settings)
527 self._cleanup_repo_settings(settings)
528
528
529 def test_repo_cache_is_invalidated_when_settings_are_updated(
529 def test_repo_cache_is_invalidated_when_settings_are_updated(
530 self, autologin_user, backend, csrf_token):
530 self, autologin_user, backend, csrf_token):
531 repo_name = backend.repo_name
531 repo_name = backend.repo_name
532 data = self.FORM_DATA.copy()
532 data = self.FORM_DATA.copy()
533 data['csrf_token'] = csrf_token
533 data['csrf_token'] = csrf_token
534 data['inherit_global_settings'] = True
534 data['inherit_global_settings'] = True
535 settings = SettingsModel(repo=repo_name)
535 settings = SettingsModel(repo=repo_name)
536
536
537 invalidation_patcher = mock.patch(
537 invalidation_patcher = mock.patch(
538 'rhodecode.model.scm.ScmModel.mark_for_invalidation')
538 'rhodecode.model.scm.ScmModel.mark_for_invalidation')
539 with invalidation_patcher as invalidation_mock:
539 with invalidation_patcher as invalidation_mock:
540 self.app.post(
540 self.app.post(
541 route_path('edit_repo_vcs_update', repo_name=repo_name), data,
541 route_path('edit_repo_vcs_update', repo_name=repo_name), data,
542 status=302)
542 status=302)
543 try:
543 try:
544 invalidation_mock.assert_called_once_with(repo_name, delete=True)
544 invalidation_mock.assert_called_once_with(repo_name, delete=True)
545 finally:
545 finally:
546 self._cleanup_repo_settings(settings)
546 self._cleanup_repo_settings(settings)
547
547
548 def test_other_settings_not_saved_inherit_global_settings_is_true(
548 def test_other_settings_not_saved_inherit_global_settings_is_true(
549 self, autologin_user, backend, csrf_token):
549 self, autologin_user, backend, csrf_token):
550 repo_name = backend.repo_name
550 repo_name = backend.repo_name
551 data = self.FORM_DATA.copy()
551 data = self.FORM_DATA.copy()
552 data['csrf_token'] = csrf_token
552 data['csrf_token'] = csrf_token
553 data['inherit_global_settings'] = True
553 data['inherit_global_settings'] = True
554 self.app.post(
554 self.app.post(
555 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
555 route_path('edit_repo_vcs_update', repo_name=repo_name), data, status=302)
556
556
557 settings = SettingsModel(repo=repo_name)
557 settings = SettingsModel(repo=repo_name)
558 ui_settings = (
558 ui_settings = (
559 VcsSettingsModel.HOOKS_SETTINGS + VcsSettingsModel.HG_SETTINGS)
559 VcsSettingsModel.HOOKS_SETTINGS + VcsSettingsModel.HG_SETTINGS)
560
560
561 vcs_settings = []
561 vcs_settings = []
562 try:
562 try:
563 for section, key in ui_settings:
563 for section, key in ui_settings:
564 ui = settings.get_ui_by_section_and_key(section, key)
564 ui = settings.get_ui_by_section_and_key(section, key)
565 if ui:
565 if ui:
566 vcs_settings.append(ui)
566 vcs_settings.append(ui)
567 vcs_settings.extend(settings.get_ui_by_section(
567 vcs_settings.extend(settings.get_ui_by_section(
568 VcsSettingsModel.SVN_BRANCH_SECTION))
568 VcsSettingsModel.SVN_BRANCH_SECTION))
569 vcs_settings.extend(settings.get_ui_by_section(
569 vcs_settings.extend(settings.get_ui_by_section(
570 VcsSettingsModel.SVN_TAG_SECTION))
570 VcsSettingsModel.SVN_TAG_SECTION))
571 for name in VcsSettingsModel.GENERAL_SETTINGS:
571 for name in VcsSettingsModel.GENERAL_SETTINGS:
572 setting = settings.get_setting_by_name(name)
572 setting = settings.get_setting_by_name(name)
573 if setting:
573 if setting:
574 vcs_settings.append(setting)
574 vcs_settings.append(setting)
575 assert vcs_settings == []
575 assert vcs_settings == []
576 finally:
576 finally:
577 self._cleanup_repo_settings(settings)
577 self._cleanup_repo_settings(settings)
578
578
579 def test_delete_svn_branch_and_tag_patterns(
579 def test_delete_svn_branch_and_tag_patterns(
580 self, autologin_user, backend_svn, settings_util, csrf_token, xhr_header):
580 self, autologin_user, backend_svn, settings_util, csrf_token, xhr_header):
581 repo = backend_svn.create_repo()
581 repo = backend_svn.create_repo()
582 repo_name = repo.repo_name
582 repo_name = repo.repo_name
583 branch = settings_util.create_repo_rhodecode_ui(
583 branch = settings_util.create_repo_rhodecode_ui(
584 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch',
584 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch',
585 cleanup=False)
585 cleanup=False)
586 tag = settings_util.create_repo_rhodecode_ui(
586 tag = settings_util.create_repo_rhodecode_ui(
587 repo, VcsSettingsModel.SVN_TAG_SECTION, 'test_tag', cleanup=False)
587 repo, VcsSettingsModel.SVN_TAG_SECTION, 'test_tag', cleanup=False)
588 data = {
588 data = {
589 'csrf_token': csrf_token
589 'csrf_token': csrf_token
590 }
590 }
591 for id_ in (branch.ui_id, tag.ui_id):
591 for id_ in (branch.ui_id, tag.ui_id):
592 data['delete_svn_pattern'] = id_,
592 data['delete_svn_pattern'] = id_,
593 self.app.post(
593 self.app.post(
594 route_path('edit_repo_vcs_svn_pattern_delete', repo_name=repo_name),
594 route_path('edit_repo_vcs_svn_pattern_delete', repo_name=repo_name),
595 data, extra_environ=xhr_header, status=200)
595 data, extra_environ=xhr_header, status=200)
596 settings = VcsSettingsModel(repo=repo_name)
596 settings = VcsSettingsModel(repo=repo_name)
597 assert settings.get_repo_svn_branch_patterns() == []
597 assert settings.get_repo_svn_branch_patterns() == []
598
598
599 def test_delete_svn_branch_requires_repo_admin_permission(
599 def test_delete_svn_branch_requires_repo_admin_permission(
600 self, backend_svn, user_util, settings_util, csrf_token, xhr_header):
600 self, backend_svn, user_util, settings_util, csrf_token, xhr_header):
601 repo = backend_svn.create_repo()
601 repo = backend_svn.create_repo()
602 repo_name = repo.repo_name
602 repo_name = repo.repo_name
603
603
604 logout_user_session(self.app, csrf_token)
604 logout_user_session(self.app, csrf_token)
605 session = login_user_session(
605 session = login_user_session(
606 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
606 self.app, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
607 csrf_token = auth.get_csrf_token(session)
607 csrf_token = auth.get_csrf_token(session)
608
608
609 repo = Repository.get_by_repo_name(repo_name)
609 repo = Repository.get_by_repo_name(repo_name)
610 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
610 user = UserModel().get_by_username(TEST_USER_REGULAR_LOGIN)
611 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
611 user_util.grant_user_permission_to_repo(repo, user, 'repository.admin')
612 branch = settings_util.create_repo_rhodecode_ui(
612 branch = settings_util.create_repo_rhodecode_ui(
613 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch',
613 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'test_branch',
614 cleanup=False)
614 cleanup=False)
615 data = {
615 data = {
616 'csrf_token': csrf_token,
616 'csrf_token': csrf_token,
617 'delete_svn_pattern': branch.ui_id
617 'delete_svn_pattern': branch.ui_id
618 }
618 }
619 self.app.post(
619 self.app.post(
620 route_path('edit_repo_vcs_svn_pattern_delete', repo_name=repo_name),
620 route_path('edit_repo_vcs_svn_pattern_delete', repo_name=repo_name),
621 data, extra_environ=xhr_header, status=200)
621 data, extra_environ=xhr_header, status=200)
622
622
623 def test_delete_svn_branch_raises_400_when_not_found(
623 def test_delete_svn_branch_raises_400_when_not_found(
624 self, autologin_user, backend_svn, settings_util, csrf_token, xhr_header):
624 self, autologin_user, backend_svn, settings_util, csrf_token, xhr_header):
625 repo_name = backend_svn.repo_name
625 repo_name = backend_svn.repo_name
626 data = {
626 data = {
627 'delete_svn_pattern': 123,
627 'delete_svn_pattern': 123,
628 'csrf_token': csrf_token
628 'csrf_token': csrf_token
629 }
629 }
630 self.app.post(
630 self.app.post(
631 route_path('edit_repo_vcs_svn_pattern_delete', repo_name=repo_name),
631 route_path('edit_repo_vcs_svn_pattern_delete', repo_name=repo_name),
632 data, extra_environ=xhr_header, status=400)
632 data, extra_environ=xhr_header, status=400)
633
633
634 def test_delete_svn_branch_raises_400_when_no_id_specified(
634 def test_delete_svn_branch_raises_400_when_no_id_specified(
635 self, autologin_user, backend_svn, settings_util, csrf_token, xhr_header):
635 self, autologin_user, backend_svn, settings_util, csrf_token, xhr_header):
636 repo_name = backend_svn.repo_name
636 repo_name = backend_svn.repo_name
637 data = {
637 data = {
638 'csrf_token': csrf_token
638 'csrf_token': csrf_token
639 }
639 }
640 self.app.post(
640 self.app.post(
641 route_path('edit_repo_vcs_svn_pattern_delete', repo_name=repo_name),
641 route_path('edit_repo_vcs_svn_pattern_delete', repo_name=repo_name),
642 data, extra_environ=xhr_header, status=400)
642 data, extra_environ=xhr_header, status=400)
643
643
644 def _cleanup_repo_settings(self, settings_model):
644 def _cleanup_repo_settings(self, settings_model):
645 cleanup = []
645 cleanup = []
646 ui_settings = (
646 ui_settings = (
647 VcsSettingsModel.HOOKS_SETTINGS + VcsSettingsModel.HG_SETTINGS)
647 VcsSettingsModel.HOOKS_SETTINGS + VcsSettingsModel.HG_SETTINGS)
648
648
649 for section, key in ui_settings:
649 for section, key in ui_settings:
650 ui = settings_model.get_ui_by_section_and_key(section, key)
650 ui = settings_model.get_ui_by_section_and_key(section, key)
651 if ui:
651 if ui:
652 cleanup.append(ui)
652 cleanup.append(ui)
653
653
654 cleanup.extend(settings_model.get_ui_by_section(
654 cleanup.extend(settings_model.get_ui_by_section(
655 VcsSettingsModel.INHERIT_SETTINGS))
655 VcsSettingsModel.INHERIT_SETTINGS))
656 cleanup.extend(settings_model.get_ui_by_section(
656 cleanup.extend(settings_model.get_ui_by_section(
657 VcsSettingsModel.SVN_BRANCH_SECTION))
657 VcsSettingsModel.SVN_BRANCH_SECTION))
658 cleanup.extend(settings_model.get_ui_by_section(
658 cleanup.extend(settings_model.get_ui_by_section(
659 VcsSettingsModel.SVN_TAG_SECTION))
659 VcsSettingsModel.SVN_TAG_SECTION))
660
660
661 for name in VcsSettingsModel.GENERAL_SETTINGS:
661 for name in VcsSettingsModel.GENERAL_SETTINGS:
662 setting = settings_model.get_setting_by_name(name)
662 setting = settings_model.get_setting_by_name(name)
663 if setting:
663 if setting:
664 cleanup.append(setting)
664 cleanup.append(setting)
665
665
666 for object_ in cleanup:
666 for object_ in cleanup:
667 Session().delete(object_)
667 Session().delete(object_)
668 Session().commit()
668 Session().commit()
669
669
670 def assert_repo_value_equals_global_value(self, response, setting):
670 def assert_repo_value_equals_global_value(self, response, setting):
671 assert_response = AssertResponse(response)
671 assert_response = response.assert_response()
672 global_css_selector = '[name={}_inherited]'.format(setting)
672 global_css_selector = '[name={}_inherited]'.format(setting)
673 repo_css_selector = '[name={}]'.format(setting)
673 repo_css_selector = '[name={}]'.format(setting)
674 repo_element = assert_response.get_element(repo_css_selector)
674 repo_element = assert_response.get_element(repo_css_selector)
675 global_element = assert_response.get_element(global_css_selector)
675 global_element = assert_response.get_element(global_css_selector)
676 assert repo_element.value == global_element.value
676 assert repo_element.value == global_element.value
677
677
678
678
679 def _get_permission_for_user(user, repo):
679 def _get_permission_for_user(user, repo):
680 perm = UserRepoToPerm.query()\
680 perm = UserRepoToPerm.query()\
681 .filter(UserRepoToPerm.repository ==
681 .filter(UserRepoToPerm.repository ==
682 Repository.get_by_repo_name(repo))\
682 Repository.get_by_repo_name(repo))\
683 .filter(UserRepoToPerm.user == User.get_by_username(user))\
683 .filter(UserRepoToPerm.user == User.get_by_username(user))\
684 .all()
684 .all()
685 return perm
685 return perm
@@ -1,104 +1,104 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 mock
21 import mock
22 import pytest
22 import pytest
23
23
24 import rhodecode
24 import rhodecode
25 from rhodecode.model.db import Repository
25 from rhodecode.model.db import Repository
26 from rhodecode.model.settings import SettingsModel
26 from rhodecode.model.settings import SettingsModel
27 from rhodecode.tests.utils import AssertResponse
27 from rhodecode.tests.utils import AssertResponse
28
28
29
29
30 def route_path(name, params=None, **kwargs):
30 def route_path(name, params=None, **kwargs):
31 import urllib
31 import urllib
32
32
33 base_url = {
33 base_url = {
34 'edit_repo': '/{repo_name}/settings',
34 'edit_repo': '/{repo_name}/settings',
35 'edit_repo_vcs': '/{repo_name}/settings/vcs',
35 'edit_repo_vcs': '/{repo_name}/settings/vcs',
36 'edit_repo_vcs_update': '/{repo_name}/settings/vcs/update',
36 'edit_repo_vcs_update': '/{repo_name}/settings/vcs/update',
37 }[name].format(**kwargs)
37 }[name].format(**kwargs)
38
38
39 if params:
39 if params:
40 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
40 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
41 return base_url
41 return base_url
42
42
43
43
44 @pytest.mark.usefixtures('autologin_user', 'app')
44 @pytest.mark.usefixtures('autologin_user', 'app')
45 class TestAdminRepoVcsSettings(object):
45 class TestAdminRepoVcsSettings(object):
46
46
47 @pytest.mark.parametrize('setting_name, setting_backends', [
47 @pytest.mark.parametrize('setting_name, setting_backends', [
48 ('hg_use_rebase_for_merging', ['hg']),
48 ('hg_use_rebase_for_merging', ['hg']),
49 ])
49 ])
50 def test_labs_settings_visible_if_enabled(
50 def test_labs_settings_visible_if_enabled(
51 self, setting_name, setting_backends, backend):
51 self, setting_name, setting_backends, backend):
52 if backend.alias not in setting_backends:
52 if backend.alias not in setting_backends:
53 pytest.skip('Setting not available for backend {}'.format(backend))
53 pytest.skip('Setting not available for backend {}'.format(backend))
54
54
55 vcs_settings_url = route_path(
55 vcs_settings_url = route_path(
56 'edit_repo_vcs', repo_name=backend.repo.repo_name)
56 'edit_repo_vcs', repo_name=backend.repo.repo_name)
57
57
58 with mock.patch.dict(
58 with mock.patch.dict(
59 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
59 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
60 response = self.app.get(vcs_settings_url)
60 response = self.app.get(vcs_settings_url)
61
61
62 assertr = AssertResponse(response)
62 assertr = response.assert_response()
63 assertr.one_element_exists('#rhodecode_{}'.format(setting_name))
63 assertr.one_element_exists('#rhodecode_{}'.format(setting_name))
64
64
65 @pytest.mark.parametrize('setting_name, setting_backends', [
65 @pytest.mark.parametrize('setting_name, setting_backends', [
66 ('hg_use_rebase_for_merging', ['hg']),
66 ('hg_use_rebase_for_merging', ['hg']),
67 ])
67 ])
68 def test_update_boolean_settings(
68 def test_update_boolean_settings(
69 self, csrf_token, setting_name, setting_backends, backend):
69 self, csrf_token, setting_name, setting_backends, backend):
70 if backend.alias not in setting_backends:
70 if backend.alias not in setting_backends:
71 pytest.skip('Setting not available for backend {}'.format(backend))
71 pytest.skip('Setting not available for backend {}'.format(backend))
72
72
73 repo = backend.create_repo()
73 repo = backend.create_repo()
74 repo_name = repo.repo_name
74 repo_name = repo.repo_name
75
75
76 settings_model = SettingsModel(repo=repo)
76 settings_model = SettingsModel(repo=repo)
77 vcs_settings_url = route_path(
77 vcs_settings_url = route_path(
78 'edit_repo_vcs_update', repo_name=repo_name)
78 'edit_repo_vcs_update', repo_name=repo_name)
79
79
80 self.app.post(
80 self.app.post(
81 vcs_settings_url,
81 vcs_settings_url,
82 params={
82 params={
83 'inherit_global_settings': False,
83 'inherit_global_settings': False,
84 'new_svn_branch': 'dummy-value-for-testing',
84 'new_svn_branch': 'dummy-value-for-testing',
85 'new_svn_tag': 'dummy-value-for-testing',
85 'new_svn_tag': 'dummy-value-for-testing',
86 'rhodecode_{}'.format(setting_name): 'true',
86 'rhodecode_{}'.format(setting_name): 'true',
87 'csrf_token': csrf_token,
87 'csrf_token': csrf_token,
88 })
88 })
89 settings_model = SettingsModel(repo=Repository.get_by_repo_name(repo_name))
89 settings_model = SettingsModel(repo=Repository.get_by_repo_name(repo_name))
90 setting = settings_model.get_setting_by_name(setting_name)
90 setting = settings_model.get_setting_by_name(setting_name)
91 assert setting.app_settings_value
91 assert setting.app_settings_value
92
92
93 self.app.post(
93 self.app.post(
94 vcs_settings_url,
94 vcs_settings_url,
95 params={
95 params={
96 'inherit_global_settings': False,
96 'inherit_global_settings': False,
97 'new_svn_branch': 'dummy-value-for-testing',
97 'new_svn_branch': 'dummy-value-for-testing',
98 'new_svn_tag': 'dummy-value-for-testing',
98 'new_svn_tag': 'dummy-value-for-testing',
99 'rhodecode_{}'.format(setting_name): 'false',
99 'rhodecode_{}'.format(setting_name): 'false',
100 'csrf_token': csrf_token,
100 'csrf_token': csrf_token,
101 })
101 })
102 settings_model = SettingsModel(repo=Repository.get_by_repo_name(repo_name))
102 settings_model = SettingsModel(repo=Repository.get_by_repo_name(repo_name))
103 setting = settings_model.get_setting_by_name(setting_name)
103 setting = settings_model.get_setting_by_name(setting_name)
104 assert not setting.app_settings_value
104 assert not setting.app_settings_value
@@ -1,202 +1,202 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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
22
23 import mock
23 import mock
24 import pytest
24 import pytest
25 from whoosh import query
25 from whoosh import query
26
26
27 from rhodecode.tests import (
27 from rhodecode.tests import (
28 TestController, HG_REPO,
28 TestController, HG_REPO,
29 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
29 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
30 from rhodecode.tests.utils import AssertResponse
30 from rhodecode.tests.utils import AssertResponse
31
31
32
32
33 def route_path(name, **kwargs):
33 def route_path(name, **kwargs):
34 from rhodecode.apps._base import ADMIN_PREFIX
34 from rhodecode.apps._base import ADMIN_PREFIX
35 return {
35 return {
36 'search':
36 'search':
37 ADMIN_PREFIX + '/search',
37 ADMIN_PREFIX + '/search',
38 'search_repo':
38 'search_repo':
39 '/{repo_name}/search',
39 '/{repo_name}/search',
40
40
41 }[name].format(**kwargs)
41 }[name].format(**kwargs)
42
42
43
43
44 class TestSearchController(TestController):
44 class TestSearchController(TestController):
45
45
46 def test_index(self):
46 def test_index(self):
47 self.log_user()
47 self.log_user()
48 response = self.app.get(route_path('search'))
48 response = self.app.get(route_path('search'))
49 assert_response = AssertResponse(response)
49 assert_response = response.assert_response()
50 assert_response.one_element_exists('input#q')
50 assert_response.one_element_exists('input#q')
51
51
52 def test_search_files_empty_search(self):
52 def test_search_files_empty_search(self):
53 if os.path.isdir(self.index_location):
53 if os.path.isdir(self.index_location):
54 pytest.skip('skipped due to existing index')
54 pytest.skip('skipped due to existing index')
55 else:
55 else:
56 self.log_user()
56 self.log_user()
57 response = self.app.get(route_path('search'),
57 response = self.app.get(route_path('search'),
58 {'q': HG_REPO})
58 {'q': HG_REPO})
59 response.mustcontain('There is no index to search in. '
59 response.mustcontain('There is no index to search in. '
60 'Please run whoosh indexer')
60 'Please run whoosh indexer')
61
61
62 def test_search_validation(self):
62 def test_search_validation(self):
63 self.log_user()
63 self.log_user()
64 response = self.app.get(route_path('search'),
64 response = self.app.get(route_path('search'),
65 {'q': query, 'type': 'content', 'page_limit': 1000})
65 {'q': query, 'type': 'content', 'page_limit': 1000})
66
66
67 response.mustcontain(
67 response.mustcontain(
68 'page_limit - 1000 is greater than maximum value 500')
68 'page_limit - 1000 is greater than maximum value 500')
69
69
70 @pytest.mark.parametrize("query, expected_hits, expected_paths", [
70 @pytest.mark.parametrize("query, expected_hits, expected_paths", [
71 ('todo', 23, [
71 ('todo', 23, [
72 'vcs/backends/hg/inmemory.py',
72 'vcs/backends/hg/inmemory.py',
73 'vcs/tests/test_git.py']),
73 'vcs/tests/test_git.py']),
74 ('extension:rst installation', 6, [
74 ('extension:rst installation', 6, [
75 'docs/index.rst',
75 'docs/index.rst',
76 'docs/installation.rst']),
76 'docs/installation.rst']),
77 ('def repo', 87, [
77 ('def repo', 87, [
78 'vcs/tests/test_git.py',
78 'vcs/tests/test_git.py',
79 'vcs/tests/test_changesets.py']),
79 'vcs/tests/test_changesets.py']),
80 ('repository:%s def test' % HG_REPO, 18, [
80 ('repository:%s def test' % HG_REPO, 18, [
81 'vcs/tests/test_git.py',
81 'vcs/tests/test_git.py',
82 'vcs/tests/test_changesets.py']),
82 'vcs/tests/test_changesets.py']),
83 ('"def main"', 9, [
83 ('"def main"', 9, [
84 'vcs/__init__.py',
84 'vcs/__init__.py',
85 'vcs/tests/__init__.py',
85 'vcs/tests/__init__.py',
86 'vcs/utils/progressbar.py']),
86 'vcs/utils/progressbar.py']),
87 ('owner:test_admin', 358, [
87 ('owner:test_admin', 358, [
88 'vcs/tests/base.py',
88 'vcs/tests/base.py',
89 'MANIFEST.in',
89 'MANIFEST.in',
90 'vcs/utils/termcolors.py',
90 'vcs/utils/termcolors.py',
91 'docs/theme/ADC/static/documentation.png']),
91 'docs/theme/ADC/static/documentation.png']),
92 ('owner:test_admin def main', 72, [
92 ('owner:test_admin def main', 72, [
93 'vcs/__init__.py',
93 'vcs/__init__.py',
94 'vcs/tests/test_utils_filesize.py',
94 'vcs/tests/test_utils_filesize.py',
95 'vcs/tests/test_cli.py']),
95 'vcs/tests/test_cli.py']),
96 ('owner:michaΕ‚ test', 0, []),
96 ('owner:michaΕ‚ test', 0, []),
97 ])
97 ])
98 def test_search_files(self, query, expected_hits, expected_paths):
98 def test_search_files(self, query, expected_hits, expected_paths):
99 self.log_user()
99 self.log_user()
100 response = self.app.get(route_path('search'),
100 response = self.app.get(route_path('search'),
101 {'q': query, 'type': 'content', 'page_limit': 500})
101 {'q': query, 'type': 'content', 'page_limit': 500})
102
102
103 response.mustcontain('%s results' % expected_hits)
103 response.mustcontain('%s results' % expected_hits)
104 for path in expected_paths:
104 for path in expected_paths:
105 response.mustcontain(path)
105 response.mustcontain(path)
106
106
107 @pytest.mark.parametrize("query, expected_hits, expected_commits", [
107 @pytest.mark.parametrize("query, expected_hits, expected_commits", [
108 ('bother to ask where to fetch repo during tests', 3, [
108 ('bother to ask where to fetch repo during tests', 3, [
109 ('hg', 'a00c1b6f5d7a6ae678fd553a8b81d92367f7ecf1'),
109 ('hg', 'a00c1b6f5d7a6ae678fd553a8b81d92367f7ecf1'),
110 ('git', 'c6eb379775c578a95dad8ddab53f963b80894850'),
110 ('git', 'c6eb379775c578a95dad8ddab53f963b80894850'),
111 ('svn', '98')]),
111 ('svn', '98')]),
112 ('michaΕ‚', 0, []),
112 ('michaΕ‚', 0, []),
113 ('changed:tests/utils.py', 36, [
113 ('changed:tests/utils.py', 36, [
114 ('hg', 'a00c1b6f5d7a6ae678fd553a8b81d92367f7ecf1')]),
114 ('hg', 'a00c1b6f5d7a6ae678fd553a8b81d92367f7ecf1')]),
115 ('changed:vcs/utils/archivers.py', 11, [
115 ('changed:vcs/utils/archivers.py', 11, [
116 ('hg', '25213a5fbb048dff8ba65d21e466a835536e5b70'),
116 ('hg', '25213a5fbb048dff8ba65d21e466a835536e5b70'),
117 ('hg', '47aedd538bf616eedcb0e7d630ea476df0e159c7'),
117 ('hg', '47aedd538bf616eedcb0e7d630ea476df0e159c7'),
118 ('hg', 'f5d23247fad4856a1dabd5838afade1e0eed24fb'),
118 ('hg', 'f5d23247fad4856a1dabd5838afade1e0eed24fb'),
119 ('hg', '04ad456aefd6461aea24f90b63954b6b1ce07b3e'),
119 ('hg', '04ad456aefd6461aea24f90b63954b6b1ce07b3e'),
120 ('git', 'c994f0de03b2a0aa848a04fc2c0d7e737dba31fc'),
120 ('git', 'c994f0de03b2a0aa848a04fc2c0d7e737dba31fc'),
121 ('git', 'd1f898326327e20524fe22417c22d71064fe54a1'),
121 ('git', 'd1f898326327e20524fe22417c22d71064fe54a1'),
122 ('git', 'fe568b4081755c12abf6ba673ba777fc02a415f3'),
122 ('git', 'fe568b4081755c12abf6ba673ba777fc02a415f3'),
123 ('git', 'bafe786f0d8c2ff7da5c1dcfcfa577de0b5e92f1')]),
123 ('git', 'bafe786f0d8c2ff7da5c1dcfcfa577de0b5e92f1')]),
124 ('added:README.rst', 3, [
124 ('added:README.rst', 3, [
125 ('hg', '3803844fdbd3b711175fc3da9bdacfcd6d29a6fb'),
125 ('hg', '3803844fdbd3b711175fc3da9bdacfcd6d29a6fb'),
126 ('git', 'ff7ca51e58c505fec0dd2491de52c622bb7a806b'),
126 ('git', 'ff7ca51e58c505fec0dd2491de52c622bb7a806b'),
127 ('svn', '8')]),
127 ('svn', '8')]),
128 ('changed:lazy.py', 15, [
128 ('changed:lazy.py', 15, [
129 ('hg', 'eaa291c5e6ae6126a203059de9854ccf7b5baa12'),
129 ('hg', 'eaa291c5e6ae6126a203059de9854ccf7b5baa12'),
130 ('git', '17438a11f72b93f56d0e08e7d1fa79a378578a82'),
130 ('git', '17438a11f72b93f56d0e08e7d1fa79a378578a82'),
131 ('svn', '82'),
131 ('svn', '82'),
132 ('svn', '262'),
132 ('svn', '262'),
133 ('hg', 'f5d23247fad4856a1dabd5838afade1e0eed24fb'),
133 ('hg', 'f5d23247fad4856a1dabd5838afade1e0eed24fb'),
134 ('git', '33fa3223355104431402a888fa77a4e9956feb3e')
134 ('git', '33fa3223355104431402a888fa77a4e9956feb3e')
135 ]),
135 ]),
136 ('author:marcin@python-blog.com '
136 ('author:marcin@python-blog.com '
137 'commit_id:b986218ba1c9b0d6a259fac9b050b1724ed8e545', 1, [
137 'commit_id:b986218ba1c9b0d6a259fac9b050b1724ed8e545', 1, [
138 ('hg', 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]),
138 ('hg', 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]),
139 ('b986218ba1c9b0d6a259fac9b050b1724ed8e545', 1, [
139 ('b986218ba1c9b0d6a259fac9b050b1724ed8e545', 1, [
140 ('hg', 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]),
140 ('hg', 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]),
141 ('b986218b', 1, [
141 ('b986218b', 1, [
142 ('hg', 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]),
142 ('hg', 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]),
143 ])
143 ])
144 def test_search_commit_messages(
144 def test_search_commit_messages(
145 self, query, expected_hits, expected_commits, enabled_backends):
145 self, query, expected_hits, expected_commits, enabled_backends):
146 self.log_user()
146 self.log_user()
147 response = self.app.get(route_path('search'),
147 response = self.app.get(route_path('search'),
148 {'q': query, 'type': 'commit', 'page_limit': 500})
148 {'q': query, 'type': 'commit', 'page_limit': 500})
149
149
150 response.mustcontain('%s results' % expected_hits)
150 response.mustcontain('%s results' % expected_hits)
151 for backend, commit_id in expected_commits:
151 for backend, commit_id in expected_commits:
152 if backend in enabled_backends:
152 if backend in enabled_backends:
153 response.mustcontain(commit_id)
153 response.mustcontain(commit_id)
154
154
155 @pytest.mark.parametrize("query, expected_hits, expected_paths", [
155 @pytest.mark.parametrize("query, expected_hits, expected_paths", [
156 ('readme.rst', 3, []),
156 ('readme.rst', 3, []),
157 ('test*', 75, []),
157 ('test*', 75, []),
158 ('*model*', 1, []),
158 ('*model*', 1, []),
159 ('extension:rst', 48, []),
159 ('extension:rst', 48, []),
160 ('extension:rst api', 24, []),
160 ('extension:rst api', 24, []),
161 ])
161 ])
162 def test_search_file_paths(self, query, expected_hits, expected_paths):
162 def test_search_file_paths(self, query, expected_hits, expected_paths):
163 self.log_user()
163 self.log_user()
164 response = self.app.get(route_path('search'),
164 response = self.app.get(route_path('search'),
165 {'q': query, 'type': 'path', 'page_limit': 500})
165 {'q': query, 'type': 'path', 'page_limit': 500})
166
166
167 response.mustcontain('%s results' % expected_hits)
167 response.mustcontain('%s results' % expected_hits)
168 for path in expected_paths:
168 for path in expected_paths:
169 response.mustcontain(path)
169 response.mustcontain(path)
170
170
171 def test_search_commit_message_specific_repo(self, backend):
171 def test_search_commit_message_specific_repo(self, backend):
172 self.log_user()
172 self.log_user()
173 response = self.app.get(
173 response = self.app.get(
174 route_path('search_repo',repo_name=backend.repo_name),
174 route_path('search_repo',repo_name=backend.repo_name),
175 {'q': 'bother to ask where to fetch repo during tests',
175 {'q': 'bother to ask where to fetch repo during tests',
176 'type': 'commit'})
176 'type': 'commit'})
177
177
178 response.mustcontain('1 results')
178 response.mustcontain('1 results')
179
179
180 def test_filters_are_not_applied_for_admin_user(self):
180 def test_filters_are_not_applied_for_admin_user(self):
181 self.log_user()
181 self.log_user()
182 with mock.patch('whoosh.searching.Searcher.search') as search_mock:
182 with mock.patch('whoosh.searching.Searcher.search') as search_mock:
183 self.app.get(route_path('search'),
183 self.app.get(route_path('search'),
184 {'q': 'test query', 'type': 'commit'})
184 {'q': 'test query', 'type': 'commit'})
185 assert search_mock.call_count == 1
185 assert search_mock.call_count == 1
186 _, kwargs = search_mock.call_args
186 _, kwargs = search_mock.call_args
187 assert kwargs['filter'] is None
187 assert kwargs['filter'] is None
188
188
189 def test_filters_are_applied_for_normal_user(self, enabled_backends):
189 def test_filters_are_applied_for_normal_user(self, enabled_backends):
190 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
190 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
191 with mock.patch('whoosh.searching.Searcher.search') as search_mock:
191 with mock.patch('whoosh.searching.Searcher.search') as search_mock:
192 self.app.get(route_path('search'),
192 self.app.get(route_path('search'),
193 {'q': 'test query', 'type': 'commit'})
193 {'q': 'test query', 'type': 'commit'})
194 assert search_mock.call_count == 1
194 assert search_mock.call_count == 1
195 _, kwargs = search_mock.call_args
195 _, kwargs = search_mock.call_args
196 assert isinstance(kwargs['filter'], query.Or)
196 assert isinstance(kwargs['filter'], query.Or)
197 expected_repositories = [
197 expected_repositories = [
198 'vcs_test_{}'.format(b) for b in enabled_backends]
198 'vcs_test_{}'.format(b) for b in enabled_backends]
199 queried_repositories = [
199 queried_repositories = [
200 name for type_, name in kwargs['filter'].all_terms()]
200 name for type_, name in kwargs['filter'].all_terms()]
201 for repository in expected_repositories:
201 for repository in expected_repositories:
202 assert repository in queried_repositories
202 assert repository in queried_repositories
@@ -1,75 +1,75 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 pytest
21 import pytest
22
22
23 from rhodecode.model.db import User
23 from rhodecode.model.db import User
24 from rhodecode.tests import (
24 from rhodecode.tests import (
25 TestController, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
25 TestController, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
26 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
26 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
27 from rhodecode.tests.fixture import Fixture
27 from rhodecode.tests.fixture import Fixture
28 from rhodecode.tests.utils import AssertResponse
28 from rhodecode.tests.utils import AssertResponse
29
29
30 fixture = Fixture()
30 fixture = Fixture()
31
31
32
32
33 def route_path(name, **kwargs):
33 def route_path(name, **kwargs):
34 return '/_profiles/{username}'.format(**kwargs)
34 return '/_profiles/{username}'.format(**kwargs)
35
35
36
36
37 class TestUsersController(TestController):
37 class TestUsersController(TestController):
38
38
39 def test_user_profile(self, user_util):
39 def test_user_profile(self, user_util):
40 edit_link_css = '.user-profile .panel-edit'
40 edit_link_css = '.user-profile .panel-edit'
41 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
41 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
42 user = user_util.create_user(
42 user = user_util.create_user(
43 'test-my-user', password='qweqwe', email='testme@rhodecode.org')
43 'test-my-user', password='qweqwe', email='testme@rhodecode.org')
44 username = user.username
44 username = user.username
45
45
46 response = self.app.get(route_path('user_profile', username=username))
46 response = self.app.get(route_path('user_profile', username=username))
47 response.mustcontain('testme')
47 response.mustcontain('testme')
48 response.mustcontain('testme@rhodecode.org')
48 response.mustcontain('testme@rhodecode.org')
49 assert_response = AssertResponse(response)
49 assert_response = response.assert_response()
50 assert_response.no_element_exists(edit_link_css)
50 assert_response.no_element_exists(edit_link_css)
51
51
52 # edit should be available to superadmin users
52 # edit should be available to superadmin users
53 self.logout_user()
53 self.logout_user()
54 self.log_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
54 self.log_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
55 response = self.app.get(route_path('user_profile', username=username))
55 response = self.app.get(route_path('user_profile', username=username))
56 assert_response = AssertResponse(response)
56 assert_response = response.assert_response()
57 assert_response.element_contains(edit_link_css, 'Edit')
57 assert_response.element_contains(edit_link_css, 'Edit')
58
58
59 def test_user_profile_not_available(self, user_util):
59 def test_user_profile_not_available(self, user_util):
60 user = user_util.create_user()
60 user = user_util.create_user()
61 username = user.username
61 username = user.username
62
62
63 # not logged in, redirect
63 # not logged in, redirect
64 self.app.get(route_path('user_profile', username=username), status=302)
64 self.app.get(route_path('user_profile', username=username), status=302)
65
65
66 self.log_user()
66 self.log_user()
67 # after log-in show
67 # after log-in show
68 self.app.get(route_path('user_profile', username=username), status=200)
68 self.app.get(route_path('user_profile', username=username), status=200)
69
69
70 # default user, not allowed to show it
70 # default user, not allowed to show it
71 self.app.get(
71 self.app.get(
72 route_path('user_profile', username=User.DEFAULT_USER), status=404)
72 route_path('user_profile', username=User.DEFAULT_USER), status=404)
73
73
74 # actual 404
74 # actual 404
75 self.app.get(route_path('user_profile', username='unknown'), status=404)
75 self.app.get(route_path('user_profile', username='unknown'), status=404)
@@ -1,5218 +1,5218 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import re
26 import os
26 import os
27 import time
27 import time
28 import string
28 import string
29 import hashlib
29 import hashlib
30 import logging
30 import logging
31 import datetime
31 import datetime
32 import uuid
32 import uuid
33 import warnings
33 import warnings
34 import ipaddress
34 import ipaddress
35 import functools
35 import functools
36 import traceback
36 import traceback
37 import collections
37 import collections
38
38
39 from sqlalchemy import (
39 from sqlalchemy import (
40 or_, and_, not_, func, TypeDecorator, event,
40 or_, and_, not_, func, TypeDecorator, event,
41 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
41 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
42 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
42 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
43 Text, Float, PickleType)
43 Text, Float, PickleType)
44 from sqlalchemy.sql.expression import true, false, case
44 from sqlalchemy.sql.expression import true, false, case
45 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
45 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
46 from sqlalchemy.orm import (
46 from sqlalchemy.orm import (
47 relationship, joinedload, class_mapper, validates, aliased)
47 relationship, joinedload, class_mapper, validates, aliased)
48 from sqlalchemy.ext.declarative import declared_attr
48 from sqlalchemy.ext.declarative import declared_attr
49 from sqlalchemy.ext.hybrid import hybrid_property
49 from sqlalchemy.ext.hybrid import hybrid_property
50 from sqlalchemy.exc import IntegrityError # pragma: no cover
50 from sqlalchemy.exc import IntegrityError # pragma: no cover
51 from sqlalchemy.dialects.mysql import LONGTEXT
51 from sqlalchemy.dialects.mysql import LONGTEXT
52 from zope.cachedescriptors.property import Lazy as LazyProperty
52 from zope.cachedescriptors.property import Lazy as LazyProperty
53 from pyramid import compat
53 from pyramid import compat
54 from pyramid.threadlocal import get_current_request
54 from pyramid.threadlocal import get_current_request
55 from webhelpers.text import collapse, remove_formatting
55 from webhelpers.text import collapse, remove_formatting
56
56
57 from rhodecode.translation import _
57 from rhodecode.translation import _
58 from rhodecode.lib.vcs import get_vcs_instance
58 from rhodecode.lib.vcs import get_vcs_instance
59 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
59 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
60 from rhodecode.lib.utils2 import (
60 from rhodecode.lib.utils2 import (
61 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
61 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
62 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
62 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
63 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
63 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
64 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
64 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
65 JsonRaw
65 JsonRaw
66 from rhodecode.lib.ext_json import json
66 from rhodecode.lib.ext_json import json
67 from rhodecode.lib.caching_query import FromCache
67 from rhodecode.lib.caching_query import FromCache
68 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
68 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
69 from rhodecode.lib.encrypt2 import Encryptor
69 from rhodecode.lib.encrypt2 import Encryptor
70 from rhodecode.model.meta import Base, Session
70 from rhodecode.model.meta import Base, Session
71
71
72 URL_SEP = '/'
72 URL_SEP = '/'
73 log = logging.getLogger(__name__)
73 log = logging.getLogger(__name__)
74
74
75 # =============================================================================
75 # =============================================================================
76 # BASE CLASSES
76 # BASE CLASSES
77 # =============================================================================
77 # =============================================================================
78
78
79 # this is propagated from .ini file rhodecode.encrypted_values.secret or
79 # this is propagated from .ini file rhodecode.encrypted_values.secret or
80 # beaker.session.secret if first is not set.
80 # beaker.session.secret if first is not set.
81 # and initialized at environment.py
81 # and initialized at environment.py
82 ENCRYPTION_KEY = None
82 ENCRYPTION_KEY = None
83
83
84 # used to sort permissions by types, '#' used here is not allowed to be in
84 # used to sort permissions by types, '#' used here is not allowed to be in
85 # usernames, and it's very early in sorted string.printable table.
85 # usernames, and it's very early in sorted string.printable table.
86 PERMISSION_TYPE_SORT = {
86 PERMISSION_TYPE_SORT = {
87 'admin': '####',
87 'admin': '####',
88 'write': '###',
88 'write': '###',
89 'read': '##',
89 'read': '##',
90 'none': '#',
90 'none': '#',
91 }
91 }
92
92
93
93
94 def display_user_sort(obj):
94 def display_user_sort(obj):
95 """
95 """
96 Sort function used to sort permissions in .permissions() function of
96 Sort function used to sort permissions in .permissions() function of
97 Repository, RepoGroup, UserGroup. Also it put the default user in front
97 Repository, RepoGroup, UserGroup. Also it put the default user in front
98 of all other resources
98 of all other resources
99 """
99 """
100
100
101 if obj.username == User.DEFAULT_USER:
101 if obj.username == User.DEFAULT_USER:
102 return '#####'
102 return '#####'
103 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
103 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
104 return prefix + obj.username
104 return prefix + obj.username
105
105
106
106
107 def display_user_group_sort(obj):
107 def display_user_group_sort(obj):
108 """
108 """
109 Sort function used to sort permissions in .permissions() function of
109 Sort function used to sort permissions in .permissions() function of
110 Repository, RepoGroup, UserGroup. Also it put the default user in front
110 Repository, RepoGroup, UserGroup. Also it put the default user in front
111 of all other resources
111 of all other resources
112 """
112 """
113
113
114 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
114 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
115 return prefix + obj.users_group_name
115 return prefix + obj.users_group_name
116
116
117
117
118 def _hash_key(k):
118 def _hash_key(k):
119 return sha1_safe(k)
119 return sha1_safe(k)
120
120
121
121
122 def in_filter_generator(qry, items, limit=500):
122 def in_filter_generator(qry, items, limit=500):
123 """
123 """
124 Splits IN() into multiple with OR
124 Splits IN() into multiple with OR
125 e.g.::
125 e.g.::
126 cnt = Repository.query().filter(
126 cnt = Repository.query().filter(
127 or_(
127 or_(
128 *in_filter_generator(Repository.repo_id, range(100000))
128 *in_filter_generator(Repository.repo_id, range(100000))
129 )).count()
129 )).count()
130 """
130 """
131 if not items:
131 if not items:
132 # empty list will cause empty query which might cause security issues
132 # empty list will cause empty query which might cause security issues
133 # this can lead to hidden unpleasant results
133 # this can lead to hidden unpleasant results
134 items = [-1]
134 items = [-1]
135
135
136 parts = []
136 parts = []
137 for chunk in xrange(0, len(items), limit):
137 for chunk in xrange(0, len(items), limit):
138 parts.append(
138 parts.append(
139 qry.in_(items[chunk: chunk + limit])
139 qry.in_(items[chunk: chunk + limit])
140 )
140 )
141
141
142 return parts
142 return parts
143
143
144
144
145 base_table_args = {
145 base_table_args = {
146 'extend_existing': True,
146 'extend_existing': True,
147 'mysql_engine': 'InnoDB',
147 'mysql_engine': 'InnoDB',
148 'mysql_charset': 'utf8',
148 'mysql_charset': 'utf8',
149 'sqlite_autoincrement': True
149 'sqlite_autoincrement': True
150 }
150 }
151
151
152
152
153 class EncryptedTextValue(TypeDecorator):
153 class EncryptedTextValue(TypeDecorator):
154 """
154 """
155 Special column for encrypted long text data, use like::
155 Special column for encrypted long text data, use like::
156
156
157 value = Column("encrypted_value", EncryptedValue(), nullable=False)
157 value = Column("encrypted_value", EncryptedValue(), nullable=False)
158
158
159 This column is intelligent so if value is in unencrypted form it return
159 This column is intelligent so if value is in unencrypted form it return
160 unencrypted form, but on save it always encrypts
160 unencrypted form, but on save it always encrypts
161 """
161 """
162 impl = Text
162 impl = Text
163
163
164 def process_bind_param(self, value, dialect):
164 def process_bind_param(self, value, dialect):
165 """
165 """
166 Setter for storing value
166 Setter for storing value
167 """
167 """
168 import rhodecode
168 import rhodecode
169 if not value:
169 if not value:
170 return value
170 return value
171
171
172 # protect against double encrypting if values is already encrypted
172 # protect against double encrypting if values is already encrypted
173 if value.startswith('enc$aes$') \
173 if value.startswith('enc$aes$') \
174 or value.startswith('enc$aes_hmac$') \
174 or value.startswith('enc$aes_hmac$') \
175 or value.startswith('enc2$'):
175 or value.startswith('enc2$'):
176 raise ValueError('value needs to be in unencrypted format, '
176 raise ValueError('value needs to be in unencrypted format, '
177 'ie. not starting with enc$ or enc2$')
177 'ie. not starting with enc$ or enc2$')
178
178
179 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
179 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
180 if algo == 'aes':
180 if algo == 'aes':
181 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
181 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
182 elif algo == 'fernet':
182 elif algo == 'fernet':
183 return Encryptor(ENCRYPTION_KEY).encrypt(value)
183 return Encryptor(ENCRYPTION_KEY).encrypt(value)
184 else:
184 else:
185 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
185 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
186
186
187 def process_result_value(self, value, dialect):
187 def process_result_value(self, value, dialect):
188 """
188 """
189 Getter for retrieving value
189 Getter for retrieving value
190 """
190 """
191
191
192 import rhodecode
192 import rhodecode
193 if not value:
193 if not value:
194 return value
194 return value
195
195
196 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
196 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
197 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
197 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
198 if algo == 'aes':
198 if algo == 'aes':
199 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
199 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
200 elif algo == 'fernet':
200 elif algo == 'fernet':
201 return Encryptor(ENCRYPTION_KEY).decrypt(value)
201 return Encryptor(ENCRYPTION_KEY).decrypt(value)
202 else:
202 else:
203 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
203 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
204 return decrypted_data
204 return decrypted_data
205
205
206
206
207 class BaseModel(object):
207 class BaseModel(object):
208 """
208 """
209 Base Model for all classes
209 Base Model for all classes
210 """
210 """
211
211
212 @classmethod
212 @classmethod
213 def _get_keys(cls):
213 def _get_keys(cls):
214 """return column names for this model """
214 """return column names for this model """
215 return class_mapper(cls).c.keys()
215 return class_mapper(cls).c.keys()
216
216
217 def get_dict(self):
217 def get_dict(self):
218 """
218 """
219 return dict with keys and values corresponding
219 return dict with keys and values corresponding
220 to this model data """
220 to this model data """
221
221
222 d = {}
222 d = {}
223 for k in self._get_keys():
223 for k in self._get_keys():
224 d[k] = getattr(self, k)
224 d[k] = getattr(self, k)
225
225
226 # also use __json__() if present to get additional fields
226 # also use __json__() if present to get additional fields
227 _json_attr = getattr(self, '__json__', None)
227 _json_attr = getattr(self, '__json__', None)
228 if _json_attr:
228 if _json_attr:
229 # update with attributes from __json__
229 # update with attributes from __json__
230 if callable(_json_attr):
230 if callable(_json_attr):
231 _json_attr = _json_attr()
231 _json_attr = _json_attr()
232 for k, val in _json_attr.iteritems():
232 for k, val in _json_attr.iteritems():
233 d[k] = val
233 d[k] = val
234 return d
234 return d
235
235
236 def get_appstruct(self):
236 def get_appstruct(self):
237 """return list with keys and values tuples corresponding
237 """return list with keys and values tuples corresponding
238 to this model data """
238 to this model data """
239
239
240 lst = []
240 lst = []
241 for k in self._get_keys():
241 for k in self._get_keys():
242 lst.append((k, getattr(self, k),))
242 lst.append((k, getattr(self, k),))
243 return lst
243 return lst
244
244
245 def populate_obj(self, populate_dict):
245 def populate_obj(self, populate_dict):
246 """populate model with data from given populate_dict"""
246 """populate model with data from given populate_dict"""
247
247
248 for k in self._get_keys():
248 for k in self._get_keys():
249 if k in populate_dict:
249 if k in populate_dict:
250 setattr(self, k, populate_dict[k])
250 setattr(self, k, populate_dict[k])
251
251
252 @classmethod
252 @classmethod
253 def query(cls):
253 def query(cls):
254 return Session().query(cls)
254 return Session().query(cls)
255
255
256 @classmethod
256 @classmethod
257 def get(cls, id_):
257 def get(cls, id_):
258 if id_:
258 if id_:
259 return cls.query().get(id_)
259 return cls.query().get(id_)
260
260
261 @classmethod
261 @classmethod
262 def get_or_404(cls, id_):
262 def get_or_404(cls, id_):
263 from pyramid.httpexceptions import HTTPNotFound
263 from pyramid.httpexceptions import HTTPNotFound
264
264
265 try:
265 try:
266 id_ = int(id_)
266 id_ = int(id_)
267 except (TypeError, ValueError):
267 except (TypeError, ValueError):
268 raise HTTPNotFound()
268 raise HTTPNotFound()
269
269
270 res = cls.query().get(id_)
270 res = cls.query().get(id_)
271 if not res:
271 if not res:
272 raise HTTPNotFound()
272 raise HTTPNotFound()
273 return res
273 return res
274
274
275 @classmethod
275 @classmethod
276 def getAll(cls):
276 def getAll(cls):
277 # deprecated and left for backward compatibility
277 # deprecated and left for backward compatibility
278 return cls.get_all()
278 return cls.get_all()
279
279
280 @classmethod
280 @classmethod
281 def get_all(cls):
281 def get_all(cls):
282 return cls.query().all()
282 return cls.query().all()
283
283
284 @classmethod
284 @classmethod
285 def delete(cls, id_):
285 def delete(cls, id_):
286 obj = cls.query().get(id_)
286 obj = cls.query().get(id_)
287 Session().delete(obj)
287 Session().delete(obj)
288
288
289 @classmethod
289 @classmethod
290 def identity_cache(cls, session, attr_name, value):
290 def identity_cache(cls, session, attr_name, value):
291 exist_in_session = []
291 exist_in_session = []
292 for (item_cls, pkey), instance in session.identity_map.items():
292 for (item_cls, pkey), instance in session.identity_map.items():
293 if cls == item_cls and getattr(instance, attr_name) == value:
293 if cls == item_cls and getattr(instance, attr_name) == value:
294 exist_in_session.append(instance)
294 exist_in_session.append(instance)
295 if exist_in_session:
295 if exist_in_session:
296 if len(exist_in_session) == 1:
296 if len(exist_in_session) == 1:
297 return exist_in_session[0]
297 return exist_in_session[0]
298 log.exception(
298 log.exception(
299 'multiple objects with attr %s and '
299 'multiple objects with attr %s and '
300 'value %s found with same name: %r',
300 'value %s found with same name: %r',
301 attr_name, value, exist_in_session)
301 attr_name, value, exist_in_session)
302
302
303 def __repr__(self):
303 def __repr__(self):
304 if hasattr(self, '__unicode__'):
304 if hasattr(self, '__unicode__'):
305 # python repr needs to return str
305 # python repr needs to return str
306 try:
306 try:
307 return safe_str(self.__unicode__())
307 return safe_str(self.__unicode__())
308 except UnicodeDecodeError:
308 except UnicodeDecodeError:
309 pass
309 pass
310 return '<DB:%s>' % (self.__class__.__name__)
310 return '<DB:%s>' % (self.__class__.__name__)
311
311
312
312
313 class RhodeCodeSetting(Base, BaseModel):
313 class RhodeCodeSetting(Base, BaseModel):
314 __tablename__ = 'rhodecode_settings'
314 __tablename__ = 'rhodecode_settings'
315 __table_args__ = (
315 __table_args__ = (
316 UniqueConstraint('app_settings_name'),
316 UniqueConstraint('app_settings_name'),
317 base_table_args
317 base_table_args
318 )
318 )
319
319
320 SETTINGS_TYPES = {
320 SETTINGS_TYPES = {
321 'str': safe_str,
321 'str': safe_str,
322 'int': safe_int,
322 'int': safe_int,
323 'unicode': safe_unicode,
323 'unicode': safe_unicode,
324 'bool': str2bool,
324 'bool': str2bool,
325 'list': functools.partial(aslist, sep=',')
325 'list': functools.partial(aslist, sep=',')
326 }
326 }
327 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
327 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
328 GLOBAL_CONF_KEY = 'app_settings'
328 GLOBAL_CONF_KEY = 'app_settings'
329
329
330 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
330 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
331 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
331 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
332 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
332 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
333 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
333 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
334
334
335 def __init__(self, key='', val='', type='unicode'):
335 def __init__(self, key='', val='', type='unicode'):
336 self.app_settings_name = key
336 self.app_settings_name = key
337 self.app_settings_type = type
337 self.app_settings_type = type
338 self.app_settings_value = val
338 self.app_settings_value = val
339
339
340 @validates('_app_settings_value')
340 @validates('_app_settings_value')
341 def validate_settings_value(self, key, val):
341 def validate_settings_value(self, key, val):
342 assert type(val) == unicode
342 assert type(val) == unicode
343 return val
343 return val
344
344
345 @hybrid_property
345 @hybrid_property
346 def app_settings_value(self):
346 def app_settings_value(self):
347 v = self._app_settings_value
347 v = self._app_settings_value
348 _type = self.app_settings_type
348 _type = self.app_settings_type
349 if _type:
349 if _type:
350 _type = self.app_settings_type.split('.')[0]
350 _type = self.app_settings_type.split('.')[0]
351 # decode the encrypted value
351 # decode the encrypted value
352 if 'encrypted' in self.app_settings_type:
352 if 'encrypted' in self.app_settings_type:
353 cipher = EncryptedTextValue()
353 cipher = EncryptedTextValue()
354 v = safe_unicode(cipher.process_result_value(v, None))
354 v = safe_unicode(cipher.process_result_value(v, None))
355
355
356 converter = self.SETTINGS_TYPES.get(_type) or \
356 converter = self.SETTINGS_TYPES.get(_type) or \
357 self.SETTINGS_TYPES['unicode']
357 self.SETTINGS_TYPES['unicode']
358 return converter(v)
358 return converter(v)
359
359
360 @app_settings_value.setter
360 @app_settings_value.setter
361 def app_settings_value(self, val):
361 def app_settings_value(self, val):
362 """
362 """
363 Setter that will always make sure we use unicode in app_settings_value
363 Setter that will always make sure we use unicode in app_settings_value
364
364
365 :param val:
365 :param val:
366 """
366 """
367 val = safe_unicode(val)
367 val = safe_unicode(val)
368 # encode the encrypted value
368 # encode the encrypted value
369 if 'encrypted' in self.app_settings_type:
369 if 'encrypted' in self.app_settings_type:
370 cipher = EncryptedTextValue()
370 cipher = EncryptedTextValue()
371 val = safe_unicode(cipher.process_bind_param(val, None))
371 val = safe_unicode(cipher.process_bind_param(val, None))
372 self._app_settings_value = val
372 self._app_settings_value = val
373
373
374 @hybrid_property
374 @hybrid_property
375 def app_settings_type(self):
375 def app_settings_type(self):
376 return self._app_settings_type
376 return self._app_settings_type
377
377
378 @app_settings_type.setter
378 @app_settings_type.setter
379 def app_settings_type(self, val):
379 def app_settings_type(self, val):
380 if val.split('.')[0] not in self.SETTINGS_TYPES:
380 if val.split('.')[0] not in self.SETTINGS_TYPES:
381 raise Exception('type must be one of %s got %s'
381 raise Exception('type must be one of %s got %s'
382 % (self.SETTINGS_TYPES.keys(), val))
382 % (self.SETTINGS_TYPES.keys(), val))
383 self._app_settings_type = val
383 self._app_settings_type = val
384
384
385 @classmethod
385 @classmethod
386 def get_by_prefix(cls, prefix):
386 def get_by_prefix(cls, prefix):
387 return RhodeCodeSetting.query()\
387 return RhodeCodeSetting.query()\
388 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
388 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
389 .all()
389 .all()
390
390
391 def __unicode__(self):
391 def __unicode__(self):
392 return u"<%s('%s:%s[%s]')>" % (
392 return u"<%s('%s:%s[%s]')>" % (
393 self.__class__.__name__,
393 self.__class__.__name__,
394 self.app_settings_name, self.app_settings_value,
394 self.app_settings_name, self.app_settings_value,
395 self.app_settings_type
395 self.app_settings_type
396 )
396 )
397
397
398
398
399 class RhodeCodeUi(Base, BaseModel):
399 class RhodeCodeUi(Base, BaseModel):
400 __tablename__ = 'rhodecode_ui'
400 __tablename__ = 'rhodecode_ui'
401 __table_args__ = (
401 __table_args__ = (
402 UniqueConstraint('ui_key'),
402 UniqueConstraint('ui_key'),
403 base_table_args
403 base_table_args
404 )
404 )
405
405
406 HOOK_REPO_SIZE = 'changegroup.repo_size'
406 HOOK_REPO_SIZE = 'changegroup.repo_size'
407 # HG
407 # HG
408 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
408 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
409 HOOK_PULL = 'outgoing.pull_logger'
409 HOOK_PULL = 'outgoing.pull_logger'
410 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
410 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
411 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
411 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
412 HOOK_PUSH = 'changegroup.push_logger'
412 HOOK_PUSH = 'changegroup.push_logger'
413 HOOK_PUSH_KEY = 'pushkey.key_push'
413 HOOK_PUSH_KEY = 'pushkey.key_push'
414
414
415 HOOKS_BUILTIN = [
415 HOOKS_BUILTIN = [
416 HOOK_PRE_PULL,
416 HOOK_PRE_PULL,
417 HOOK_PULL,
417 HOOK_PULL,
418 HOOK_PRE_PUSH,
418 HOOK_PRE_PUSH,
419 HOOK_PRETX_PUSH,
419 HOOK_PRETX_PUSH,
420 HOOK_PUSH,
420 HOOK_PUSH,
421 HOOK_PUSH_KEY,
421 HOOK_PUSH_KEY,
422 ]
422 ]
423
423
424 # TODO: johbo: Unify way how hooks are configured for git and hg,
424 # TODO: johbo: Unify way how hooks are configured for git and hg,
425 # git part is currently hardcoded.
425 # git part is currently hardcoded.
426
426
427 # SVN PATTERNS
427 # SVN PATTERNS
428 SVN_BRANCH_ID = 'vcs_svn_branch'
428 SVN_BRANCH_ID = 'vcs_svn_branch'
429 SVN_TAG_ID = 'vcs_svn_tag'
429 SVN_TAG_ID = 'vcs_svn_tag'
430
430
431 ui_id = Column(
431 ui_id = Column(
432 "ui_id", Integer(), nullable=False, unique=True, default=None,
432 "ui_id", Integer(), nullable=False, unique=True, default=None,
433 primary_key=True)
433 primary_key=True)
434 ui_section = Column(
434 ui_section = Column(
435 "ui_section", String(255), nullable=True, unique=None, default=None)
435 "ui_section", String(255), nullable=True, unique=None, default=None)
436 ui_key = Column(
436 ui_key = Column(
437 "ui_key", String(255), nullable=True, unique=None, default=None)
437 "ui_key", String(255), nullable=True, unique=None, default=None)
438 ui_value = Column(
438 ui_value = Column(
439 "ui_value", String(255), nullable=True, unique=None, default=None)
439 "ui_value", String(255), nullable=True, unique=None, default=None)
440 ui_active = Column(
440 ui_active = Column(
441 "ui_active", Boolean(), nullable=True, unique=None, default=True)
441 "ui_active", Boolean(), nullable=True, unique=None, default=True)
442
442
443 def __repr__(self):
443 def __repr__(self):
444 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
444 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
445 self.ui_key, self.ui_value)
445 self.ui_key, self.ui_value)
446
446
447
447
448 class RepoRhodeCodeSetting(Base, BaseModel):
448 class RepoRhodeCodeSetting(Base, BaseModel):
449 __tablename__ = 'repo_rhodecode_settings'
449 __tablename__ = 'repo_rhodecode_settings'
450 __table_args__ = (
450 __table_args__ = (
451 UniqueConstraint(
451 UniqueConstraint(
452 'app_settings_name', 'repository_id',
452 'app_settings_name', 'repository_id',
453 name='uq_repo_rhodecode_setting_name_repo_id'),
453 name='uq_repo_rhodecode_setting_name_repo_id'),
454 base_table_args
454 base_table_args
455 )
455 )
456
456
457 repository_id = Column(
457 repository_id = Column(
458 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
458 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
459 nullable=False)
459 nullable=False)
460 app_settings_id = Column(
460 app_settings_id = Column(
461 "app_settings_id", Integer(), nullable=False, unique=True,
461 "app_settings_id", Integer(), nullable=False, unique=True,
462 default=None, primary_key=True)
462 default=None, primary_key=True)
463 app_settings_name = Column(
463 app_settings_name = Column(
464 "app_settings_name", String(255), nullable=True, unique=None,
464 "app_settings_name", String(255), nullable=True, unique=None,
465 default=None)
465 default=None)
466 _app_settings_value = Column(
466 _app_settings_value = Column(
467 "app_settings_value", String(4096), nullable=True, unique=None,
467 "app_settings_value", String(4096), nullable=True, unique=None,
468 default=None)
468 default=None)
469 _app_settings_type = Column(
469 _app_settings_type = Column(
470 "app_settings_type", String(255), nullable=True, unique=None,
470 "app_settings_type", String(255), nullable=True, unique=None,
471 default=None)
471 default=None)
472
472
473 repository = relationship('Repository')
473 repository = relationship('Repository')
474
474
475 def __init__(self, repository_id, key='', val='', type='unicode'):
475 def __init__(self, repository_id, key='', val='', type='unicode'):
476 self.repository_id = repository_id
476 self.repository_id = repository_id
477 self.app_settings_name = key
477 self.app_settings_name = key
478 self.app_settings_type = type
478 self.app_settings_type = type
479 self.app_settings_value = val
479 self.app_settings_value = val
480
480
481 @validates('_app_settings_value')
481 @validates('_app_settings_value')
482 def validate_settings_value(self, key, val):
482 def validate_settings_value(self, key, val):
483 assert type(val) == unicode
483 assert type(val) == unicode
484 return val
484 return val
485
485
486 @hybrid_property
486 @hybrid_property
487 def app_settings_value(self):
487 def app_settings_value(self):
488 v = self._app_settings_value
488 v = self._app_settings_value
489 type_ = self.app_settings_type
489 type_ = self.app_settings_type
490 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
490 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
491 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
491 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
492 return converter(v)
492 return converter(v)
493
493
494 @app_settings_value.setter
494 @app_settings_value.setter
495 def app_settings_value(self, val):
495 def app_settings_value(self, val):
496 """
496 """
497 Setter that will always make sure we use unicode in app_settings_value
497 Setter that will always make sure we use unicode in app_settings_value
498
498
499 :param val:
499 :param val:
500 """
500 """
501 self._app_settings_value = safe_unicode(val)
501 self._app_settings_value = safe_unicode(val)
502
502
503 @hybrid_property
503 @hybrid_property
504 def app_settings_type(self):
504 def app_settings_type(self):
505 return self._app_settings_type
505 return self._app_settings_type
506
506
507 @app_settings_type.setter
507 @app_settings_type.setter
508 def app_settings_type(self, val):
508 def app_settings_type(self, val):
509 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
509 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
510 if val not in SETTINGS_TYPES:
510 if val not in SETTINGS_TYPES:
511 raise Exception('type must be one of %s got %s'
511 raise Exception('type must be one of %s got %s'
512 % (SETTINGS_TYPES.keys(), val))
512 % (SETTINGS_TYPES.keys(), val))
513 self._app_settings_type = val
513 self._app_settings_type = val
514
514
515 def __unicode__(self):
515 def __unicode__(self):
516 return u"<%s('%s:%s:%s[%s]')>" % (
516 return u"<%s('%s:%s:%s[%s]')>" % (
517 self.__class__.__name__, self.repository.repo_name,
517 self.__class__.__name__, self.repository.repo_name,
518 self.app_settings_name, self.app_settings_value,
518 self.app_settings_name, self.app_settings_value,
519 self.app_settings_type
519 self.app_settings_type
520 )
520 )
521
521
522
522
523 class RepoRhodeCodeUi(Base, BaseModel):
523 class RepoRhodeCodeUi(Base, BaseModel):
524 __tablename__ = 'repo_rhodecode_ui'
524 __tablename__ = 'repo_rhodecode_ui'
525 __table_args__ = (
525 __table_args__ = (
526 UniqueConstraint(
526 UniqueConstraint(
527 'repository_id', 'ui_section', 'ui_key',
527 'repository_id', 'ui_section', 'ui_key',
528 name='uq_repo_rhodecode_ui_repository_id_section_key'),
528 name='uq_repo_rhodecode_ui_repository_id_section_key'),
529 base_table_args
529 base_table_args
530 )
530 )
531
531
532 repository_id = Column(
532 repository_id = Column(
533 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
533 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
534 nullable=False)
534 nullable=False)
535 ui_id = Column(
535 ui_id = Column(
536 "ui_id", Integer(), nullable=False, unique=True, default=None,
536 "ui_id", Integer(), nullable=False, unique=True, default=None,
537 primary_key=True)
537 primary_key=True)
538 ui_section = Column(
538 ui_section = Column(
539 "ui_section", String(255), nullable=True, unique=None, default=None)
539 "ui_section", String(255), nullable=True, unique=None, default=None)
540 ui_key = Column(
540 ui_key = Column(
541 "ui_key", String(255), nullable=True, unique=None, default=None)
541 "ui_key", String(255), nullable=True, unique=None, default=None)
542 ui_value = Column(
542 ui_value = Column(
543 "ui_value", String(255), nullable=True, unique=None, default=None)
543 "ui_value", String(255), nullable=True, unique=None, default=None)
544 ui_active = Column(
544 ui_active = Column(
545 "ui_active", Boolean(), nullable=True, unique=None, default=True)
545 "ui_active", Boolean(), nullable=True, unique=None, default=True)
546
546
547 repository = relationship('Repository')
547 repository = relationship('Repository')
548
548
549 def __repr__(self):
549 def __repr__(self):
550 return '<%s[%s:%s]%s=>%s]>' % (
550 return '<%s[%s:%s]%s=>%s]>' % (
551 self.__class__.__name__, self.repository.repo_name,
551 self.__class__.__name__, self.repository.repo_name,
552 self.ui_section, self.ui_key, self.ui_value)
552 self.ui_section, self.ui_key, self.ui_value)
553
553
554
554
555 class User(Base, BaseModel):
555 class User(Base, BaseModel):
556 __tablename__ = 'users'
556 __tablename__ = 'users'
557 __table_args__ = (
557 __table_args__ = (
558 UniqueConstraint('username'), UniqueConstraint('email'),
558 UniqueConstraint('username'), UniqueConstraint('email'),
559 Index('u_username_idx', 'username'),
559 Index('u_username_idx', 'username'),
560 Index('u_email_idx', 'email'),
560 Index('u_email_idx', 'email'),
561 base_table_args
561 base_table_args
562 )
562 )
563
563
564 DEFAULT_USER = 'default'
564 DEFAULT_USER = 'default'
565 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
565 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
566 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
566 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
567
567
568 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
568 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
569 username = Column("username", String(255), nullable=True, unique=None, default=None)
569 username = Column("username", String(255), nullable=True, unique=None, default=None)
570 password = Column("password", String(255), nullable=True, unique=None, default=None)
570 password = Column("password", String(255), nullable=True, unique=None, default=None)
571 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
571 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
572 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
572 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
573 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
573 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
574 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
574 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
575 _email = Column("email", String(255), nullable=True, unique=None, default=None)
575 _email = Column("email", String(255), nullable=True, unique=None, default=None)
576 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
576 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
577 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
577 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
578
578
579 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
579 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
580 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
580 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
581 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
581 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
582 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
582 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
583 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
583 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
584 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
584 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
585
585
586 user_log = relationship('UserLog')
586 user_log = relationship('UserLog')
587 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
587 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
588
588
589 repositories = relationship('Repository')
589 repositories = relationship('Repository')
590 repository_groups = relationship('RepoGroup')
590 repository_groups = relationship('RepoGroup')
591 user_groups = relationship('UserGroup')
591 user_groups = relationship('UserGroup')
592
592
593 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
593 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
594 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
594 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
595
595
596 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
596 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
597 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
597 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
598 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
598 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
599
599
600 group_member = relationship('UserGroupMember', cascade='all')
600 group_member = relationship('UserGroupMember', cascade='all')
601
601
602 notifications = relationship('UserNotification', cascade='all')
602 notifications = relationship('UserNotification', cascade='all')
603 # notifications assigned to this user
603 # notifications assigned to this user
604 user_created_notifications = relationship('Notification', cascade='all')
604 user_created_notifications = relationship('Notification', cascade='all')
605 # comments created by this user
605 # comments created by this user
606 user_comments = relationship('ChangesetComment', cascade='all')
606 user_comments = relationship('ChangesetComment', cascade='all')
607 # user profile extra info
607 # user profile extra info
608 user_emails = relationship('UserEmailMap', cascade='all')
608 user_emails = relationship('UserEmailMap', cascade='all')
609 user_ip_map = relationship('UserIpMap', cascade='all')
609 user_ip_map = relationship('UserIpMap', cascade='all')
610 user_auth_tokens = relationship('UserApiKeys', cascade='all')
610 user_auth_tokens = relationship('UserApiKeys', cascade='all')
611 user_ssh_keys = relationship('UserSshKeys', cascade='all')
611 user_ssh_keys = relationship('UserSshKeys', cascade='all')
612
612
613 # gists
613 # gists
614 user_gists = relationship('Gist', cascade='all')
614 user_gists = relationship('Gist', cascade='all')
615 # user pull requests
615 # user pull requests
616 user_pull_requests = relationship('PullRequest', cascade='all')
616 user_pull_requests = relationship('PullRequest', cascade='all')
617 # external identities
617 # external identities
618 extenal_identities = relationship(
618 extenal_identities = relationship(
619 'ExternalIdentity',
619 'ExternalIdentity',
620 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
620 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
621 cascade='all')
621 cascade='all')
622 # review rules
622 # review rules
623 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
623 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
624
624
625 def __unicode__(self):
625 def __unicode__(self):
626 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
626 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
627 self.user_id, self.username)
627 self.user_id, self.username)
628
628
629 @hybrid_property
629 @hybrid_property
630 def email(self):
630 def email(self):
631 return self._email
631 return self._email
632
632
633 @email.setter
633 @email.setter
634 def email(self, val):
634 def email(self, val):
635 self._email = val.lower() if val else None
635 self._email = val.lower() if val else None
636
636
637 @hybrid_property
637 @hybrid_property
638 def first_name(self):
638 def first_name(self):
639 from rhodecode.lib import helpers as h
639 from rhodecode.lib import helpers as h
640 if self.name:
640 if self.name:
641 return h.escape(self.name)
641 return h.escape(self.name)
642 return self.name
642 return self.name
643
643
644 @hybrid_property
644 @hybrid_property
645 def last_name(self):
645 def last_name(self):
646 from rhodecode.lib import helpers as h
646 from rhodecode.lib import helpers as h
647 if self.lastname:
647 if self.lastname:
648 return h.escape(self.lastname)
648 return h.escape(self.lastname)
649 return self.lastname
649 return self.lastname
650
650
651 @hybrid_property
651 @hybrid_property
652 def api_key(self):
652 def api_key(self):
653 """
653 """
654 Fetch if exist an auth-token with role ALL connected to this user
654 Fetch if exist an auth-token with role ALL connected to this user
655 """
655 """
656 user_auth_token = UserApiKeys.query()\
656 user_auth_token = UserApiKeys.query()\
657 .filter(UserApiKeys.user_id == self.user_id)\
657 .filter(UserApiKeys.user_id == self.user_id)\
658 .filter(or_(UserApiKeys.expires == -1,
658 .filter(or_(UserApiKeys.expires == -1,
659 UserApiKeys.expires >= time.time()))\
659 UserApiKeys.expires >= time.time()))\
660 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
660 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
661 if user_auth_token:
661 if user_auth_token:
662 user_auth_token = user_auth_token.api_key
662 user_auth_token = user_auth_token.api_key
663
663
664 return user_auth_token
664 return user_auth_token
665
665
666 @api_key.setter
666 @api_key.setter
667 def api_key(self, val):
667 def api_key(self, val):
668 # don't allow to set API key this is deprecated for now
668 # don't allow to set API key this is deprecated for now
669 self._api_key = None
669 self._api_key = None
670
670
671 @property
671 @property
672 def reviewer_pull_requests(self):
672 def reviewer_pull_requests(self):
673 return PullRequestReviewers.query() \
673 return PullRequestReviewers.query() \
674 .options(joinedload(PullRequestReviewers.pull_request)) \
674 .options(joinedload(PullRequestReviewers.pull_request)) \
675 .filter(PullRequestReviewers.user_id == self.user_id) \
675 .filter(PullRequestReviewers.user_id == self.user_id) \
676 .all()
676 .all()
677
677
678 @property
678 @property
679 def firstname(self):
679 def firstname(self):
680 # alias for future
680 # alias for future
681 return self.name
681 return self.name
682
682
683 @property
683 @property
684 def emails(self):
684 def emails(self):
685 other = UserEmailMap.query()\
685 other = UserEmailMap.query()\
686 .filter(UserEmailMap.user == self) \
686 .filter(UserEmailMap.user == self) \
687 .order_by(UserEmailMap.email_id.asc()) \
687 .order_by(UserEmailMap.email_id.asc()) \
688 .all()
688 .all()
689 return [self.email] + [x.email for x in other]
689 return [self.email] + [x.email for x in other]
690
690
691 @property
691 @property
692 def auth_tokens(self):
692 def auth_tokens(self):
693 auth_tokens = self.get_auth_tokens()
693 auth_tokens = self.get_auth_tokens()
694 return [x.api_key for x in auth_tokens]
694 return [x.api_key for x in auth_tokens]
695
695
696 def get_auth_tokens(self):
696 def get_auth_tokens(self):
697 return UserApiKeys.query()\
697 return UserApiKeys.query()\
698 .filter(UserApiKeys.user == self)\
698 .filter(UserApiKeys.user == self)\
699 .order_by(UserApiKeys.user_api_key_id.asc())\
699 .order_by(UserApiKeys.user_api_key_id.asc())\
700 .all()
700 .all()
701
701
702 @LazyProperty
702 @LazyProperty
703 def feed_token(self):
703 def feed_token(self):
704 return self.get_feed_token()
704 return self.get_feed_token()
705
705
706 def get_feed_token(self, cache=True):
706 def get_feed_token(self, cache=True):
707 feed_tokens = UserApiKeys.query()\
707 feed_tokens = UserApiKeys.query()\
708 .filter(UserApiKeys.user == self)\
708 .filter(UserApiKeys.user == self)\
709 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
709 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
710 if cache:
710 if cache:
711 feed_tokens = feed_tokens.options(
711 feed_tokens = feed_tokens.options(
712 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
712 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
713
713
714 feed_tokens = feed_tokens.all()
714 feed_tokens = feed_tokens.all()
715 if feed_tokens:
715 if feed_tokens:
716 return feed_tokens[0].api_key
716 return feed_tokens[0].api_key
717 return 'NO_FEED_TOKEN_AVAILABLE'
717 return 'NO_FEED_TOKEN_AVAILABLE'
718
718
719 @classmethod
719 @classmethod
720 def get(cls, user_id, cache=False):
720 def get(cls, user_id, cache=False):
721 if not user_id:
721 if not user_id:
722 return
722 return
723
723
724 user = cls.query()
724 user = cls.query()
725 if cache:
725 if cache:
726 user = user.options(
726 user = user.options(
727 FromCache("sql_cache_short", "get_users_%s" % user_id))
727 FromCache("sql_cache_short", "get_users_%s" % user_id))
728 return user.get(user_id)
728 return user.get(user_id)
729
729
730 @classmethod
730 @classmethod
731 def extra_valid_auth_tokens(cls, user, role=None):
731 def extra_valid_auth_tokens(cls, user, role=None):
732 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
732 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
733 .filter(or_(UserApiKeys.expires == -1,
733 .filter(or_(UserApiKeys.expires == -1,
734 UserApiKeys.expires >= time.time()))
734 UserApiKeys.expires >= time.time()))
735 if role:
735 if role:
736 tokens = tokens.filter(or_(UserApiKeys.role == role,
736 tokens = tokens.filter(or_(UserApiKeys.role == role,
737 UserApiKeys.role == UserApiKeys.ROLE_ALL))
737 UserApiKeys.role == UserApiKeys.ROLE_ALL))
738 return tokens.all()
738 return tokens.all()
739
739
740 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
740 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
741 from rhodecode.lib import auth
741 from rhodecode.lib import auth
742
742
743 log.debug('Trying to authenticate user: %s via auth-token, '
743 log.debug('Trying to authenticate user: %s via auth-token, '
744 'and roles: %s', self, roles)
744 'and roles: %s', self, roles)
745
745
746 if not auth_token:
746 if not auth_token:
747 return False
747 return False
748
748
749 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
749 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
750 tokens_q = UserApiKeys.query()\
750 tokens_q = UserApiKeys.query()\
751 .filter(UserApiKeys.user_id == self.user_id)\
751 .filter(UserApiKeys.user_id == self.user_id)\
752 .filter(or_(UserApiKeys.expires == -1,
752 .filter(or_(UserApiKeys.expires == -1,
753 UserApiKeys.expires >= time.time()))
753 UserApiKeys.expires >= time.time()))
754
754
755 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
755 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
756
756
757 crypto_backend = auth.crypto_backend()
757 crypto_backend = auth.crypto_backend()
758 enc_token_map = {}
758 enc_token_map = {}
759 plain_token_map = {}
759 plain_token_map = {}
760 for token in tokens_q:
760 for token in tokens_q:
761 if token.api_key.startswith(crypto_backend.ENC_PREF):
761 if token.api_key.startswith(crypto_backend.ENC_PREF):
762 enc_token_map[token.api_key] = token
762 enc_token_map[token.api_key] = token
763 else:
763 else:
764 plain_token_map[token.api_key] = token
764 plain_token_map[token.api_key] = token
765 log.debug(
765 log.debug(
766 'Found %s plain and %s encrypted user tokens to check for authentication',
766 'Found %s plain and %s encrypted user tokens to check for authentication',
767 len(plain_token_map), len(enc_token_map))
767 len(plain_token_map), len(enc_token_map))
768
768
769 # plain token match comes first
769 # plain token match comes first
770 match = plain_token_map.get(auth_token)
770 match = plain_token_map.get(auth_token)
771
771
772 # check encrypted tokens now
772 # check encrypted tokens now
773 if not match:
773 if not match:
774 for token_hash, token in enc_token_map.items():
774 for token_hash, token in enc_token_map.items():
775 # NOTE(marcink): this is expensive to calculate, but most secure
775 # NOTE(marcink): this is expensive to calculate, but most secure
776 if crypto_backend.hash_check(auth_token, token_hash):
776 if crypto_backend.hash_check(auth_token, token_hash):
777 match = token
777 match = token
778 break
778 break
779
779
780 if match:
780 if match:
781 log.debug('Found matching token %s', match)
781 log.debug('Found matching token %s', match)
782 if match.repo_id:
782 if match.repo_id:
783 log.debug('Found scope, checking for scope match of token %s', match)
783 log.debug('Found scope, checking for scope match of token %s', match)
784 if match.repo_id == scope_repo_id:
784 if match.repo_id == scope_repo_id:
785 return True
785 return True
786 else:
786 else:
787 log.debug(
787 log.debug(
788 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
788 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
789 'and calling scope is:%s, skipping further checks',
789 'and calling scope is:%s, skipping further checks',
790 match.repo, scope_repo_id)
790 match.repo, scope_repo_id)
791 return False
791 return False
792 else:
792 else:
793 return True
793 return True
794
794
795 return False
795 return False
796
796
797 @property
797 @property
798 def ip_addresses(self):
798 def ip_addresses(self):
799 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
799 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
800 return [x.ip_addr for x in ret]
800 return [x.ip_addr for x in ret]
801
801
802 @property
802 @property
803 def username_and_name(self):
803 def username_and_name(self):
804 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
804 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
805
805
806 @property
806 @property
807 def username_or_name_or_email(self):
807 def username_or_name_or_email(self):
808 full_name = self.full_name if self.full_name is not ' ' else None
808 full_name = self.full_name if self.full_name is not ' ' else None
809 return self.username or full_name or self.email
809 return self.username or full_name or self.email
810
810
811 @property
811 @property
812 def full_name(self):
812 def full_name(self):
813 return '%s %s' % (self.first_name, self.last_name)
813 return '%s %s' % (self.first_name, self.last_name)
814
814
815 @property
815 @property
816 def full_name_or_username(self):
816 def full_name_or_username(self):
817 return ('%s %s' % (self.first_name, self.last_name)
817 return ('%s %s' % (self.first_name, self.last_name)
818 if (self.first_name and self.last_name) else self.username)
818 if (self.first_name and self.last_name) else self.username)
819
819
820 @property
820 @property
821 def full_contact(self):
821 def full_contact(self):
822 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
822 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
823
823
824 @property
824 @property
825 def short_contact(self):
825 def short_contact(self):
826 return '%s %s' % (self.first_name, self.last_name)
826 return '%s %s' % (self.first_name, self.last_name)
827
827
828 @property
828 @property
829 def is_admin(self):
829 def is_admin(self):
830 return self.admin
830 return self.admin
831
831
832 def AuthUser(self, **kwargs):
832 def AuthUser(self, **kwargs):
833 """
833 """
834 Returns instance of AuthUser for this user
834 Returns instance of AuthUser for this user
835 """
835 """
836 from rhodecode.lib.auth import AuthUser
836 from rhodecode.lib.auth import AuthUser
837 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
837 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
838
838
839 @hybrid_property
839 @hybrid_property
840 def user_data(self):
840 def user_data(self):
841 if not self._user_data:
841 if not self._user_data:
842 return {}
842 return {}
843
843
844 try:
844 try:
845 return json.loads(self._user_data)
845 return json.loads(self._user_data)
846 except TypeError:
846 except TypeError:
847 return {}
847 return {}
848
848
849 @user_data.setter
849 @user_data.setter
850 def user_data(self, val):
850 def user_data(self, val):
851 if not isinstance(val, dict):
851 if not isinstance(val, dict):
852 raise Exception('user_data must be dict, got %s' % type(val))
852 raise Exception('user_data must be dict, got %s' % type(val))
853 try:
853 try:
854 self._user_data = json.dumps(val)
854 self._user_data = json.dumps(val)
855 except Exception:
855 except Exception:
856 log.error(traceback.format_exc())
856 log.error(traceback.format_exc())
857
857
858 @classmethod
858 @classmethod
859 def get_by_username(cls, username, case_insensitive=False,
859 def get_by_username(cls, username, case_insensitive=False,
860 cache=False, identity_cache=False):
860 cache=False, identity_cache=False):
861 session = Session()
861 session = Session()
862
862
863 if case_insensitive:
863 if case_insensitive:
864 q = cls.query().filter(
864 q = cls.query().filter(
865 func.lower(cls.username) == func.lower(username))
865 func.lower(cls.username) == func.lower(username))
866 else:
866 else:
867 q = cls.query().filter(cls.username == username)
867 q = cls.query().filter(cls.username == username)
868
868
869 if cache:
869 if cache:
870 if identity_cache:
870 if identity_cache:
871 val = cls.identity_cache(session, 'username', username)
871 val = cls.identity_cache(session, 'username', username)
872 if val:
872 if val:
873 return val
873 return val
874 else:
874 else:
875 cache_key = "get_user_by_name_%s" % _hash_key(username)
875 cache_key = "get_user_by_name_%s" % _hash_key(username)
876 q = q.options(
876 q = q.options(
877 FromCache("sql_cache_short", cache_key))
877 FromCache("sql_cache_short", cache_key))
878
878
879 return q.scalar()
879 return q.scalar()
880
880
881 @classmethod
881 @classmethod
882 def get_by_auth_token(cls, auth_token, cache=False):
882 def get_by_auth_token(cls, auth_token, cache=False):
883 q = UserApiKeys.query()\
883 q = UserApiKeys.query()\
884 .filter(UserApiKeys.api_key == auth_token)\
884 .filter(UserApiKeys.api_key == auth_token)\
885 .filter(or_(UserApiKeys.expires == -1,
885 .filter(or_(UserApiKeys.expires == -1,
886 UserApiKeys.expires >= time.time()))
886 UserApiKeys.expires >= time.time()))
887 if cache:
887 if cache:
888 q = q.options(
888 q = q.options(
889 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
889 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
890
890
891 match = q.first()
891 match = q.first()
892 if match:
892 if match:
893 return match.user
893 return match.user
894
894
895 @classmethod
895 @classmethod
896 def get_by_email(cls, email, case_insensitive=False, cache=False):
896 def get_by_email(cls, email, case_insensitive=False, cache=False):
897
897
898 if case_insensitive:
898 if case_insensitive:
899 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
899 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
900
900
901 else:
901 else:
902 q = cls.query().filter(cls.email == email)
902 q = cls.query().filter(cls.email == email)
903
903
904 email_key = _hash_key(email)
904 email_key = _hash_key(email)
905 if cache:
905 if cache:
906 q = q.options(
906 q = q.options(
907 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
907 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
908
908
909 ret = q.scalar()
909 ret = q.scalar()
910 if ret is None:
910 if ret is None:
911 q = UserEmailMap.query()
911 q = UserEmailMap.query()
912 # try fetching in alternate email map
912 # try fetching in alternate email map
913 if case_insensitive:
913 if case_insensitive:
914 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
914 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
915 else:
915 else:
916 q = q.filter(UserEmailMap.email == email)
916 q = q.filter(UserEmailMap.email == email)
917 q = q.options(joinedload(UserEmailMap.user))
917 q = q.options(joinedload(UserEmailMap.user))
918 if cache:
918 if cache:
919 q = q.options(
919 q = q.options(
920 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
920 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
921 ret = getattr(q.scalar(), 'user', None)
921 ret = getattr(q.scalar(), 'user', None)
922
922
923 return ret
923 return ret
924
924
925 @classmethod
925 @classmethod
926 def get_from_cs_author(cls, author):
926 def get_from_cs_author(cls, author):
927 """
927 """
928 Tries to get User objects out of commit author string
928 Tries to get User objects out of commit author string
929
929
930 :param author:
930 :param author:
931 """
931 """
932 from rhodecode.lib.helpers import email, author_name
932 from rhodecode.lib.helpers import email, author_name
933 # Valid email in the attribute passed, see if they're in the system
933 # Valid email in the attribute passed, see if they're in the system
934 _email = email(author)
934 _email = email(author)
935 if _email:
935 if _email:
936 user = cls.get_by_email(_email, case_insensitive=True)
936 user = cls.get_by_email(_email, case_insensitive=True)
937 if user:
937 if user:
938 return user
938 return user
939 # Maybe we can match by username?
939 # Maybe we can match by username?
940 _author = author_name(author)
940 _author = author_name(author)
941 user = cls.get_by_username(_author, case_insensitive=True)
941 user = cls.get_by_username(_author, case_insensitive=True)
942 if user:
942 if user:
943 return user
943 return user
944
944
945 def update_userdata(self, **kwargs):
945 def update_userdata(self, **kwargs):
946 usr = self
946 usr = self
947 old = usr.user_data
947 old = usr.user_data
948 old.update(**kwargs)
948 old.update(**kwargs)
949 usr.user_data = old
949 usr.user_data = old
950 Session().add(usr)
950 Session().add(usr)
951 log.debug('updated userdata with %s', kwargs)
951 log.debug('updated userdata with %s', kwargs)
952
952
953 def update_lastlogin(self):
953 def update_lastlogin(self):
954 """Update user lastlogin"""
954 """Update user lastlogin"""
955 self.last_login = datetime.datetime.now()
955 self.last_login = datetime.datetime.now()
956 Session().add(self)
956 Session().add(self)
957 log.debug('updated user %s lastlogin', self.username)
957 log.debug('updated user %s lastlogin', self.username)
958
958
959 def update_password(self, new_password):
959 def update_password(self, new_password):
960 from rhodecode.lib.auth import get_crypt_password
960 from rhodecode.lib.auth import get_crypt_password
961
961
962 self.password = get_crypt_password(new_password)
962 self.password = get_crypt_password(new_password)
963 Session().add(self)
963 Session().add(self)
964
964
965 @classmethod
965 @classmethod
966 def get_first_super_admin(cls):
966 def get_first_super_admin(cls):
967 user = User.query()\
967 user = User.query()\
968 .filter(User.admin == true()) \
968 .filter(User.admin == true()) \
969 .order_by(User.user_id.asc()) \
969 .order_by(User.user_id.asc()) \
970 .first()
970 .first()
971
971
972 if user is None:
972 if user is None:
973 raise Exception('FATAL: Missing administrative account!')
973 raise Exception('FATAL: Missing administrative account!')
974 return user
974 return user
975
975
976 @classmethod
976 @classmethod
977 def get_all_super_admins(cls, only_active=False):
977 def get_all_super_admins(cls, only_active=False):
978 """
978 """
979 Returns all admin accounts sorted by username
979 Returns all admin accounts sorted by username
980 """
980 """
981 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
981 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
982 if only_active:
982 if only_active:
983 qry = qry.filter(User.active == true())
983 qry = qry.filter(User.active == true())
984 return qry.all()
984 return qry.all()
985
985
986 @classmethod
986 @classmethod
987 def get_default_user(cls, cache=False, refresh=False):
987 def get_default_user(cls, cache=False, refresh=False):
988 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
988 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
989 if user is None:
989 if user is None:
990 raise Exception('FATAL: Missing default account!')
990 raise Exception('FATAL: Missing default account!')
991 if refresh:
991 if refresh:
992 # The default user might be based on outdated state which
992 # The default user might be based on outdated state which
993 # has been loaded from the cache.
993 # has been loaded from the cache.
994 # A call to refresh() ensures that the
994 # A call to refresh() ensures that the
995 # latest state from the database is used.
995 # latest state from the database is used.
996 Session().refresh(user)
996 Session().refresh(user)
997 return user
997 return user
998
998
999 def _get_default_perms(self, user, suffix=''):
999 def _get_default_perms(self, user, suffix=''):
1000 from rhodecode.model.permission import PermissionModel
1000 from rhodecode.model.permission import PermissionModel
1001 return PermissionModel().get_default_perms(user.user_perms, suffix)
1001 return PermissionModel().get_default_perms(user.user_perms, suffix)
1002
1002
1003 def get_default_perms(self, suffix=''):
1003 def get_default_perms(self, suffix=''):
1004 return self._get_default_perms(self, suffix)
1004 return self._get_default_perms(self, suffix)
1005
1005
1006 def get_api_data(self, include_secrets=False, details='full'):
1006 def get_api_data(self, include_secrets=False, details='full'):
1007 """
1007 """
1008 Common function for generating user related data for API
1008 Common function for generating user related data for API
1009
1009
1010 :param include_secrets: By default secrets in the API data will be replaced
1010 :param include_secrets: By default secrets in the API data will be replaced
1011 by a placeholder value to prevent exposing this data by accident. In case
1011 by a placeholder value to prevent exposing this data by accident. In case
1012 this data shall be exposed, set this flag to ``True``.
1012 this data shall be exposed, set this flag to ``True``.
1013
1013
1014 :param details: details can be 'basic|full' basic gives only a subset of
1014 :param details: details can be 'basic|full' basic gives only a subset of
1015 the available user information that includes user_id, name and emails.
1015 the available user information that includes user_id, name and emails.
1016 """
1016 """
1017 user = self
1017 user = self
1018 user_data = self.user_data
1018 user_data = self.user_data
1019 data = {
1019 data = {
1020 'user_id': user.user_id,
1020 'user_id': user.user_id,
1021 'username': user.username,
1021 'username': user.username,
1022 'firstname': user.name,
1022 'firstname': user.name,
1023 'lastname': user.lastname,
1023 'lastname': user.lastname,
1024 'email': user.email,
1024 'email': user.email,
1025 'emails': user.emails,
1025 'emails': user.emails,
1026 }
1026 }
1027 if details == 'basic':
1027 if details == 'basic':
1028 return data
1028 return data
1029
1029
1030 auth_token_length = 40
1030 auth_token_length = 40
1031 auth_token_replacement = '*' * auth_token_length
1031 auth_token_replacement = '*' * auth_token_length
1032
1032
1033 extras = {
1033 extras = {
1034 'auth_tokens': [auth_token_replacement],
1034 'auth_tokens': [auth_token_replacement],
1035 'active': user.active,
1035 'active': user.active,
1036 'admin': user.admin,
1036 'admin': user.admin,
1037 'extern_type': user.extern_type,
1037 'extern_type': user.extern_type,
1038 'extern_name': user.extern_name,
1038 'extern_name': user.extern_name,
1039 'last_login': user.last_login,
1039 'last_login': user.last_login,
1040 'last_activity': user.last_activity,
1040 'last_activity': user.last_activity,
1041 'ip_addresses': user.ip_addresses,
1041 'ip_addresses': user.ip_addresses,
1042 'language': user_data.get('language')
1042 'language': user_data.get('language')
1043 }
1043 }
1044 data.update(extras)
1044 data.update(extras)
1045
1045
1046 if include_secrets:
1046 if include_secrets:
1047 data['auth_tokens'] = user.auth_tokens
1047 data['auth_tokens'] = user.auth_tokens
1048 return data
1048 return data
1049
1049
1050 def __json__(self):
1050 def __json__(self):
1051 data = {
1051 data = {
1052 'full_name': self.full_name,
1052 'full_name': self.full_name,
1053 'full_name_or_username': self.full_name_or_username,
1053 'full_name_or_username': self.full_name_or_username,
1054 'short_contact': self.short_contact,
1054 'short_contact': self.short_contact,
1055 'full_contact': self.full_contact,
1055 'full_contact': self.full_contact,
1056 }
1056 }
1057 data.update(self.get_api_data())
1057 data.update(self.get_api_data())
1058 return data
1058 return data
1059
1059
1060
1060
1061 class UserApiKeys(Base, BaseModel):
1061 class UserApiKeys(Base, BaseModel):
1062 __tablename__ = 'user_api_keys'
1062 __tablename__ = 'user_api_keys'
1063 __table_args__ = (
1063 __table_args__ = (
1064 Index('uak_api_key_idx', 'api_key'),
1064 Index('uak_api_key_idx', 'api_key'),
1065 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1065 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1066 base_table_args
1066 base_table_args
1067 )
1067 )
1068 __mapper_args__ = {}
1068 __mapper_args__ = {}
1069
1069
1070 # ApiKey role
1070 # ApiKey role
1071 ROLE_ALL = 'token_role_all'
1071 ROLE_ALL = 'token_role_all'
1072 ROLE_HTTP = 'token_role_http'
1072 ROLE_HTTP = 'token_role_http'
1073 ROLE_VCS = 'token_role_vcs'
1073 ROLE_VCS = 'token_role_vcs'
1074 ROLE_API = 'token_role_api'
1074 ROLE_API = 'token_role_api'
1075 ROLE_FEED = 'token_role_feed'
1075 ROLE_FEED = 'token_role_feed'
1076 ROLE_PASSWORD_RESET = 'token_password_reset'
1076 ROLE_PASSWORD_RESET = 'token_password_reset'
1077
1077
1078 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1078 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1079
1079
1080 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1080 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1081 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1081 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1082 api_key = Column("api_key", String(255), nullable=False, unique=True)
1082 api_key = Column("api_key", String(255), nullable=False, unique=True)
1083 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1083 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1084 expires = Column('expires', Float(53), nullable=False)
1084 expires = Column('expires', Float(53), nullable=False)
1085 role = Column('role', String(255), nullable=True)
1085 role = Column('role', String(255), nullable=True)
1086 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1086 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1087
1087
1088 # scope columns
1088 # scope columns
1089 repo_id = Column(
1089 repo_id = Column(
1090 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1090 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1091 nullable=True, unique=None, default=None)
1091 nullable=True, unique=None, default=None)
1092 repo = relationship('Repository', lazy='joined')
1092 repo = relationship('Repository', lazy='joined')
1093
1093
1094 repo_group_id = Column(
1094 repo_group_id = Column(
1095 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1095 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1096 nullable=True, unique=None, default=None)
1096 nullable=True, unique=None, default=None)
1097 repo_group = relationship('RepoGroup', lazy='joined')
1097 repo_group = relationship('RepoGroup', lazy='joined')
1098
1098
1099 user = relationship('User', lazy='joined')
1099 user = relationship('User', lazy='joined')
1100
1100
1101 def __unicode__(self):
1101 def __unicode__(self):
1102 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1102 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1103
1103
1104 def __json__(self):
1104 def __json__(self):
1105 data = {
1105 data = {
1106 'auth_token': self.api_key,
1106 'auth_token': self.api_key,
1107 'role': self.role,
1107 'role': self.role,
1108 'scope': self.scope_humanized,
1108 'scope': self.scope_humanized,
1109 'expired': self.expired
1109 'expired': self.expired
1110 }
1110 }
1111 return data
1111 return data
1112
1112
1113 def get_api_data(self, include_secrets=False):
1113 def get_api_data(self, include_secrets=False):
1114 data = self.__json__()
1114 data = self.__json__()
1115 if include_secrets:
1115 if include_secrets:
1116 return data
1116 return data
1117 else:
1117 else:
1118 data['auth_token'] = self.token_obfuscated
1118 data['auth_token'] = self.token_obfuscated
1119 return data
1119 return data
1120
1120
1121 @hybrid_property
1121 @hybrid_property
1122 def description_safe(self):
1122 def description_safe(self):
1123 from rhodecode.lib import helpers as h
1123 from rhodecode.lib import helpers as h
1124 return h.escape(self.description)
1124 return h.escape(self.description)
1125
1125
1126 @property
1126 @property
1127 def expired(self):
1127 def expired(self):
1128 if self.expires == -1:
1128 if self.expires == -1:
1129 return False
1129 return False
1130 return time.time() > self.expires
1130 return time.time() > self.expires
1131
1131
1132 @classmethod
1132 @classmethod
1133 def _get_role_name(cls, role):
1133 def _get_role_name(cls, role):
1134 return {
1134 return {
1135 cls.ROLE_ALL: _('all'),
1135 cls.ROLE_ALL: _('all'),
1136 cls.ROLE_HTTP: _('http/web interface'),
1136 cls.ROLE_HTTP: _('http/web interface'),
1137 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1137 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1138 cls.ROLE_API: _('api calls'),
1138 cls.ROLE_API: _('api calls'),
1139 cls.ROLE_FEED: _('feed access'),
1139 cls.ROLE_FEED: _('feed access'),
1140 }.get(role, role)
1140 }.get(role, role)
1141
1141
1142 @property
1142 @property
1143 def role_humanized(self):
1143 def role_humanized(self):
1144 return self._get_role_name(self.role)
1144 return self._get_role_name(self.role)
1145
1145
1146 def _get_scope(self):
1146 def _get_scope(self):
1147 if self.repo:
1147 if self.repo:
1148 return 'Repository: {}'.format(self.repo.repo_name)
1148 return 'Repository: {}'.format(self.repo.repo_name)
1149 if self.repo_group:
1149 if self.repo_group:
1150 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1150 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1151 return 'Global'
1151 return 'Global'
1152
1152
1153 @property
1153 @property
1154 def scope_humanized(self):
1154 def scope_humanized(self):
1155 return self._get_scope()
1155 return self._get_scope()
1156
1156
1157 @property
1157 @property
1158 def token_obfuscated(self):
1158 def token_obfuscated(self):
1159 if self.api_key:
1159 if self.api_key:
1160 return self.api_key[:4] + "****"
1160 return self.api_key[:4] + "****"
1161
1161
1162
1162
1163 class UserEmailMap(Base, BaseModel):
1163 class UserEmailMap(Base, BaseModel):
1164 __tablename__ = 'user_email_map'
1164 __tablename__ = 'user_email_map'
1165 __table_args__ = (
1165 __table_args__ = (
1166 Index('uem_email_idx', 'email'),
1166 Index('uem_email_idx', 'email'),
1167 UniqueConstraint('email'),
1167 UniqueConstraint('email'),
1168 base_table_args
1168 base_table_args
1169 )
1169 )
1170 __mapper_args__ = {}
1170 __mapper_args__ = {}
1171
1171
1172 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1172 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1173 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1173 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1174 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1174 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1175 user = relationship('User', lazy='joined')
1175 user = relationship('User', lazy='joined')
1176
1176
1177 @validates('_email')
1177 @validates('_email')
1178 def validate_email(self, key, email):
1178 def validate_email(self, key, email):
1179 # check if this email is not main one
1179 # check if this email is not main one
1180 main_email = Session().query(User).filter(User.email == email).scalar()
1180 main_email = Session().query(User).filter(User.email == email).scalar()
1181 if main_email is not None:
1181 if main_email is not None:
1182 raise AttributeError('email %s is present is user table' % email)
1182 raise AttributeError('email %s is present is user table' % email)
1183 return email
1183 return email
1184
1184
1185 @hybrid_property
1185 @hybrid_property
1186 def email(self):
1186 def email(self):
1187 return self._email
1187 return self._email
1188
1188
1189 @email.setter
1189 @email.setter
1190 def email(self, val):
1190 def email(self, val):
1191 self._email = val.lower() if val else None
1191 self._email = val.lower() if val else None
1192
1192
1193
1193
1194 class UserIpMap(Base, BaseModel):
1194 class UserIpMap(Base, BaseModel):
1195 __tablename__ = 'user_ip_map'
1195 __tablename__ = 'user_ip_map'
1196 __table_args__ = (
1196 __table_args__ = (
1197 UniqueConstraint('user_id', 'ip_addr'),
1197 UniqueConstraint('user_id', 'ip_addr'),
1198 base_table_args
1198 base_table_args
1199 )
1199 )
1200 __mapper_args__ = {}
1200 __mapper_args__ = {}
1201
1201
1202 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1202 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1203 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1203 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1204 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1204 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1205 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1205 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1206 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1206 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1207 user = relationship('User', lazy='joined')
1207 user = relationship('User', lazy='joined')
1208
1208
1209 @hybrid_property
1209 @hybrid_property
1210 def description_safe(self):
1210 def description_safe(self):
1211 from rhodecode.lib import helpers as h
1211 from rhodecode.lib import helpers as h
1212 return h.escape(self.description)
1212 return h.escape(self.description)
1213
1213
1214 @classmethod
1214 @classmethod
1215 def _get_ip_range(cls, ip_addr):
1215 def _get_ip_range(cls, ip_addr):
1216 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1216 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1217 return [str(net.network_address), str(net.broadcast_address)]
1217 return [str(net.network_address), str(net.broadcast_address)]
1218
1218
1219 def __json__(self):
1219 def __json__(self):
1220 return {
1220 return {
1221 'ip_addr': self.ip_addr,
1221 'ip_addr': self.ip_addr,
1222 'ip_range': self._get_ip_range(self.ip_addr),
1222 'ip_range': self._get_ip_range(self.ip_addr),
1223 }
1223 }
1224
1224
1225 def __unicode__(self):
1225 def __unicode__(self):
1226 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1226 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1227 self.user_id, self.ip_addr)
1227 self.user_id, self.ip_addr)
1228
1228
1229
1229
1230 class UserSshKeys(Base, BaseModel):
1230 class UserSshKeys(Base, BaseModel):
1231 __tablename__ = 'user_ssh_keys'
1231 __tablename__ = 'user_ssh_keys'
1232 __table_args__ = (
1232 __table_args__ = (
1233 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1233 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1234
1234
1235 UniqueConstraint('ssh_key_fingerprint'),
1235 UniqueConstraint('ssh_key_fingerprint'),
1236
1236
1237 base_table_args
1237 base_table_args
1238 )
1238 )
1239 __mapper_args__ = {}
1239 __mapper_args__ = {}
1240
1240
1241 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1241 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1242 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1242 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1243 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1243 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1244
1244
1245 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1245 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1246
1246
1247 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1247 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1248 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1248 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1249 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1249 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1250
1250
1251 user = relationship('User', lazy='joined')
1251 user = relationship('User', lazy='joined')
1252
1252
1253 def __json__(self):
1253 def __json__(self):
1254 data = {
1254 data = {
1255 'ssh_fingerprint': self.ssh_key_fingerprint,
1255 'ssh_fingerprint': self.ssh_key_fingerprint,
1256 'description': self.description,
1256 'description': self.description,
1257 'created_on': self.created_on
1257 'created_on': self.created_on
1258 }
1258 }
1259 return data
1259 return data
1260
1260
1261 def get_api_data(self):
1261 def get_api_data(self):
1262 data = self.__json__()
1262 data = self.__json__()
1263 return data
1263 return data
1264
1264
1265
1265
1266 class UserLog(Base, BaseModel):
1266 class UserLog(Base, BaseModel):
1267 __tablename__ = 'user_logs'
1267 __tablename__ = 'user_logs'
1268 __table_args__ = (
1268 __table_args__ = (
1269 base_table_args,
1269 base_table_args,
1270 )
1270 )
1271
1271
1272 VERSION_1 = 'v1'
1272 VERSION_1 = 'v1'
1273 VERSION_2 = 'v2'
1273 VERSION_2 = 'v2'
1274 VERSIONS = [VERSION_1, VERSION_2]
1274 VERSIONS = [VERSION_1, VERSION_2]
1275
1275
1276 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1276 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1277 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1277 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1278 username = Column("username", String(255), nullable=True, unique=None, default=None)
1278 username = Column("username", String(255), nullable=True, unique=None, default=None)
1279 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1279 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1280 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1280 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1281 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1281 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1282 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1282 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1283 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1283 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1284
1284
1285 version = Column("version", String(255), nullable=True, default=VERSION_1)
1285 version = Column("version", String(255), nullable=True, default=VERSION_1)
1286 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1286 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1287 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1287 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1288
1288
1289 def __unicode__(self):
1289 def __unicode__(self):
1290 return u"<%s('id:%s:%s')>" % (
1290 return u"<%s('id:%s:%s')>" % (
1291 self.__class__.__name__, self.repository_name, self.action)
1291 self.__class__.__name__, self.repository_name, self.action)
1292
1292
1293 def __json__(self):
1293 def __json__(self):
1294 return {
1294 return {
1295 'user_id': self.user_id,
1295 'user_id': self.user_id,
1296 'username': self.username,
1296 'username': self.username,
1297 'repository_id': self.repository_id,
1297 'repository_id': self.repository_id,
1298 'repository_name': self.repository_name,
1298 'repository_name': self.repository_name,
1299 'user_ip': self.user_ip,
1299 'user_ip': self.user_ip,
1300 'action_date': self.action_date,
1300 'action_date': self.action_date,
1301 'action': self.action,
1301 'action': self.action,
1302 }
1302 }
1303
1303
1304 @hybrid_property
1304 @hybrid_property
1305 def entry_id(self):
1305 def entry_id(self):
1306 return self.user_log_id
1306 return self.user_log_id
1307
1307
1308 @property
1308 @property
1309 def action_as_day(self):
1309 def action_as_day(self):
1310 return datetime.date(*self.action_date.timetuple()[:3])
1310 return datetime.date(*self.action_date.timetuple()[:3])
1311
1311
1312 user = relationship('User')
1312 user = relationship('User')
1313 repository = relationship('Repository', cascade='')
1313 repository = relationship('Repository', cascade='')
1314
1314
1315
1315
1316 class UserGroup(Base, BaseModel):
1316 class UserGroup(Base, BaseModel):
1317 __tablename__ = 'users_groups'
1317 __tablename__ = 'users_groups'
1318 __table_args__ = (
1318 __table_args__ = (
1319 base_table_args,
1319 base_table_args,
1320 )
1320 )
1321
1321
1322 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1322 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1323 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1323 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1324 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1324 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1325 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1325 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1326 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1326 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1327 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1327 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1328 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1328 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1329 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1329 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1330
1330
1331 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1331 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
1332 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1332 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1333 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1333 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1334 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1334 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1335 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1335 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1336 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1336 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1337
1337
1338 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1338 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1339 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1339 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1340
1340
1341 @classmethod
1341 @classmethod
1342 def _load_group_data(cls, column):
1342 def _load_group_data(cls, column):
1343 if not column:
1343 if not column:
1344 return {}
1344 return {}
1345
1345
1346 try:
1346 try:
1347 return json.loads(column) or {}
1347 return json.loads(column) or {}
1348 except TypeError:
1348 except TypeError:
1349 return {}
1349 return {}
1350
1350
1351 @hybrid_property
1351 @hybrid_property
1352 def description_safe(self):
1352 def description_safe(self):
1353 from rhodecode.lib import helpers as h
1353 from rhodecode.lib import helpers as h
1354 return h.escape(self.user_group_description)
1354 return h.escape(self.user_group_description)
1355
1355
1356 @hybrid_property
1356 @hybrid_property
1357 def group_data(self):
1357 def group_data(self):
1358 return self._load_group_data(self._group_data)
1358 return self._load_group_data(self._group_data)
1359
1359
1360 @group_data.expression
1360 @group_data.expression
1361 def group_data(self, **kwargs):
1361 def group_data(self, **kwargs):
1362 return self._group_data
1362 return self._group_data
1363
1363
1364 @group_data.setter
1364 @group_data.setter
1365 def group_data(self, val):
1365 def group_data(self, val):
1366 try:
1366 try:
1367 self._group_data = json.dumps(val)
1367 self._group_data = json.dumps(val)
1368 except Exception:
1368 except Exception:
1369 log.error(traceback.format_exc())
1369 log.error(traceback.format_exc())
1370
1370
1371 @classmethod
1371 @classmethod
1372 def _load_sync(cls, group_data):
1372 def _load_sync(cls, group_data):
1373 if group_data:
1373 if group_data:
1374 return group_data.get('extern_type')
1374 return group_data.get('extern_type')
1375
1375
1376 @property
1376 @property
1377 def sync(self):
1377 def sync(self):
1378 return self._load_sync(self.group_data)
1378 return self._load_sync(self.group_data)
1379
1379
1380 def __unicode__(self):
1380 def __unicode__(self):
1381 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1381 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1382 self.users_group_id,
1382 self.users_group_id,
1383 self.users_group_name)
1383 self.users_group_name)
1384
1384
1385 @classmethod
1385 @classmethod
1386 def get_by_group_name(cls, group_name, cache=False,
1386 def get_by_group_name(cls, group_name, cache=False,
1387 case_insensitive=False):
1387 case_insensitive=False):
1388 if case_insensitive:
1388 if case_insensitive:
1389 q = cls.query().filter(func.lower(cls.users_group_name) ==
1389 q = cls.query().filter(func.lower(cls.users_group_name) ==
1390 func.lower(group_name))
1390 func.lower(group_name))
1391
1391
1392 else:
1392 else:
1393 q = cls.query().filter(cls.users_group_name == group_name)
1393 q = cls.query().filter(cls.users_group_name == group_name)
1394 if cache:
1394 if cache:
1395 q = q.options(
1395 q = q.options(
1396 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1396 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1397 return q.scalar()
1397 return q.scalar()
1398
1398
1399 @classmethod
1399 @classmethod
1400 def get(cls, user_group_id, cache=False):
1400 def get(cls, user_group_id, cache=False):
1401 if not user_group_id:
1401 if not user_group_id:
1402 return
1402 return
1403
1403
1404 user_group = cls.query()
1404 user_group = cls.query()
1405 if cache:
1405 if cache:
1406 user_group = user_group.options(
1406 user_group = user_group.options(
1407 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1407 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1408 return user_group.get(user_group_id)
1408 return user_group.get(user_group_id)
1409
1409
1410 def permissions(self, with_admins=True, with_owner=True,
1410 def permissions(self, with_admins=True, with_owner=True,
1411 expand_from_user_groups=False):
1411 expand_from_user_groups=False):
1412 """
1412 """
1413 Permissions for user groups
1413 Permissions for user groups
1414 """
1414 """
1415 _admin_perm = 'usergroup.admin'
1415 _admin_perm = 'usergroup.admin'
1416
1416
1417 owner_row = []
1417 owner_row = []
1418 if with_owner:
1418 if with_owner:
1419 usr = AttributeDict(self.user.get_dict())
1419 usr = AttributeDict(self.user.get_dict())
1420 usr.owner_row = True
1420 usr.owner_row = True
1421 usr.permission = _admin_perm
1421 usr.permission = _admin_perm
1422 owner_row.append(usr)
1422 owner_row.append(usr)
1423
1423
1424 super_admin_ids = []
1424 super_admin_ids = []
1425 super_admin_rows = []
1425 super_admin_rows = []
1426 if with_admins:
1426 if with_admins:
1427 for usr in User.get_all_super_admins():
1427 for usr in User.get_all_super_admins():
1428 super_admin_ids.append(usr.user_id)
1428 super_admin_ids.append(usr.user_id)
1429 # if this admin is also owner, don't double the record
1429 # if this admin is also owner, don't double the record
1430 if usr.user_id == owner_row[0].user_id:
1430 if usr.user_id == owner_row[0].user_id:
1431 owner_row[0].admin_row = True
1431 owner_row[0].admin_row = True
1432 else:
1432 else:
1433 usr = AttributeDict(usr.get_dict())
1433 usr = AttributeDict(usr.get_dict())
1434 usr.admin_row = True
1434 usr.admin_row = True
1435 usr.permission = _admin_perm
1435 usr.permission = _admin_perm
1436 super_admin_rows.append(usr)
1436 super_admin_rows.append(usr)
1437
1437
1438 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1438 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1439 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1439 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1440 joinedload(UserUserGroupToPerm.user),
1440 joinedload(UserUserGroupToPerm.user),
1441 joinedload(UserUserGroupToPerm.permission),)
1441 joinedload(UserUserGroupToPerm.permission),)
1442
1442
1443 # get owners and admins and permissions. We do a trick of re-writing
1443 # get owners and admins and permissions. We do a trick of re-writing
1444 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1444 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1445 # has a global reference and changing one object propagates to all
1445 # has a global reference and changing one object propagates to all
1446 # others. This means if admin is also an owner admin_row that change
1446 # others. This means if admin is also an owner admin_row that change
1447 # would propagate to both objects
1447 # would propagate to both objects
1448 perm_rows = []
1448 perm_rows = []
1449 for _usr in q.all():
1449 for _usr in q.all():
1450 usr = AttributeDict(_usr.user.get_dict())
1450 usr = AttributeDict(_usr.user.get_dict())
1451 # if this user is also owner/admin, mark as duplicate record
1451 # if this user is also owner/admin, mark as duplicate record
1452 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1452 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1453 usr.duplicate_perm = True
1453 usr.duplicate_perm = True
1454 usr.permission = _usr.permission.permission_name
1454 usr.permission = _usr.permission.permission_name
1455 perm_rows.append(usr)
1455 perm_rows.append(usr)
1456
1456
1457 # filter the perm rows by 'default' first and then sort them by
1457 # filter the perm rows by 'default' first and then sort them by
1458 # admin,write,read,none permissions sorted again alphabetically in
1458 # admin,write,read,none permissions sorted again alphabetically in
1459 # each group
1459 # each group
1460 perm_rows = sorted(perm_rows, key=display_user_sort)
1460 perm_rows = sorted(perm_rows, key=display_user_sort)
1461
1461
1462 user_groups_rows = []
1462 user_groups_rows = []
1463 if expand_from_user_groups:
1463 if expand_from_user_groups:
1464 for ug in self.permission_user_groups(with_members=True):
1464 for ug in self.permission_user_groups(with_members=True):
1465 for user_data in ug.members:
1465 for user_data in ug.members:
1466 user_groups_rows.append(user_data)
1466 user_groups_rows.append(user_data)
1467
1467
1468 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1468 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1469
1469
1470 def permission_user_groups(self, with_members=False):
1470 def permission_user_groups(self, with_members=False):
1471 q = UserGroupUserGroupToPerm.query()\
1471 q = UserGroupUserGroupToPerm.query()\
1472 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1472 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1473 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1473 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1474 joinedload(UserGroupUserGroupToPerm.target_user_group),
1474 joinedload(UserGroupUserGroupToPerm.target_user_group),
1475 joinedload(UserGroupUserGroupToPerm.permission),)
1475 joinedload(UserGroupUserGroupToPerm.permission),)
1476
1476
1477 perm_rows = []
1477 perm_rows = []
1478 for _user_group in q.all():
1478 for _user_group in q.all():
1479 entry = AttributeDict(_user_group.user_group.get_dict())
1479 entry = AttributeDict(_user_group.user_group.get_dict())
1480 entry.permission = _user_group.permission.permission_name
1480 entry.permission = _user_group.permission.permission_name
1481 if with_members:
1481 if with_members:
1482 entry.members = [x.user.get_dict()
1482 entry.members = [x.user.get_dict()
1483 for x in _user_group.user_group.members]
1483 for x in _user_group.user_group.members]
1484 perm_rows.append(entry)
1484 perm_rows.append(entry)
1485
1485
1486 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1486 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1487 return perm_rows
1487 return perm_rows
1488
1488
1489 def _get_default_perms(self, user_group, suffix=''):
1489 def _get_default_perms(self, user_group, suffix=''):
1490 from rhodecode.model.permission import PermissionModel
1490 from rhodecode.model.permission import PermissionModel
1491 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1491 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1492
1492
1493 def get_default_perms(self, suffix=''):
1493 def get_default_perms(self, suffix=''):
1494 return self._get_default_perms(self, suffix)
1494 return self._get_default_perms(self, suffix)
1495
1495
1496 def get_api_data(self, with_group_members=True, include_secrets=False):
1496 def get_api_data(self, with_group_members=True, include_secrets=False):
1497 """
1497 """
1498 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1498 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1499 basically forwarded.
1499 basically forwarded.
1500
1500
1501 """
1501 """
1502 user_group = self
1502 user_group = self
1503 data = {
1503 data = {
1504 'users_group_id': user_group.users_group_id,
1504 'users_group_id': user_group.users_group_id,
1505 'group_name': user_group.users_group_name,
1505 'group_name': user_group.users_group_name,
1506 'group_description': user_group.user_group_description,
1506 'group_description': user_group.user_group_description,
1507 'active': user_group.users_group_active,
1507 'active': user_group.users_group_active,
1508 'owner': user_group.user.username,
1508 'owner': user_group.user.username,
1509 'sync': user_group.sync,
1509 'sync': user_group.sync,
1510 'owner_email': user_group.user.email,
1510 'owner_email': user_group.user.email,
1511 }
1511 }
1512
1512
1513 if with_group_members:
1513 if with_group_members:
1514 users = []
1514 users = []
1515 for user in user_group.members:
1515 for user in user_group.members:
1516 user = user.user
1516 user = user.user
1517 users.append(user.get_api_data(include_secrets=include_secrets))
1517 users.append(user.get_api_data(include_secrets=include_secrets))
1518 data['users'] = users
1518 data['users'] = users
1519
1519
1520 return data
1520 return data
1521
1521
1522
1522
1523 class UserGroupMember(Base, BaseModel):
1523 class UserGroupMember(Base, BaseModel):
1524 __tablename__ = 'users_groups_members'
1524 __tablename__ = 'users_groups_members'
1525 __table_args__ = (
1525 __table_args__ = (
1526 base_table_args,
1526 base_table_args,
1527 )
1527 )
1528
1528
1529 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1529 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1530 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1530 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1531 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1531 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1532
1532
1533 user = relationship('User', lazy='joined')
1533 user = relationship('User', lazy='joined')
1534 users_group = relationship('UserGroup')
1534 users_group = relationship('UserGroup')
1535
1535
1536 def __init__(self, gr_id='', u_id=''):
1536 def __init__(self, gr_id='', u_id=''):
1537 self.users_group_id = gr_id
1537 self.users_group_id = gr_id
1538 self.user_id = u_id
1538 self.user_id = u_id
1539
1539
1540
1540
1541 class RepositoryField(Base, BaseModel):
1541 class RepositoryField(Base, BaseModel):
1542 __tablename__ = 'repositories_fields'
1542 __tablename__ = 'repositories_fields'
1543 __table_args__ = (
1543 __table_args__ = (
1544 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1544 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1545 base_table_args,
1545 base_table_args,
1546 )
1546 )
1547
1547
1548 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1548 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1549
1549
1550 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1550 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1551 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1551 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1552 field_key = Column("field_key", String(250))
1552 field_key = Column("field_key", String(250))
1553 field_label = Column("field_label", String(1024), nullable=False)
1553 field_label = Column("field_label", String(1024), nullable=False)
1554 field_value = Column("field_value", String(10000), nullable=False)
1554 field_value = Column("field_value", String(10000), nullable=False)
1555 field_desc = Column("field_desc", String(1024), nullable=False)
1555 field_desc = Column("field_desc", String(1024), nullable=False)
1556 field_type = Column("field_type", String(255), nullable=False, unique=None)
1556 field_type = Column("field_type", String(255), nullable=False, unique=None)
1557 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1557 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1558
1558
1559 repository = relationship('Repository')
1559 repository = relationship('Repository')
1560
1560
1561 @property
1561 @property
1562 def field_key_prefixed(self):
1562 def field_key_prefixed(self):
1563 return 'ex_%s' % self.field_key
1563 return 'ex_%s' % self.field_key
1564
1564
1565 @classmethod
1565 @classmethod
1566 def un_prefix_key(cls, key):
1566 def un_prefix_key(cls, key):
1567 if key.startswith(cls.PREFIX):
1567 if key.startswith(cls.PREFIX):
1568 return key[len(cls.PREFIX):]
1568 return key[len(cls.PREFIX):]
1569 return key
1569 return key
1570
1570
1571 @classmethod
1571 @classmethod
1572 def get_by_key_name(cls, key, repo):
1572 def get_by_key_name(cls, key, repo):
1573 row = cls.query()\
1573 row = cls.query()\
1574 .filter(cls.repository == repo)\
1574 .filter(cls.repository == repo)\
1575 .filter(cls.field_key == key).scalar()
1575 .filter(cls.field_key == key).scalar()
1576 return row
1576 return row
1577
1577
1578
1578
1579 class Repository(Base, BaseModel):
1579 class Repository(Base, BaseModel):
1580 __tablename__ = 'repositories'
1580 __tablename__ = 'repositories'
1581 __table_args__ = (
1581 __table_args__ = (
1582 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1582 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1583 base_table_args,
1583 base_table_args,
1584 )
1584 )
1585 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1585 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1586 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1586 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1587 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1587 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1588
1588
1589 STATE_CREATED = 'repo_state_created'
1589 STATE_CREATED = 'repo_state_created'
1590 STATE_PENDING = 'repo_state_pending'
1590 STATE_PENDING = 'repo_state_pending'
1591 STATE_ERROR = 'repo_state_error'
1591 STATE_ERROR = 'repo_state_error'
1592
1592
1593 LOCK_AUTOMATIC = 'lock_auto'
1593 LOCK_AUTOMATIC = 'lock_auto'
1594 LOCK_API = 'lock_api'
1594 LOCK_API = 'lock_api'
1595 LOCK_WEB = 'lock_web'
1595 LOCK_WEB = 'lock_web'
1596 LOCK_PULL = 'lock_pull'
1596 LOCK_PULL = 'lock_pull'
1597
1597
1598 NAME_SEP = URL_SEP
1598 NAME_SEP = URL_SEP
1599
1599
1600 repo_id = Column(
1600 repo_id = Column(
1601 "repo_id", Integer(), nullable=False, unique=True, default=None,
1601 "repo_id", Integer(), nullable=False, unique=True, default=None,
1602 primary_key=True)
1602 primary_key=True)
1603 _repo_name = Column(
1603 _repo_name = Column(
1604 "repo_name", Text(), nullable=False, default=None)
1604 "repo_name", Text(), nullable=False, default=None)
1605 _repo_name_hash = Column(
1605 _repo_name_hash = Column(
1606 "repo_name_hash", String(255), nullable=False, unique=True)
1606 "repo_name_hash", String(255), nullable=False, unique=True)
1607 repo_state = Column("repo_state", String(255), nullable=True)
1607 repo_state = Column("repo_state", String(255), nullable=True)
1608
1608
1609 clone_uri = Column(
1609 clone_uri = Column(
1610 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1610 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1611 default=None)
1611 default=None)
1612 push_uri = Column(
1612 push_uri = Column(
1613 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1613 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1614 default=None)
1614 default=None)
1615 repo_type = Column(
1615 repo_type = Column(
1616 "repo_type", String(255), nullable=False, unique=False, default=None)
1616 "repo_type", String(255), nullable=False, unique=False, default=None)
1617 user_id = Column(
1617 user_id = Column(
1618 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1618 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1619 unique=False, default=None)
1619 unique=False, default=None)
1620 private = Column(
1620 private = Column(
1621 "private", Boolean(), nullable=True, unique=None, default=None)
1621 "private", Boolean(), nullable=True, unique=None, default=None)
1622 archived = Column(
1622 archived = Column(
1623 "archived", Boolean(), nullable=True, unique=None, default=None)
1623 "archived", Boolean(), nullable=True, unique=None, default=None)
1624 enable_statistics = Column(
1624 enable_statistics = Column(
1625 "statistics", Boolean(), nullable=True, unique=None, default=True)
1625 "statistics", Boolean(), nullable=True, unique=None, default=True)
1626 enable_downloads = Column(
1626 enable_downloads = Column(
1627 "downloads", Boolean(), nullable=True, unique=None, default=True)
1627 "downloads", Boolean(), nullable=True, unique=None, default=True)
1628 description = Column(
1628 description = Column(
1629 "description", String(10000), nullable=True, unique=None, default=None)
1629 "description", String(10000), nullable=True, unique=None, default=None)
1630 created_on = Column(
1630 created_on = Column(
1631 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1631 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1632 default=datetime.datetime.now)
1632 default=datetime.datetime.now)
1633 updated_on = Column(
1633 updated_on = Column(
1634 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1634 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1635 default=datetime.datetime.now)
1635 default=datetime.datetime.now)
1636 _landing_revision = Column(
1636 _landing_revision = Column(
1637 "landing_revision", String(255), nullable=False, unique=False,
1637 "landing_revision", String(255), nullable=False, unique=False,
1638 default=None)
1638 default=None)
1639 enable_locking = Column(
1639 enable_locking = Column(
1640 "enable_locking", Boolean(), nullable=False, unique=None,
1640 "enable_locking", Boolean(), nullable=False, unique=None,
1641 default=False)
1641 default=False)
1642 _locked = Column(
1642 _locked = Column(
1643 "locked", String(255), nullable=True, unique=False, default=None)
1643 "locked", String(255), nullable=True, unique=False, default=None)
1644 _changeset_cache = Column(
1644 _changeset_cache = Column(
1645 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1645 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1646
1646
1647 fork_id = Column(
1647 fork_id = Column(
1648 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1648 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1649 nullable=True, unique=False, default=None)
1649 nullable=True, unique=False, default=None)
1650 group_id = Column(
1650 group_id = Column(
1651 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1651 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1652 unique=False, default=None)
1652 unique=False, default=None)
1653
1653
1654 user = relationship('User', lazy='joined')
1654 user = relationship('User', lazy='joined')
1655 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1655 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1656 group = relationship('RepoGroup', lazy='joined')
1656 group = relationship('RepoGroup', lazy='joined')
1657 repo_to_perm = relationship(
1657 repo_to_perm = relationship(
1658 'UserRepoToPerm', cascade='all',
1658 'UserRepoToPerm', cascade='all',
1659 order_by='UserRepoToPerm.repo_to_perm_id')
1659 order_by='UserRepoToPerm.repo_to_perm_id')
1660 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1660 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1661 stats = relationship('Statistics', cascade='all', uselist=False)
1661 stats = relationship('Statistics', cascade='all', uselist=False)
1662
1662
1663 followers = relationship(
1663 followers = relationship(
1664 'UserFollowing',
1664 'UserFollowing',
1665 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1665 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1666 cascade='all')
1666 cascade='all')
1667 extra_fields = relationship(
1667 extra_fields = relationship(
1668 'RepositoryField', cascade="all, delete, delete-orphan")
1668 'RepositoryField', cascade="all, delete-orphan")
1669 logs = relationship('UserLog')
1669 logs = relationship('UserLog')
1670 comments = relationship(
1670 comments = relationship(
1671 'ChangesetComment', cascade="all, delete, delete-orphan")
1671 'ChangesetComment', cascade="all, delete-orphan")
1672 pull_requests_source = relationship(
1672 pull_requests_source = relationship(
1673 'PullRequest',
1673 'PullRequest',
1674 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1674 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1675 cascade="all, delete, delete-orphan")
1675 cascade="all, delete-orphan")
1676 pull_requests_target = relationship(
1676 pull_requests_target = relationship(
1677 'PullRequest',
1677 'PullRequest',
1678 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1678 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1679 cascade="all, delete, delete-orphan")
1679 cascade="all, delete-orphan")
1680 ui = relationship('RepoRhodeCodeUi', cascade="all")
1680 ui = relationship('RepoRhodeCodeUi', cascade="all")
1681 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1681 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1682 integrations = relationship('Integration', cascade="all, delete, delete-orphan")
1682 integrations = relationship('Integration', cascade="all, delete-orphan")
1683
1683
1684 scoped_tokens = relationship('UserApiKeys', cascade="all")
1684 scoped_tokens = relationship('UserApiKeys', cascade="all")
1685
1685
1686 artifacts = relationship('FileStore', cascade="all")
1686 artifacts = relationship('FileStore', cascade="all")
1687
1687
1688 def __unicode__(self):
1688 def __unicode__(self):
1689 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1689 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1690 safe_unicode(self.repo_name))
1690 safe_unicode(self.repo_name))
1691
1691
1692 @hybrid_property
1692 @hybrid_property
1693 def description_safe(self):
1693 def description_safe(self):
1694 from rhodecode.lib import helpers as h
1694 from rhodecode.lib import helpers as h
1695 return h.escape(self.description)
1695 return h.escape(self.description)
1696
1696
1697 @hybrid_property
1697 @hybrid_property
1698 def landing_rev(self):
1698 def landing_rev(self):
1699 # always should return [rev_type, rev]
1699 # always should return [rev_type, rev]
1700 if self._landing_revision:
1700 if self._landing_revision:
1701 _rev_info = self._landing_revision.split(':')
1701 _rev_info = self._landing_revision.split(':')
1702 if len(_rev_info) < 2:
1702 if len(_rev_info) < 2:
1703 _rev_info.insert(0, 'rev')
1703 _rev_info.insert(0, 'rev')
1704 return [_rev_info[0], _rev_info[1]]
1704 return [_rev_info[0], _rev_info[1]]
1705 return [None, None]
1705 return [None, None]
1706
1706
1707 @landing_rev.setter
1707 @landing_rev.setter
1708 def landing_rev(self, val):
1708 def landing_rev(self, val):
1709 if ':' not in val:
1709 if ':' not in val:
1710 raise ValueError('value must be delimited with `:` and consist '
1710 raise ValueError('value must be delimited with `:` and consist '
1711 'of <rev_type>:<rev>, got %s instead' % val)
1711 'of <rev_type>:<rev>, got %s instead' % val)
1712 self._landing_revision = val
1712 self._landing_revision = val
1713
1713
1714 @hybrid_property
1714 @hybrid_property
1715 def locked(self):
1715 def locked(self):
1716 if self._locked:
1716 if self._locked:
1717 user_id, timelocked, reason = self._locked.split(':')
1717 user_id, timelocked, reason = self._locked.split(':')
1718 lock_values = int(user_id), timelocked, reason
1718 lock_values = int(user_id), timelocked, reason
1719 else:
1719 else:
1720 lock_values = [None, None, None]
1720 lock_values = [None, None, None]
1721 return lock_values
1721 return lock_values
1722
1722
1723 @locked.setter
1723 @locked.setter
1724 def locked(self, val):
1724 def locked(self, val):
1725 if val and isinstance(val, (list, tuple)):
1725 if val and isinstance(val, (list, tuple)):
1726 self._locked = ':'.join(map(str, val))
1726 self._locked = ':'.join(map(str, val))
1727 else:
1727 else:
1728 self._locked = None
1728 self._locked = None
1729
1729
1730 @hybrid_property
1730 @hybrid_property
1731 def changeset_cache(self):
1731 def changeset_cache(self):
1732 from rhodecode.lib.vcs.backends.base import EmptyCommit
1732 from rhodecode.lib.vcs.backends.base import EmptyCommit
1733 dummy = EmptyCommit().__json__()
1733 dummy = EmptyCommit().__json__()
1734 if not self._changeset_cache:
1734 if not self._changeset_cache:
1735 dummy['source_repo_id'] = self.repo_id
1735 dummy['source_repo_id'] = self.repo_id
1736 return json.loads(json.dumps(dummy))
1736 return json.loads(json.dumps(dummy))
1737
1737
1738 try:
1738 try:
1739 return json.loads(self._changeset_cache)
1739 return json.loads(self._changeset_cache)
1740 except TypeError:
1740 except TypeError:
1741 return dummy
1741 return dummy
1742 except Exception:
1742 except Exception:
1743 log.error(traceback.format_exc())
1743 log.error(traceback.format_exc())
1744 return dummy
1744 return dummy
1745
1745
1746 @changeset_cache.setter
1746 @changeset_cache.setter
1747 def changeset_cache(self, val):
1747 def changeset_cache(self, val):
1748 try:
1748 try:
1749 self._changeset_cache = json.dumps(val)
1749 self._changeset_cache = json.dumps(val)
1750 except Exception:
1750 except Exception:
1751 log.error(traceback.format_exc())
1751 log.error(traceback.format_exc())
1752
1752
1753 @hybrid_property
1753 @hybrid_property
1754 def repo_name(self):
1754 def repo_name(self):
1755 return self._repo_name
1755 return self._repo_name
1756
1756
1757 @repo_name.setter
1757 @repo_name.setter
1758 def repo_name(self, value):
1758 def repo_name(self, value):
1759 self._repo_name = value
1759 self._repo_name = value
1760 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1760 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1761
1761
1762 @classmethod
1762 @classmethod
1763 def normalize_repo_name(cls, repo_name):
1763 def normalize_repo_name(cls, repo_name):
1764 """
1764 """
1765 Normalizes os specific repo_name to the format internally stored inside
1765 Normalizes os specific repo_name to the format internally stored inside
1766 database using URL_SEP
1766 database using URL_SEP
1767
1767
1768 :param cls:
1768 :param cls:
1769 :param repo_name:
1769 :param repo_name:
1770 """
1770 """
1771 return cls.NAME_SEP.join(repo_name.split(os.sep))
1771 return cls.NAME_SEP.join(repo_name.split(os.sep))
1772
1772
1773 @classmethod
1773 @classmethod
1774 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1774 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1775 session = Session()
1775 session = Session()
1776 q = session.query(cls).filter(cls.repo_name == repo_name)
1776 q = session.query(cls).filter(cls.repo_name == repo_name)
1777
1777
1778 if cache:
1778 if cache:
1779 if identity_cache:
1779 if identity_cache:
1780 val = cls.identity_cache(session, 'repo_name', repo_name)
1780 val = cls.identity_cache(session, 'repo_name', repo_name)
1781 if val:
1781 if val:
1782 return val
1782 return val
1783 else:
1783 else:
1784 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1784 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1785 q = q.options(
1785 q = q.options(
1786 FromCache("sql_cache_short", cache_key))
1786 FromCache("sql_cache_short", cache_key))
1787
1787
1788 return q.scalar()
1788 return q.scalar()
1789
1789
1790 @classmethod
1790 @classmethod
1791 def get_by_id_or_repo_name(cls, repoid):
1791 def get_by_id_or_repo_name(cls, repoid):
1792 if isinstance(repoid, (int, long)):
1792 if isinstance(repoid, (int, long)):
1793 try:
1793 try:
1794 repo = cls.get(repoid)
1794 repo = cls.get(repoid)
1795 except ValueError:
1795 except ValueError:
1796 repo = None
1796 repo = None
1797 else:
1797 else:
1798 repo = cls.get_by_repo_name(repoid)
1798 repo = cls.get_by_repo_name(repoid)
1799 return repo
1799 return repo
1800
1800
1801 @classmethod
1801 @classmethod
1802 def get_by_full_path(cls, repo_full_path):
1802 def get_by_full_path(cls, repo_full_path):
1803 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1803 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1804 repo_name = cls.normalize_repo_name(repo_name)
1804 repo_name = cls.normalize_repo_name(repo_name)
1805 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1805 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1806
1806
1807 @classmethod
1807 @classmethod
1808 def get_repo_forks(cls, repo_id):
1808 def get_repo_forks(cls, repo_id):
1809 return cls.query().filter(Repository.fork_id == repo_id)
1809 return cls.query().filter(Repository.fork_id == repo_id)
1810
1810
1811 @classmethod
1811 @classmethod
1812 def base_path(cls):
1812 def base_path(cls):
1813 """
1813 """
1814 Returns base path when all repos are stored
1814 Returns base path when all repos are stored
1815
1815
1816 :param cls:
1816 :param cls:
1817 """
1817 """
1818 q = Session().query(RhodeCodeUi)\
1818 q = Session().query(RhodeCodeUi)\
1819 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1819 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1820 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1820 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1821 return q.one().ui_value
1821 return q.one().ui_value
1822
1822
1823 @classmethod
1823 @classmethod
1824 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1824 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1825 case_insensitive=True, archived=False):
1825 case_insensitive=True, archived=False):
1826 q = Repository.query()
1826 q = Repository.query()
1827
1827
1828 if not archived:
1828 if not archived:
1829 q = q.filter(Repository.archived.isnot(true()))
1829 q = q.filter(Repository.archived.isnot(true()))
1830
1830
1831 if not isinstance(user_id, Optional):
1831 if not isinstance(user_id, Optional):
1832 q = q.filter(Repository.user_id == user_id)
1832 q = q.filter(Repository.user_id == user_id)
1833
1833
1834 if not isinstance(group_id, Optional):
1834 if not isinstance(group_id, Optional):
1835 q = q.filter(Repository.group_id == group_id)
1835 q = q.filter(Repository.group_id == group_id)
1836
1836
1837 if case_insensitive:
1837 if case_insensitive:
1838 q = q.order_by(func.lower(Repository.repo_name))
1838 q = q.order_by(func.lower(Repository.repo_name))
1839 else:
1839 else:
1840 q = q.order_by(Repository.repo_name)
1840 q = q.order_by(Repository.repo_name)
1841
1841
1842 return q.all()
1842 return q.all()
1843
1843
1844 @property
1844 @property
1845 def repo_uid(self):
1845 def repo_uid(self):
1846 return '_{}'.format(self.repo_id)
1846 return '_{}'.format(self.repo_id)
1847
1847
1848 @property
1848 @property
1849 def forks(self):
1849 def forks(self):
1850 """
1850 """
1851 Return forks of this repo
1851 Return forks of this repo
1852 """
1852 """
1853 return Repository.get_repo_forks(self.repo_id)
1853 return Repository.get_repo_forks(self.repo_id)
1854
1854
1855 @property
1855 @property
1856 def parent(self):
1856 def parent(self):
1857 """
1857 """
1858 Returns fork parent
1858 Returns fork parent
1859 """
1859 """
1860 return self.fork
1860 return self.fork
1861
1861
1862 @property
1862 @property
1863 def just_name(self):
1863 def just_name(self):
1864 return self.repo_name.split(self.NAME_SEP)[-1]
1864 return self.repo_name.split(self.NAME_SEP)[-1]
1865
1865
1866 @property
1866 @property
1867 def groups_with_parents(self):
1867 def groups_with_parents(self):
1868 groups = []
1868 groups = []
1869 if self.group is None:
1869 if self.group is None:
1870 return groups
1870 return groups
1871
1871
1872 cur_gr = self.group
1872 cur_gr = self.group
1873 groups.insert(0, cur_gr)
1873 groups.insert(0, cur_gr)
1874 while 1:
1874 while 1:
1875 gr = getattr(cur_gr, 'parent_group', None)
1875 gr = getattr(cur_gr, 'parent_group', None)
1876 cur_gr = cur_gr.parent_group
1876 cur_gr = cur_gr.parent_group
1877 if gr is None:
1877 if gr is None:
1878 break
1878 break
1879 groups.insert(0, gr)
1879 groups.insert(0, gr)
1880
1880
1881 return groups
1881 return groups
1882
1882
1883 @property
1883 @property
1884 def groups_and_repo(self):
1884 def groups_and_repo(self):
1885 return self.groups_with_parents, self
1885 return self.groups_with_parents, self
1886
1886
1887 @LazyProperty
1887 @LazyProperty
1888 def repo_path(self):
1888 def repo_path(self):
1889 """
1889 """
1890 Returns base full path for that repository means where it actually
1890 Returns base full path for that repository means where it actually
1891 exists on a filesystem
1891 exists on a filesystem
1892 """
1892 """
1893 q = Session().query(RhodeCodeUi).filter(
1893 q = Session().query(RhodeCodeUi).filter(
1894 RhodeCodeUi.ui_key == self.NAME_SEP)
1894 RhodeCodeUi.ui_key == self.NAME_SEP)
1895 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1895 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1896 return q.one().ui_value
1896 return q.one().ui_value
1897
1897
1898 @property
1898 @property
1899 def repo_full_path(self):
1899 def repo_full_path(self):
1900 p = [self.repo_path]
1900 p = [self.repo_path]
1901 # we need to split the name by / since this is how we store the
1901 # we need to split the name by / since this is how we store the
1902 # names in the database, but that eventually needs to be converted
1902 # names in the database, but that eventually needs to be converted
1903 # into a valid system path
1903 # into a valid system path
1904 p += self.repo_name.split(self.NAME_SEP)
1904 p += self.repo_name.split(self.NAME_SEP)
1905 return os.path.join(*map(safe_unicode, p))
1905 return os.path.join(*map(safe_unicode, p))
1906
1906
1907 @property
1907 @property
1908 def cache_keys(self):
1908 def cache_keys(self):
1909 """
1909 """
1910 Returns associated cache keys for that repo
1910 Returns associated cache keys for that repo
1911 """
1911 """
1912 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1912 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1913 repo_id=self.repo_id)
1913 repo_id=self.repo_id)
1914 return CacheKey.query()\
1914 return CacheKey.query()\
1915 .filter(CacheKey.cache_args == invalidation_namespace)\
1915 .filter(CacheKey.cache_args == invalidation_namespace)\
1916 .order_by(CacheKey.cache_key)\
1916 .order_by(CacheKey.cache_key)\
1917 .all()
1917 .all()
1918
1918
1919 @property
1919 @property
1920 def cached_diffs_relative_dir(self):
1920 def cached_diffs_relative_dir(self):
1921 """
1921 """
1922 Return a relative to the repository store path of cached diffs
1922 Return a relative to the repository store path of cached diffs
1923 used for safe display for users, who shouldn't know the absolute store
1923 used for safe display for users, who shouldn't know the absolute store
1924 path
1924 path
1925 """
1925 """
1926 return os.path.join(
1926 return os.path.join(
1927 os.path.dirname(self.repo_name),
1927 os.path.dirname(self.repo_name),
1928 self.cached_diffs_dir.split(os.path.sep)[-1])
1928 self.cached_diffs_dir.split(os.path.sep)[-1])
1929
1929
1930 @property
1930 @property
1931 def cached_diffs_dir(self):
1931 def cached_diffs_dir(self):
1932 path = self.repo_full_path
1932 path = self.repo_full_path
1933 return os.path.join(
1933 return os.path.join(
1934 os.path.dirname(path),
1934 os.path.dirname(path),
1935 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1935 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1936
1936
1937 def cached_diffs(self):
1937 def cached_diffs(self):
1938 diff_cache_dir = self.cached_diffs_dir
1938 diff_cache_dir = self.cached_diffs_dir
1939 if os.path.isdir(diff_cache_dir):
1939 if os.path.isdir(diff_cache_dir):
1940 return os.listdir(diff_cache_dir)
1940 return os.listdir(diff_cache_dir)
1941 return []
1941 return []
1942
1942
1943 def shadow_repos(self):
1943 def shadow_repos(self):
1944 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1944 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1945 return [
1945 return [
1946 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1946 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1947 if x.startswith(shadow_repos_pattern)]
1947 if x.startswith(shadow_repos_pattern)]
1948
1948
1949 def get_new_name(self, repo_name):
1949 def get_new_name(self, repo_name):
1950 """
1950 """
1951 returns new full repository name based on assigned group and new new
1951 returns new full repository name based on assigned group and new new
1952
1952
1953 :param group_name:
1953 :param group_name:
1954 """
1954 """
1955 path_prefix = self.group.full_path_splitted if self.group else []
1955 path_prefix = self.group.full_path_splitted if self.group else []
1956 return self.NAME_SEP.join(path_prefix + [repo_name])
1956 return self.NAME_SEP.join(path_prefix + [repo_name])
1957
1957
1958 @property
1958 @property
1959 def _config(self):
1959 def _config(self):
1960 """
1960 """
1961 Returns db based config object.
1961 Returns db based config object.
1962 """
1962 """
1963 from rhodecode.lib.utils import make_db_config
1963 from rhodecode.lib.utils import make_db_config
1964 return make_db_config(clear_session=False, repo=self)
1964 return make_db_config(clear_session=False, repo=self)
1965
1965
1966 def permissions(self, with_admins=True, with_owner=True,
1966 def permissions(self, with_admins=True, with_owner=True,
1967 expand_from_user_groups=False):
1967 expand_from_user_groups=False):
1968 """
1968 """
1969 Permissions for repositories
1969 Permissions for repositories
1970 """
1970 """
1971 _admin_perm = 'repository.admin'
1971 _admin_perm = 'repository.admin'
1972
1972
1973 owner_row = []
1973 owner_row = []
1974 if with_owner:
1974 if with_owner:
1975 usr = AttributeDict(self.user.get_dict())
1975 usr = AttributeDict(self.user.get_dict())
1976 usr.owner_row = True
1976 usr.owner_row = True
1977 usr.permission = _admin_perm
1977 usr.permission = _admin_perm
1978 usr.permission_id = None
1978 usr.permission_id = None
1979 owner_row.append(usr)
1979 owner_row.append(usr)
1980
1980
1981 super_admin_ids = []
1981 super_admin_ids = []
1982 super_admin_rows = []
1982 super_admin_rows = []
1983 if with_admins:
1983 if with_admins:
1984 for usr in User.get_all_super_admins():
1984 for usr in User.get_all_super_admins():
1985 super_admin_ids.append(usr.user_id)
1985 super_admin_ids.append(usr.user_id)
1986 # if this admin is also owner, don't double the record
1986 # if this admin is also owner, don't double the record
1987 if usr.user_id == owner_row[0].user_id:
1987 if usr.user_id == owner_row[0].user_id:
1988 owner_row[0].admin_row = True
1988 owner_row[0].admin_row = True
1989 else:
1989 else:
1990 usr = AttributeDict(usr.get_dict())
1990 usr = AttributeDict(usr.get_dict())
1991 usr.admin_row = True
1991 usr.admin_row = True
1992 usr.permission = _admin_perm
1992 usr.permission = _admin_perm
1993 usr.permission_id = None
1993 usr.permission_id = None
1994 super_admin_rows.append(usr)
1994 super_admin_rows.append(usr)
1995
1995
1996 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1996 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1997 q = q.options(joinedload(UserRepoToPerm.repository),
1997 q = q.options(joinedload(UserRepoToPerm.repository),
1998 joinedload(UserRepoToPerm.user),
1998 joinedload(UserRepoToPerm.user),
1999 joinedload(UserRepoToPerm.permission),)
1999 joinedload(UserRepoToPerm.permission),)
2000
2000
2001 # get owners and admins and permissions. We do a trick of re-writing
2001 # get owners and admins and permissions. We do a trick of re-writing
2002 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2002 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2003 # has a global reference and changing one object propagates to all
2003 # has a global reference and changing one object propagates to all
2004 # others. This means if admin is also an owner admin_row that change
2004 # others. This means if admin is also an owner admin_row that change
2005 # would propagate to both objects
2005 # would propagate to both objects
2006 perm_rows = []
2006 perm_rows = []
2007 for _usr in q.all():
2007 for _usr in q.all():
2008 usr = AttributeDict(_usr.user.get_dict())
2008 usr = AttributeDict(_usr.user.get_dict())
2009 # if this user is also owner/admin, mark as duplicate record
2009 # if this user is also owner/admin, mark as duplicate record
2010 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2010 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2011 usr.duplicate_perm = True
2011 usr.duplicate_perm = True
2012 # also check if this permission is maybe used by branch_permissions
2012 # also check if this permission is maybe used by branch_permissions
2013 if _usr.branch_perm_entry:
2013 if _usr.branch_perm_entry:
2014 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2014 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2015
2015
2016 usr.permission = _usr.permission.permission_name
2016 usr.permission = _usr.permission.permission_name
2017 usr.permission_id = _usr.repo_to_perm_id
2017 usr.permission_id = _usr.repo_to_perm_id
2018 perm_rows.append(usr)
2018 perm_rows.append(usr)
2019
2019
2020 # filter the perm rows by 'default' first and then sort them by
2020 # filter the perm rows by 'default' first and then sort them by
2021 # admin,write,read,none permissions sorted again alphabetically in
2021 # admin,write,read,none permissions sorted again alphabetically in
2022 # each group
2022 # each group
2023 perm_rows = sorted(perm_rows, key=display_user_sort)
2023 perm_rows = sorted(perm_rows, key=display_user_sort)
2024
2024
2025 user_groups_rows = []
2025 user_groups_rows = []
2026 if expand_from_user_groups:
2026 if expand_from_user_groups:
2027 for ug in self.permission_user_groups(with_members=True):
2027 for ug in self.permission_user_groups(with_members=True):
2028 for user_data in ug.members:
2028 for user_data in ug.members:
2029 user_groups_rows.append(user_data)
2029 user_groups_rows.append(user_data)
2030
2030
2031 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2031 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2032
2032
2033 def permission_user_groups(self, with_members=True):
2033 def permission_user_groups(self, with_members=True):
2034 q = UserGroupRepoToPerm.query()\
2034 q = UserGroupRepoToPerm.query()\
2035 .filter(UserGroupRepoToPerm.repository == self)
2035 .filter(UserGroupRepoToPerm.repository == self)
2036 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2036 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2037 joinedload(UserGroupRepoToPerm.users_group),
2037 joinedload(UserGroupRepoToPerm.users_group),
2038 joinedload(UserGroupRepoToPerm.permission),)
2038 joinedload(UserGroupRepoToPerm.permission),)
2039
2039
2040 perm_rows = []
2040 perm_rows = []
2041 for _user_group in q.all():
2041 for _user_group in q.all():
2042 entry = AttributeDict(_user_group.users_group.get_dict())
2042 entry = AttributeDict(_user_group.users_group.get_dict())
2043 entry.permission = _user_group.permission.permission_name
2043 entry.permission = _user_group.permission.permission_name
2044 if with_members:
2044 if with_members:
2045 entry.members = [x.user.get_dict()
2045 entry.members = [x.user.get_dict()
2046 for x in _user_group.users_group.members]
2046 for x in _user_group.users_group.members]
2047 perm_rows.append(entry)
2047 perm_rows.append(entry)
2048
2048
2049 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2049 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2050 return perm_rows
2050 return perm_rows
2051
2051
2052 def get_api_data(self, include_secrets=False):
2052 def get_api_data(self, include_secrets=False):
2053 """
2053 """
2054 Common function for generating repo api data
2054 Common function for generating repo api data
2055
2055
2056 :param include_secrets: See :meth:`User.get_api_data`.
2056 :param include_secrets: See :meth:`User.get_api_data`.
2057
2057
2058 """
2058 """
2059 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2059 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2060 # move this methods on models level.
2060 # move this methods on models level.
2061 from rhodecode.model.settings import SettingsModel
2061 from rhodecode.model.settings import SettingsModel
2062 from rhodecode.model.repo import RepoModel
2062 from rhodecode.model.repo import RepoModel
2063
2063
2064 repo = self
2064 repo = self
2065 _user_id, _time, _reason = self.locked
2065 _user_id, _time, _reason = self.locked
2066
2066
2067 data = {
2067 data = {
2068 'repo_id': repo.repo_id,
2068 'repo_id': repo.repo_id,
2069 'repo_name': repo.repo_name,
2069 'repo_name': repo.repo_name,
2070 'repo_type': repo.repo_type,
2070 'repo_type': repo.repo_type,
2071 'clone_uri': repo.clone_uri or '',
2071 'clone_uri': repo.clone_uri or '',
2072 'push_uri': repo.push_uri or '',
2072 'push_uri': repo.push_uri or '',
2073 'url': RepoModel().get_url(self),
2073 'url': RepoModel().get_url(self),
2074 'private': repo.private,
2074 'private': repo.private,
2075 'created_on': repo.created_on,
2075 'created_on': repo.created_on,
2076 'description': repo.description_safe,
2076 'description': repo.description_safe,
2077 'landing_rev': repo.landing_rev,
2077 'landing_rev': repo.landing_rev,
2078 'owner': repo.user.username,
2078 'owner': repo.user.username,
2079 'fork_of': repo.fork.repo_name if repo.fork else None,
2079 'fork_of': repo.fork.repo_name if repo.fork else None,
2080 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2080 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2081 'enable_statistics': repo.enable_statistics,
2081 'enable_statistics': repo.enable_statistics,
2082 'enable_locking': repo.enable_locking,
2082 'enable_locking': repo.enable_locking,
2083 'enable_downloads': repo.enable_downloads,
2083 'enable_downloads': repo.enable_downloads,
2084 'last_changeset': repo.changeset_cache,
2084 'last_changeset': repo.changeset_cache,
2085 'locked_by': User.get(_user_id).get_api_data(
2085 'locked_by': User.get(_user_id).get_api_data(
2086 include_secrets=include_secrets) if _user_id else None,
2086 include_secrets=include_secrets) if _user_id else None,
2087 'locked_date': time_to_datetime(_time) if _time else None,
2087 'locked_date': time_to_datetime(_time) if _time else None,
2088 'lock_reason': _reason if _reason else None,
2088 'lock_reason': _reason if _reason else None,
2089 }
2089 }
2090
2090
2091 # TODO: mikhail: should be per-repo settings here
2091 # TODO: mikhail: should be per-repo settings here
2092 rc_config = SettingsModel().get_all_settings()
2092 rc_config = SettingsModel().get_all_settings()
2093 repository_fields = str2bool(
2093 repository_fields = str2bool(
2094 rc_config.get('rhodecode_repository_fields'))
2094 rc_config.get('rhodecode_repository_fields'))
2095 if repository_fields:
2095 if repository_fields:
2096 for f in self.extra_fields:
2096 for f in self.extra_fields:
2097 data[f.field_key_prefixed] = f.field_value
2097 data[f.field_key_prefixed] = f.field_value
2098
2098
2099 return data
2099 return data
2100
2100
2101 @classmethod
2101 @classmethod
2102 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2102 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2103 if not lock_time:
2103 if not lock_time:
2104 lock_time = time.time()
2104 lock_time = time.time()
2105 if not lock_reason:
2105 if not lock_reason:
2106 lock_reason = cls.LOCK_AUTOMATIC
2106 lock_reason = cls.LOCK_AUTOMATIC
2107 repo.locked = [user_id, lock_time, lock_reason]
2107 repo.locked = [user_id, lock_time, lock_reason]
2108 Session().add(repo)
2108 Session().add(repo)
2109 Session().commit()
2109 Session().commit()
2110
2110
2111 @classmethod
2111 @classmethod
2112 def unlock(cls, repo):
2112 def unlock(cls, repo):
2113 repo.locked = None
2113 repo.locked = None
2114 Session().add(repo)
2114 Session().add(repo)
2115 Session().commit()
2115 Session().commit()
2116
2116
2117 @classmethod
2117 @classmethod
2118 def getlock(cls, repo):
2118 def getlock(cls, repo):
2119 return repo.locked
2119 return repo.locked
2120
2120
2121 def is_user_lock(self, user_id):
2121 def is_user_lock(self, user_id):
2122 if self.lock[0]:
2122 if self.lock[0]:
2123 lock_user_id = safe_int(self.lock[0])
2123 lock_user_id = safe_int(self.lock[0])
2124 user_id = safe_int(user_id)
2124 user_id = safe_int(user_id)
2125 # both are ints, and they are equal
2125 # both are ints, and they are equal
2126 return all([lock_user_id, user_id]) and lock_user_id == user_id
2126 return all([lock_user_id, user_id]) and lock_user_id == user_id
2127
2127
2128 return False
2128 return False
2129
2129
2130 def get_locking_state(self, action, user_id, only_when_enabled=True):
2130 def get_locking_state(self, action, user_id, only_when_enabled=True):
2131 """
2131 """
2132 Checks locking on this repository, if locking is enabled and lock is
2132 Checks locking on this repository, if locking is enabled and lock is
2133 present returns a tuple of make_lock, locked, locked_by.
2133 present returns a tuple of make_lock, locked, locked_by.
2134 make_lock can have 3 states None (do nothing) True, make lock
2134 make_lock can have 3 states None (do nothing) True, make lock
2135 False release lock, This value is later propagated to hooks, which
2135 False release lock, This value is later propagated to hooks, which
2136 do the locking. Think about this as signals passed to hooks what to do.
2136 do the locking. Think about this as signals passed to hooks what to do.
2137
2137
2138 """
2138 """
2139 # TODO: johbo: This is part of the business logic and should be moved
2139 # TODO: johbo: This is part of the business logic and should be moved
2140 # into the RepositoryModel.
2140 # into the RepositoryModel.
2141
2141
2142 if action not in ('push', 'pull'):
2142 if action not in ('push', 'pull'):
2143 raise ValueError("Invalid action value: %s" % repr(action))
2143 raise ValueError("Invalid action value: %s" % repr(action))
2144
2144
2145 # defines if locked error should be thrown to user
2145 # defines if locked error should be thrown to user
2146 currently_locked = False
2146 currently_locked = False
2147 # defines if new lock should be made, tri-state
2147 # defines if new lock should be made, tri-state
2148 make_lock = None
2148 make_lock = None
2149 repo = self
2149 repo = self
2150 user = User.get(user_id)
2150 user = User.get(user_id)
2151
2151
2152 lock_info = repo.locked
2152 lock_info = repo.locked
2153
2153
2154 if repo and (repo.enable_locking or not only_when_enabled):
2154 if repo and (repo.enable_locking or not only_when_enabled):
2155 if action == 'push':
2155 if action == 'push':
2156 # check if it's already locked !, if it is compare users
2156 # check if it's already locked !, if it is compare users
2157 locked_by_user_id = lock_info[0]
2157 locked_by_user_id = lock_info[0]
2158 if user.user_id == locked_by_user_id:
2158 if user.user_id == locked_by_user_id:
2159 log.debug(
2159 log.debug(
2160 'Got `push` action from user %s, now unlocking', user)
2160 'Got `push` action from user %s, now unlocking', user)
2161 # unlock if we have push from user who locked
2161 # unlock if we have push from user who locked
2162 make_lock = False
2162 make_lock = False
2163 else:
2163 else:
2164 # we're not the same user who locked, ban with
2164 # we're not the same user who locked, ban with
2165 # code defined in settings (default is 423 HTTP Locked) !
2165 # code defined in settings (default is 423 HTTP Locked) !
2166 log.debug('Repo %s is currently locked by %s', repo, user)
2166 log.debug('Repo %s is currently locked by %s', repo, user)
2167 currently_locked = True
2167 currently_locked = True
2168 elif action == 'pull':
2168 elif action == 'pull':
2169 # [0] user [1] date
2169 # [0] user [1] date
2170 if lock_info[0] and lock_info[1]:
2170 if lock_info[0] and lock_info[1]:
2171 log.debug('Repo %s is currently locked by %s', repo, user)
2171 log.debug('Repo %s is currently locked by %s', repo, user)
2172 currently_locked = True
2172 currently_locked = True
2173 else:
2173 else:
2174 log.debug('Setting lock on repo %s by %s', repo, user)
2174 log.debug('Setting lock on repo %s by %s', repo, user)
2175 make_lock = True
2175 make_lock = True
2176
2176
2177 else:
2177 else:
2178 log.debug('Repository %s do not have locking enabled', repo)
2178 log.debug('Repository %s do not have locking enabled', repo)
2179
2179
2180 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2180 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2181 make_lock, currently_locked, lock_info)
2181 make_lock, currently_locked, lock_info)
2182
2182
2183 from rhodecode.lib.auth import HasRepoPermissionAny
2183 from rhodecode.lib.auth import HasRepoPermissionAny
2184 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2184 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2185 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2185 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2186 # if we don't have at least write permission we cannot make a lock
2186 # if we don't have at least write permission we cannot make a lock
2187 log.debug('lock state reset back to FALSE due to lack '
2187 log.debug('lock state reset back to FALSE due to lack '
2188 'of at least read permission')
2188 'of at least read permission')
2189 make_lock = False
2189 make_lock = False
2190
2190
2191 return make_lock, currently_locked, lock_info
2191 return make_lock, currently_locked, lock_info
2192
2192
2193 @property
2193 @property
2194 def last_commit_cache_update_diff(self):
2194 def last_commit_cache_update_diff(self):
2195 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2195 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2196
2196
2197 @property
2197 @property
2198 def last_commit_change(self):
2198 def last_commit_change(self):
2199 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2199 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2200 empty_date = datetime.datetime.fromtimestamp(0)
2200 empty_date = datetime.datetime.fromtimestamp(0)
2201 date_latest = self.changeset_cache.get('date', empty_date)
2201 date_latest = self.changeset_cache.get('date', empty_date)
2202 try:
2202 try:
2203 return parse_datetime(date_latest)
2203 return parse_datetime(date_latest)
2204 except Exception:
2204 except Exception:
2205 return empty_date
2205 return empty_date
2206
2206
2207 @property
2207 @property
2208 def last_db_change(self):
2208 def last_db_change(self):
2209 return self.updated_on
2209 return self.updated_on
2210
2210
2211 @property
2211 @property
2212 def clone_uri_hidden(self):
2212 def clone_uri_hidden(self):
2213 clone_uri = self.clone_uri
2213 clone_uri = self.clone_uri
2214 if clone_uri:
2214 if clone_uri:
2215 import urlobject
2215 import urlobject
2216 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2216 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2217 if url_obj.password:
2217 if url_obj.password:
2218 clone_uri = url_obj.with_password('*****')
2218 clone_uri = url_obj.with_password('*****')
2219 return clone_uri
2219 return clone_uri
2220
2220
2221 @property
2221 @property
2222 def push_uri_hidden(self):
2222 def push_uri_hidden(self):
2223 push_uri = self.push_uri
2223 push_uri = self.push_uri
2224 if push_uri:
2224 if push_uri:
2225 import urlobject
2225 import urlobject
2226 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2226 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2227 if url_obj.password:
2227 if url_obj.password:
2228 push_uri = url_obj.with_password('*****')
2228 push_uri = url_obj.with_password('*****')
2229 return push_uri
2229 return push_uri
2230
2230
2231 def clone_url(self, **override):
2231 def clone_url(self, **override):
2232 from rhodecode.model.settings import SettingsModel
2232 from rhodecode.model.settings import SettingsModel
2233
2233
2234 uri_tmpl = None
2234 uri_tmpl = None
2235 if 'with_id' in override:
2235 if 'with_id' in override:
2236 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2236 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2237 del override['with_id']
2237 del override['with_id']
2238
2238
2239 if 'uri_tmpl' in override:
2239 if 'uri_tmpl' in override:
2240 uri_tmpl = override['uri_tmpl']
2240 uri_tmpl = override['uri_tmpl']
2241 del override['uri_tmpl']
2241 del override['uri_tmpl']
2242
2242
2243 ssh = False
2243 ssh = False
2244 if 'ssh' in override:
2244 if 'ssh' in override:
2245 ssh = True
2245 ssh = True
2246 del override['ssh']
2246 del override['ssh']
2247
2247
2248 # we didn't override our tmpl from **overrides
2248 # we didn't override our tmpl from **overrides
2249 request = get_current_request()
2249 request = get_current_request()
2250 if not uri_tmpl:
2250 if not uri_tmpl:
2251 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2251 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2252 rc_config = request.call_context.rc_config
2252 rc_config = request.call_context.rc_config
2253 else:
2253 else:
2254 rc_config = SettingsModel().get_all_settings(cache=True)
2254 rc_config = SettingsModel().get_all_settings(cache=True)
2255 if ssh:
2255 if ssh:
2256 uri_tmpl = rc_config.get(
2256 uri_tmpl = rc_config.get(
2257 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2257 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2258 else:
2258 else:
2259 uri_tmpl = rc_config.get(
2259 uri_tmpl = rc_config.get(
2260 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2260 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2261
2261
2262 return get_clone_url(request=request,
2262 return get_clone_url(request=request,
2263 uri_tmpl=uri_tmpl,
2263 uri_tmpl=uri_tmpl,
2264 repo_name=self.repo_name,
2264 repo_name=self.repo_name,
2265 repo_id=self.repo_id, **override)
2265 repo_id=self.repo_id, **override)
2266
2266
2267 def set_state(self, state):
2267 def set_state(self, state):
2268 self.repo_state = state
2268 self.repo_state = state
2269 Session().add(self)
2269 Session().add(self)
2270 #==========================================================================
2270 #==========================================================================
2271 # SCM PROPERTIES
2271 # SCM PROPERTIES
2272 #==========================================================================
2272 #==========================================================================
2273
2273
2274 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2274 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2275 return get_commit_safe(
2275 return get_commit_safe(
2276 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2276 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2277
2277
2278 def get_changeset(self, rev=None, pre_load=None):
2278 def get_changeset(self, rev=None, pre_load=None):
2279 warnings.warn("Use get_commit", DeprecationWarning)
2279 warnings.warn("Use get_commit", DeprecationWarning)
2280 commit_id = None
2280 commit_id = None
2281 commit_idx = None
2281 commit_idx = None
2282 if isinstance(rev, compat.string_types):
2282 if isinstance(rev, compat.string_types):
2283 commit_id = rev
2283 commit_id = rev
2284 else:
2284 else:
2285 commit_idx = rev
2285 commit_idx = rev
2286 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2286 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2287 pre_load=pre_load)
2287 pre_load=pre_load)
2288
2288
2289 def get_landing_commit(self):
2289 def get_landing_commit(self):
2290 """
2290 """
2291 Returns landing commit, or if that doesn't exist returns the tip
2291 Returns landing commit, or if that doesn't exist returns the tip
2292 """
2292 """
2293 _rev_type, _rev = self.landing_rev
2293 _rev_type, _rev = self.landing_rev
2294 commit = self.get_commit(_rev)
2294 commit = self.get_commit(_rev)
2295 if isinstance(commit, EmptyCommit):
2295 if isinstance(commit, EmptyCommit):
2296 return self.get_commit()
2296 return self.get_commit()
2297 return commit
2297 return commit
2298
2298
2299 def update_commit_cache(self, cs_cache=None, config=None):
2299 def update_commit_cache(self, cs_cache=None, config=None):
2300 """
2300 """
2301 Update cache of last commit for repository, keys should be::
2301 Update cache of last commit for repository, keys should be::
2302
2302
2303 source_repo_id
2303 source_repo_id
2304 short_id
2304 short_id
2305 raw_id
2305 raw_id
2306 revision
2306 revision
2307 parents
2307 parents
2308 message
2308 message
2309 date
2309 date
2310 author
2310 author
2311 updated_on
2311 updated_on
2312
2312
2313 """
2313 """
2314 from rhodecode.lib.vcs.backends.base import BaseChangeset
2314 from rhodecode.lib.vcs.backends.base import BaseChangeset
2315 if cs_cache is None:
2315 if cs_cache is None:
2316 # use no-cache version here
2316 # use no-cache version here
2317 scm_repo = self.scm_instance(cache=False, config=config)
2317 scm_repo = self.scm_instance(cache=False, config=config)
2318
2318
2319 empty = scm_repo is None or scm_repo.is_empty()
2319 empty = scm_repo is None or scm_repo.is_empty()
2320 if not empty:
2320 if not empty:
2321 cs_cache = scm_repo.get_commit(
2321 cs_cache = scm_repo.get_commit(
2322 pre_load=["author", "date", "message", "parents", "branch"])
2322 pre_load=["author", "date", "message", "parents", "branch"])
2323 else:
2323 else:
2324 cs_cache = EmptyCommit()
2324 cs_cache = EmptyCommit()
2325
2325
2326 if isinstance(cs_cache, BaseChangeset):
2326 if isinstance(cs_cache, BaseChangeset):
2327 cs_cache = cs_cache.__json__()
2327 cs_cache = cs_cache.__json__()
2328
2328
2329 def is_outdated(new_cs_cache):
2329 def is_outdated(new_cs_cache):
2330 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2330 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2331 new_cs_cache['revision'] != self.changeset_cache['revision']):
2331 new_cs_cache['revision'] != self.changeset_cache['revision']):
2332 return True
2332 return True
2333 return False
2333 return False
2334
2334
2335 # check if we have maybe already latest cached revision
2335 # check if we have maybe already latest cached revision
2336 if is_outdated(cs_cache) or not self.changeset_cache:
2336 if is_outdated(cs_cache) or not self.changeset_cache:
2337 _default = datetime.datetime.utcnow()
2337 _default = datetime.datetime.utcnow()
2338 last_change = cs_cache.get('date') or _default
2338 last_change = cs_cache.get('date') or _default
2339 # we check if last update is newer than the new value
2339 # we check if last update is newer than the new value
2340 # if yes, we use the current timestamp instead. Imagine you get
2340 # if yes, we use the current timestamp instead. Imagine you get
2341 # old commit pushed 1y ago, we'd set last update 1y to ago.
2341 # old commit pushed 1y ago, we'd set last update 1y to ago.
2342 last_change_timestamp = datetime_to_time(last_change)
2342 last_change_timestamp = datetime_to_time(last_change)
2343 current_timestamp = datetime_to_time(last_change)
2343 current_timestamp = datetime_to_time(last_change)
2344 if last_change_timestamp > current_timestamp:
2344 if last_change_timestamp > current_timestamp:
2345 cs_cache['date'] = _default
2345 cs_cache['date'] = _default
2346
2346
2347 cs_cache['updated_on'] = time.time()
2347 cs_cache['updated_on'] = time.time()
2348 self.changeset_cache = cs_cache
2348 self.changeset_cache = cs_cache
2349 Session().add(self)
2349 Session().add(self)
2350 Session().commit()
2350 Session().commit()
2351
2351
2352 log.debug('updated repo %s with new commit cache %s',
2352 log.debug('updated repo %s with new commit cache %s',
2353 self.repo_name, cs_cache)
2353 self.repo_name, cs_cache)
2354 else:
2354 else:
2355 cs_cache = self.changeset_cache
2355 cs_cache = self.changeset_cache
2356 cs_cache['updated_on'] = time.time()
2356 cs_cache['updated_on'] = time.time()
2357 self.changeset_cache = cs_cache
2357 self.changeset_cache = cs_cache
2358 Session().add(self)
2358 Session().add(self)
2359 Session().commit()
2359 Session().commit()
2360
2360
2361 log.debug('Skipping update_commit_cache for repo:`%s` '
2361 log.debug('Skipping update_commit_cache for repo:`%s` '
2362 'commit already with latest changes', self.repo_name)
2362 'commit already with latest changes', self.repo_name)
2363
2363
2364 @property
2364 @property
2365 def tip(self):
2365 def tip(self):
2366 return self.get_commit('tip')
2366 return self.get_commit('tip')
2367
2367
2368 @property
2368 @property
2369 def author(self):
2369 def author(self):
2370 return self.tip.author
2370 return self.tip.author
2371
2371
2372 @property
2372 @property
2373 def last_change(self):
2373 def last_change(self):
2374 return self.scm_instance().last_change
2374 return self.scm_instance().last_change
2375
2375
2376 def get_comments(self, revisions=None):
2376 def get_comments(self, revisions=None):
2377 """
2377 """
2378 Returns comments for this repository grouped by revisions
2378 Returns comments for this repository grouped by revisions
2379
2379
2380 :param revisions: filter query by revisions only
2380 :param revisions: filter query by revisions only
2381 """
2381 """
2382 cmts = ChangesetComment.query()\
2382 cmts = ChangesetComment.query()\
2383 .filter(ChangesetComment.repo == self)
2383 .filter(ChangesetComment.repo == self)
2384 if revisions:
2384 if revisions:
2385 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2385 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2386 grouped = collections.defaultdict(list)
2386 grouped = collections.defaultdict(list)
2387 for cmt in cmts.all():
2387 for cmt in cmts.all():
2388 grouped[cmt.revision].append(cmt)
2388 grouped[cmt.revision].append(cmt)
2389 return grouped
2389 return grouped
2390
2390
2391 def statuses(self, revisions=None):
2391 def statuses(self, revisions=None):
2392 """
2392 """
2393 Returns statuses for this repository
2393 Returns statuses for this repository
2394
2394
2395 :param revisions: list of revisions to get statuses for
2395 :param revisions: list of revisions to get statuses for
2396 """
2396 """
2397 statuses = ChangesetStatus.query()\
2397 statuses = ChangesetStatus.query()\
2398 .filter(ChangesetStatus.repo == self)\
2398 .filter(ChangesetStatus.repo == self)\
2399 .filter(ChangesetStatus.version == 0)
2399 .filter(ChangesetStatus.version == 0)
2400
2400
2401 if revisions:
2401 if revisions:
2402 # Try doing the filtering in chunks to avoid hitting limits
2402 # Try doing the filtering in chunks to avoid hitting limits
2403 size = 500
2403 size = 500
2404 status_results = []
2404 status_results = []
2405 for chunk in xrange(0, len(revisions), size):
2405 for chunk in xrange(0, len(revisions), size):
2406 status_results += statuses.filter(
2406 status_results += statuses.filter(
2407 ChangesetStatus.revision.in_(
2407 ChangesetStatus.revision.in_(
2408 revisions[chunk: chunk+size])
2408 revisions[chunk: chunk+size])
2409 ).all()
2409 ).all()
2410 else:
2410 else:
2411 status_results = statuses.all()
2411 status_results = statuses.all()
2412
2412
2413 grouped = {}
2413 grouped = {}
2414
2414
2415 # maybe we have open new pullrequest without a status?
2415 # maybe we have open new pullrequest without a status?
2416 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2416 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2417 status_lbl = ChangesetStatus.get_status_lbl(stat)
2417 status_lbl = ChangesetStatus.get_status_lbl(stat)
2418 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2418 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2419 for rev in pr.revisions:
2419 for rev in pr.revisions:
2420 pr_id = pr.pull_request_id
2420 pr_id = pr.pull_request_id
2421 pr_repo = pr.target_repo.repo_name
2421 pr_repo = pr.target_repo.repo_name
2422 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2422 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2423
2423
2424 for stat in status_results:
2424 for stat in status_results:
2425 pr_id = pr_repo = None
2425 pr_id = pr_repo = None
2426 if stat.pull_request:
2426 if stat.pull_request:
2427 pr_id = stat.pull_request.pull_request_id
2427 pr_id = stat.pull_request.pull_request_id
2428 pr_repo = stat.pull_request.target_repo.repo_name
2428 pr_repo = stat.pull_request.target_repo.repo_name
2429 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2429 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2430 pr_id, pr_repo]
2430 pr_id, pr_repo]
2431 return grouped
2431 return grouped
2432
2432
2433 # ==========================================================================
2433 # ==========================================================================
2434 # SCM CACHE INSTANCE
2434 # SCM CACHE INSTANCE
2435 # ==========================================================================
2435 # ==========================================================================
2436
2436
2437 def scm_instance(self, **kwargs):
2437 def scm_instance(self, **kwargs):
2438 import rhodecode
2438 import rhodecode
2439
2439
2440 # Passing a config will not hit the cache currently only used
2440 # Passing a config will not hit the cache currently only used
2441 # for repo2dbmapper
2441 # for repo2dbmapper
2442 config = kwargs.pop('config', None)
2442 config = kwargs.pop('config', None)
2443 cache = kwargs.pop('cache', None)
2443 cache = kwargs.pop('cache', None)
2444 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2444 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2445 if vcs_full_cache is not None:
2445 if vcs_full_cache is not None:
2446 # allows override global config
2446 # allows override global config
2447 full_cache = vcs_full_cache
2447 full_cache = vcs_full_cache
2448 else:
2448 else:
2449 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2449 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2450 # if cache is NOT defined use default global, else we have a full
2450 # if cache is NOT defined use default global, else we have a full
2451 # control over cache behaviour
2451 # control over cache behaviour
2452 if cache is None and full_cache and not config:
2452 if cache is None and full_cache and not config:
2453 log.debug('Initializing pure cached instance for %s', self.repo_path)
2453 log.debug('Initializing pure cached instance for %s', self.repo_path)
2454 return self._get_instance_cached()
2454 return self._get_instance_cached()
2455
2455
2456 # cache here is sent to the "vcs server"
2456 # cache here is sent to the "vcs server"
2457 return self._get_instance(cache=bool(cache), config=config)
2457 return self._get_instance(cache=bool(cache), config=config)
2458
2458
2459 def _get_instance_cached(self):
2459 def _get_instance_cached(self):
2460 from rhodecode.lib import rc_cache
2460 from rhodecode.lib import rc_cache
2461
2461
2462 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2462 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2463 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2463 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2464 repo_id=self.repo_id)
2464 repo_id=self.repo_id)
2465 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2465 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2466
2466
2467 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2467 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2468 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2468 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2469 return self._get_instance(repo_state_uid=_cache_state_uid)
2469 return self._get_instance(repo_state_uid=_cache_state_uid)
2470
2470
2471 # we must use thread scoped cache here,
2471 # we must use thread scoped cache here,
2472 # because each thread of gevent needs it's own not shared connection and cache
2472 # because each thread of gevent needs it's own not shared connection and cache
2473 # we also alter `args` so the cache key is individual for every green thread.
2473 # we also alter `args` so the cache key is individual for every green thread.
2474 inv_context_manager = rc_cache.InvalidationContext(
2474 inv_context_manager = rc_cache.InvalidationContext(
2475 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2475 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2476 thread_scoped=True)
2476 thread_scoped=True)
2477 with inv_context_manager as invalidation_context:
2477 with inv_context_manager as invalidation_context:
2478 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2478 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2479 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2479 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2480
2480
2481 # re-compute and store cache if we get invalidate signal
2481 # re-compute and store cache if we get invalidate signal
2482 if invalidation_context.should_invalidate():
2482 if invalidation_context.should_invalidate():
2483 instance = get_instance_cached.refresh(*args)
2483 instance = get_instance_cached.refresh(*args)
2484 else:
2484 else:
2485 instance = get_instance_cached(*args)
2485 instance = get_instance_cached(*args)
2486
2486
2487 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2487 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2488 return instance
2488 return instance
2489
2489
2490 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2490 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2491 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2491 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2492 self.repo_type, self.repo_path, cache)
2492 self.repo_type, self.repo_path, cache)
2493 config = config or self._config
2493 config = config or self._config
2494 custom_wire = {
2494 custom_wire = {
2495 'cache': cache, # controls the vcs.remote cache
2495 'cache': cache, # controls the vcs.remote cache
2496 'repo_state_uid': repo_state_uid
2496 'repo_state_uid': repo_state_uid
2497 }
2497 }
2498 repo = get_vcs_instance(
2498 repo = get_vcs_instance(
2499 repo_path=safe_str(self.repo_full_path),
2499 repo_path=safe_str(self.repo_full_path),
2500 config=config,
2500 config=config,
2501 with_wire=custom_wire,
2501 with_wire=custom_wire,
2502 create=False,
2502 create=False,
2503 _vcs_alias=self.repo_type)
2503 _vcs_alias=self.repo_type)
2504 if repo is not None:
2504 if repo is not None:
2505 repo.count() # cache rebuild
2505 repo.count() # cache rebuild
2506 return repo
2506 return repo
2507
2507
2508 def get_shadow_repository_path(self, workspace_id):
2508 def get_shadow_repository_path(self, workspace_id):
2509 from rhodecode.lib.vcs.backends.base import BaseRepository
2509 from rhodecode.lib.vcs.backends.base import BaseRepository
2510 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2510 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2511 self.repo_full_path, self.repo_id, workspace_id)
2511 self.repo_full_path, self.repo_id, workspace_id)
2512 return shadow_repo_path
2512 return shadow_repo_path
2513
2513
2514 def __json__(self):
2514 def __json__(self):
2515 return {'landing_rev': self.landing_rev}
2515 return {'landing_rev': self.landing_rev}
2516
2516
2517 def get_dict(self):
2517 def get_dict(self):
2518
2518
2519 # Since we transformed `repo_name` to a hybrid property, we need to
2519 # Since we transformed `repo_name` to a hybrid property, we need to
2520 # keep compatibility with the code which uses `repo_name` field.
2520 # keep compatibility with the code which uses `repo_name` field.
2521
2521
2522 result = super(Repository, self).get_dict()
2522 result = super(Repository, self).get_dict()
2523 result['repo_name'] = result.pop('_repo_name', None)
2523 result['repo_name'] = result.pop('_repo_name', None)
2524 return result
2524 return result
2525
2525
2526
2526
2527 class RepoGroup(Base, BaseModel):
2527 class RepoGroup(Base, BaseModel):
2528 __tablename__ = 'groups'
2528 __tablename__ = 'groups'
2529 __table_args__ = (
2529 __table_args__ = (
2530 UniqueConstraint('group_name', 'group_parent_id'),
2530 UniqueConstraint('group_name', 'group_parent_id'),
2531 base_table_args,
2531 base_table_args,
2532 )
2532 )
2533 __mapper_args__ = {'order_by': 'group_name'}
2533 __mapper_args__ = {'order_by': 'group_name'}
2534
2534
2535 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2535 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2536
2536
2537 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2537 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2538 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2538 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2539 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2539 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2540 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2540 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2541 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2541 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2542 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2542 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2543 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2543 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2544 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2544 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2545 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2545 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2546 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2546 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2547 _changeset_cache = Column(
2547 _changeset_cache = Column(
2548 "changeset_cache", LargeBinary(), nullable=True) # JSON data
2548 "changeset_cache", LargeBinary(), nullable=True) # JSON data
2549
2549
2550 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2550 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2551 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2551 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2552 parent_group = relationship('RepoGroup', remote_side=group_id)
2552 parent_group = relationship('RepoGroup', remote_side=group_id)
2553 user = relationship('User')
2553 user = relationship('User')
2554 integrations = relationship('Integration', cascade="all, delete, delete-orphan")
2554 integrations = relationship('Integration', cascade="all, delete-orphan")
2555
2555
2556 def __init__(self, group_name='', parent_group=None):
2556 def __init__(self, group_name='', parent_group=None):
2557 self.group_name = group_name
2557 self.group_name = group_name
2558 self.parent_group = parent_group
2558 self.parent_group = parent_group
2559
2559
2560 def __unicode__(self):
2560 def __unicode__(self):
2561 return u"<%s('id:%s:%s')>" % (
2561 return u"<%s('id:%s:%s')>" % (
2562 self.__class__.__name__, self.group_id, self.group_name)
2562 self.__class__.__name__, self.group_id, self.group_name)
2563
2563
2564 @hybrid_property
2564 @hybrid_property
2565 def group_name(self):
2565 def group_name(self):
2566 return self._group_name
2566 return self._group_name
2567
2567
2568 @group_name.setter
2568 @group_name.setter
2569 def group_name(self, value):
2569 def group_name(self, value):
2570 self._group_name = value
2570 self._group_name = value
2571 self.group_name_hash = self.hash_repo_group_name(value)
2571 self.group_name_hash = self.hash_repo_group_name(value)
2572
2572
2573 @hybrid_property
2573 @hybrid_property
2574 def changeset_cache(self):
2574 def changeset_cache(self):
2575 from rhodecode.lib.vcs.backends.base import EmptyCommit
2575 from rhodecode.lib.vcs.backends.base import EmptyCommit
2576 dummy = EmptyCommit().__json__()
2576 dummy = EmptyCommit().__json__()
2577 if not self._changeset_cache:
2577 if not self._changeset_cache:
2578 dummy['source_repo_id'] = ''
2578 dummy['source_repo_id'] = ''
2579 return json.loads(json.dumps(dummy))
2579 return json.loads(json.dumps(dummy))
2580
2580
2581 try:
2581 try:
2582 return json.loads(self._changeset_cache)
2582 return json.loads(self._changeset_cache)
2583 except TypeError:
2583 except TypeError:
2584 return dummy
2584 return dummy
2585 except Exception:
2585 except Exception:
2586 log.error(traceback.format_exc())
2586 log.error(traceback.format_exc())
2587 return dummy
2587 return dummy
2588
2588
2589 @changeset_cache.setter
2589 @changeset_cache.setter
2590 def changeset_cache(self, val):
2590 def changeset_cache(self, val):
2591 try:
2591 try:
2592 self._changeset_cache = json.dumps(val)
2592 self._changeset_cache = json.dumps(val)
2593 except Exception:
2593 except Exception:
2594 log.error(traceback.format_exc())
2594 log.error(traceback.format_exc())
2595
2595
2596 @validates('group_parent_id')
2596 @validates('group_parent_id')
2597 def validate_group_parent_id(self, key, val):
2597 def validate_group_parent_id(self, key, val):
2598 """
2598 """
2599 Check cycle references for a parent group to self
2599 Check cycle references for a parent group to self
2600 """
2600 """
2601 if self.group_id and val:
2601 if self.group_id and val:
2602 assert val != self.group_id
2602 assert val != self.group_id
2603
2603
2604 return val
2604 return val
2605
2605
2606 @hybrid_property
2606 @hybrid_property
2607 def description_safe(self):
2607 def description_safe(self):
2608 from rhodecode.lib import helpers as h
2608 from rhodecode.lib import helpers as h
2609 return h.escape(self.group_description)
2609 return h.escape(self.group_description)
2610
2610
2611 @classmethod
2611 @classmethod
2612 def hash_repo_group_name(cls, repo_group_name):
2612 def hash_repo_group_name(cls, repo_group_name):
2613 val = remove_formatting(repo_group_name)
2613 val = remove_formatting(repo_group_name)
2614 val = safe_str(val).lower()
2614 val = safe_str(val).lower()
2615 chars = []
2615 chars = []
2616 for c in val:
2616 for c in val:
2617 if c not in string.ascii_letters:
2617 if c not in string.ascii_letters:
2618 c = str(ord(c))
2618 c = str(ord(c))
2619 chars.append(c)
2619 chars.append(c)
2620
2620
2621 return ''.join(chars)
2621 return ''.join(chars)
2622
2622
2623 @classmethod
2623 @classmethod
2624 def _generate_choice(cls, repo_group):
2624 def _generate_choice(cls, repo_group):
2625 from webhelpers.html import literal as _literal
2625 from webhelpers.html import literal as _literal
2626 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2626 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2627 return repo_group.group_id, _name(repo_group.full_path_splitted)
2627 return repo_group.group_id, _name(repo_group.full_path_splitted)
2628
2628
2629 @classmethod
2629 @classmethod
2630 def groups_choices(cls, groups=None, show_empty_group=True):
2630 def groups_choices(cls, groups=None, show_empty_group=True):
2631 if not groups:
2631 if not groups:
2632 groups = cls.query().all()
2632 groups = cls.query().all()
2633
2633
2634 repo_groups = []
2634 repo_groups = []
2635 if show_empty_group:
2635 if show_empty_group:
2636 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2636 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2637
2637
2638 repo_groups.extend([cls._generate_choice(x) for x in groups])
2638 repo_groups.extend([cls._generate_choice(x) for x in groups])
2639
2639
2640 repo_groups = sorted(
2640 repo_groups = sorted(
2641 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2641 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2642 return repo_groups
2642 return repo_groups
2643
2643
2644 @classmethod
2644 @classmethod
2645 def url_sep(cls):
2645 def url_sep(cls):
2646 return URL_SEP
2646 return URL_SEP
2647
2647
2648 @classmethod
2648 @classmethod
2649 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2649 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2650 if case_insensitive:
2650 if case_insensitive:
2651 gr = cls.query().filter(func.lower(cls.group_name)
2651 gr = cls.query().filter(func.lower(cls.group_name)
2652 == func.lower(group_name))
2652 == func.lower(group_name))
2653 else:
2653 else:
2654 gr = cls.query().filter(cls.group_name == group_name)
2654 gr = cls.query().filter(cls.group_name == group_name)
2655 if cache:
2655 if cache:
2656 name_key = _hash_key(group_name)
2656 name_key = _hash_key(group_name)
2657 gr = gr.options(
2657 gr = gr.options(
2658 FromCache("sql_cache_short", "get_group_%s" % name_key))
2658 FromCache("sql_cache_short", "get_group_%s" % name_key))
2659 return gr.scalar()
2659 return gr.scalar()
2660
2660
2661 @classmethod
2661 @classmethod
2662 def get_user_personal_repo_group(cls, user_id):
2662 def get_user_personal_repo_group(cls, user_id):
2663 user = User.get(user_id)
2663 user = User.get(user_id)
2664 if user.username == User.DEFAULT_USER:
2664 if user.username == User.DEFAULT_USER:
2665 return None
2665 return None
2666
2666
2667 return cls.query()\
2667 return cls.query()\
2668 .filter(cls.personal == true()) \
2668 .filter(cls.personal == true()) \
2669 .filter(cls.user == user) \
2669 .filter(cls.user == user) \
2670 .order_by(cls.group_id.asc()) \
2670 .order_by(cls.group_id.asc()) \
2671 .first()
2671 .first()
2672
2672
2673 @classmethod
2673 @classmethod
2674 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2674 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2675 case_insensitive=True):
2675 case_insensitive=True):
2676 q = RepoGroup.query()
2676 q = RepoGroup.query()
2677
2677
2678 if not isinstance(user_id, Optional):
2678 if not isinstance(user_id, Optional):
2679 q = q.filter(RepoGroup.user_id == user_id)
2679 q = q.filter(RepoGroup.user_id == user_id)
2680
2680
2681 if not isinstance(group_id, Optional):
2681 if not isinstance(group_id, Optional):
2682 q = q.filter(RepoGroup.group_parent_id == group_id)
2682 q = q.filter(RepoGroup.group_parent_id == group_id)
2683
2683
2684 if case_insensitive:
2684 if case_insensitive:
2685 q = q.order_by(func.lower(RepoGroup.group_name))
2685 q = q.order_by(func.lower(RepoGroup.group_name))
2686 else:
2686 else:
2687 q = q.order_by(RepoGroup.group_name)
2687 q = q.order_by(RepoGroup.group_name)
2688 return q.all()
2688 return q.all()
2689
2689
2690 @property
2690 @property
2691 def parents(self, parents_recursion_limit = 10):
2691 def parents(self, parents_recursion_limit = 10):
2692 groups = []
2692 groups = []
2693 if self.parent_group is None:
2693 if self.parent_group is None:
2694 return groups
2694 return groups
2695 cur_gr = self.parent_group
2695 cur_gr = self.parent_group
2696 groups.insert(0, cur_gr)
2696 groups.insert(0, cur_gr)
2697 cnt = 0
2697 cnt = 0
2698 while 1:
2698 while 1:
2699 cnt += 1
2699 cnt += 1
2700 gr = getattr(cur_gr, 'parent_group', None)
2700 gr = getattr(cur_gr, 'parent_group', None)
2701 cur_gr = cur_gr.parent_group
2701 cur_gr = cur_gr.parent_group
2702 if gr is None:
2702 if gr is None:
2703 break
2703 break
2704 if cnt == parents_recursion_limit:
2704 if cnt == parents_recursion_limit:
2705 # this will prevent accidental infinit loops
2705 # this will prevent accidental infinit loops
2706 log.error('more than %s parents found for group %s, stopping '
2706 log.error('more than %s parents found for group %s, stopping '
2707 'recursive parent fetching', parents_recursion_limit, self)
2707 'recursive parent fetching', parents_recursion_limit, self)
2708 break
2708 break
2709
2709
2710 groups.insert(0, gr)
2710 groups.insert(0, gr)
2711 return groups
2711 return groups
2712
2712
2713 @property
2713 @property
2714 def last_commit_cache_update_diff(self):
2714 def last_commit_cache_update_diff(self):
2715 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2715 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2716
2716
2717 @property
2717 @property
2718 def last_commit_change(self):
2718 def last_commit_change(self):
2719 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2719 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2720 empty_date = datetime.datetime.fromtimestamp(0)
2720 empty_date = datetime.datetime.fromtimestamp(0)
2721 date_latest = self.changeset_cache.get('date', empty_date)
2721 date_latest = self.changeset_cache.get('date', empty_date)
2722 try:
2722 try:
2723 return parse_datetime(date_latest)
2723 return parse_datetime(date_latest)
2724 except Exception:
2724 except Exception:
2725 return empty_date
2725 return empty_date
2726
2726
2727 @property
2727 @property
2728 def last_db_change(self):
2728 def last_db_change(self):
2729 return self.updated_on
2729 return self.updated_on
2730
2730
2731 @property
2731 @property
2732 def children(self):
2732 def children(self):
2733 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2733 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2734
2734
2735 @property
2735 @property
2736 def name(self):
2736 def name(self):
2737 return self.group_name.split(RepoGroup.url_sep())[-1]
2737 return self.group_name.split(RepoGroup.url_sep())[-1]
2738
2738
2739 @property
2739 @property
2740 def full_path(self):
2740 def full_path(self):
2741 return self.group_name
2741 return self.group_name
2742
2742
2743 @property
2743 @property
2744 def full_path_splitted(self):
2744 def full_path_splitted(self):
2745 return self.group_name.split(RepoGroup.url_sep())
2745 return self.group_name.split(RepoGroup.url_sep())
2746
2746
2747 @property
2747 @property
2748 def repositories(self):
2748 def repositories(self):
2749 return Repository.query()\
2749 return Repository.query()\
2750 .filter(Repository.group == self)\
2750 .filter(Repository.group == self)\
2751 .order_by(Repository.repo_name)
2751 .order_by(Repository.repo_name)
2752
2752
2753 @property
2753 @property
2754 def repositories_recursive_count(self):
2754 def repositories_recursive_count(self):
2755 cnt = self.repositories.count()
2755 cnt = self.repositories.count()
2756
2756
2757 def children_count(group):
2757 def children_count(group):
2758 cnt = 0
2758 cnt = 0
2759 for child in group.children:
2759 for child in group.children:
2760 cnt += child.repositories.count()
2760 cnt += child.repositories.count()
2761 cnt += children_count(child)
2761 cnt += children_count(child)
2762 return cnt
2762 return cnt
2763
2763
2764 return cnt + children_count(self)
2764 return cnt + children_count(self)
2765
2765
2766 def _recursive_objects(self, include_repos=True, include_groups=True):
2766 def _recursive_objects(self, include_repos=True, include_groups=True):
2767 all_ = []
2767 all_ = []
2768
2768
2769 def _get_members(root_gr):
2769 def _get_members(root_gr):
2770 if include_repos:
2770 if include_repos:
2771 for r in root_gr.repositories:
2771 for r in root_gr.repositories:
2772 all_.append(r)
2772 all_.append(r)
2773 childs = root_gr.children.all()
2773 childs = root_gr.children.all()
2774 if childs:
2774 if childs:
2775 for gr in childs:
2775 for gr in childs:
2776 if include_groups:
2776 if include_groups:
2777 all_.append(gr)
2777 all_.append(gr)
2778 _get_members(gr)
2778 _get_members(gr)
2779
2779
2780 root_group = []
2780 root_group = []
2781 if include_groups:
2781 if include_groups:
2782 root_group = [self]
2782 root_group = [self]
2783
2783
2784 _get_members(self)
2784 _get_members(self)
2785 return root_group + all_
2785 return root_group + all_
2786
2786
2787 def recursive_groups_and_repos(self):
2787 def recursive_groups_and_repos(self):
2788 """
2788 """
2789 Recursive return all groups, with repositories in those groups
2789 Recursive return all groups, with repositories in those groups
2790 """
2790 """
2791 return self._recursive_objects()
2791 return self._recursive_objects()
2792
2792
2793 def recursive_groups(self):
2793 def recursive_groups(self):
2794 """
2794 """
2795 Returns all children groups for this group including children of children
2795 Returns all children groups for this group including children of children
2796 """
2796 """
2797 return self._recursive_objects(include_repos=False)
2797 return self._recursive_objects(include_repos=False)
2798
2798
2799 def recursive_repos(self):
2799 def recursive_repos(self):
2800 """
2800 """
2801 Returns all children repositories for this group
2801 Returns all children repositories for this group
2802 """
2802 """
2803 return self._recursive_objects(include_groups=False)
2803 return self._recursive_objects(include_groups=False)
2804
2804
2805 def get_new_name(self, group_name):
2805 def get_new_name(self, group_name):
2806 """
2806 """
2807 returns new full group name based on parent and new name
2807 returns new full group name based on parent and new name
2808
2808
2809 :param group_name:
2809 :param group_name:
2810 """
2810 """
2811 path_prefix = (self.parent_group.full_path_splitted if
2811 path_prefix = (self.parent_group.full_path_splitted if
2812 self.parent_group else [])
2812 self.parent_group else [])
2813 return RepoGroup.url_sep().join(path_prefix + [group_name])
2813 return RepoGroup.url_sep().join(path_prefix + [group_name])
2814
2814
2815 def update_commit_cache(self, config=None):
2815 def update_commit_cache(self, config=None):
2816 """
2816 """
2817 Update cache of last changeset for newest repository inside this group, keys should be::
2817 Update cache of last changeset for newest repository inside this group, keys should be::
2818
2818
2819 source_repo_id
2819 source_repo_id
2820 short_id
2820 short_id
2821 raw_id
2821 raw_id
2822 revision
2822 revision
2823 parents
2823 parents
2824 message
2824 message
2825 date
2825 date
2826 author
2826 author
2827
2827
2828 """
2828 """
2829 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2829 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2830
2830
2831 def repo_groups_and_repos():
2831 def repo_groups_and_repos():
2832 all_entries = OrderedDefaultDict(list)
2832 all_entries = OrderedDefaultDict(list)
2833
2833
2834 def _get_members(root_gr, pos=0):
2834 def _get_members(root_gr, pos=0):
2835
2835
2836 for repo in root_gr.repositories:
2836 for repo in root_gr.repositories:
2837 all_entries[root_gr].append(repo)
2837 all_entries[root_gr].append(repo)
2838
2838
2839 # fill in all parent positions
2839 # fill in all parent positions
2840 for parent_group in root_gr.parents:
2840 for parent_group in root_gr.parents:
2841 all_entries[parent_group].extend(all_entries[root_gr])
2841 all_entries[parent_group].extend(all_entries[root_gr])
2842
2842
2843 children_groups = root_gr.children.all()
2843 children_groups = root_gr.children.all()
2844 if children_groups:
2844 if children_groups:
2845 for cnt, gr in enumerate(children_groups, 1):
2845 for cnt, gr in enumerate(children_groups, 1):
2846 _get_members(gr, pos=pos+cnt)
2846 _get_members(gr, pos=pos+cnt)
2847
2847
2848 _get_members(root_gr=self)
2848 _get_members(root_gr=self)
2849 return all_entries
2849 return all_entries
2850
2850
2851 empty_date = datetime.datetime.fromtimestamp(0)
2851 empty_date = datetime.datetime.fromtimestamp(0)
2852 for repo_group, repos in repo_groups_and_repos().items():
2852 for repo_group, repos in repo_groups_and_repos().items():
2853
2853
2854 latest_repo_cs_cache = {}
2854 latest_repo_cs_cache = {}
2855 for repo in repos:
2855 for repo in repos:
2856 repo_cs_cache = repo.changeset_cache
2856 repo_cs_cache = repo.changeset_cache
2857 date_latest = latest_repo_cs_cache.get('date', empty_date)
2857 date_latest = latest_repo_cs_cache.get('date', empty_date)
2858 date_current = repo_cs_cache.get('date', empty_date)
2858 date_current = repo_cs_cache.get('date', empty_date)
2859 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2859 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2860 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2860 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2861 latest_repo_cs_cache = repo_cs_cache
2861 latest_repo_cs_cache = repo_cs_cache
2862 latest_repo_cs_cache['source_repo_id'] = repo.repo_id
2862 latest_repo_cs_cache['source_repo_id'] = repo.repo_id
2863
2863
2864 latest_repo_cs_cache['updated_on'] = time.time()
2864 latest_repo_cs_cache['updated_on'] = time.time()
2865 repo_group.changeset_cache = latest_repo_cs_cache
2865 repo_group.changeset_cache = latest_repo_cs_cache
2866 Session().add(repo_group)
2866 Session().add(repo_group)
2867 Session().commit()
2867 Session().commit()
2868
2868
2869 log.debug('updated repo group %s with new commit cache %s',
2869 log.debug('updated repo group %s with new commit cache %s',
2870 repo_group.group_name, latest_repo_cs_cache)
2870 repo_group.group_name, latest_repo_cs_cache)
2871
2871
2872 def permissions(self, with_admins=True, with_owner=True,
2872 def permissions(self, with_admins=True, with_owner=True,
2873 expand_from_user_groups=False):
2873 expand_from_user_groups=False):
2874 """
2874 """
2875 Permissions for repository groups
2875 Permissions for repository groups
2876 """
2876 """
2877 _admin_perm = 'group.admin'
2877 _admin_perm = 'group.admin'
2878
2878
2879 owner_row = []
2879 owner_row = []
2880 if with_owner:
2880 if with_owner:
2881 usr = AttributeDict(self.user.get_dict())
2881 usr = AttributeDict(self.user.get_dict())
2882 usr.owner_row = True
2882 usr.owner_row = True
2883 usr.permission = _admin_perm
2883 usr.permission = _admin_perm
2884 owner_row.append(usr)
2884 owner_row.append(usr)
2885
2885
2886 super_admin_ids = []
2886 super_admin_ids = []
2887 super_admin_rows = []
2887 super_admin_rows = []
2888 if with_admins:
2888 if with_admins:
2889 for usr in User.get_all_super_admins():
2889 for usr in User.get_all_super_admins():
2890 super_admin_ids.append(usr.user_id)
2890 super_admin_ids.append(usr.user_id)
2891 # if this admin is also owner, don't double the record
2891 # if this admin is also owner, don't double the record
2892 if usr.user_id == owner_row[0].user_id:
2892 if usr.user_id == owner_row[0].user_id:
2893 owner_row[0].admin_row = True
2893 owner_row[0].admin_row = True
2894 else:
2894 else:
2895 usr = AttributeDict(usr.get_dict())
2895 usr = AttributeDict(usr.get_dict())
2896 usr.admin_row = True
2896 usr.admin_row = True
2897 usr.permission = _admin_perm
2897 usr.permission = _admin_perm
2898 super_admin_rows.append(usr)
2898 super_admin_rows.append(usr)
2899
2899
2900 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2900 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2901 q = q.options(joinedload(UserRepoGroupToPerm.group),
2901 q = q.options(joinedload(UserRepoGroupToPerm.group),
2902 joinedload(UserRepoGroupToPerm.user),
2902 joinedload(UserRepoGroupToPerm.user),
2903 joinedload(UserRepoGroupToPerm.permission),)
2903 joinedload(UserRepoGroupToPerm.permission),)
2904
2904
2905 # get owners and admins and permissions. We do a trick of re-writing
2905 # get owners and admins and permissions. We do a trick of re-writing
2906 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2906 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2907 # has a global reference and changing one object propagates to all
2907 # has a global reference and changing one object propagates to all
2908 # others. This means if admin is also an owner admin_row that change
2908 # others. This means if admin is also an owner admin_row that change
2909 # would propagate to both objects
2909 # would propagate to both objects
2910 perm_rows = []
2910 perm_rows = []
2911 for _usr in q.all():
2911 for _usr in q.all():
2912 usr = AttributeDict(_usr.user.get_dict())
2912 usr = AttributeDict(_usr.user.get_dict())
2913 # if this user is also owner/admin, mark as duplicate record
2913 # if this user is also owner/admin, mark as duplicate record
2914 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2914 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2915 usr.duplicate_perm = True
2915 usr.duplicate_perm = True
2916 usr.permission = _usr.permission.permission_name
2916 usr.permission = _usr.permission.permission_name
2917 perm_rows.append(usr)
2917 perm_rows.append(usr)
2918
2918
2919 # filter the perm rows by 'default' first and then sort them by
2919 # filter the perm rows by 'default' first and then sort them by
2920 # admin,write,read,none permissions sorted again alphabetically in
2920 # admin,write,read,none permissions sorted again alphabetically in
2921 # each group
2921 # each group
2922 perm_rows = sorted(perm_rows, key=display_user_sort)
2922 perm_rows = sorted(perm_rows, key=display_user_sort)
2923
2923
2924 user_groups_rows = []
2924 user_groups_rows = []
2925 if expand_from_user_groups:
2925 if expand_from_user_groups:
2926 for ug in self.permission_user_groups(with_members=True):
2926 for ug in self.permission_user_groups(with_members=True):
2927 for user_data in ug.members:
2927 for user_data in ug.members:
2928 user_groups_rows.append(user_data)
2928 user_groups_rows.append(user_data)
2929
2929
2930 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2930 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2931
2931
2932 def permission_user_groups(self, with_members=False):
2932 def permission_user_groups(self, with_members=False):
2933 q = UserGroupRepoGroupToPerm.query()\
2933 q = UserGroupRepoGroupToPerm.query()\
2934 .filter(UserGroupRepoGroupToPerm.group == self)
2934 .filter(UserGroupRepoGroupToPerm.group == self)
2935 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2935 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2936 joinedload(UserGroupRepoGroupToPerm.users_group),
2936 joinedload(UserGroupRepoGroupToPerm.users_group),
2937 joinedload(UserGroupRepoGroupToPerm.permission),)
2937 joinedload(UserGroupRepoGroupToPerm.permission),)
2938
2938
2939 perm_rows = []
2939 perm_rows = []
2940 for _user_group in q.all():
2940 for _user_group in q.all():
2941 entry = AttributeDict(_user_group.users_group.get_dict())
2941 entry = AttributeDict(_user_group.users_group.get_dict())
2942 entry.permission = _user_group.permission.permission_name
2942 entry.permission = _user_group.permission.permission_name
2943 if with_members:
2943 if with_members:
2944 entry.members = [x.user.get_dict()
2944 entry.members = [x.user.get_dict()
2945 for x in _user_group.users_group.members]
2945 for x in _user_group.users_group.members]
2946 perm_rows.append(entry)
2946 perm_rows.append(entry)
2947
2947
2948 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2948 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2949 return perm_rows
2949 return perm_rows
2950
2950
2951 def get_api_data(self):
2951 def get_api_data(self):
2952 """
2952 """
2953 Common function for generating api data
2953 Common function for generating api data
2954
2954
2955 """
2955 """
2956 group = self
2956 group = self
2957 data = {
2957 data = {
2958 'group_id': group.group_id,
2958 'group_id': group.group_id,
2959 'group_name': group.group_name,
2959 'group_name': group.group_name,
2960 'group_description': group.description_safe,
2960 'group_description': group.description_safe,
2961 'parent_group': group.parent_group.group_name if group.parent_group else None,
2961 'parent_group': group.parent_group.group_name if group.parent_group else None,
2962 'repositories': [x.repo_name for x in group.repositories],
2962 'repositories': [x.repo_name for x in group.repositories],
2963 'owner': group.user.username,
2963 'owner': group.user.username,
2964 }
2964 }
2965 return data
2965 return data
2966
2966
2967 def get_dict(self):
2967 def get_dict(self):
2968 # Since we transformed `group_name` to a hybrid property, we need to
2968 # Since we transformed `group_name` to a hybrid property, we need to
2969 # keep compatibility with the code which uses `group_name` field.
2969 # keep compatibility with the code which uses `group_name` field.
2970 result = super(RepoGroup, self).get_dict()
2970 result = super(RepoGroup, self).get_dict()
2971 result['group_name'] = result.pop('_group_name', None)
2971 result['group_name'] = result.pop('_group_name', None)
2972 return result
2972 return result
2973
2973
2974
2974
2975 class Permission(Base, BaseModel):
2975 class Permission(Base, BaseModel):
2976 __tablename__ = 'permissions'
2976 __tablename__ = 'permissions'
2977 __table_args__ = (
2977 __table_args__ = (
2978 Index('p_perm_name_idx', 'permission_name'),
2978 Index('p_perm_name_idx', 'permission_name'),
2979 base_table_args,
2979 base_table_args,
2980 )
2980 )
2981
2981
2982 PERMS = [
2982 PERMS = [
2983 ('hg.admin', _('RhodeCode Super Administrator')),
2983 ('hg.admin', _('RhodeCode Super Administrator')),
2984
2984
2985 ('repository.none', _('Repository no access')),
2985 ('repository.none', _('Repository no access')),
2986 ('repository.read', _('Repository read access')),
2986 ('repository.read', _('Repository read access')),
2987 ('repository.write', _('Repository write access')),
2987 ('repository.write', _('Repository write access')),
2988 ('repository.admin', _('Repository admin access')),
2988 ('repository.admin', _('Repository admin access')),
2989
2989
2990 ('group.none', _('Repository group no access')),
2990 ('group.none', _('Repository group no access')),
2991 ('group.read', _('Repository group read access')),
2991 ('group.read', _('Repository group read access')),
2992 ('group.write', _('Repository group write access')),
2992 ('group.write', _('Repository group write access')),
2993 ('group.admin', _('Repository group admin access')),
2993 ('group.admin', _('Repository group admin access')),
2994
2994
2995 ('usergroup.none', _('User group no access')),
2995 ('usergroup.none', _('User group no access')),
2996 ('usergroup.read', _('User group read access')),
2996 ('usergroup.read', _('User group read access')),
2997 ('usergroup.write', _('User group write access')),
2997 ('usergroup.write', _('User group write access')),
2998 ('usergroup.admin', _('User group admin access')),
2998 ('usergroup.admin', _('User group admin access')),
2999
2999
3000 ('branch.none', _('Branch no permissions')),
3000 ('branch.none', _('Branch no permissions')),
3001 ('branch.merge', _('Branch access by web merge')),
3001 ('branch.merge', _('Branch access by web merge')),
3002 ('branch.push', _('Branch access by push')),
3002 ('branch.push', _('Branch access by push')),
3003 ('branch.push_force', _('Branch access by push with force')),
3003 ('branch.push_force', _('Branch access by push with force')),
3004
3004
3005 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3005 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3006 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3006 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3007
3007
3008 ('hg.usergroup.create.false', _('User Group creation disabled')),
3008 ('hg.usergroup.create.false', _('User Group creation disabled')),
3009 ('hg.usergroup.create.true', _('User Group creation enabled')),
3009 ('hg.usergroup.create.true', _('User Group creation enabled')),
3010
3010
3011 ('hg.create.none', _('Repository creation disabled')),
3011 ('hg.create.none', _('Repository creation disabled')),
3012 ('hg.create.repository', _('Repository creation enabled')),
3012 ('hg.create.repository', _('Repository creation enabled')),
3013 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3013 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3014 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3014 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3015
3015
3016 ('hg.fork.none', _('Repository forking disabled')),
3016 ('hg.fork.none', _('Repository forking disabled')),
3017 ('hg.fork.repository', _('Repository forking enabled')),
3017 ('hg.fork.repository', _('Repository forking enabled')),
3018
3018
3019 ('hg.register.none', _('Registration disabled')),
3019 ('hg.register.none', _('Registration disabled')),
3020 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3020 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3021 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3021 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3022
3022
3023 ('hg.password_reset.enabled', _('Password reset enabled')),
3023 ('hg.password_reset.enabled', _('Password reset enabled')),
3024 ('hg.password_reset.hidden', _('Password reset hidden')),
3024 ('hg.password_reset.hidden', _('Password reset hidden')),
3025 ('hg.password_reset.disabled', _('Password reset disabled')),
3025 ('hg.password_reset.disabled', _('Password reset disabled')),
3026
3026
3027 ('hg.extern_activate.manual', _('Manual activation of external account')),
3027 ('hg.extern_activate.manual', _('Manual activation of external account')),
3028 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3028 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3029
3029
3030 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3030 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3031 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3031 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3032 ]
3032 ]
3033
3033
3034 # definition of system default permissions for DEFAULT user, created on
3034 # definition of system default permissions for DEFAULT user, created on
3035 # system setup
3035 # system setup
3036 DEFAULT_USER_PERMISSIONS = [
3036 DEFAULT_USER_PERMISSIONS = [
3037 # object perms
3037 # object perms
3038 'repository.read',
3038 'repository.read',
3039 'group.read',
3039 'group.read',
3040 'usergroup.read',
3040 'usergroup.read',
3041 # branch, for backward compat we need same value as before so forced pushed
3041 # branch, for backward compat we need same value as before so forced pushed
3042 'branch.push_force',
3042 'branch.push_force',
3043 # global
3043 # global
3044 'hg.create.repository',
3044 'hg.create.repository',
3045 'hg.repogroup.create.false',
3045 'hg.repogroup.create.false',
3046 'hg.usergroup.create.false',
3046 'hg.usergroup.create.false',
3047 'hg.create.write_on_repogroup.true',
3047 'hg.create.write_on_repogroup.true',
3048 'hg.fork.repository',
3048 'hg.fork.repository',
3049 'hg.register.manual_activate',
3049 'hg.register.manual_activate',
3050 'hg.password_reset.enabled',
3050 'hg.password_reset.enabled',
3051 'hg.extern_activate.auto',
3051 'hg.extern_activate.auto',
3052 'hg.inherit_default_perms.true',
3052 'hg.inherit_default_perms.true',
3053 ]
3053 ]
3054
3054
3055 # defines which permissions are more important higher the more important
3055 # defines which permissions are more important higher the more important
3056 # Weight defines which permissions are more important.
3056 # Weight defines which permissions are more important.
3057 # The higher number the more important.
3057 # The higher number the more important.
3058 PERM_WEIGHTS = {
3058 PERM_WEIGHTS = {
3059 'repository.none': 0,
3059 'repository.none': 0,
3060 'repository.read': 1,
3060 'repository.read': 1,
3061 'repository.write': 3,
3061 'repository.write': 3,
3062 'repository.admin': 4,
3062 'repository.admin': 4,
3063
3063
3064 'group.none': 0,
3064 'group.none': 0,
3065 'group.read': 1,
3065 'group.read': 1,
3066 'group.write': 3,
3066 'group.write': 3,
3067 'group.admin': 4,
3067 'group.admin': 4,
3068
3068
3069 'usergroup.none': 0,
3069 'usergroup.none': 0,
3070 'usergroup.read': 1,
3070 'usergroup.read': 1,
3071 'usergroup.write': 3,
3071 'usergroup.write': 3,
3072 'usergroup.admin': 4,
3072 'usergroup.admin': 4,
3073
3073
3074 'branch.none': 0,
3074 'branch.none': 0,
3075 'branch.merge': 1,
3075 'branch.merge': 1,
3076 'branch.push': 3,
3076 'branch.push': 3,
3077 'branch.push_force': 4,
3077 'branch.push_force': 4,
3078
3078
3079 'hg.repogroup.create.false': 0,
3079 'hg.repogroup.create.false': 0,
3080 'hg.repogroup.create.true': 1,
3080 'hg.repogroup.create.true': 1,
3081
3081
3082 'hg.usergroup.create.false': 0,
3082 'hg.usergroup.create.false': 0,
3083 'hg.usergroup.create.true': 1,
3083 'hg.usergroup.create.true': 1,
3084
3084
3085 'hg.fork.none': 0,
3085 'hg.fork.none': 0,
3086 'hg.fork.repository': 1,
3086 'hg.fork.repository': 1,
3087 'hg.create.none': 0,
3087 'hg.create.none': 0,
3088 'hg.create.repository': 1
3088 'hg.create.repository': 1
3089 }
3089 }
3090
3090
3091 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3091 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3092 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3092 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3093 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3093 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3094
3094
3095 def __unicode__(self):
3095 def __unicode__(self):
3096 return u"<%s('%s:%s')>" % (
3096 return u"<%s('%s:%s')>" % (
3097 self.__class__.__name__, self.permission_id, self.permission_name
3097 self.__class__.__name__, self.permission_id, self.permission_name
3098 )
3098 )
3099
3099
3100 @classmethod
3100 @classmethod
3101 def get_by_key(cls, key):
3101 def get_by_key(cls, key):
3102 return cls.query().filter(cls.permission_name == key).scalar()
3102 return cls.query().filter(cls.permission_name == key).scalar()
3103
3103
3104 @classmethod
3104 @classmethod
3105 def get_default_repo_perms(cls, user_id, repo_id=None):
3105 def get_default_repo_perms(cls, user_id, repo_id=None):
3106 q = Session().query(UserRepoToPerm, Repository, Permission)\
3106 q = Session().query(UserRepoToPerm, Repository, Permission)\
3107 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3107 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3108 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3108 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3109 .filter(UserRepoToPerm.user_id == user_id)
3109 .filter(UserRepoToPerm.user_id == user_id)
3110 if repo_id:
3110 if repo_id:
3111 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3111 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3112 return q.all()
3112 return q.all()
3113
3113
3114 @classmethod
3114 @classmethod
3115 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3115 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3116 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3116 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3117 .join(
3117 .join(
3118 Permission,
3118 Permission,
3119 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3119 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3120 .join(
3120 .join(
3121 UserRepoToPerm,
3121 UserRepoToPerm,
3122 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3122 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3123 .filter(UserRepoToPerm.user_id == user_id)
3123 .filter(UserRepoToPerm.user_id == user_id)
3124
3124
3125 if repo_id:
3125 if repo_id:
3126 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3126 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3127 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3127 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3128
3128
3129 @classmethod
3129 @classmethod
3130 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3130 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3131 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3131 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3132 .join(
3132 .join(
3133 Permission,
3133 Permission,
3134 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3134 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3135 .join(
3135 .join(
3136 Repository,
3136 Repository,
3137 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3137 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3138 .join(
3138 .join(
3139 UserGroup,
3139 UserGroup,
3140 UserGroupRepoToPerm.users_group_id ==
3140 UserGroupRepoToPerm.users_group_id ==
3141 UserGroup.users_group_id)\
3141 UserGroup.users_group_id)\
3142 .join(
3142 .join(
3143 UserGroupMember,
3143 UserGroupMember,
3144 UserGroupRepoToPerm.users_group_id ==
3144 UserGroupRepoToPerm.users_group_id ==
3145 UserGroupMember.users_group_id)\
3145 UserGroupMember.users_group_id)\
3146 .filter(
3146 .filter(
3147 UserGroupMember.user_id == user_id,
3147 UserGroupMember.user_id == user_id,
3148 UserGroup.users_group_active == true())
3148 UserGroup.users_group_active == true())
3149 if repo_id:
3149 if repo_id:
3150 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3150 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3151 return q.all()
3151 return q.all()
3152
3152
3153 @classmethod
3153 @classmethod
3154 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3154 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3155 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3155 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3156 .join(
3156 .join(
3157 Permission,
3157 Permission,
3158 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3158 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3159 .join(
3159 .join(
3160 UserGroupRepoToPerm,
3160 UserGroupRepoToPerm,
3161 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3161 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3162 .join(
3162 .join(
3163 UserGroup,
3163 UserGroup,
3164 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3164 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3165 .join(
3165 .join(
3166 UserGroupMember,
3166 UserGroupMember,
3167 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3167 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3168 .filter(
3168 .filter(
3169 UserGroupMember.user_id == user_id,
3169 UserGroupMember.user_id == user_id,
3170 UserGroup.users_group_active == true())
3170 UserGroup.users_group_active == true())
3171
3171
3172 if repo_id:
3172 if repo_id:
3173 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3173 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3174 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3174 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3175
3175
3176 @classmethod
3176 @classmethod
3177 def get_default_group_perms(cls, user_id, repo_group_id=None):
3177 def get_default_group_perms(cls, user_id, repo_group_id=None):
3178 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3178 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3179 .join(
3179 .join(
3180 Permission,
3180 Permission,
3181 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3181 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3182 .join(
3182 .join(
3183 RepoGroup,
3183 RepoGroup,
3184 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3184 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3185 .filter(UserRepoGroupToPerm.user_id == user_id)
3185 .filter(UserRepoGroupToPerm.user_id == user_id)
3186 if repo_group_id:
3186 if repo_group_id:
3187 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3187 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3188 return q.all()
3188 return q.all()
3189
3189
3190 @classmethod
3190 @classmethod
3191 def get_default_group_perms_from_user_group(
3191 def get_default_group_perms_from_user_group(
3192 cls, user_id, repo_group_id=None):
3192 cls, user_id, repo_group_id=None):
3193 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3193 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3194 .join(
3194 .join(
3195 Permission,
3195 Permission,
3196 UserGroupRepoGroupToPerm.permission_id ==
3196 UserGroupRepoGroupToPerm.permission_id ==
3197 Permission.permission_id)\
3197 Permission.permission_id)\
3198 .join(
3198 .join(
3199 RepoGroup,
3199 RepoGroup,
3200 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3200 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3201 .join(
3201 .join(
3202 UserGroup,
3202 UserGroup,
3203 UserGroupRepoGroupToPerm.users_group_id ==
3203 UserGroupRepoGroupToPerm.users_group_id ==
3204 UserGroup.users_group_id)\
3204 UserGroup.users_group_id)\
3205 .join(
3205 .join(
3206 UserGroupMember,
3206 UserGroupMember,
3207 UserGroupRepoGroupToPerm.users_group_id ==
3207 UserGroupRepoGroupToPerm.users_group_id ==
3208 UserGroupMember.users_group_id)\
3208 UserGroupMember.users_group_id)\
3209 .filter(
3209 .filter(
3210 UserGroupMember.user_id == user_id,
3210 UserGroupMember.user_id == user_id,
3211 UserGroup.users_group_active == true())
3211 UserGroup.users_group_active == true())
3212 if repo_group_id:
3212 if repo_group_id:
3213 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3213 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3214 return q.all()
3214 return q.all()
3215
3215
3216 @classmethod
3216 @classmethod
3217 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3217 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3218 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3218 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3219 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3219 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3220 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3220 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3221 .filter(UserUserGroupToPerm.user_id == user_id)
3221 .filter(UserUserGroupToPerm.user_id == user_id)
3222 if user_group_id:
3222 if user_group_id:
3223 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3223 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3224 return q.all()
3224 return q.all()
3225
3225
3226 @classmethod
3226 @classmethod
3227 def get_default_user_group_perms_from_user_group(
3227 def get_default_user_group_perms_from_user_group(
3228 cls, user_id, user_group_id=None):
3228 cls, user_id, user_group_id=None):
3229 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3229 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3230 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3230 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3231 .join(
3231 .join(
3232 Permission,
3232 Permission,
3233 UserGroupUserGroupToPerm.permission_id ==
3233 UserGroupUserGroupToPerm.permission_id ==
3234 Permission.permission_id)\
3234 Permission.permission_id)\
3235 .join(
3235 .join(
3236 TargetUserGroup,
3236 TargetUserGroup,
3237 UserGroupUserGroupToPerm.target_user_group_id ==
3237 UserGroupUserGroupToPerm.target_user_group_id ==
3238 TargetUserGroup.users_group_id)\
3238 TargetUserGroup.users_group_id)\
3239 .join(
3239 .join(
3240 UserGroup,
3240 UserGroup,
3241 UserGroupUserGroupToPerm.user_group_id ==
3241 UserGroupUserGroupToPerm.user_group_id ==
3242 UserGroup.users_group_id)\
3242 UserGroup.users_group_id)\
3243 .join(
3243 .join(
3244 UserGroupMember,
3244 UserGroupMember,
3245 UserGroupUserGroupToPerm.user_group_id ==
3245 UserGroupUserGroupToPerm.user_group_id ==
3246 UserGroupMember.users_group_id)\
3246 UserGroupMember.users_group_id)\
3247 .filter(
3247 .filter(
3248 UserGroupMember.user_id == user_id,
3248 UserGroupMember.user_id == user_id,
3249 UserGroup.users_group_active == true())
3249 UserGroup.users_group_active == true())
3250 if user_group_id:
3250 if user_group_id:
3251 q = q.filter(
3251 q = q.filter(
3252 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3252 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3253
3253
3254 return q.all()
3254 return q.all()
3255
3255
3256
3256
3257 class UserRepoToPerm(Base, BaseModel):
3257 class UserRepoToPerm(Base, BaseModel):
3258 __tablename__ = 'repo_to_perm'
3258 __tablename__ = 'repo_to_perm'
3259 __table_args__ = (
3259 __table_args__ = (
3260 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3260 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3261 base_table_args
3261 base_table_args
3262 )
3262 )
3263
3263
3264 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3264 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3265 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3265 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3266 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3266 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3267 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3267 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3268
3268
3269 user = relationship('User')
3269 user = relationship('User')
3270 repository = relationship('Repository')
3270 repository = relationship('Repository')
3271 permission = relationship('Permission')
3271 permission = relationship('Permission')
3272
3272
3273 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete, delete-orphan", lazy='joined')
3273 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined')
3274
3274
3275 @classmethod
3275 @classmethod
3276 def create(cls, user, repository, permission):
3276 def create(cls, user, repository, permission):
3277 n = cls()
3277 n = cls()
3278 n.user = user
3278 n.user = user
3279 n.repository = repository
3279 n.repository = repository
3280 n.permission = permission
3280 n.permission = permission
3281 Session().add(n)
3281 Session().add(n)
3282 return n
3282 return n
3283
3283
3284 def __unicode__(self):
3284 def __unicode__(self):
3285 return u'<%s => %s >' % (self.user, self.repository)
3285 return u'<%s => %s >' % (self.user, self.repository)
3286
3286
3287
3287
3288 class UserUserGroupToPerm(Base, BaseModel):
3288 class UserUserGroupToPerm(Base, BaseModel):
3289 __tablename__ = 'user_user_group_to_perm'
3289 __tablename__ = 'user_user_group_to_perm'
3290 __table_args__ = (
3290 __table_args__ = (
3291 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3291 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3292 base_table_args
3292 base_table_args
3293 )
3293 )
3294
3294
3295 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3295 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3296 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3296 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3297 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3297 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3298 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3298 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3299
3299
3300 user = relationship('User')
3300 user = relationship('User')
3301 user_group = relationship('UserGroup')
3301 user_group = relationship('UserGroup')
3302 permission = relationship('Permission')
3302 permission = relationship('Permission')
3303
3303
3304 @classmethod
3304 @classmethod
3305 def create(cls, user, user_group, permission):
3305 def create(cls, user, user_group, permission):
3306 n = cls()
3306 n = cls()
3307 n.user = user
3307 n.user = user
3308 n.user_group = user_group
3308 n.user_group = user_group
3309 n.permission = permission
3309 n.permission = permission
3310 Session().add(n)
3310 Session().add(n)
3311 return n
3311 return n
3312
3312
3313 def __unicode__(self):
3313 def __unicode__(self):
3314 return u'<%s => %s >' % (self.user, self.user_group)
3314 return u'<%s => %s >' % (self.user, self.user_group)
3315
3315
3316
3316
3317 class UserToPerm(Base, BaseModel):
3317 class UserToPerm(Base, BaseModel):
3318 __tablename__ = 'user_to_perm'
3318 __tablename__ = 'user_to_perm'
3319 __table_args__ = (
3319 __table_args__ = (
3320 UniqueConstraint('user_id', 'permission_id'),
3320 UniqueConstraint('user_id', 'permission_id'),
3321 base_table_args
3321 base_table_args
3322 )
3322 )
3323
3323
3324 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3324 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3325 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3325 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3326 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3326 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3327
3327
3328 user = relationship('User')
3328 user = relationship('User')
3329 permission = relationship('Permission', lazy='joined')
3329 permission = relationship('Permission', lazy='joined')
3330
3330
3331 def __unicode__(self):
3331 def __unicode__(self):
3332 return u'<%s => %s >' % (self.user, self.permission)
3332 return u'<%s => %s >' % (self.user, self.permission)
3333
3333
3334
3334
3335 class UserGroupRepoToPerm(Base, BaseModel):
3335 class UserGroupRepoToPerm(Base, BaseModel):
3336 __tablename__ = 'users_group_repo_to_perm'
3336 __tablename__ = 'users_group_repo_to_perm'
3337 __table_args__ = (
3337 __table_args__ = (
3338 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3338 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3339 base_table_args
3339 base_table_args
3340 )
3340 )
3341
3341
3342 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3342 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3343 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3343 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3344 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3344 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3345 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3345 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3346
3346
3347 users_group = relationship('UserGroup')
3347 users_group = relationship('UserGroup')
3348 permission = relationship('Permission')
3348 permission = relationship('Permission')
3349 repository = relationship('Repository')
3349 repository = relationship('Repository')
3350 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3350 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3351
3351
3352 @classmethod
3352 @classmethod
3353 def create(cls, users_group, repository, permission):
3353 def create(cls, users_group, repository, permission):
3354 n = cls()
3354 n = cls()
3355 n.users_group = users_group
3355 n.users_group = users_group
3356 n.repository = repository
3356 n.repository = repository
3357 n.permission = permission
3357 n.permission = permission
3358 Session().add(n)
3358 Session().add(n)
3359 return n
3359 return n
3360
3360
3361 def __unicode__(self):
3361 def __unicode__(self):
3362 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3362 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3363
3363
3364
3364
3365 class UserGroupUserGroupToPerm(Base, BaseModel):
3365 class UserGroupUserGroupToPerm(Base, BaseModel):
3366 __tablename__ = 'user_group_user_group_to_perm'
3366 __tablename__ = 'user_group_user_group_to_perm'
3367 __table_args__ = (
3367 __table_args__ = (
3368 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3368 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3369 CheckConstraint('target_user_group_id != user_group_id'),
3369 CheckConstraint('target_user_group_id != user_group_id'),
3370 base_table_args
3370 base_table_args
3371 )
3371 )
3372
3372
3373 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3373 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3374 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3374 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3375 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3375 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3376 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3376 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3377
3377
3378 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3378 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3379 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3379 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3380 permission = relationship('Permission')
3380 permission = relationship('Permission')
3381
3381
3382 @classmethod
3382 @classmethod
3383 def create(cls, target_user_group, user_group, permission):
3383 def create(cls, target_user_group, user_group, permission):
3384 n = cls()
3384 n = cls()
3385 n.target_user_group = target_user_group
3385 n.target_user_group = target_user_group
3386 n.user_group = user_group
3386 n.user_group = user_group
3387 n.permission = permission
3387 n.permission = permission
3388 Session().add(n)
3388 Session().add(n)
3389 return n
3389 return n
3390
3390
3391 def __unicode__(self):
3391 def __unicode__(self):
3392 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3392 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3393
3393
3394
3394
3395 class UserGroupToPerm(Base, BaseModel):
3395 class UserGroupToPerm(Base, BaseModel):
3396 __tablename__ = 'users_group_to_perm'
3396 __tablename__ = 'users_group_to_perm'
3397 __table_args__ = (
3397 __table_args__ = (
3398 UniqueConstraint('users_group_id', 'permission_id',),
3398 UniqueConstraint('users_group_id', 'permission_id',),
3399 base_table_args
3399 base_table_args
3400 )
3400 )
3401
3401
3402 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3402 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3403 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3403 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3404 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3404 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3405
3405
3406 users_group = relationship('UserGroup')
3406 users_group = relationship('UserGroup')
3407 permission = relationship('Permission')
3407 permission = relationship('Permission')
3408
3408
3409
3409
3410 class UserRepoGroupToPerm(Base, BaseModel):
3410 class UserRepoGroupToPerm(Base, BaseModel):
3411 __tablename__ = 'user_repo_group_to_perm'
3411 __tablename__ = 'user_repo_group_to_perm'
3412 __table_args__ = (
3412 __table_args__ = (
3413 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3413 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3414 base_table_args
3414 base_table_args
3415 )
3415 )
3416
3416
3417 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3417 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3418 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3418 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3419 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3419 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3420 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3420 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3421
3421
3422 user = relationship('User')
3422 user = relationship('User')
3423 group = relationship('RepoGroup')
3423 group = relationship('RepoGroup')
3424 permission = relationship('Permission')
3424 permission = relationship('Permission')
3425
3425
3426 @classmethod
3426 @classmethod
3427 def create(cls, user, repository_group, permission):
3427 def create(cls, user, repository_group, permission):
3428 n = cls()
3428 n = cls()
3429 n.user = user
3429 n.user = user
3430 n.group = repository_group
3430 n.group = repository_group
3431 n.permission = permission
3431 n.permission = permission
3432 Session().add(n)
3432 Session().add(n)
3433 return n
3433 return n
3434
3434
3435
3435
3436 class UserGroupRepoGroupToPerm(Base, BaseModel):
3436 class UserGroupRepoGroupToPerm(Base, BaseModel):
3437 __tablename__ = 'users_group_repo_group_to_perm'
3437 __tablename__ = 'users_group_repo_group_to_perm'
3438 __table_args__ = (
3438 __table_args__ = (
3439 UniqueConstraint('users_group_id', 'group_id'),
3439 UniqueConstraint('users_group_id', 'group_id'),
3440 base_table_args
3440 base_table_args
3441 )
3441 )
3442
3442
3443 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3443 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3444 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3444 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3445 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3445 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3446 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3446 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3447
3447
3448 users_group = relationship('UserGroup')
3448 users_group = relationship('UserGroup')
3449 permission = relationship('Permission')
3449 permission = relationship('Permission')
3450 group = relationship('RepoGroup')
3450 group = relationship('RepoGroup')
3451
3451
3452 @classmethod
3452 @classmethod
3453 def create(cls, user_group, repository_group, permission):
3453 def create(cls, user_group, repository_group, permission):
3454 n = cls()
3454 n = cls()
3455 n.users_group = user_group
3455 n.users_group = user_group
3456 n.group = repository_group
3456 n.group = repository_group
3457 n.permission = permission
3457 n.permission = permission
3458 Session().add(n)
3458 Session().add(n)
3459 return n
3459 return n
3460
3460
3461 def __unicode__(self):
3461 def __unicode__(self):
3462 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3462 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3463
3463
3464
3464
3465 class Statistics(Base, BaseModel):
3465 class Statistics(Base, BaseModel):
3466 __tablename__ = 'statistics'
3466 __tablename__ = 'statistics'
3467 __table_args__ = (
3467 __table_args__ = (
3468 base_table_args
3468 base_table_args
3469 )
3469 )
3470
3470
3471 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3471 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3472 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3472 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3473 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3473 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3474 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3474 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3475 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3475 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3476 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3476 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3477
3477
3478 repository = relationship('Repository', single_parent=True)
3478 repository = relationship('Repository', single_parent=True)
3479
3479
3480
3480
3481 class UserFollowing(Base, BaseModel):
3481 class UserFollowing(Base, BaseModel):
3482 __tablename__ = 'user_followings'
3482 __tablename__ = 'user_followings'
3483 __table_args__ = (
3483 __table_args__ = (
3484 UniqueConstraint('user_id', 'follows_repository_id'),
3484 UniqueConstraint('user_id', 'follows_repository_id'),
3485 UniqueConstraint('user_id', 'follows_user_id'),
3485 UniqueConstraint('user_id', 'follows_user_id'),
3486 base_table_args
3486 base_table_args
3487 )
3487 )
3488
3488
3489 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3489 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3490 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3490 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3491 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3491 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3492 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3492 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3493 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3493 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3494
3494
3495 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3495 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3496
3496
3497 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3497 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3498 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3498 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3499
3499
3500 @classmethod
3500 @classmethod
3501 def get_repo_followers(cls, repo_id):
3501 def get_repo_followers(cls, repo_id):
3502 return cls.query().filter(cls.follows_repo_id == repo_id)
3502 return cls.query().filter(cls.follows_repo_id == repo_id)
3503
3503
3504
3504
3505 class CacheKey(Base, BaseModel):
3505 class CacheKey(Base, BaseModel):
3506 __tablename__ = 'cache_invalidation'
3506 __tablename__ = 'cache_invalidation'
3507 __table_args__ = (
3507 __table_args__ = (
3508 UniqueConstraint('cache_key'),
3508 UniqueConstraint('cache_key'),
3509 Index('key_idx', 'cache_key'),
3509 Index('key_idx', 'cache_key'),
3510 base_table_args,
3510 base_table_args,
3511 )
3511 )
3512
3512
3513 CACHE_TYPE_FEED = 'FEED'
3513 CACHE_TYPE_FEED = 'FEED'
3514
3514
3515 # namespaces used to register process/thread aware caches
3515 # namespaces used to register process/thread aware caches
3516 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3516 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3517 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3517 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3518
3518
3519 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3519 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3520 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3520 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3521 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3521 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3522 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3522 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3523 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3523 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3524
3524
3525 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3525 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3526 self.cache_key = cache_key
3526 self.cache_key = cache_key
3527 self.cache_args = cache_args
3527 self.cache_args = cache_args
3528 self.cache_active = False
3528 self.cache_active = False
3529 # first key should be same for all entries, since all workers should share it
3529 # first key should be same for all entries, since all workers should share it
3530 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3530 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3531
3531
3532 def __unicode__(self):
3532 def __unicode__(self):
3533 return u"<%s('%s:%s[%s]')>" % (
3533 return u"<%s('%s:%s[%s]')>" % (
3534 self.__class__.__name__,
3534 self.__class__.__name__,
3535 self.cache_id, self.cache_key, self.cache_active)
3535 self.cache_id, self.cache_key, self.cache_active)
3536
3536
3537 def _cache_key_partition(self):
3537 def _cache_key_partition(self):
3538 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3538 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3539 return prefix, repo_name, suffix
3539 return prefix, repo_name, suffix
3540
3540
3541 def get_prefix(self):
3541 def get_prefix(self):
3542 """
3542 """
3543 Try to extract prefix from existing cache key. The key could consist
3543 Try to extract prefix from existing cache key. The key could consist
3544 of prefix, repo_name, suffix
3544 of prefix, repo_name, suffix
3545 """
3545 """
3546 # this returns prefix, repo_name, suffix
3546 # this returns prefix, repo_name, suffix
3547 return self._cache_key_partition()[0]
3547 return self._cache_key_partition()[0]
3548
3548
3549 def get_suffix(self):
3549 def get_suffix(self):
3550 """
3550 """
3551 get suffix that might have been used in _get_cache_key to
3551 get suffix that might have been used in _get_cache_key to
3552 generate self.cache_key. Only used for informational purposes
3552 generate self.cache_key. Only used for informational purposes
3553 in repo_edit.mako.
3553 in repo_edit.mako.
3554 """
3554 """
3555 # prefix, repo_name, suffix
3555 # prefix, repo_name, suffix
3556 return self._cache_key_partition()[2]
3556 return self._cache_key_partition()[2]
3557
3557
3558 @classmethod
3558 @classmethod
3559 def generate_new_state_uid(cls, based_on=None):
3559 def generate_new_state_uid(cls, based_on=None):
3560 if based_on:
3560 if based_on:
3561 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3561 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3562 else:
3562 else:
3563 return str(uuid.uuid4())
3563 return str(uuid.uuid4())
3564
3564
3565 @classmethod
3565 @classmethod
3566 def delete_all_cache(cls):
3566 def delete_all_cache(cls):
3567 """
3567 """
3568 Delete all cache keys from database.
3568 Delete all cache keys from database.
3569 Should only be run when all instances are down and all entries
3569 Should only be run when all instances are down and all entries
3570 thus stale.
3570 thus stale.
3571 """
3571 """
3572 cls.query().delete()
3572 cls.query().delete()
3573 Session().commit()
3573 Session().commit()
3574
3574
3575 @classmethod
3575 @classmethod
3576 def set_invalidate(cls, cache_uid, delete=False):
3576 def set_invalidate(cls, cache_uid, delete=False):
3577 """
3577 """
3578 Mark all caches of a repo as invalid in the database.
3578 Mark all caches of a repo as invalid in the database.
3579 """
3579 """
3580
3580
3581 try:
3581 try:
3582 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3582 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3583 if delete:
3583 if delete:
3584 qry.delete()
3584 qry.delete()
3585 log.debug('cache objects deleted for cache args %s',
3585 log.debug('cache objects deleted for cache args %s',
3586 safe_str(cache_uid))
3586 safe_str(cache_uid))
3587 else:
3587 else:
3588 qry.update({"cache_active": False,
3588 qry.update({"cache_active": False,
3589 "cache_state_uid": cls.generate_new_state_uid()})
3589 "cache_state_uid": cls.generate_new_state_uid()})
3590 log.debug('cache objects marked as invalid for cache args %s',
3590 log.debug('cache objects marked as invalid for cache args %s',
3591 safe_str(cache_uid))
3591 safe_str(cache_uid))
3592
3592
3593 Session().commit()
3593 Session().commit()
3594 except Exception:
3594 except Exception:
3595 log.exception(
3595 log.exception(
3596 'Cache key invalidation failed for cache args %s',
3596 'Cache key invalidation failed for cache args %s',
3597 safe_str(cache_uid))
3597 safe_str(cache_uid))
3598 Session().rollback()
3598 Session().rollback()
3599
3599
3600 @classmethod
3600 @classmethod
3601 def get_active_cache(cls, cache_key):
3601 def get_active_cache(cls, cache_key):
3602 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3602 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3603 if inv_obj:
3603 if inv_obj:
3604 return inv_obj
3604 return inv_obj
3605 return None
3605 return None
3606
3606
3607 @classmethod
3607 @classmethod
3608 def get_namespace_map(cls, namespace):
3608 def get_namespace_map(cls, namespace):
3609 return {
3609 return {
3610 x.cache_key: x
3610 x.cache_key: x
3611 for x in cls.query().filter(cls.cache_args == namespace)}
3611 for x in cls.query().filter(cls.cache_args == namespace)}
3612
3612
3613
3613
3614 class ChangesetComment(Base, BaseModel):
3614 class ChangesetComment(Base, BaseModel):
3615 __tablename__ = 'changeset_comments'
3615 __tablename__ = 'changeset_comments'
3616 __table_args__ = (
3616 __table_args__ = (
3617 Index('cc_revision_idx', 'revision'),
3617 Index('cc_revision_idx', 'revision'),
3618 base_table_args,
3618 base_table_args,
3619 )
3619 )
3620
3620
3621 COMMENT_OUTDATED = u'comment_outdated'
3621 COMMENT_OUTDATED = u'comment_outdated'
3622 COMMENT_TYPE_NOTE = u'note'
3622 COMMENT_TYPE_NOTE = u'note'
3623 COMMENT_TYPE_TODO = u'todo'
3623 COMMENT_TYPE_TODO = u'todo'
3624 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3624 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3625
3625
3626 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3626 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3627 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3627 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3628 revision = Column('revision', String(40), nullable=True)
3628 revision = Column('revision', String(40), nullable=True)
3629 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3629 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3630 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3630 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3631 line_no = Column('line_no', Unicode(10), nullable=True)
3631 line_no = Column('line_no', Unicode(10), nullable=True)
3632 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3632 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3633 f_path = Column('f_path', Unicode(1000), nullable=True)
3633 f_path = Column('f_path', Unicode(1000), nullable=True)
3634 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3634 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3635 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3635 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3636 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3636 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3637 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3637 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3638 renderer = Column('renderer', Unicode(64), nullable=True)
3638 renderer = Column('renderer', Unicode(64), nullable=True)
3639 display_state = Column('display_state', Unicode(128), nullable=True)
3639 display_state = Column('display_state', Unicode(128), nullable=True)
3640
3640
3641 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3641 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3642 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3642 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3643
3643
3644 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3644 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3645 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3645 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3646
3646
3647 author = relationship('User', lazy='joined')
3647 author = relationship('User', lazy='joined')
3648 repo = relationship('Repository')
3648 repo = relationship('Repository')
3649 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3649 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='joined')
3650 pull_request = relationship('PullRequest', lazy='joined')
3650 pull_request = relationship('PullRequest', lazy='joined')
3651 pull_request_version = relationship('PullRequestVersion')
3651 pull_request_version = relationship('PullRequestVersion')
3652
3652
3653 @classmethod
3653 @classmethod
3654 def get_users(cls, revision=None, pull_request_id=None):
3654 def get_users(cls, revision=None, pull_request_id=None):
3655 """
3655 """
3656 Returns user associated with this ChangesetComment. ie those
3656 Returns user associated with this ChangesetComment. ie those
3657 who actually commented
3657 who actually commented
3658
3658
3659 :param cls:
3659 :param cls:
3660 :param revision:
3660 :param revision:
3661 """
3661 """
3662 q = Session().query(User)\
3662 q = Session().query(User)\
3663 .join(ChangesetComment.author)
3663 .join(ChangesetComment.author)
3664 if revision:
3664 if revision:
3665 q = q.filter(cls.revision == revision)
3665 q = q.filter(cls.revision == revision)
3666 elif pull_request_id:
3666 elif pull_request_id:
3667 q = q.filter(cls.pull_request_id == pull_request_id)
3667 q = q.filter(cls.pull_request_id == pull_request_id)
3668 return q.all()
3668 return q.all()
3669
3669
3670 @classmethod
3670 @classmethod
3671 def get_index_from_version(cls, pr_version, versions):
3671 def get_index_from_version(cls, pr_version, versions):
3672 num_versions = [x.pull_request_version_id for x in versions]
3672 num_versions = [x.pull_request_version_id for x in versions]
3673 try:
3673 try:
3674 return num_versions.index(pr_version) +1
3674 return num_versions.index(pr_version) +1
3675 except (IndexError, ValueError):
3675 except (IndexError, ValueError):
3676 return
3676 return
3677
3677
3678 @property
3678 @property
3679 def outdated(self):
3679 def outdated(self):
3680 return self.display_state == self.COMMENT_OUTDATED
3680 return self.display_state == self.COMMENT_OUTDATED
3681
3681
3682 def outdated_at_version(self, version):
3682 def outdated_at_version(self, version):
3683 """
3683 """
3684 Checks if comment is outdated for given pull request version
3684 Checks if comment is outdated for given pull request version
3685 """
3685 """
3686 return self.outdated and self.pull_request_version_id != version
3686 return self.outdated and self.pull_request_version_id != version
3687
3687
3688 def older_than_version(self, version):
3688 def older_than_version(self, version):
3689 """
3689 """
3690 Checks if comment is made from previous version than given
3690 Checks if comment is made from previous version than given
3691 """
3691 """
3692 if version is None:
3692 if version is None:
3693 return self.pull_request_version_id is not None
3693 return self.pull_request_version_id is not None
3694
3694
3695 return self.pull_request_version_id < version
3695 return self.pull_request_version_id < version
3696
3696
3697 @property
3697 @property
3698 def resolved(self):
3698 def resolved(self):
3699 return self.resolved_by[0] if self.resolved_by else None
3699 return self.resolved_by[0] if self.resolved_by else None
3700
3700
3701 @property
3701 @property
3702 def is_todo(self):
3702 def is_todo(self):
3703 return self.comment_type == self.COMMENT_TYPE_TODO
3703 return self.comment_type == self.COMMENT_TYPE_TODO
3704
3704
3705 @property
3705 @property
3706 def is_inline(self):
3706 def is_inline(self):
3707 return self.line_no and self.f_path
3707 return self.line_no and self.f_path
3708
3708
3709 def get_index_version(self, versions):
3709 def get_index_version(self, versions):
3710 return self.get_index_from_version(
3710 return self.get_index_from_version(
3711 self.pull_request_version_id, versions)
3711 self.pull_request_version_id, versions)
3712
3712
3713 def __repr__(self):
3713 def __repr__(self):
3714 if self.comment_id:
3714 if self.comment_id:
3715 return '<DB:Comment #%s>' % self.comment_id
3715 return '<DB:Comment #%s>' % self.comment_id
3716 else:
3716 else:
3717 return '<DB:Comment at %#x>' % id(self)
3717 return '<DB:Comment at %#x>' % id(self)
3718
3718
3719 def get_api_data(self):
3719 def get_api_data(self):
3720 comment = self
3720 comment = self
3721 data = {
3721 data = {
3722 'comment_id': comment.comment_id,
3722 'comment_id': comment.comment_id,
3723 'comment_type': comment.comment_type,
3723 'comment_type': comment.comment_type,
3724 'comment_text': comment.text,
3724 'comment_text': comment.text,
3725 'comment_status': comment.status_change,
3725 'comment_status': comment.status_change,
3726 'comment_f_path': comment.f_path,
3726 'comment_f_path': comment.f_path,
3727 'comment_lineno': comment.line_no,
3727 'comment_lineno': comment.line_no,
3728 'comment_author': comment.author,
3728 'comment_author': comment.author,
3729 'comment_created_on': comment.created_on,
3729 'comment_created_on': comment.created_on,
3730 'comment_resolved_by': self.resolved
3730 'comment_resolved_by': self.resolved
3731 }
3731 }
3732 return data
3732 return data
3733
3733
3734 def __json__(self):
3734 def __json__(self):
3735 data = dict()
3735 data = dict()
3736 data.update(self.get_api_data())
3736 data.update(self.get_api_data())
3737 return data
3737 return data
3738
3738
3739
3739
3740 class ChangesetStatus(Base, BaseModel):
3740 class ChangesetStatus(Base, BaseModel):
3741 __tablename__ = 'changeset_statuses'
3741 __tablename__ = 'changeset_statuses'
3742 __table_args__ = (
3742 __table_args__ = (
3743 Index('cs_revision_idx', 'revision'),
3743 Index('cs_revision_idx', 'revision'),
3744 Index('cs_version_idx', 'version'),
3744 Index('cs_version_idx', 'version'),
3745 UniqueConstraint('repo_id', 'revision', 'version'),
3745 UniqueConstraint('repo_id', 'revision', 'version'),
3746 base_table_args
3746 base_table_args
3747 )
3747 )
3748
3748
3749 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3749 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3750 STATUS_APPROVED = 'approved'
3750 STATUS_APPROVED = 'approved'
3751 STATUS_REJECTED = 'rejected'
3751 STATUS_REJECTED = 'rejected'
3752 STATUS_UNDER_REVIEW = 'under_review'
3752 STATUS_UNDER_REVIEW = 'under_review'
3753
3753
3754 STATUSES = [
3754 STATUSES = [
3755 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3755 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3756 (STATUS_APPROVED, _("Approved")),
3756 (STATUS_APPROVED, _("Approved")),
3757 (STATUS_REJECTED, _("Rejected")),
3757 (STATUS_REJECTED, _("Rejected")),
3758 (STATUS_UNDER_REVIEW, _("Under Review")),
3758 (STATUS_UNDER_REVIEW, _("Under Review")),
3759 ]
3759 ]
3760
3760
3761 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3761 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3762 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3762 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3763 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3763 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3764 revision = Column('revision', String(40), nullable=False)
3764 revision = Column('revision', String(40), nullable=False)
3765 status = Column('status', String(128), nullable=False, default=DEFAULT)
3765 status = Column('status', String(128), nullable=False, default=DEFAULT)
3766 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3766 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3767 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3767 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3768 version = Column('version', Integer(), nullable=False, default=0)
3768 version = Column('version', Integer(), nullable=False, default=0)
3769 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3769 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3770
3770
3771 author = relationship('User', lazy='joined')
3771 author = relationship('User', lazy='joined')
3772 repo = relationship('Repository')
3772 repo = relationship('Repository')
3773 comment = relationship('ChangesetComment', lazy='joined')
3773 comment = relationship('ChangesetComment', lazy='joined')
3774 pull_request = relationship('PullRequest', lazy='joined')
3774 pull_request = relationship('PullRequest', lazy='joined')
3775
3775
3776 def __unicode__(self):
3776 def __unicode__(self):
3777 return u"<%s('%s[v%s]:%s')>" % (
3777 return u"<%s('%s[v%s]:%s')>" % (
3778 self.__class__.__name__,
3778 self.__class__.__name__,
3779 self.status, self.version, self.author
3779 self.status, self.version, self.author
3780 )
3780 )
3781
3781
3782 @classmethod
3782 @classmethod
3783 def get_status_lbl(cls, value):
3783 def get_status_lbl(cls, value):
3784 return dict(cls.STATUSES).get(value)
3784 return dict(cls.STATUSES).get(value)
3785
3785
3786 @property
3786 @property
3787 def status_lbl(self):
3787 def status_lbl(self):
3788 return ChangesetStatus.get_status_lbl(self.status)
3788 return ChangesetStatus.get_status_lbl(self.status)
3789
3789
3790 def get_api_data(self):
3790 def get_api_data(self):
3791 status = self
3791 status = self
3792 data = {
3792 data = {
3793 'status_id': status.changeset_status_id,
3793 'status_id': status.changeset_status_id,
3794 'status': status.status,
3794 'status': status.status,
3795 }
3795 }
3796 return data
3796 return data
3797
3797
3798 def __json__(self):
3798 def __json__(self):
3799 data = dict()
3799 data = dict()
3800 data.update(self.get_api_data())
3800 data.update(self.get_api_data())
3801 return data
3801 return data
3802
3802
3803
3803
3804 class _SetState(object):
3804 class _SetState(object):
3805 """
3805 """
3806 Context processor allowing changing state for sensitive operation such as
3806 Context processor allowing changing state for sensitive operation such as
3807 pull request update or merge
3807 pull request update or merge
3808 """
3808 """
3809
3809
3810 def __init__(self, pull_request, pr_state, back_state=None):
3810 def __init__(self, pull_request, pr_state, back_state=None):
3811 self._pr = pull_request
3811 self._pr = pull_request
3812 self._org_state = back_state or pull_request.pull_request_state
3812 self._org_state = back_state or pull_request.pull_request_state
3813 self._pr_state = pr_state
3813 self._pr_state = pr_state
3814 self._current_state = None
3814 self._current_state = None
3815
3815
3816 def __enter__(self):
3816 def __enter__(self):
3817 log.debug('StateLock: entering set state context, setting state to: `%s`',
3817 log.debug('StateLock: entering set state context, setting state to: `%s`',
3818 self._pr_state)
3818 self._pr_state)
3819 self.set_pr_state(self._pr_state)
3819 self.set_pr_state(self._pr_state)
3820 return self
3820 return self
3821
3821
3822 def __exit__(self, exc_type, exc_val, exc_tb):
3822 def __exit__(self, exc_type, exc_val, exc_tb):
3823 if exc_val is not None:
3823 if exc_val is not None:
3824 log.error(traceback.format_exc(exc_tb))
3824 log.error(traceback.format_exc(exc_tb))
3825 return None
3825 return None
3826
3826
3827 self.set_pr_state(self._org_state)
3827 self.set_pr_state(self._org_state)
3828 log.debug('StateLock: exiting set state context, setting state to: `%s`',
3828 log.debug('StateLock: exiting set state context, setting state to: `%s`',
3829 self._org_state)
3829 self._org_state)
3830 @property
3830 @property
3831 def state(self):
3831 def state(self):
3832 return self._current_state
3832 return self._current_state
3833
3833
3834 def set_pr_state(self, pr_state):
3834 def set_pr_state(self, pr_state):
3835 try:
3835 try:
3836 self._pr.pull_request_state = pr_state
3836 self._pr.pull_request_state = pr_state
3837 Session().add(self._pr)
3837 Session().add(self._pr)
3838 Session().commit()
3838 Session().commit()
3839 self._current_state = pr_state
3839 self._current_state = pr_state
3840 except Exception:
3840 except Exception:
3841 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
3841 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
3842 raise
3842 raise
3843
3843
3844 class _PullRequestBase(BaseModel):
3844 class _PullRequestBase(BaseModel):
3845 """
3845 """
3846 Common attributes of pull request and version entries.
3846 Common attributes of pull request and version entries.
3847 """
3847 """
3848
3848
3849 # .status values
3849 # .status values
3850 STATUS_NEW = u'new'
3850 STATUS_NEW = u'new'
3851 STATUS_OPEN = u'open'
3851 STATUS_OPEN = u'open'
3852 STATUS_CLOSED = u'closed'
3852 STATUS_CLOSED = u'closed'
3853
3853
3854 # available states
3854 # available states
3855 STATE_CREATING = u'creating'
3855 STATE_CREATING = u'creating'
3856 STATE_UPDATING = u'updating'
3856 STATE_UPDATING = u'updating'
3857 STATE_MERGING = u'merging'
3857 STATE_MERGING = u'merging'
3858 STATE_CREATED = u'created'
3858 STATE_CREATED = u'created'
3859
3859
3860 title = Column('title', Unicode(255), nullable=True)
3860 title = Column('title', Unicode(255), nullable=True)
3861 description = Column(
3861 description = Column(
3862 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3862 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3863 nullable=True)
3863 nullable=True)
3864 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3864 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3865
3865
3866 # new/open/closed status of pull request (not approve/reject/etc)
3866 # new/open/closed status of pull request (not approve/reject/etc)
3867 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3867 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3868 created_on = Column(
3868 created_on = Column(
3869 'created_on', DateTime(timezone=False), nullable=False,
3869 'created_on', DateTime(timezone=False), nullable=False,
3870 default=datetime.datetime.now)
3870 default=datetime.datetime.now)
3871 updated_on = Column(
3871 updated_on = Column(
3872 'updated_on', DateTime(timezone=False), nullable=False,
3872 'updated_on', DateTime(timezone=False), nullable=False,
3873 default=datetime.datetime.now)
3873 default=datetime.datetime.now)
3874
3874
3875 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3875 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3876
3876
3877 @declared_attr
3877 @declared_attr
3878 def user_id(cls):
3878 def user_id(cls):
3879 return Column(
3879 return Column(
3880 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3880 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3881 unique=None)
3881 unique=None)
3882
3882
3883 # 500 revisions max
3883 # 500 revisions max
3884 _revisions = Column(
3884 _revisions = Column(
3885 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3885 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3886
3886
3887 @declared_attr
3887 @declared_attr
3888 def source_repo_id(cls):
3888 def source_repo_id(cls):
3889 # TODO: dan: rename column to source_repo_id
3889 # TODO: dan: rename column to source_repo_id
3890 return Column(
3890 return Column(
3891 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3891 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3892 nullable=False)
3892 nullable=False)
3893
3893
3894 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3894 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3895
3895
3896 @hybrid_property
3896 @hybrid_property
3897 def source_ref(self):
3897 def source_ref(self):
3898 return self._source_ref
3898 return self._source_ref
3899
3899
3900 @source_ref.setter
3900 @source_ref.setter
3901 def source_ref(self, val):
3901 def source_ref(self, val):
3902 parts = (val or '').split(':')
3902 parts = (val or '').split(':')
3903 if len(parts) != 3:
3903 if len(parts) != 3:
3904 raise ValueError(
3904 raise ValueError(
3905 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3905 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3906 self._source_ref = safe_unicode(val)
3906 self._source_ref = safe_unicode(val)
3907
3907
3908 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3908 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3909
3909
3910 @hybrid_property
3910 @hybrid_property
3911 def target_ref(self):
3911 def target_ref(self):
3912 return self._target_ref
3912 return self._target_ref
3913
3913
3914 @target_ref.setter
3914 @target_ref.setter
3915 def target_ref(self, val):
3915 def target_ref(self, val):
3916 parts = (val or '').split(':')
3916 parts = (val or '').split(':')
3917 if len(parts) != 3:
3917 if len(parts) != 3:
3918 raise ValueError(
3918 raise ValueError(
3919 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3919 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3920 self._target_ref = safe_unicode(val)
3920 self._target_ref = safe_unicode(val)
3921
3921
3922 @declared_attr
3922 @declared_attr
3923 def target_repo_id(cls):
3923 def target_repo_id(cls):
3924 # TODO: dan: rename column to target_repo_id
3924 # TODO: dan: rename column to target_repo_id
3925 return Column(
3925 return Column(
3926 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3926 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3927 nullable=False)
3927 nullable=False)
3928
3928
3929 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3929 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3930
3930
3931 # TODO: dan: rename column to last_merge_source_rev
3931 # TODO: dan: rename column to last_merge_source_rev
3932 _last_merge_source_rev = Column(
3932 _last_merge_source_rev = Column(
3933 'last_merge_org_rev', String(40), nullable=True)
3933 'last_merge_org_rev', String(40), nullable=True)
3934 # TODO: dan: rename column to last_merge_target_rev
3934 # TODO: dan: rename column to last_merge_target_rev
3935 _last_merge_target_rev = Column(
3935 _last_merge_target_rev = Column(
3936 'last_merge_other_rev', String(40), nullable=True)
3936 'last_merge_other_rev', String(40), nullable=True)
3937 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3937 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3938 merge_rev = Column('merge_rev', String(40), nullable=True)
3938 merge_rev = Column('merge_rev', String(40), nullable=True)
3939
3939
3940 reviewer_data = Column(
3940 reviewer_data = Column(
3941 'reviewer_data_json', MutationObj.as_mutable(
3941 'reviewer_data_json', MutationObj.as_mutable(
3942 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3942 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3943
3943
3944 @property
3944 @property
3945 def reviewer_data_json(self):
3945 def reviewer_data_json(self):
3946 return json.dumps(self.reviewer_data)
3946 return json.dumps(self.reviewer_data)
3947
3947
3948 @hybrid_property
3948 @hybrid_property
3949 def description_safe(self):
3949 def description_safe(self):
3950 from rhodecode.lib import helpers as h
3950 from rhodecode.lib import helpers as h
3951 return h.escape(self.description)
3951 return h.escape(self.description)
3952
3952
3953 @hybrid_property
3953 @hybrid_property
3954 def revisions(self):
3954 def revisions(self):
3955 return self._revisions.split(':') if self._revisions else []
3955 return self._revisions.split(':') if self._revisions else []
3956
3956
3957 @revisions.setter
3957 @revisions.setter
3958 def revisions(self, val):
3958 def revisions(self, val):
3959 self._revisions = u':'.join(val)
3959 self._revisions = u':'.join(val)
3960
3960
3961 @hybrid_property
3961 @hybrid_property
3962 def last_merge_status(self):
3962 def last_merge_status(self):
3963 return safe_int(self._last_merge_status)
3963 return safe_int(self._last_merge_status)
3964
3964
3965 @last_merge_status.setter
3965 @last_merge_status.setter
3966 def last_merge_status(self, val):
3966 def last_merge_status(self, val):
3967 self._last_merge_status = val
3967 self._last_merge_status = val
3968
3968
3969 @declared_attr
3969 @declared_attr
3970 def author(cls):
3970 def author(cls):
3971 return relationship('User', lazy='joined')
3971 return relationship('User', lazy='joined')
3972
3972
3973 @declared_attr
3973 @declared_attr
3974 def source_repo(cls):
3974 def source_repo(cls):
3975 return relationship(
3975 return relationship(
3976 'Repository',
3976 'Repository',
3977 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3977 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3978
3978
3979 @property
3979 @property
3980 def source_ref_parts(self):
3980 def source_ref_parts(self):
3981 return self.unicode_to_reference(self.source_ref)
3981 return self.unicode_to_reference(self.source_ref)
3982
3982
3983 @declared_attr
3983 @declared_attr
3984 def target_repo(cls):
3984 def target_repo(cls):
3985 return relationship(
3985 return relationship(
3986 'Repository',
3986 'Repository',
3987 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3987 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3988
3988
3989 @property
3989 @property
3990 def target_ref_parts(self):
3990 def target_ref_parts(self):
3991 return self.unicode_to_reference(self.target_ref)
3991 return self.unicode_to_reference(self.target_ref)
3992
3992
3993 @property
3993 @property
3994 def shadow_merge_ref(self):
3994 def shadow_merge_ref(self):
3995 return self.unicode_to_reference(self._shadow_merge_ref)
3995 return self.unicode_to_reference(self._shadow_merge_ref)
3996
3996
3997 @shadow_merge_ref.setter
3997 @shadow_merge_ref.setter
3998 def shadow_merge_ref(self, ref):
3998 def shadow_merge_ref(self, ref):
3999 self._shadow_merge_ref = self.reference_to_unicode(ref)
3999 self._shadow_merge_ref = self.reference_to_unicode(ref)
4000
4000
4001 @staticmethod
4001 @staticmethod
4002 def unicode_to_reference(raw):
4002 def unicode_to_reference(raw):
4003 """
4003 """
4004 Convert a unicode (or string) to a reference object.
4004 Convert a unicode (or string) to a reference object.
4005 If unicode evaluates to False it returns None.
4005 If unicode evaluates to False it returns None.
4006 """
4006 """
4007 if raw:
4007 if raw:
4008 refs = raw.split(':')
4008 refs = raw.split(':')
4009 return Reference(*refs)
4009 return Reference(*refs)
4010 else:
4010 else:
4011 return None
4011 return None
4012
4012
4013 @staticmethod
4013 @staticmethod
4014 def reference_to_unicode(ref):
4014 def reference_to_unicode(ref):
4015 """
4015 """
4016 Convert a reference object to unicode.
4016 Convert a reference object to unicode.
4017 If reference is None it returns None.
4017 If reference is None it returns None.
4018 """
4018 """
4019 if ref:
4019 if ref:
4020 return u':'.join(ref)
4020 return u':'.join(ref)
4021 else:
4021 else:
4022 return None
4022 return None
4023
4023
4024 def get_api_data(self, with_merge_state=True):
4024 def get_api_data(self, with_merge_state=True):
4025 from rhodecode.model.pull_request import PullRequestModel
4025 from rhodecode.model.pull_request import PullRequestModel
4026
4026
4027 pull_request = self
4027 pull_request = self
4028 if with_merge_state:
4028 if with_merge_state:
4029 merge_status = PullRequestModel().merge_status(pull_request)
4029 merge_status = PullRequestModel().merge_status(pull_request)
4030 merge_state = {
4030 merge_state = {
4031 'status': merge_status[0],
4031 'status': merge_status[0],
4032 'message': safe_unicode(merge_status[1]),
4032 'message': safe_unicode(merge_status[1]),
4033 }
4033 }
4034 else:
4034 else:
4035 merge_state = {'status': 'not_available',
4035 merge_state = {'status': 'not_available',
4036 'message': 'not_available'}
4036 'message': 'not_available'}
4037
4037
4038 merge_data = {
4038 merge_data = {
4039 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4039 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4040 'reference': (
4040 'reference': (
4041 pull_request.shadow_merge_ref._asdict()
4041 pull_request.shadow_merge_ref._asdict()
4042 if pull_request.shadow_merge_ref else None),
4042 if pull_request.shadow_merge_ref else None),
4043 }
4043 }
4044
4044
4045 data = {
4045 data = {
4046 'pull_request_id': pull_request.pull_request_id,
4046 'pull_request_id': pull_request.pull_request_id,
4047 'url': PullRequestModel().get_url(pull_request),
4047 'url': PullRequestModel().get_url(pull_request),
4048 'title': pull_request.title,
4048 'title': pull_request.title,
4049 'description': pull_request.description,
4049 'description': pull_request.description,
4050 'status': pull_request.status,
4050 'status': pull_request.status,
4051 'state': pull_request.pull_request_state,
4051 'state': pull_request.pull_request_state,
4052 'created_on': pull_request.created_on,
4052 'created_on': pull_request.created_on,
4053 'updated_on': pull_request.updated_on,
4053 'updated_on': pull_request.updated_on,
4054 'commit_ids': pull_request.revisions,
4054 'commit_ids': pull_request.revisions,
4055 'review_status': pull_request.calculated_review_status(),
4055 'review_status': pull_request.calculated_review_status(),
4056 'mergeable': merge_state,
4056 'mergeable': merge_state,
4057 'source': {
4057 'source': {
4058 'clone_url': pull_request.source_repo.clone_url(),
4058 'clone_url': pull_request.source_repo.clone_url(),
4059 'repository': pull_request.source_repo.repo_name,
4059 'repository': pull_request.source_repo.repo_name,
4060 'reference': {
4060 'reference': {
4061 'name': pull_request.source_ref_parts.name,
4061 'name': pull_request.source_ref_parts.name,
4062 'type': pull_request.source_ref_parts.type,
4062 'type': pull_request.source_ref_parts.type,
4063 'commit_id': pull_request.source_ref_parts.commit_id,
4063 'commit_id': pull_request.source_ref_parts.commit_id,
4064 },
4064 },
4065 },
4065 },
4066 'target': {
4066 'target': {
4067 'clone_url': pull_request.target_repo.clone_url(),
4067 'clone_url': pull_request.target_repo.clone_url(),
4068 'repository': pull_request.target_repo.repo_name,
4068 'repository': pull_request.target_repo.repo_name,
4069 'reference': {
4069 'reference': {
4070 'name': pull_request.target_ref_parts.name,
4070 'name': pull_request.target_ref_parts.name,
4071 'type': pull_request.target_ref_parts.type,
4071 'type': pull_request.target_ref_parts.type,
4072 'commit_id': pull_request.target_ref_parts.commit_id,
4072 'commit_id': pull_request.target_ref_parts.commit_id,
4073 },
4073 },
4074 },
4074 },
4075 'merge': merge_data,
4075 'merge': merge_data,
4076 'author': pull_request.author.get_api_data(include_secrets=False,
4076 'author': pull_request.author.get_api_data(include_secrets=False,
4077 details='basic'),
4077 details='basic'),
4078 'reviewers': [
4078 'reviewers': [
4079 {
4079 {
4080 'user': reviewer.get_api_data(include_secrets=False,
4080 'user': reviewer.get_api_data(include_secrets=False,
4081 details='basic'),
4081 details='basic'),
4082 'reasons': reasons,
4082 'reasons': reasons,
4083 'review_status': st[0][1].status if st else 'not_reviewed',
4083 'review_status': st[0][1].status if st else 'not_reviewed',
4084 }
4084 }
4085 for obj, reviewer, reasons, mandatory, st in
4085 for obj, reviewer, reasons, mandatory, st in
4086 pull_request.reviewers_statuses()
4086 pull_request.reviewers_statuses()
4087 ]
4087 ]
4088 }
4088 }
4089
4089
4090 return data
4090 return data
4091
4091
4092 def set_state(self, pull_request_state, final_state=None):
4092 def set_state(self, pull_request_state, final_state=None):
4093 """
4093 """
4094 # goes from initial state to updating to initial state.
4094 # goes from initial state to updating to initial state.
4095 # initial state can be changed by specifying back_state=
4095 # initial state can be changed by specifying back_state=
4096 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4096 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4097 pull_request.merge()
4097 pull_request.merge()
4098
4098
4099 :param pull_request_state:
4099 :param pull_request_state:
4100 :param final_state:
4100 :param final_state:
4101
4101
4102 """
4102 """
4103
4103
4104 return _SetState(self, pull_request_state, back_state=final_state)
4104 return _SetState(self, pull_request_state, back_state=final_state)
4105
4105
4106
4106
4107 class PullRequest(Base, _PullRequestBase):
4107 class PullRequest(Base, _PullRequestBase):
4108 __tablename__ = 'pull_requests'
4108 __tablename__ = 'pull_requests'
4109 __table_args__ = (
4109 __table_args__ = (
4110 base_table_args,
4110 base_table_args,
4111 )
4111 )
4112
4112
4113 pull_request_id = Column(
4113 pull_request_id = Column(
4114 'pull_request_id', Integer(), nullable=False, primary_key=True)
4114 'pull_request_id', Integer(), nullable=False, primary_key=True)
4115
4115
4116 def __repr__(self):
4116 def __repr__(self):
4117 if self.pull_request_id:
4117 if self.pull_request_id:
4118 return '<DB:PullRequest #%s>' % self.pull_request_id
4118 return '<DB:PullRequest #%s>' % self.pull_request_id
4119 else:
4119 else:
4120 return '<DB:PullRequest at %#x>' % id(self)
4120 return '<DB:PullRequest at %#x>' % id(self)
4121
4121
4122 reviewers = relationship('PullRequestReviewers',
4122 reviewers = relationship('PullRequestReviewers',
4123 cascade="all, delete, delete-orphan")
4123 cascade="all, delete-orphan")
4124 statuses = relationship('ChangesetStatus',
4124 statuses = relationship('ChangesetStatus',
4125 cascade="all, delete, delete-orphan")
4125 cascade="all, delete-orphan")
4126 comments = relationship('ChangesetComment',
4126 comments = relationship('ChangesetComment',
4127 cascade="all, delete, delete-orphan")
4127 cascade="all, delete-orphan")
4128 versions = relationship('PullRequestVersion',
4128 versions = relationship('PullRequestVersion',
4129 cascade="all, delete, delete-orphan",
4129 cascade="all, delete-orphan",
4130 lazy='dynamic')
4130 lazy='dynamic')
4131
4131
4132 @classmethod
4132 @classmethod
4133 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4133 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4134 internal_methods=None):
4134 internal_methods=None):
4135
4135
4136 class PullRequestDisplay(object):
4136 class PullRequestDisplay(object):
4137 """
4137 """
4138 Special object wrapper for showing PullRequest data via Versions
4138 Special object wrapper for showing PullRequest data via Versions
4139 It mimics PR object as close as possible. This is read only object
4139 It mimics PR object as close as possible. This is read only object
4140 just for display
4140 just for display
4141 """
4141 """
4142
4142
4143 def __init__(self, attrs, internal=None):
4143 def __init__(self, attrs, internal=None):
4144 self.attrs = attrs
4144 self.attrs = attrs
4145 # internal have priority over the given ones via attrs
4145 # internal have priority over the given ones via attrs
4146 self.internal = internal or ['versions']
4146 self.internal = internal or ['versions']
4147
4147
4148 def __getattr__(self, item):
4148 def __getattr__(self, item):
4149 if item in self.internal:
4149 if item in self.internal:
4150 return getattr(self, item)
4150 return getattr(self, item)
4151 try:
4151 try:
4152 return self.attrs[item]
4152 return self.attrs[item]
4153 except KeyError:
4153 except KeyError:
4154 raise AttributeError(
4154 raise AttributeError(
4155 '%s object has no attribute %s' % (self, item))
4155 '%s object has no attribute %s' % (self, item))
4156
4156
4157 def __repr__(self):
4157 def __repr__(self):
4158 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4158 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4159
4159
4160 def versions(self):
4160 def versions(self):
4161 return pull_request_obj.versions.order_by(
4161 return pull_request_obj.versions.order_by(
4162 PullRequestVersion.pull_request_version_id).all()
4162 PullRequestVersion.pull_request_version_id).all()
4163
4163
4164 def is_closed(self):
4164 def is_closed(self):
4165 return pull_request_obj.is_closed()
4165 return pull_request_obj.is_closed()
4166
4166
4167 @property
4167 @property
4168 def pull_request_version_id(self):
4168 def pull_request_version_id(self):
4169 return getattr(pull_request_obj, 'pull_request_version_id', None)
4169 return getattr(pull_request_obj, 'pull_request_version_id', None)
4170
4170
4171 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4171 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4172
4172
4173 attrs.author = StrictAttributeDict(
4173 attrs.author = StrictAttributeDict(
4174 pull_request_obj.author.get_api_data())
4174 pull_request_obj.author.get_api_data())
4175 if pull_request_obj.target_repo:
4175 if pull_request_obj.target_repo:
4176 attrs.target_repo = StrictAttributeDict(
4176 attrs.target_repo = StrictAttributeDict(
4177 pull_request_obj.target_repo.get_api_data())
4177 pull_request_obj.target_repo.get_api_data())
4178 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4178 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4179
4179
4180 if pull_request_obj.source_repo:
4180 if pull_request_obj.source_repo:
4181 attrs.source_repo = StrictAttributeDict(
4181 attrs.source_repo = StrictAttributeDict(
4182 pull_request_obj.source_repo.get_api_data())
4182 pull_request_obj.source_repo.get_api_data())
4183 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4183 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4184
4184
4185 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4185 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4186 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4186 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4187 attrs.revisions = pull_request_obj.revisions
4187 attrs.revisions = pull_request_obj.revisions
4188
4188
4189 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4189 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4190 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4190 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4191 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4191 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4192
4192
4193 return PullRequestDisplay(attrs, internal=internal_methods)
4193 return PullRequestDisplay(attrs, internal=internal_methods)
4194
4194
4195 def is_closed(self):
4195 def is_closed(self):
4196 return self.status == self.STATUS_CLOSED
4196 return self.status == self.STATUS_CLOSED
4197
4197
4198 def __json__(self):
4198 def __json__(self):
4199 return {
4199 return {
4200 'revisions': self.revisions,
4200 'revisions': self.revisions,
4201 }
4201 }
4202
4202
4203 def calculated_review_status(self):
4203 def calculated_review_status(self):
4204 from rhodecode.model.changeset_status import ChangesetStatusModel
4204 from rhodecode.model.changeset_status import ChangesetStatusModel
4205 return ChangesetStatusModel().calculated_review_status(self)
4205 return ChangesetStatusModel().calculated_review_status(self)
4206
4206
4207 def reviewers_statuses(self):
4207 def reviewers_statuses(self):
4208 from rhodecode.model.changeset_status import ChangesetStatusModel
4208 from rhodecode.model.changeset_status import ChangesetStatusModel
4209 return ChangesetStatusModel().reviewers_statuses(self)
4209 return ChangesetStatusModel().reviewers_statuses(self)
4210
4210
4211 @property
4211 @property
4212 def workspace_id(self):
4212 def workspace_id(self):
4213 from rhodecode.model.pull_request import PullRequestModel
4213 from rhodecode.model.pull_request import PullRequestModel
4214 return PullRequestModel()._workspace_id(self)
4214 return PullRequestModel()._workspace_id(self)
4215
4215
4216 def get_shadow_repo(self):
4216 def get_shadow_repo(self):
4217 workspace_id = self.workspace_id
4217 workspace_id = self.workspace_id
4218 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4218 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4219 if os.path.isdir(shadow_repository_path):
4219 if os.path.isdir(shadow_repository_path):
4220 vcs_obj = self.target_repo.scm_instance()
4220 vcs_obj = self.target_repo.scm_instance()
4221 return vcs_obj.get_shadow_instance(shadow_repository_path)
4221 return vcs_obj.get_shadow_instance(shadow_repository_path)
4222
4222
4223
4223
4224 class PullRequestVersion(Base, _PullRequestBase):
4224 class PullRequestVersion(Base, _PullRequestBase):
4225 __tablename__ = 'pull_request_versions'
4225 __tablename__ = 'pull_request_versions'
4226 __table_args__ = (
4226 __table_args__ = (
4227 base_table_args,
4227 base_table_args,
4228 )
4228 )
4229
4229
4230 pull_request_version_id = Column(
4230 pull_request_version_id = Column(
4231 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4231 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4232 pull_request_id = Column(
4232 pull_request_id = Column(
4233 'pull_request_id', Integer(),
4233 'pull_request_id', Integer(),
4234 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4234 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4235 pull_request = relationship('PullRequest')
4235 pull_request = relationship('PullRequest')
4236
4236
4237 def __repr__(self):
4237 def __repr__(self):
4238 if self.pull_request_version_id:
4238 if self.pull_request_version_id:
4239 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4239 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4240 else:
4240 else:
4241 return '<DB:PullRequestVersion at %#x>' % id(self)
4241 return '<DB:PullRequestVersion at %#x>' % id(self)
4242
4242
4243 @property
4243 @property
4244 def reviewers(self):
4244 def reviewers(self):
4245 return self.pull_request.reviewers
4245 return self.pull_request.reviewers
4246
4246
4247 @property
4247 @property
4248 def versions(self):
4248 def versions(self):
4249 return self.pull_request.versions
4249 return self.pull_request.versions
4250
4250
4251 def is_closed(self):
4251 def is_closed(self):
4252 # calculate from original
4252 # calculate from original
4253 return self.pull_request.status == self.STATUS_CLOSED
4253 return self.pull_request.status == self.STATUS_CLOSED
4254
4254
4255 def calculated_review_status(self):
4255 def calculated_review_status(self):
4256 return self.pull_request.calculated_review_status()
4256 return self.pull_request.calculated_review_status()
4257
4257
4258 def reviewers_statuses(self):
4258 def reviewers_statuses(self):
4259 return self.pull_request.reviewers_statuses()
4259 return self.pull_request.reviewers_statuses()
4260
4260
4261
4261
4262 class PullRequestReviewers(Base, BaseModel):
4262 class PullRequestReviewers(Base, BaseModel):
4263 __tablename__ = 'pull_request_reviewers'
4263 __tablename__ = 'pull_request_reviewers'
4264 __table_args__ = (
4264 __table_args__ = (
4265 base_table_args,
4265 base_table_args,
4266 )
4266 )
4267
4267
4268 @hybrid_property
4268 @hybrid_property
4269 def reasons(self):
4269 def reasons(self):
4270 if not self._reasons:
4270 if not self._reasons:
4271 return []
4271 return []
4272 return self._reasons
4272 return self._reasons
4273
4273
4274 @reasons.setter
4274 @reasons.setter
4275 def reasons(self, val):
4275 def reasons(self, val):
4276 val = val or []
4276 val = val or []
4277 if any(not isinstance(x, compat.string_types) for x in val):
4277 if any(not isinstance(x, compat.string_types) for x in val):
4278 raise Exception('invalid reasons type, must be list of strings')
4278 raise Exception('invalid reasons type, must be list of strings')
4279 self._reasons = val
4279 self._reasons = val
4280
4280
4281 pull_requests_reviewers_id = Column(
4281 pull_requests_reviewers_id = Column(
4282 'pull_requests_reviewers_id', Integer(), nullable=False,
4282 'pull_requests_reviewers_id', Integer(), nullable=False,
4283 primary_key=True)
4283 primary_key=True)
4284 pull_request_id = Column(
4284 pull_request_id = Column(
4285 "pull_request_id", Integer(),
4285 "pull_request_id", Integer(),
4286 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4286 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4287 user_id = Column(
4287 user_id = Column(
4288 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4288 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4289 _reasons = Column(
4289 _reasons = Column(
4290 'reason', MutationList.as_mutable(
4290 'reason', MutationList.as_mutable(
4291 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4291 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4292
4292
4293 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4293 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4294 user = relationship('User')
4294 user = relationship('User')
4295 pull_request = relationship('PullRequest')
4295 pull_request = relationship('PullRequest')
4296
4296
4297 rule_data = Column(
4297 rule_data = Column(
4298 'rule_data_json',
4298 'rule_data_json',
4299 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4299 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4300
4300
4301 def rule_user_group_data(self):
4301 def rule_user_group_data(self):
4302 """
4302 """
4303 Returns the voting user group rule data for this reviewer
4303 Returns the voting user group rule data for this reviewer
4304 """
4304 """
4305
4305
4306 if self.rule_data and 'vote_rule' in self.rule_data:
4306 if self.rule_data and 'vote_rule' in self.rule_data:
4307 user_group_data = {}
4307 user_group_data = {}
4308 if 'rule_user_group_entry_id' in self.rule_data:
4308 if 'rule_user_group_entry_id' in self.rule_data:
4309 # means a group with voting rules !
4309 # means a group with voting rules !
4310 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4310 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4311 user_group_data['name'] = self.rule_data['rule_name']
4311 user_group_data['name'] = self.rule_data['rule_name']
4312 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4312 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4313
4313
4314 return user_group_data
4314 return user_group_data
4315
4315
4316 def __unicode__(self):
4316 def __unicode__(self):
4317 return u"<%s('id:%s')>" % (self.__class__.__name__,
4317 return u"<%s('id:%s')>" % (self.__class__.__name__,
4318 self.pull_requests_reviewers_id)
4318 self.pull_requests_reviewers_id)
4319
4319
4320
4320
4321 class Notification(Base, BaseModel):
4321 class Notification(Base, BaseModel):
4322 __tablename__ = 'notifications'
4322 __tablename__ = 'notifications'
4323 __table_args__ = (
4323 __table_args__ = (
4324 Index('notification_type_idx', 'type'),
4324 Index('notification_type_idx', 'type'),
4325 base_table_args,
4325 base_table_args,
4326 )
4326 )
4327
4327
4328 TYPE_CHANGESET_COMMENT = u'cs_comment'
4328 TYPE_CHANGESET_COMMENT = u'cs_comment'
4329 TYPE_MESSAGE = u'message'
4329 TYPE_MESSAGE = u'message'
4330 TYPE_MENTION = u'mention'
4330 TYPE_MENTION = u'mention'
4331 TYPE_REGISTRATION = u'registration'
4331 TYPE_REGISTRATION = u'registration'
4332 TYPE_PULL_REQUEST = u'pull_request'
4332 TYPE_PULL_REQUEST = u'pull_request'
4333 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4333 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4334
4334
4335 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4335 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4336 subject = Column('subject', Unicode(512), nullable=True)
4336 subject = Column('subject', Unicode(512), nullable=True)
4337 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4337 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4338 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4338 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4339 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4339 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4340 type_ = Column('type', Unicode(255))
4340 type_ = Column('type', Unicode(255))
4341
4341
4342 created_by_user = relationship('User')
4342 created_by_user = relationship('User')
4343 notifications_to_users = relationship('UserNotification', lazy='joined',
4343 notifications_to_users = relationship('UserNotification', lazy='joined',
4344 cascade="all, delete, delete-orphan")
4344 cascade="all, delete-orphan")
4345
4345
4346 @property
4346 @property
4347 def recipients(self):
4347 def recipients(self):
4348 return [x.user for x in UserNotification.query()\
4348 return [x.user for x in UserNotification.query()\
4349 .filter(UserNotification.notification == self)\
4349 .filter(UserNotification.notification == self)\
4350 .order_by(UserNotification.user_id.asc()).all()]
4350 .order_by(UserNotification.user_id.asc()).all()]
4351
4351
4352 @classmethod
4352 @classmethod
4353 def create(cls, created_by, subject, body, recipients, type_=None):
4353 def create(cls, created_by, subject, body, recipients, type_=None):
4354 if type_ is None:
4354 if type_ is None:
4355 type_ = Notification.TYPE_MESSAGE
4355 type_ = Notification.TYPE_MESSAGE
4356
4356
4357 notification = cls()
4357 notification = cls()
4358 notification.created_by_user = created_by
4358 notification.created_by_user = created_by
4359 notification.subject = subject
4359 notification.subject = subject
4360 notification.body = body
4360 notification.body = body
4361 notification.type_ = type_
4361 notification.type_ = type_
4362 notification.created_on = datetime.datetime.now()
4362 notification.created_on = datetime.datetime.now()
4363
4363
4364 # For each recipient link the created notification to his account
4364 # For each recipient link the created notification to his account
4365 for u in recipients:
4365 for u in recipients:
4366 assoc = UserNotification()
4366 assoc = UserNotification()
4367 assoc.user_id = u.user_id
4367 assoc.user_id = u.user_id
4368 assoc.notification = notification
4368 assoc.notification = notification
4369
4369
4370 # if created_by is inside recipients mark his notification
4370 # if created_by is inside recipients mark his notification
4371 # as read
4371 # as read
4372 if u.user_id == created_by.user_id:
4372 if u.user_id == created_by.user_id:
4373 assoc.read = True
4373 assoc.read = True
4374 Session().add(assoc)
4374 Session().add(assoc)
4375
4375
4376 Session().add(notification)
4376 Session().add(notification)
4377
4377
4378 return notification
4378 return notification
4379
4379
4380
4380
4381 class UserNotification(Base, BaseModel):
4381 class UserNotification(Base, BaseModel):
4382 __tablename__ = 'user_to_notification'
4382 __tablename__ = 'user_to_notification'
4383 __table_args__ = (
4383 __table_args__ = (
4384 UniqueConstraint('user_id', 'notification_id'),
4384 UniqueConstraint('user_id', 'notification_id'),
4385 base_table_args
4385 base_table_args
4386 )
4386 )
4387
4387
4388 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4388 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4389 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4389 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4390 read = Column('read', Boolean, default=False)
4390 read = Column('read', Boolean, default=False)
4391 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4391 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4392
4392
4393 user = relationship('User', lazy="joined")
4393 user = relationship('User', lazy="joined")
4394 notification = relationship('Notification', lazy="joined",
4394 notification = relationship('Notification', lazy="joined",
4395 order_by=lambda: Notification.created_on.desc(),)
4395 order_by=lambda: Notification.created_on.desc(),)
4396
4396
4397 def mark_as_read(self):
4397 def mark_as_read(self):
4398 self.read = True
4398 self.read = True
4399 Session().add(self)
4399 Session().add(self)
4400
4400
4401
4401
4402 class Gist(Base, BaseModel):
4402 class Gist(Base, BaseModel):
4403 __tablename__ = 'gists'
4403 __tablename__ = 'gists'
4404 __table_args__ = (
4404 __table_args__ = (
4405 Index('g_gist_access_id_idx', 'gist_access_id'),
4405 Index('g_gist_access_id_idx', 'gist_access_id'),
4406 Index('g_created_on_idx', 'created_on'),
4406 Index('g_created_on_idx', 'created_on'),
4407 base_table_args
4407 base_table_args
4408 )
4408 )
4409
4409
4410 GIST_PUBLIC = u'public'
4410 GIST_PUBLIC = u'public'
4411 GIST_PRIVATE = u'private'
4411 GIST_PRIVATE = u'private'
4412 DEFAULT_FILENAME = u'gistfile1.txt'
4412 DEFAULT_FILENAME = u'gistfile1.txt'
4413
4413
4414 ACL_LEVEL_PUBLIC = u'acl_public'
4414 ACL_LEVEL_PUBLIC = u'acl_public'
4415 ACL_LEVEL_PRIVATE = u'acl_private'
4415 ACL_LEVEL_PRIVATE = u'acl_private'
4416
4416
4417 gist_id = Column('gist_id', Integer(), primary_key=True)
4417 gist_id = Column('gist_id', Integer(), primary_key=True)
4418 gist_access_id = Column('gist_access_id', Unicode(250))
4418 gist_access_id = Column('gist_access_id', Unicode(250))
4419 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4419 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4420 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4420 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4421 gist_expires = Column('gist_expires', Float(53), nullable=False)
4421 gist_expires = Column('gist_expires', Float(53), nullable=False)
4422 gist_type = Column('gist_type', Unicode(128), nullable=False)
4422 gist_type = Column('gist_type', Unicode(128), nullable=False)
4423 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4423 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4424 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4424 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4425 acl_level = Column('acl_level', Unicode(128), nullable=True)
4425 acl_level = Column('acl_level', Unicode(128), nullable=True)
4426
4426
4427 owner = relationship('User')
4427 owner = relationship('User')
4428
4428
4429 def __repr__(self):
4429 def __repr__(self):
4430 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4430 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4431
4431
4432 @hybrid_property
4432 @hybrid_property
4433 def description_safe(self):
4433 def description_safe(self):
4434 from rhodecode.lib import helpers as h
4434 from rhodecode.lib import helpers as h
4435 return h.escape(self.gist_description)
4435 return h.escape(self.gist_description)
4436
4436
4437 @classmethod
4437 @classmethod
4438 def get_or_404(cls, id_):
4438 def get_or_404(cls, id_):
4439 from pyramid.httpexceptions import HTTPNotFound
4439 from pyramid.httpexceptions import HTTPNotFound
4440
4440
4441 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4441 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4442 if not res:
4442 if not res:
4443 raise HTTPNotFound()
4443 raise HTTPNotFound()
4444 return res
4444 return res
4445
4445
4446 @classmethod
4446 @classmethod
4447 def get_by_access_id(cls, gist_access_id):
4447 def get_by_access_id(cls, gist_access_id):
4448 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4448 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4449
4449
4450 def gist_url(self):
4450 def gist_url(self):
4451 from rhodecode.model.gist import GistModel
4451 from rhodecode.model.gist import GistModel
4452 return GistModel().get_url(self)
4452 return GistModel().get_url(self)
4453
4453
4454 @classmethod
4454 @classmethod
4455 def base_path(cls):
4455 def base_path(cls):
4456 """
4456 """
4457 Returns base path when all gists are stored
4457 Returns base path when all gists are stored
4458
4458
4459 :param cls:
4459 :param cls:
4460 """
4460 """
4461 from rhodecode.model.gist import GIST_STORE_LOC
4461 from rhodecode.model.gist import GIST_STORE_LOC
4462 q = Session().query(RhodeCodeUi)\
4462 q = Session().query(RhodeCodeUi)\
4463 .filter(RhodeCodeUi.ui_key == URL_SEP)
4463 .filter(RhodeCodeUi.ui_key == URL_SEP)
4464 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4464 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4465 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4465 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4466
4466
4467 def get_api_data(self):
4467 def get_api_data(self):
4468 """
4468 """
4469 Common function for generating gist related data for API
4469 Common function for generating gist related data for API
4470 """
4470 """
4471 gist = self
4471 gist = self
4472 data = {
4472 data = {
4473 'gist_id': gist.gist_id,
4473 'gist_id': gist.gist_id,
4474 'type': gist.gist_type,
4474 'type': gist.gist_type,
4475 'access_id': gist.gist_access_id,
4475 'access_id': gist.gist_access_id,
4476 'description': gist.gist_description,
4476 'description': gist.gist_description,
4477 'url': gist.gist_url(),
4477 'url': gist.gist_url(),
4478 'expires': gist.gist_expires,
4478 'expires': gist.gist_expires,
4479 'created_on': gist.created_on,
4479 'created_on': gist.created_on,
4480 'modified_at': gist.modified_at,
4480 'modified_at': gist.modified_at,
4481 'content': None,
4481 'content': None,
4482 'acl_level': gist.acl_level,
4482 'acl_level': gist.acl_level,
4483 }
4483 }
4484 return data
4484 return data
4485
4485
4486 def __json__(self):
4486 def __json__(self):
4487 data = dict(
4487 data = dict(
4488 )
4488 )
4489 data.update(self.get_api_data())
4489 data.update(self.get_api_data())
4490 return data
4490 return data
4491 # SCM functions
4491 # SCM functions
4492
4492
4493 def scm_instance(self, **kwargs):
4493 def scm_instance(self, **kwargs):
4494 """
4494 """
4495 Get an instance of VCS Repository
4495 Get an instance of VCS Repository
4496
4496
4497 :param kwargs:
4497 :param kwargs:
4498 """
4498 """
4499 from rhodecode.model.gist import GistModel
4499 from rhodecode.model.gist import GistModel
4500 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4500 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4501 return get_vcs_instance(
4501 return get_vcs_instance(
4502 repo_path=safe_str(full_repo_path), create=False,
4502 repo_path=safe_str(full_repo_path), create=False,
4503 _vcs_alias=GistModel.vcs_backend)
4503 _vcs_alias=GistModel.vcs_backend)
4504
4504
4505
4505
4506 class ExternalIdentity(Base, BaseModel):
4506 class ExternalIdentity(Base, BaseModel):
4507 __tablename__ = 'external_identities'
4507 __tablename__ = 'external_identities'
4508 __table_args__ = (
4508 __table_args__ = (
4509 Index('local_user_id_idx', 'local_user_id'),
4509 Index('local_user_id_idx', 'local_user_id'),
4510 Index('external_id_idx', 'external_id'),
4510 Index('external_id_idx', 'external_id'),
4511 base_table_args
4511 base_table_args
4512 )
4512 )
4513
4513
4514 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4514 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4515 external_username = Column('external_username', Unicode(1024), default=u'')
4515 external_username = Column('external_username', Unicode(1024), default=u'')
4516 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4516 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4517 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4517 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4518 access_token = Column('access_token', String(1024), default=u'')
4518 access_token = Column('access_token', String(1024), default=u'')
4519 alt_token = Column('alt_token', String(1024), default=u'')
4519 alt_token = Column('alt_token', String(1024), default=u'')
4520 token_secret = Column('token_secret', String(1024), default=u'')
4520 token_secret = Column('token_secret', String(1024), default=u'')
4521
4521
4522 @classmethod
4522 @classmethod
4523 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4523 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4524 """
4524 """
4525 Returns ExternalIdentity instance based on search params
4525 Returns ExternalIdentity instance based on search params
4526
4526
4527 :param external_id:
4527 :param external_id:
4528 :param provider_name:
4528 :param provider_name:
4529 :return: ExternalIdentity
4529 :return: ExternalIdentity
4530 """
4530 """
4531 query = cls.query()
4531 query = cls.query()
4532 query = query.filter(cls.external_id == external_id)
4532 query = query.filter(cls.external_id == external_id)
4533 query = query.filter(cls.provider_name == provider_name)
4533 query = query.filter(cls.provider_name == provider_name)
4534 if local_user_id:
4534 if local_user_id:
4535 query = query.filter(cls.local_user_id == local_user_id)
4535 query = query.filter(cls.local_user_id == local_user_id)
4536 return query.first()
4536 return query.first()
4537
4537
4538 @classmethod
4538 @classmethod
4539 def user_by_external_id_and_provider(cls, external_id, provider_name):
4539 def user_by_external_id_and_provider(cls, external_id, provider_name):
4540 """
4540 """
4541 Returns User instance based on search params
4541 Returns User instance based on search params
4542
4542
4543 :param external_id:
4543 :param external_id:
4544 :param provider_name:
4544 :param provider_name:
4545 :return: User
4545 :return: User
4546 """
4546 """
4547 query = User.query()
4547 query = User.query()
4548 query = query.filter(cls.external_id == external_id)
4548 query = query.filter(cls.external_id == external_id)
4549 query = query.filter(cls.provider_name == provider_name)
4549 query = query.filter(cls.provider_name == provider_name)
4550 query = query.filter(User.user_id == cls.local_user_id)
4550 query = query.filter(User.user_id == cls.local_user_id)
4551 return query.first()
4551 return query.first()
4552
4552
4553 @classmethod
4553 @classmethod
4554 def by_local_user_id(cls, local_user_id):
4554 def by_local_user_id(cls, local_user_id):
4555 """
4555 """
4556 Returns all tokens for user
4556 Returns all tokens for user
4557
4557
4558 :param local_user_id:
4558 :param local_user_id:
4559 :return: ExternalIdentity
4559 :return: ExternalIdentity
4560 """
4560 """
4561 query = cls.query()
4561 query = cls.query()
4562 query = query.filter(cls.local_user_id == local_user_id)
4562 query = query.filter(cls.local_user_id == local_user_id)
4563 return query
4563 return query
4564
4564
4565 @classmethod
4565 @classmethod
4566 def load_provider_plugin(cls, plugin_id):
4566 def load_provider_plugin(cls, plugin_id):
4567 from rhodecode.authentication.base import loadplugin
4567 from rhodecode.authentication.base import loadplugin
4568 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4568 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4569 auth_plugin = loadplugin(_plugin_id)
4569 auth_plugin = loadplugin(_plugin_id)
4570 return auth_plugin
4570 return auth_plugin
4571
4571
4572
4572
4573 class Integration(Base, BaseModel):
4573 class Integration(Base, BaseModel):
4574 __tablename__ = 'integrations'
4574 __tablename__ = 'integrations'
4575 __table_args__ = (
4575 __table_args__ = (
4576 base_table_args
4576 base_table_args
4577 )
4577 )
4578
4578
4579 integration_id = Column('integration_id', Integer(), primary_key=True)
4579 integration_id = Column('integration_id', Integer(), primary_key=True)
4580 integration_type = Column('integration_type', String(255))
4580 integration_type = Column('integration_type', String(255))
4581 enabled = Column('enabled', Boolean(), nullable=False)
4581 enabled = Column('enabled', Boolean(), nullable=False)
4582 name = Column('name', String(255), nullable=False)
4582 name = Column('name', String(255), nullable=False)
4583 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4583 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4584 default=False)
4584 default=False)
4585
4585
4586 settings = Column(
4586 settings = Column(
4587 'settings_json', MutationObj.as_mutable(
4587 'settings_json', MutationObj.as_mutable(
4588 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4588 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4589 repo_id = Column(
4589 repo_id = Column(
4590 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4590 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4591 nullable=True, unique=None, default=None)
4591 nullable=True, unique=None, default=None)
4592 repo = relationship('Repository', lazy='joined')
4592 repo = relationship('Repository', lazy='joined')
4593
4593
4594 repo_group_id = Column(
4594 repo_group_id = Column(
4595 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4595 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4596 nullable=True, unique=None, default=None)
4596 nullable=True, unique=None, default=None)
4597 repo_group = relationship('RepoGroup', lazy='joined')
4597 repo_group = relationship('RepoGroup', lazy='joined')
4598
4598
4599 @property
4599 @property
4600 def scope(self):
4600 def scope(self):
4601 if self.repo:
4601 if self.repo:
4602 return repr(self.repo)
4602 return repr(self.repo)
4603 if self.repo_group:
4603 if self.repo_group:
4604 if self.child_repos_only:
4604 if self.child_repos_only:
4605 return repr(self.repo_group) + ' (child repos only)'
4605 return repr(self.repo_group) + ' (child repos only)'
4606 else:
4606 else:
4607 return repr(self.repo_group) + ' (recursive)'
4607 return repr(self.repo_group) + ' (recursive)'
4608 if self.child_repos_only:
4608 if self.child_repos_only:
4609 return 'root_repos'
4609 return 'root_repos'
4610 return 'global'
4610 return 'global'
4611
4611
4612 def __repr__(self):
4612 def __repr__(self):
4613 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4613 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4614
4614
4615
4615
4616 class RepoReviewRuleUser(Base, BaseModel):
4616 class RepoReviewRuleUser(Base, BaseModel):
4617 __tablename__ = 'repo_review_rules_users'
4617 __tablename__ = 'repo_review_rules_users'
4618 __table_args__ = (
4618 __table_args__ = (
4619 base_table_args
4619 base_table_args
4620 )
4620 )
4621
4621
4622 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4622 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4623 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4623 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4624 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4624 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4625 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4625 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4626 user = relationship('User')
4626 user = relationship('User')
4627
4627
4628 def rule_data(self):
4628 def rule_data(self):
4629 return {
4629 return {
4630 'mandatory': self.mandatory
4630 'mandatory': self.mandatory
4631 }
4631 }
4632
4632
4633
4633
4634 class RepoReviewRuleUserGroup(Base, BaseModel):
4634 class RepoReviewRuleUserGroup(Base, BaseModel):
4635 __tablename__ = 'repo_review_rules_users_groups'
4635 __tablename__ = 'repo_review_rules_users_groups'
4636 __table_args__ = (
4636 __table_args__ = (
4637 base_table_args
4637 base_table_args
4638 )
4638 )
4639
4639
4640 VOTE_RULE_ALL = -1
4640 VOTE_RULE_ALL = -1
4641
4641
4642 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4642 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4643 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4643 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4644 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4644 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4645 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4645 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4646 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4646 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4647 users_group = relationship('UserGroup')
4647 users_group = relationship('UserGroup')
4648
4648
4649 def rule_data(self):
4649 def rule_data(self):
4650 return {
4650 return {
4651 'mandatory': self.mandatory,
4651 'mandatory': self.mandatory,
4652 'vote_rule': self.vote_rule
4652 'vote_rule': self.vote_rule
4653 }
4653 }
4654
4654
4655 @property
4655 @property
4656 def vote_rule_label(self):
4656 def vote_rule_label(self):
4657 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4657 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4658 return 'all must vote'
4658 return 'all must vote'
4659 else:
4659 else:
4660 return 'min. vote {}'.format(self.vote_rule)
4660 return 'min. vote {}'.format(self.vote_rule)
4661
4661
4662
4662
4663 class RepoReviewRule(Base, BaseModel):
4663 class RepoReviewRule(Base, BaseModel):
4664 __tablename__ = 'repo_review_rules'
4664 __tablename__ = 'repo_review_rules'
4665 __table_args__ = (
4665 __table_args__ = (
4666 base_table_args
4666 base_table_args
4667 )
4667 )
4668
4668
4669 repo_review_rule_id = Column(
4669 repo_review_rule_id = Column(
4670 'repo_review_rule_id', Integer(), primary_key=True)
4670 'repo_review_rule_id', Integer(), primary_key=True)
4671 repo_id = Column(
4671 repo_id = Column(
4672 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4672 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4673 repo = relationship('Repository', backref='review_rules')
4673 repo = relationship('Repository', backref='review_rules')
4674
4674
4675 review_rule_name = Column('review_rule_name', String(255))
4675 review_rule_name = Column('review_rule_name', String(255))
4676 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4676 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4677 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4677 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4678 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4678 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4679
4679
4680 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4680 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4681 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4681 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4682 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4682 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4683 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4683 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4684
4684
4685 rule_users = relationship('RepoReviewRuleUser')
4685 rule_users = relationship('RepoReviewRuleUser')
4686 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4686 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4687
4687
4688 def _validate_pattern(self, value):
4688 def _validate_pattern(self, value):
4689 re.compile('^' + glob2re(value) + '$')
4689 re.compile('^' + glob2re(value) + '$')
4690
4690
4691 @hybrid_property
4691 @hybrid_property
4692 def source_branch_pattern(self):
4692 def source_branch_pattern(self):
4693 return self._branch_pattern or '*'
4693 return self._branch_pattern or '*'
4694
4694
4695 @source_branch_pattern.setter
4695 @source_branch_pattern.setter
4696 def source_branch_pattern(self, value):
4696 def source_branch_pattern(self, value):
4697 self._validate_pattern(value)
4697 self._validate_pattern(value)
4698 self._branch_pattern = value or '*'
4698 self._branch_pattern = value or '*'
4699
4699
4700 @hybrid_property
4700 @hybrid_property
4701 def target_branch_pattern(self):
4701 def target_branch_pattern(self):
4702 return self._target_branch_pattern or '*'
4702 return self._target_branch_pattern or '*'
4703
4703
4704 @target_branch_pattern.setter
4704 @target_branch_pattern.setter
4705 def target_branch_pattern(self, value):
4705 def target_branch_pattern(self, value):
4706 self._validate_pattern(value)
4706 self._validate_pattern(value)
4707 self._target_branch_pattern = value or '*'
4707 self._target_branch_pattern = value or '*'
4708
4708
4709 @hybrid_property
4709 @hybrid_property
4710 def file_pattern(self):
4710 def file_pattern(self):
4711 return self._file_pattern or '*'
4711 return self._file_pattern or '*'
4712
4712
4713 @file_pattern.setter
4713 @file_pattern.setter
4714 def file_pattern(self, value):
4714 def file_pattern(self, value):
4715 self._validate_pattern(value)
4715 self._validate_pattern(value)
4716 self._file_pattern = value or '*'
4716 self._file_pattern = value or '*'
4717
4717
4718 def matches(self, source_branch, target_branch, files_changed):
4718 def matches(self, source_branch, target_branch, files_changed):
4719 """
4719 """
4720 Check if this review rule matches a branch/files in a pull request
4720 Check if this review rule matches a branch/files in a pull request
4721
4721
4722 :param source_branch: source branch name for the commit
4722 :param source_branch: source branch name for the commit
4723 :param target_branch: target branch name for the commit
4723 :param target_branch: target branch name for the commit
4724 :param files_changed: list of file paths changed in the pull request
4724 :param files_changed: list of file paths changed in the pull request
4725 """
4725 """
4726
4726
4727 source_branch = source_branch or ''
4727 source_branch = source_branch or ''
4728 target_branch = target_branch or ''
4728 target_branch = target_branch or ''
4729 files_changed = files_changed or []
4729 files_changed = files_changed or []
4730
4730
4731 branch_matches = True
4731 branch_matches = True
4732 if source_branch or target_branch:
4732 if source_branch or target_branch:
4733 if self.source_branch_pattern == '*':
4733 if self.source_branch_pattern == '*':
4734 source_branch_match = True
4734 source_branch_match = True
4735 else:
4735 else:
4736 if self.source_branch_pattern.startswith('re:'):
4736 if self.source_branch_pattern.startswith('re:'):
4737 source_pattern = self.source_branch_pattern[3:]
4737 source_pattern = self.source_branch_pattern[3:]
4738 else:
4738 else:
4739 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4739 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4740 source_branch_regex = re.compile(source_pattern)
4740 source_branch_regex = re.compile(source_pattern)
4741 source_branch_match = bool(source_branch_regex.search(source_branch))
4741 source_branch_match = bool(source_branch_regex.search(source_branch))
4742 if self.target_branch_pattern == '*':
4742 if self.target_branch_pattern == '*':
4743 target_branch_match = True
4743 target_branch_match = True
4744 else:
4744 else:
4745 if self.target_branch_pattern.startswith('re:'):
4745 if self.target_branch_pattern.startswith('re:'):
4746 target_pattern = self.target_branch_pattern[3:]
4746 target_pattern = self.target_branch_pattern[3:]
4747 else:
4747 else:
4748 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4748 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4749 target_branch_regex = re.compile(target_pattern)
4749 target_branch_regex = re.compile(target_pattern)
4750 target_branch_match = bool(target_branch_regex.search(target_branch))
4750 target_branch_match = bool(target_branch_regex.search(target_branch))
4751
4751
4752 branch_matches = source_branch_match and target_branch_match
4752 branch_matches = source_branch_match and target_branch_match
4753
4753
4754 files_matches = True
4754 files_matches = True
4755 if self.file_pattern != '*':
4755 if self.file_pattern != '*':
4756 files_matches = False
4756 files_matches = False
4757 if self.file_pattern.startswith('re:'):
4757 if self.file_pattern.startswith('re:'):
4758 file_pattern = self.file_pattern[3:]
4758 file_pattern = self.file_pattern[3:]
4759 else:
4759 else:
4760 file_pattern = glob2re(self.file_pattern)
4760 file_pattern = glob2re(self.file_pattern)
4761 file_regex = re.compile(file_pattern)
4761 file_regex = re.compile(file_pattern)
4762 for filename in files_changed:
4762 for filename in files_changed:
4763 if file_regex.search(filename):
4763 if file_regex.search(filename):
4764 files_matches = True
4764 files_matches = True
4765 break
4765 break
4766
4766
4767 return branch_matches and files_matches
4767 return branch_matches and files_matches
4768
4768
4769 @property
4769 @property
4770 def review_users(self):
4770 def review_users(self):
4771 """ Returns the users which this rule applies to """
4771 """ Returns the users which this rule applies to """
4772
4772
4773 users = collections.OrderedDict()
4773 users = collections.OrderedDict()
4774
4774
4775 for rule_user in self.rule_users:
4775 for rule_user in self.rule_users:
4776 if rule_user.user.active:
4776 if rule_user.user.active:
4777 if rule_user.user not in users:
4777 if rule_user.user not in users:
4778 users[rule_user.user.username] = {
4778 users[rule_user.user.username] = {
4779 'user': rule_user.user,
4779 'user': rule_user.user,
4780 'source': 'user',
4780 'source': 'user',
4781 'source_data': {},
4781 'source_data': {},
4782 'data': rule_user.rule_data()
4782 'data': rule_user.rule_data()
4783 }
4783 }
4784
4784
4785 for rule_user_group in self.rule_user_groups:
4785 for rule_user_group in self.rule_user_groups:
4786 source_data = {
4786 source_data = {
4787 'user_group_id': rule_user_group.users_group.users_group_id,
4787 'user_group_id': rule_user_group.users_group.users_group_id,
4788 'name': rule_user_group.users_group.users_group_name,
4788 'name': rule_user_group.users_group.users_group_name,
4789 'members': len(rule_user_group.users_group.members)
4789 'members': len(rule_user_group.users_group.members)
4790 }
4790 }
4791 for member in rule_user_group.users_group.members:
4791 for member in rule_user_group.users_group.members:
4792 if member.user.active:
4792 if member.user.active:
4793 key = member.user.username
4793 key = member.user.username
4794 if key in users:
4794 if key in users:
4795 # skip this member as we have him already
4795 # skip this member as we have him already
4796 # this prevents from override the "first" matched
4796 # this prevents from override the "first" matched
4797 # users with duplicates in multiple groups
4797 # users with duplicates in multiple groups
4798 continue
4798 continue
4799
4799
4800 users[key] = {
4800 users[key] = {
4801 'user': member.user,
4801 'user': member.user,
4802 'source': 'user_group',
4802 'source': 'user_group',
4803 'source_data': source_data,
4803 'source_data': source_data,
4804 'data': rule_user_group.rule_data()
4804 'data': rule_user_group.rule_data()
4805 }
4805 }
4806
4806
4807 return users
4807 return users
4808
4808
4809 def user_group_vote_rule(self, user_id):
4809 def user_group_vote_rule(self, user_id):
4810
4810
4811 rules = []
4811 rules = []
4812 if not self.rule_user_groups:
4812 if not self.rule_user_groups:
4813 return rules
4813 return rules
4814
4814
4815 for user_group in self.rule_user_groups:
4815 for user_group in self.rule_user_groups:
4816 user_group_members = [x.user_id for x in user_group.users_group.members]
4816 user_group_members = [x.user_id for x in user_group.users_group.members]
4817 if user_id in user_group_members:
4817 if user_id in user_group_members:
4818 rules.append(user_group)
4818 rules.append(user_group)
4819 return rules
4819 return rules
4820
4820
4821 def __repr__(self):
4821 def __repr__(self):
4822 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4822 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4823 self.repo_review_rule_id, self.repo)
4823 self.repo_review_rule_id, self.repo)
4824
4824
4825
4825
4826 class ScheduleEntry(Base, BaseModel):
4826 class ScheduleEntry(Base, BaseModel):
4827 __tablename__ = 'schedule_entries'
4827 __tablename__ = 'schedule_entries'
4828 __table_args__ = (
4828 __table_args__ = (
4829 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4829 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4830 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4830 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4831 base_table_args,
4831 base_table_args,
4832 )
4832 )
4833
4833
4834 schedule_types = ['crontab', 'timedelta', 'integer']
4834 schedule_types = ['crontab', 'timedelta', 'integer']
4835 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4835 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4836
4836
4837 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4837 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4838 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4838 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4839 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4839 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4840
4840
4841 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4841 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4842 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4842 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4843
4843
4844 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4844 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4845 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4845 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4846
4846
4847 # task
4847 # task
4848 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4848 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4849 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4849 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4850 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4850 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4851 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4851 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4852
4852
4853 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4853 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4854 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4854 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4855
4855
4856 @hybrid_property
4856 @hybrid_property
4857 def schedule_type(self):
4857 def schedule_type(self):
4858 return self._schedule_type
4858 return self._schedule_type
4859
4859
4860 @schedule_type.setter
4860 @schedule_type.setter
4861 def schedule_type(self, val):
4861 def schedule_type(self, val):
4862 if val not in self.schedule_types:
4862 if val not in self.schedule_types:
4863 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4863 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4864 val, self.schedule_type))
4864 val, self.schedule_type))
4865
4865
4866 self._schedule_type = val
4866 self._schedule_type = val
4867
4867
4868 @classmethod
4868 @classmethod
4869 def get_uid(cls, obj):
4869 def get_uid(cls, obj):
4870 args = obj.task_args
4870 args = obj.task_args
4871 kwargs = obj.task_kwargs
4871 kwargs = obj.task_kwargs
4872 if isinstance(args, JsonRaw):
4872 if isinstance(args, JsonRaw):
4873 try:
4873 try:
4874 args = json.loads(args)
4874 args = json.loads(args)
4875 except ValueError:
4875 except ValueError:
4876 args = tuple()
4876 args = tuple()
4877
4877
4878 if isinstance(kwargs, JsonRaw):
4878 if isinstance(kwargs, JsonRaw):
4879 try:
4879 try:
4880 kwargs = json.loads(kwargs)
4880 kwargs = json.loads(kwargs)
4881 except ValueError:
4881 except ValueError:
4882 kwargs = dict()
4882 kwargs = dict()
4883
4883
4884 dot_notation = obj.task_dot_notation
4884 dot_notation = obj.task_dot_notation
4885 val = '.'.join(map(safe_str, [
4885 val = '.'.join(map(safe_str, [
4886 sorted(dot_notation), args, sorted(kwargs.items())]))
4886 sorted(dot_notation), args, sorted(kwargs.items())]))
4887 return hashlib.sha1(val).hexdigest()
4887 return hashlib.sha1(val).hexdigest()
4888
4888
4889 @classmethod
4889 @classmethod
4890 def get_by_schedule_name(cls, schedule_name):
4890 def get_by_schedule_name(cls, schedule_name):
4891 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4891 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4892
4892
4893 @classmethod
4893 @classmethod
4894 def get_by_schedule_id(cls, schedule_id):
4894 def get_by_schedule_id(cls, schedule_id):
4895 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4895 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4896
4896
4897 @property
4897 @property
4898 def task(self):
4898 def task(self):
4899 return self.task_dot_notation
4899 return self.task_dot_notation
4900
4900
4901 @property
4901 @property
4902 def schedule(self):
4902 def schedule(self):
4903 from rhodecode.lib.celerylib.utils import raw_2_schedule
4903 from rhodecode.lib.celerylib.utils import raw_2_schedule
4904 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4904 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4905 return schedule
4905 return schedule
4906
4906
4907 @property
4907 @property
4908 def args(self):
4908 def args(self):
4909 try:
4909 try:
4910 return list(self.task_args or [])
4910 return list(self.task_args or [])
4911 except ValueError:
4911 except ValueError:
4912 return list()
4912 return list()
4913
4913
4914 @property
4914 @property
4915 def kwargs(self):
4915 def kwargs(self):
4916 try:
4916 try:
4917 return dict(self.task_kwargs or {})
4917 return dict(self.task_kwargs or {})
4918 except ValueError:
4918 except ValueError:
4919 return dict()
4919 return dict()
4920
4920
4921 def _as_raw(self, val):
4921 def _as_raw(self, val):
4922 if hasattr(val, 'de_coerce'):
4922 if hasattr(val, 'de_coerce'):
4923 val = val.de_coerce()
4923 val = val.de_coerce()
4924 if val:
4924 if val:
4925 val = json.dumps(val)
4925 val = json.dumps(val)
4926
4926
4927 return val
4927 return val
4928
4928
4929 @property
4929 @property
4930 def schedule_definition_raw(self):
4930 def schedule_definition_raw(self):
4931 return self._as_raw(self.schedule_definition)
4931 return self._as_raw(self.schedule_definition)
4932
4932
4933 @property
4933 @property
4934 def args_raw(self):
4934 def args_raw(self):
4935 return self._as_raw(self.task_args)
4935 return self._as_raw(self.task_args)
4936
4936
4937 @property
4937 @property
4938 def kwargs_raw(self):
4938 def kwargs_raw(self):
4939 return self._as_raw(self.task_kwargs)
4939 return self._as_raw(self.task_kwargs)
4940
4940
4941 def __repr__(self):
4941 def __repr__(self):
4942 return '<DB:ScheduleEntry({}:{})>'.format(
4942 return '<DB:ScheduleEntry({}:{})>'.format(
4943 self.schedule_entry_id, self.schedule_name)
4943 self.schedule_entry_id, self.schedule_name)
4944
4944
4945
4945
4946 @event.listens_for(ScheduleEntry, 'before_update')
4946 @event.listens_for(ScheduleEntry, 'before_update')
4947 def update_task_uid(mapper, connection, target):
4947 def update_task_uid(mapper, connection, target):
4948 target.task_uid = ScheduleEntry.get_uid(target)
4948 target.task_uid = ScheduleEntry.get_uid(target)
4949
4949
4950
4950
4951 @event.listens_for(ScheduleEntry, 'before_insert')
4951 @event.listens_for(ScheduleEntry, 'before_insert')
4952 def set_task_uid(mapper, connection, target):
4952 def set_task_uid(mapper, connection, target):
4953 target.task_uid = ScheduleEntry.get_uid(target)
4953 target.task_uid = ScheduleEntry.get_uid(target)
4954
4954
4955
4955
4956 class _BaseBranchPerms(BaseModel):
4956 class _BaseBranchPerms(BaseModel):
4957 @classmethod
4957 @classmethod
4958 def compute_hash(cls, value):
4958 def compute_hash(cls, value):
4959 return sha1_safe(value)
4959 return sha1_safe(value)
4960
4960
4961 @hybrid_property
4961 @hybrid_property
4962 def branch_pattern(self):
4962 def branch_pattern(self):
4963 return self._branch_pattern or '*'
4963 return self._branch_pattern or '*'
4964
4964
4965 @hybrid_property
4965 @hybrid_property
4966 def branch_hash(self):
4966 def branch_hash(self):
4967 return self._branch_hash
4967 return self._branch_hash
4968
4968
4969 def _validate_glob(self, value):
4969 def _validate_glob(self, value):
4970 re.compile('^' + glob2re(value) + '$')
4970 re.compile('^' + glob2re(value) + '$')
4971
4971
4972 @branch_pattern.setter
4972 @branch_pattern.setter
4973 def branch_pattern(self, value):
4973 def branch_pattern(self, value):
4974 self._validate_glob(value)
4974 self._validate_glob(value)
4975 self._branch_pattern = value or '*'
4975 self._branch_pattern = value or '*'
4976 # set the Hash when setting the branch pattern
4976 # set the Hash when setting the branch pattern
4977 self._branch_hash = self.compute_hash(self._branch_pattern)
4977 self._branch_hash = self.compute_hash(self._branch_pattern)
4978
4978
4979 def matches(self, branch):
4979 def matches(self, branch):
4980 """
4980 """
4981 Check if this the branch matches entry
4981 Check if this the branch matches entry
4982
4982
4983 :param branch: branch name for the commit
4983 :param branch: branch name for the commit
4984 """
4984 """
4985
4985
4986 branch = branch or ''
4986 branch = branch or ''
4987
4987
4988 branch_matches = True
4988 branch_matches = True
4989 if branch:
4989 if branch:
4990 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4990 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4991 branch_matches = bool(branch_regex.search(branch))
4991 branch_matches = bool(branch_regex.search(branch))
4992
4992
4993 return branch_matches
4993 return branch_matches
4994
4994
4995
4995
4996 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
4996 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
4997 __tablename__ = 'user_to_repo_branch_permissions'
4997 __tablename__ = 'user_to_repo_branch_permissions'
4998 __table_args__ = (
4998 __table_args__ = (
4999 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4999 {'extend_existing': True, 'mysql_engine': 'InnoDB',
5000 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
5000 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
5001 )
5001 )
5002
5002
5003 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5003 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5004
5004
5005 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5005 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5006 repo = relationship('Repository', backref='user_branch_perms')
5006 repo = relationship('Repository', backref='user_branch_perms')
5007
5007
5008 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5008 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5009 permission = relationship('Permission')
5009 permission = relationship('Permission')
5010
5010
5011 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5011 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5012 user_repo_to_perm = relationship('UserRepoToPerm')
5012 user_repo_to_perm = relationship('UserRepoToPerm')
5013
5013
5014 rule_order = Column('rule_order', Integer(), nullable=False)
5014 rule_order = Column('rule_order', Integer(), nullable=False)
5015 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5015 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5016 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5016 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5017
5017
5018 def __unicode__(self):
5018 def __unicode__(self):
5019 return u'<UserBranchPermission(%s => %r)>' % (
5019 return u'<UserBranchPermission(%s => %r)>' % (
5020 self.user_repo_to_perm, self.branch_pattern)
5020 self.user_repo_to_perm, self.branch_pattern)
5021
5021
5022
5022
5023 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5023 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5024 __tablename__ = 'user_group_to_repo_branch_permissions'
5024 __tablename__ = 'user_group_to_repo_branch_permissions'
5025 __table_args__ = (
5025 __table_args__ = (
5026 {'extend_existing': True, 'mysql_engine': 'InnoDB',
5026 {'extend_existing': True, 'mysql_engine': 'InnoDB',
5027 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
5027 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
5028 )
5028 )
5029
5029
5030 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5030 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5031
5031
5032 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5032 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5033 repo = relationship('Repository', backref='user_group_branch_perms')
5033 repo = relationship('Repository', backref='user_group_branch_perms')
5034
5034
5035 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5035 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5036 permission = relationship('Permission')
5036 permission = relationship('Permission')
5037
5037
5038 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
5038 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
5039 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5039 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5040
5040
5041 rule_order = Column('rule_order', Integer(), nullable=False)
5041 rule_order = Column('rule_order', Integer(), nullable=False)
5042 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5042 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5043 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5043 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5044
5044
5045 def __unicode__(self):
5045 def __unicode__(self):
5046 return u'<UserBranchPermission(%s => %r)>' % (
5046 return u'<UserBranchPermission(%s => %r)>' % (
5047 self.user_group_repo_to_perm, self.branch_pattern)
5047 self.user_group_repo_to_perm, self.branch_pattern)
5048
5048
5049
5049
5050 class UserBookmark(Base, BaseModel):
5050 class UserBookmark(Base, BaseModel):
5051 __tablename__ = 'user_bookmarks'
5051 __tablename__ = 'user_bookmarks'
5052 __table_args__ = (
5052 __table_args__ = (
5053 UniqueConstraint('user_id', 'bookmark_repo_id'),
5053 UniqueConstraint('user_id', 'bookmark_repo_id'),
5054 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5054 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5055 UniqueConstraint('user_id', 'bookmark_position'),
5055 UniqueConstraint('user_id', 'bookmark_position'),
5056 base_table_args
5056 base_table_args
5057 )
5057 )
5058
5058
5059 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5059 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5060 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5060 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5061 position = Column("bookmark_position", Integer(), nullable=False)
5061 position = Column("bookmark_position", Integer(), nullable=False)
5062 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5062 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5063 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5063 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5064 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5064 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5065
5065
5066 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5066 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5067 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5067 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5068
5068
5069 user = relationship("User")
5069 user = relationship("User")
5070
5070
5071 repository = relationship("Repository")
5071 repository = relationship("Repository")
5072 repository_group = relationship("RepoGroup")
5072 repository_group = relationship("RepoGroup")
5073
5073
5074 @classmethod
5074 @classmethod
5075 def get_by_position_for_user(cls, position, user_id):
5075 def get_by_position_for_user(cls, position, user_id):
5076 return cls.query() \
5076 return cls.query() \
5077 .filter(UserBookmark.user_id == user_id) \
5077 .filter(UserBookmark.user_id == user_id) \
5078 .filter(UserBookmark.position == position).scalar()
5078 .filter(UserBookmark.position == position).scalar()
5079
5079
5080 @classmethod
5080 @classmethod
5081 def get_bookmarks_for_user(cls, user_id):
5081 def get_bookmarks_for_user(cls, user_id):
5082 return cls.query() \
5082 return cls.query() \
5083 .filter(UserBookmark.user_id == user_id) \
5083 .filter(UserBookmark.user_id == user_id) \
5084 .options(joinedload(UserBookmark.repository)) \
5084 .options(joinedload(UserBookmark.repository)) \
5085 .options(joinedload(UserBookmark.repository_group)) \
5085 .options(joinedload(UserBookmark.repository_group)) \
5086 .order_by(UserBookmark.position.asc()) \
5086 .order_by(UserBookmark.position.asc()) \
5087 .all()
5087 .all()
5088
5088
5089 def __unicode__(self):
5089 def __unicode__(self):
5090 return u'<UserBookmark(%d @ %r)>' % (self.position, self.redirect_url)
5090 return u'<UserBookmark(%d @ %r)>' % (self.position, self.redirect_url)
5091
5091
5092
5092
5093 class FileStore(Base, BaseModel):
5093 class FileStore(Base, BaseModel):
5094 __tablename__ = 'file_store'
5094 __tablename__ = 'file_store'
5095 __table_args__ = (
5095 __table_args__ = (
5096 base_table_args
5096 base_table_args
5097 )
5097 )
5098
5098
5099 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5099 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5100 file_uid = Column('file_uid', String(1024), nullable=False)
5100 file_uid = Column('file_uid', String(1024), nullable=False)
5101 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5101 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5102 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5102 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5103 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5103 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5104
5104
5105 # sha256 hash
5105 # sha256 hash
5106 file_hash = Column('file_hash', String(512), nullable=False)
5106 file_hash = Column('file_hash', String(512), nullable=False)
5107 file_size = Column('file_size', Integer(), nullable=False)
5107 file_size = Column('file_size', Integer(), nullable=False)
5108
5108
5109 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5109 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5110 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5110 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5111 accessed_count = Column('accessed_count', Integer(), default=0)
5111 accessed_count = Column('accessed_count', Integer(), default=0)
5112
5112
5113 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5113 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5114
5114
5115 # if repo/repo_group reference is set, check for permissions
5115 # if repo/repo_group reference is set, check for permissions
5116 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5116 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5117
5117
5118 # hidden defines an attachment that should be hidden from showing in artifact listing
5118 # hidden defines an attachment that should be hidden from showing in artifact listing
5119 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5119 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5120
5120
5121 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5121 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5122 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5122 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5123
5123
5124 # scope limited to user, which requester have access to
5124 # scope limited to user, which requester have access to
5125 scope_user_id = Column(
5125 scope_user_id = Column(
5126 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5126 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5127 nullable=True, unique=None, default=None)
5127 nullable=True, unique=None, default=None)
5128 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5128 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5129
5129
5130 # scope limited to user group, which requester have access to
5130 # scope limited to user group, which requester have access to
5131 scope_user_group_id = Column(
5131 scope_user_group_id = Column(
5132 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5132 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5133 nullable=True, unique=None, default=None)
5133 nullable=True, unique=None, default=None)
5134 user_group = relationship('UserGroup', lazy='joined')
5134 user_group = relationship('UserGroup', lazy='joined')
5135
5135
5136 # scope limited to repo, which requester have access to
5136 # scope limited to repo, which requester have access to
5137 scope_repo_id = Column(
5137 scope_repo_id = Column(
5138 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5138 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5139 nullable=True, unique=None, default=None)
5139 nullable=True, unique=None, default=None)
5140 repo = relationship('Repository', lazy='joined')
5140 repo = relationship('Repository', lazy='joined')
5141
5141
5142 # scope limited to repo group, which requester have access to
5142 # scope limited to repo group, which requester have access to
5143 scope_repo_group_id = Column(
5143 scope_repo_group_id = Column(
5144 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5144 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5145 nullable=True, unique=None, default=None)
5145 nullable=True, unique=None, default=None)
5146 repo_group = relationship('RepoGroup', lazy='joined')
5146 repo_group = relationship('RepoGroup', lazy='joined')
5147
5147
5148 @classmethod
5148 @classmethod
5149 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5149 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5150 file_description='', enabled=True, hidden=False, check_acl=True,
5150 file_description='', enabled=True, hidden=False, check_acl=True,
5151 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5151 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5152
5152
5153 store_entry = FileStore()
5153 store_entry = FileStore()
5154 store_entry.file_uid = file_uid
5154 store_entry.file_uid = file_uid
5155 store_entry.file_display_name = file_display_name
5155 store_entry.file_display_name = file_display_name
5156 store_entry.file_org_name = filename
5156 store_entry.file_org_name = filename
5157 store_entry.file_size = file_size
5157 store_entry.file_size = file_size
5158 store_entry.file_hash = file_hash
5158 store_entry.file_hash = file_hash
5159 store_entry.file_description = file_description
5159 store_entry.file_description = file_description
5160
5160
5161 store_entry.check_acl = check_acl
5161 store_entry.check_acl = check_acl
5162 store_entry.enabled = enabled
5162 store_entry.enabled = enabled
5163 store_entry.hidden = hidden
5163 store_entry.hidden = hidden
5164
5164
5165 store_entry.user_id = user_id
5165 store_entry.user_id = user_id
5166 store_entry.scope_user_id = scope_user_id
5166 store_entry.scope_user_id = scope_user_id
5167 store_entry.scope_repo_id = scope_repo_id
5167 store_entry.scope_repo_id = scope_repo_id
5168 store_entry.scope_repo_group_id = scope_repo_group_id
5168 store_entry.scope_repo_group_id = scope_repo_group_id
5169
5169
5170 return store_entry
5170 return store_entry
5171
5171
5172 @classmethod
5172 @classmethod
5173 def bump_access_counter(cls, file_uid, commit=True):
5173 def bump_access_counter(cls, file_uid, commit=True):
5174 FileStore().query()\
5174 FileStore().query()\
5175 .filter(FileStore.file_uid == file_uid)\
5175 .filter(FileStore.file_uid == file_uid)\
5176 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5176 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5177 FileStore.accessed_on: datetime.datetime.now()})
5177 FileStore.accessed_on: datetime.datetime.now()})
5178 if commit:
5178 if commit:
5179 Session().commit()
5179 Session().commit()
5180
5180
5181 def __repr__(self):
5181 def __repr__(self):
5182 return '<FileStore({})>'.format(self.file_store_id)
5182 return '<FileStore({})>'.format(self.file_store_id)
5183
5183
5184
5184
5185 class DbMigrateVersion(Base, BaseModel):
5185 class DbMigrateVersion(Base, BaseModel):
5186 __tablename__ = 'db_migrate_version'
5186 __tablename__ = 'db_migrate_version'
5187 __table_args__ = (
5187 __table_args__ = (
5188 base_table_args,
5188 base_table_args,
5189 )
5189 )
5190
5190
5191 repository_id = Column('repository_id', String(250), primary_key=True)
5191 repository_id = Column('repository_id', String(250), primary_key=True)
5192 repository_path = Column('repository_path', Text)
5192 repository_path = Column('repository_path', Text)
5193 version = Column('version', Integer)
5193 version = Column('version', Integer)
5194
5194
5195 @classmethod
5195 @classmethod
5196 def set_version(cls, version):
5196 def set_version(cls, version):
5197 """
5197 """
5198 Helper for forcing a different version, usually for debugging purposes via ishell.
5198 Helper for forcing a different version, usually for debugging purposes via ishell.
5199 """
5199 """
5200 ver = DbMigrateVersion.query().first()
5200 ver = DbMigrateVersion.query().first()
5201 ver.version = version
5201 ver.version = version
5202 Session().commit()
5202 Session().commit()
5203
5203
5204
5204
5205 class DbSession(Base, BaseModel):
5205 class DbSession(Base, BaseModel):
5206 __tablename__ = 'db_session'
5206 __tablename__ = 'db_session'
5207 __table_args__ = (
5207 __table_args__ = (
5208 base_table_args,
5208 base_table_args,
5209 )
5209 )
5210
5210
5211 def __repr__(self):
5211 def __repr__(self):
5212 return '<DB:DbSession({})>'.format(self.id)
5212 return '<DB:DbSession({})>'.format(self.id)
5213
5213
5214 id = Column('id', Integer())
5214 id = Column('id', Integer())
5215 namespace = Column('namespace', String(255), primary_key=True)
5215 namespace = Column('namespace', String(255), primary_key=True)
5216 accessed = Column('accessed', DateTime, nullable=False)
5216 accessed = Column('accessed', DateTime, nullable=False)
5217 created = Column('created', DateTime, nullable=False)
5217 created = Column('created', DateTime, nullable=False)
5218 data = Column('data', PickleType, nullable=False)
5218 data = Column('data', PickleType, nullable=False)
@@ -1,946 +1,945 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 users model for RhodeCode
22 users model for RhodeCode
23 """
23 """
24
24
25 import logging
25 import logging
26 import traceback
26 import traceback
27 import datetime
27 import datetime
28 import ipaddress
28 import ipaddress
29
29
30 from pyramid.threadlocal import get_current_request
30 from pyramid.threadlocal import get_current_request
31 from sqlalchemy.exc import DatabaseError
31 from sqlalchemy.exc import DatabaseError
32
32
33 from rhodecode import events
33 from rhodecode import events
34 from rhodecode.lib.user_log_filter import user_log_filter
34 from rhodecode.lib.user_log_filter import user_log_filter
35 from rhodecode.lib.utils2 import (
35 from rhodecode.lib.utils2 import (
36 safe_unicode, get_current_rhodecode_user, action_logger_generic,
36 safe_unicode, get_current_rhodecode_user, action_logger_generic,
37 AttributeDict, str2bool)
37 AttributeDict, str2bool)
38 from rhodecode.lib.exceptions import (
38 from rhodecode.lib.exceptions import (
39 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
39 DefaultUserException, UserOwnsReposException, UserOwnsRepoGroupsException,
40 UserOwnsUserGroupsException, NotAllowedToCreateUserError)
40 UserOwnsUserGroupsException, NotAllowedToCreateUserError)
41 from rhodecode.lib.caching_query import FromCache
41 from rhodecode.lib.caching_query import FromCache
42 from rhodecode.model import BaseModel
42 from rhodecode.model import BaseModel
43 from rhodecode.model.auth_token import AuthTokenModel
43 from rhodecode.model.auth_token import AuthTokenModel
44 from rhodecode.model.db import (
44 from rhodecode.model.db import (
45 _hash_key, true, false, or_, joinedload, User, UserToPerm,
45 _hash_key, true, false, or_, joinedload, User, UserToPerm,
46 UserEmailMap, UserIpMap, UserLog)
46 UserEmailMap, UserIpMap, UserLog)
47 from rhodecode.model.meta import Session
47 from rhodecode.model.meta import Session
48 from rhodecode.model.repo_group import RepoGroupModel
48 from rhodecode.model.repo_group import RepoGroupModel
49
49
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 class UserModel(BaseModel):
54 class UserModel(BaseModel):
55 cls = User
55 cls = User
56
56
57 def get(self, user_id, cache=False):
57 def get(self, user_id, cache=False):
58 user = self.sa.query(User)
58 user = self.sa.query(User)
59 if cache:
59 if cache:
60 user = user.options(
60 user = user.options(
61 FromCache("sql_cache_short", "get_user_%s" % user_id))
61 FromCache("sql_cache_short", "get_user_%s" % user_id))
62 return user.get(user_id)
62 return user.get(user_id)
63
63
64 def get_user(self, user):
64 def get_user(self, user):
65 return self._get_user(user)
65 return self._get_user(user)
66
66
67 def _serialize_user(self, user):
67 def _serialize_user(self, user):
68 import rhodecode.lib.helpers as h
68 import rhodecode.lib.helpers as h
69
69
70 return {
70 return {
71 'id': user.user_id,
71 'id': user.user_id,
72 'first_name': user.first_name,
72 'first_name': user.first_name,
73 'last_name': user.last_name,
73 'last_name': user.last_name,
74 'username': user.username,
74 'username': user.username,
75 'email': user.email,
75 'email': user.email,
76 'icon_link': h.gravatar_url(user.email, 30),
76 'icon_link': h.gravatar_url(user.email, 30),
77 'profile_link': h.link_to_user(user),
77 'profile_link': h.link_to_user(user),
78 'value_display': h.escape(h.person(user)),
78 'value_display': h.escape(h.person(user)),
79 'value': user.username,
79 'value': user.username,
80 'value_type': 'user',
80 'value_type': 'user',
81 'active': user.active,
81 'active': user.active,
82 }
82 }
83
83
84 def get_users(self, name_contains=None, limit=20, only_active=True):
84 def get_users(self, name_contains=None, limit=20, only_active=True):
85
85
86 query = self.sa.query(User)
86 query = self.sa.query(User)
87 if only_active:
87 if only_active:
88 query = query.filter(User.active == true())
88 query = query.filter(User.active == true())
89
89
90 if name_contains:
90 if name_contains:
91 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
91 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
92 query = query.filter(
92 query = query.filter(
93 or_(
93 or_(
94 User.name.ilike(ilike_expression),
94 User.name.ilike(ilike_expression),
95 User.lastname.ilike(ilike_expression),
95 User.lastname.ilike(ilike_expression),
96 User.username.ilike(ilike_expression)
96 User.username.ilike(ilike_expression)
97 )
97 )
98 )
98 )
99 query = query.limit(limit)
99 query = query.limit(limit)
100 users = query.all()
100 users = query.all()
101
101
102 _users = [
102 _users = [
103 self._serialize_user(user) for user in users
103 self._serialize_user(user) for user in users
104 ]
104 ]
105 return _users
105 return _users
106
106
107 def get_by_username(self, username, cache=False, case_insensitive=False):
107 def get_by_username(self, username, cache=False, case_insensitive=False):
108
108
109 if case_insensitive:
109 if case_insensitive:
110 user = self.sa.query(User).filter(User.username.ilike(username))
110 user = self.sa.query(User).filter(User.username.ilike(username))
111 else:
111 else:
112 user = self.sa.query(User)\
112 user = self.sa.query(User)\
113 .filter(User.username == username)
113 .filter(User.username == username)
114 if cache:
114 if cache:
115 name_key = _hash_key(username)
115 name_key = _hash_key(username)
116 user = user.options(
116 user = user.options(
117 FromCache("sql_cache_short", "get_user_%s" % name_key))
117 FromCache("sql_cache_short", "get_user_%s" % name_key))
118 return user.scalar()
118 return user.scalar()
119
119
120 def get_by_email(self, email, cache=False, case_insensitive=False):
120 def get_by_email(self, email, cache=False, case_insensitive=False):
121 return User.get_by_email(email, case_insensitive, cache)
121 return User.get_by_email(email, case_insensitive, cache)
122
122
123 def get_by_auth_token(self, auth_token, cache=False):
123 def get_by_auth_token(self, auth_token, cache=False):
124 return User.get_by_auth_token(auth_token, cache)
124 return User.get_by_auth_token(auth_token, cache)
125
125
126 def get_active_user_count(self, cache=False):
126 def get_active_user_count(self, cache=False):
127 qry = User.query().filter(
127 qry = User.query().filter(
128 User.active == true()).filter(
128 User.active == true()).filter(
129 User.username != User.DEFAULT_USER)
129 User.username != User.DEFAULT_USER)
130 if cache:
130 if cache:
131 qry = qry.options(
131 qry = qry.options(
132 FromCache("sql_cache_short", "get_active_users"))
132 FromCache("sql_cache_short", "get_active_users"))
133 return qry.count()
133 return qry.count()
134
134
135 def create(self, form_data, cur_user=None):
135 def create(self, form_data, cur_user=None):
136 if not cur_user:
136 if not cur_user:
137 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
137 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
138
138
139 user_data = {
139 user_data = {
140 'username': form_data['username'],
140 'username': form_data['username'],
141 'password': form_data['password'],
141 'password': form_data['password'],
142 'email': form_data['email'],
142 'email': form_data['email'],
143 'firstname': form_data['firstname'],
143 'firstname': form_data['firstname'],
144 'lastname': form_data['lastname'],
144 'lastname': form_data['lastname'],
145 'active': form_data['active'],
145 'active': form_data['active'],
146 'extern_type': form_data['extern_type'],
146 'extern_type': form_data['extern_type'],
147 'extern_name': form_data['extern_name'],
147 'extern_name': form_data['extern_name'],
148 'admin': False,
148 'admin': False,
149 'cur_user': cur_user
149 'cur_user': cur_user
150 }
150 }
151
151
152 if 'create_repo_group' in form_data:
152 if 'create_repo_group' in form_data:
153 user_data['create_repo_group'] = str2bool(
153 user_data['create_repo_group'] = str2bool(
154 form_data.get('create_repo_group'))
154 form_data.get('create_repo_group'))
155
155
156 try:
156 try:
157 if form_data.get('password_change'):
157 if form_data.get('password_change'):
158 user_data['force_password_change'] = True
158 user_data['force_password_change'] = True
159 return UserModel().create_or_update(**user_data)
159 return UserModel().create_or_update(**user_data)
160 except Exception:
160 except Exception:
161 log.error(traceback.format_exc())
161 log.error(traceback.format_exc())
162 raise
162 raise
163
163
164 def update_user(self, user, skip_attrs=None, **kwargs):
164 def update_user(self, user, skip_attrs=None, **kwargs):
165 from rhodecode.lib.auth import get_crypt_password
165 from rhodecode.lib.auth import get_crypt_password
166
166
167 user = self._get_user(user)
167 user = self._get_user(user)
168 if user.username == User.DEFAULT_USER:
168 if user.username == User.DEFAULT_USER:
169 raise DefaultUserException(
169 raise DefaultUserException(
170 "You can't edit this user (`%(username)s`) since it's "
170 "You can't edit this user (`%(username)s`) since it's "
171 "crucial for entire application" % {
171 "crucial for entire application" % {
172 'username': user.username})
172 'username': user.username})
173
173
174 # first store only defaults
174 # first store only defaults
175 user_attrs = {
175 user_attrs = {
176 'updating_user_id': user.user_id,
176 'updating_user_id': user.user_id,
177 'username': user.username,
177 'username': user.username,
178 'password': user.password,
178 'password': user.password,
179 'email': user.email,
179 'email': user.email,
180 'firstname': user.name,
180 'firstname': user.name,
181 'lastname': user.lastname,
181 'lastname': user.lastname,
182 'active': user.active,
182 'active': user.active,
183 'admin': user.admin,
183 'admin': user.admin,
184 'extern_name': user.extern_name,
184 'extern_name': user.extern_name,
185 'extern_type': user.extern_type,
185 'extern_type': user.extern_type,
186 'language': user.user_data.get('language')
186 'language': user.user_data.get('language')
187 }
187 }
188
188
189 # in case there's new_password, that comes from form, use it to
189 # in case there's new_password, that comes from form, use it to
190 # store password
190 # store password
191 if kwargs.get('new_password'):
191 if kwargs.get('new_password'):
192 kwargs['password'] = kwargs['new_password']
192 kwargs['password'] = kwargs['new_password']
193
193
194 # cleanups, my_account password change form
194 # cleanups, my_account password change form
195 kwargs.pop('current_password', None)
195 kwargs.pop('current_password', None)
196 kwargs.pop('new_password', None)
196 kwargs.pop('new_password', None)
197
197
198 # cleanups, user edit password change form
198 # cleanups, user edit password change form
199 kwargs.pop('password_confirmation', None)
199 kwargs.pop('password_confirmation', None)
200 kwargs.pop('password_change', None)
200 kwargs.pop('password_change', None)
201
201
202 # create repo group on user creation
202 # create repo group on user creation
203 kwargs.pop('create_repo_group', None)
203 kwargs.pop('create_repo_group', None)
204
204
205 # legacy forms send name, which is the firstname
205 # legacy forms send name, which is the firstname
206 firstname = kwargs.pop('name', None)
206 firstname = kwargs.pop('name', None)
207 if firstname:
207 if firstname:
208 kwargs['firstname'] = firstname
208 kwargs['firstname'] = firstname
209
209
210 for k, v in kwargs.items():
210 for k, v in kwargs.items():
211 # skip if we don't want to update this
211 # skip if we don't want to update this
212 if skip_attrs and k in skip_attrs:
212 if skip_attrs and k in skip_attrs:
213 continue
213 continue
214
214
215 user_attrs[k] = v
215 user_attrs[k] = v
216
216
217 try:
217 try:
218 return self.create_or_update(**user_attrs)
218 return self.create_or_update(**user_attrs)
219 except Exception:
219 except Exception:
220 log.error(traceback.format_exc())
220 log.error(traceback.format_exc())
221 raise
221 raise
222
222
223 def create_or_update(
223 def create_or_update(
224 self, username, password, email, firstname='', lastname='',
224 self, username, password, email, firstname='', lastname='',
225 active=True, admin=False, extern_type=None, extern_name=None,
225 active=True, admin=False, extern_type=None, extern_name=None,
226 cur_user=None, plugin=None, force_password_change=False,
226 cur_user=None, plugin=None, force_password_change=False,
227 allow_to_create_user=True, create_repo_group=None,
227 allow_to_create_user=True, create_repo_group=None,
228 updating_user_id=None, language=None, strict_creation_check=True):
228 updating_user_id=None, language=None, strict_creation_check=True):
229 """
229 """
230 Creates a new instance if not found, or updates current one
230 Creates a new instance if not found, or updates current one
231
231
232 :param username:
232 :param username:
233 :param password:
233 :param password:
234 :param email:
234 :param email:
235 :param firstname:
235 :param firstname:
236 :param lastname:
236 :param lastname:
237 :param active:
237 :param active:
238 :param admin:
238 :param admin:
239 :param extern_type:
239 :param extern_type:
240 :param extern_name:
240 :param extern_name:
241 :param cur_user:
241 :param cur_user:
242 :param plugin: optional plugin this method was called from
242 :param plugin: optional plugin this method was called from
243 :param force_password_change: toggles new or existing user flag
243 :param force_password_change: toggles new or existing user flag
244 for password change
244 for password change
245 :param allow_to_create_user: Defines if the method can actually create
245 :param allow_to_create_user: Defines if the method can actually create
246 new users
246 new users
247 :param create_repo_group: Defines if the method should also
247 :param create_repo_group: Defines if the method should also
248 create an repo group with user name, and owner
248 create an repo group with user name, and owner
249 :param updating_user_id: if we set it up this is the user we want to
249 :param updating_user_id: if we set it up this is the user we want to
250 update this allows to editing username.
250 update this allows to editing username.
251 :param language: language of user from interface.
251 :param language: language of user from interface.
252
252
253 :returns: new User object with injected `is_new_user` attribute.
253 :returns: new User object with injected `is_new_user` attribute.
254 """
254 """
255
255
256 if not cur_user:
256 if not cur_user:
257 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
257 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
258
258
259 from rhodecode.lib.auth import (
259 from rhodecode.lib.auth import (
260 get_crypt_password, check_password, generate_auth_token)
260 get_crypt_password, check_password, generate_auth_token)
261 from rhodecode.lib.hooks_base import (
261 from rhodecode.lib.hooks_base import (
262 log_create_user, check_allowed_create_user)
262 log_create_user, check_allowed_create_user)
263
263
264 def _password_change(new_user, password):
264 def _password_change(new_user, password):
265 old_password = new_user.password or ''
265 old_password = new_user.password or ''
266 # empty password
266 # empty password
267 if not old_password:
267 if not old_password:
268 return False
268 return False
269
269
270 # password check is only needed for RhodeCode internal auth calls
270 # password check is only needed for RhodeCode internal auth calls
271 # in case it's a plugin we don't care
271 # in case it's a plugin we don't care
272 if not plugin:
272 if not plugin:
273
273
274 # first check if we gave crypted password back, and if it
274 # first check if we gave crypted password back, and if it
275 # matches it's not password change
275 # matches it's not password change
276 if new_user.password == password:
276 if new_user.password == password:
277 return False
277 return False
278
278
279 password_match = check_password(password, old_password)
279 password_match = check_password(password, old_password)
280 if not password_match:
280 if not password_match:
281 return True
281 return True
282
282
283 return False
283 return False
284
284
285 # read settings on default personal repo group creation
285 # read settings on default personal repo group creation
286 if create_repo_group is None:
286 if create_repo_group is None:
287 default_create_repo_group = RepoGroupModel()\
287 default_create_repo_group = RepoGroupModel()\
288 .get_default_create_personal_repo_group()
288 .get_default_create_personal_repo_group()
289 create_repo_group = default_create_repo_group
289 create_repo_group = default_create_repo_group
290
290
291 user_data = {
291 user_data = {
292 'username': username,
292 'username': username,
293 'password': password,
293 'password': password,
294 'email': email,
294 'email': email,
295 'firstname': firstname,
295 'firstname': firstname,
296 'lastname': lastname,
296 'lastname': lastname,
297 'active': active,
297 'active': active,
298 'admin': admin
298 'admin': admin
299 }
299 }
300
300
301 if updating_user_id:
301 if updating_user_id:
302 log.debug('Checking for existing account in RhodeCode '
302 log.debug('Checking for existing account in RhodeCode '
303 'database with user_id `%s` ', updating_user_id)
303 'database with user_id `%s` ', updating_user_id)
304 user = User.get(updating_user_id)
304 user = User.get(updating_user_id)
305 else:
305 else:
306 log.debug('Checking for existing account in RhodeCode '
306 log.debug('Checking for existing account in RhodeCode '
307 'database with username `%s` ', username)
307 'database with username `%s` ', username)
308 user = User.get_by_username(username, case_insensitive=True)
308 user = User.get_by_username(username, case_insensitive=True)
309
309
310 if user is None:
310 if user is None:
311 # we check internal flag if this method is actually allowed to
311 # we check internal flag if this method is actually allowed to
312 # create new user
312 # create new user
313 if not allow_to_create_user:
313 if not allow_to_create_user:
314 msg = ('Method wants to create new user, but it is not '
314 msg = ('Method wants to create new user, but it is not '
315 'allowed to do so')
315 'allowed to do so')
316 log.warning(msg)
316 log.warning(msg)
317 raise NotAllowedToCreateUserError(msg)
317 raise NotAllowedToCreateUserError(msg)
318
318
319 log.debug('Creating new user %s', username)
319 log.debug('Creating new user %s', username)
320
320
321 # only if we create user that is active
321 # only if we create user that is active
322 new_active_user = active
322 new_active_user = active
323 if new_active_user and strict_creation_check:
323 if new_active_user and strict_creation_check:
324 # raises UserCreationError if it's not allowed for any reason to
324 # raises UserCreationError if it's not allowed for any reason to
325 # create new active user, this also executes pre-create hooks
325 # create new active user, this also executes pre-create hooks
326 check_allowed_create_user(user_data, cur_user, strict_check=True)
326 check_allowed_create_user(user_data, cur_user, strict_check=True)
327 events.trigger(events.UserPreCreate(user_data))
327 events.trigger(events.UserPreCreate(user_data))
328 new_user = User()
328 new_user = User()
329 edit = False
329 edit = False
330 else:
330 else:
331 log.debug('updating user `%s`', username)
331 log.debug('updating user `%s`', username)
332 events.trigger(events.UserPreUpdate(user, user_data))
332 events.trigger(events.UserPreUpdate(user, user_data))
333 new_user = user
333 new_user = user
334 edit = True
334 edit = True
335
335
336 # we're not allowed to edit default user
336 # we're not allowed to edit default user
337 if user.username == User.DEFAULT_USER:
337 if user.username == User.DEFAULT_USER:
338 raise DefaultUserException(
338 raise DefaultUserException(
339 "You can't edit this user (`%(username)s`) since it's "
339 "You can't edit this user (`%(username)s`) since it's "
340 "crucial for entire application"
340 "crucial for entire application"
341 % {'username': user.username})
341 % {'username': user.username})
342
342
343 # inject special attribute that will tell us if User is new or old
343 # inject special attribute that will tell us if User is new or old
344 new_user.is_new_user = not edit
344 new_user.is_new_user = not edit
345 # for users that didn's specify auth type, we use RhodeCode built in
345 # for users that didn's specify auth type, we use RhodeCode built in
346 from rhodecode.authentication.plugins import auth_rhodecode
346 from rhodecode.authentication.plugins import auth_rhodecode
347 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.uid
347 extern_name = extern_name or auth_rhodecode.RhodeCodeAuthPlugin.uid
348 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.uid
348 extern_type = extern_type or auth_rhodecode.RhodeCodeAuthPlugin.uid
349
349
350 try:
350 try:
351 new_user.username = username
351 new_user.username = username
352 new_user.admin = admin
352 new_user.admin = admin
353 new_user.email = email
353 new_user.email = email
354 new_user.active = active
354 new_user.active = active
355 new_user.extern_name = safe_unicode(extern_name)
355 new_user.extern_name = safe_unicode(extern_name)
356 new_user.extern_type = safe_unicode(extern_type)
356 new_user.extern_type = safe_unicode(extern_type)
357 new_user.name = firstname
357 new_user.name = firstname
358 new_user.lastname = lastname
358 new_user.lastname = lastname
359
359
360 # set password only if creating an user or password is changed
360 # set password only if creating an user or password is changed
361 if not edit or _password_change(new_user, password):
361 if not edit or _password_change(new_user, password):
362 reason = 'new password' if edit else 'new user'
362 reason = 'new password' if edit else 'new user'
363 log.debug('Updating password reason=>%s', reason)
363 log.debug('Updating password reason=>%s', reason)
364 new_user.password = get_crypt_password(password) if password else None
364 new_user.password = get_crypt_password(password) if password else None
365
365
366 if force_password_change:
366 if force_password_change:
367 new_user.update_userdata(force_password_change=True)
367 new_user.update_userdata(force_password_change=True)
368 if language:
368 if language:
369 new_user.update_userdata(language=language)
369 new_user.update_userdata(language=language)
370 new_user.update_userdata(notification_status=True)
370 new_user.update_userdata(notification_status=True)
371
371
372 self.sa.add(new_user)
372 self.sa.add(new_user)
373
373
374 if not edit and create_repo_group:
374 if not edit and create_repo_group:
375 RepoGroupModel().create_personal_repo_group(
375 RepoGroupModel().create_personal_repo_group(
376 new_user, commit_early=False)
376 new_user, commit_early=False)
377
377
378 if not edit:
378 if not edit:
379 # add the RSS token
379 # add the RSS token
380 self.add_auth_token(
380 self.add_auth_token(
381 user=username, lifetime_minutes=-1,
381 user=username, lifetime_minutes=-1,
382 role=self.auth_token_role.ROLE_FEED,
382 role=self.auth_token_role.ROLE_FEED,
383 description=u'Generated feed token')
383 description=u'Generated feed token')
384
384
385 kwargs = new_user.get_dict()
385 kwargs = new_user.get_dict()
386 # backward compat, require api_keys present
386 # backward compat, require api_keys present
387 kwargs['api_keys'] = kwargs['auth_tokens']
387 kwargs['api_keys'] = kwargs['auth_tokens']
388 log_create_user(created_by=cur_user, **kwargs)
388 log_create_user(created_by=cur_user, **kwargs)
389 events.trigger(events.UserPostCreate(user_data))
389 events.trigger(events.UserPostCreate(user_data))
390 return new_user
390 return new_user
391 except (DatabaseError,):
391 except (DatabaseError,):
392 log.error(traceback.format_exc())
392 log.error(traceback.format_exc())
393 raise
393 raise
394
394
395 def create_registration(self, form_data,
395 def create_registration(self, form_data,
396 extern_name='rhodecode', extern_type='rhodecode'):
396 extern_name='rhodecode', extern_type='rhodecode'):
397 from rhodecode.model.notification import NotificationModel
397 from rhodecode.model.notification import NotificationModel
398 from rhodecode.model.notification import EmailNotificationModel
398 from rhodecode.model.notification import EmailNotificationModel
399
399
400 try:
400 try:
401 form_data['admin'] = False
401 form_data['admin'] = False
402 form_data['extern_name'] = extern_name
402 form_data['extern_name'] = extern_name
403 form_data['extern_type'] = extern_type
403 form_data['extern_type'] = extern_type
404 new_user = self.create(form_data)
404 new_user = self.create(form_data)
405
405
406 self.sa.add(new_user)
406 self.sa.add(new_user)
407 self.sa.flush()
407 self.sa.flush()
408
408
409 user_data = new_user.get_dict()
409 user_data = new_user.get_dict()
410 kwargs = {
410 kwargs = {
411 # use SQLALCHEMY safe dump of user data
411 # use SQLALCHEMY safe dump of user data
412 'user': AttributeDict(user_data),
412 'user': AttributeDict(user_data),
413 'date': datetime.datetime.now()
413 'date': datetime.datetime.now()
414 }
414 }
415 notification_type = EmailNotificationModel.TYPE_REGISTRATION
415 notification_type = EmailNotificationModel.TYPE_REGISTRATION
416 # pre-generate the subject for notification itself
416 # pre-generate the subject for notification itself
417 (subject,
417 (subject,
418 _h, _e, # we don't care about those
418 _h, _e, # we don't care about those
419 body_plaintext) = EmailNotificationModel().render_email(
419 body_plaintext) = EmailNotificationModel().render_email(
420 notification_type, **kwargs)
420 notification_type, **kwargs)
421
421
422 # create notification objects, and emails
422 # create notification objects, and emails
423 NotificationModel().create(
423 NotificationModel().create(
424 created_by=new_user,
424 created_by=new_user,
425 notification_subject=subject,
425 notification_subject=subject,
426 notification_body=body_plaintext,
426 notification_body=body_plaintext,
427 notification_type=notification_type,
427 notification_type=notification_type,
428 recipients=None, # all admins
428 recipients=None, # all admins
429 email_kwargs=kwargs,
429 email_kwargs=kwargs,
430 )
430 )
431
431
432 return new_user
432 return new_user
433 except Exception:
433 except Exception:
434 log.error(traceback.format_exc())
434 log.error(traceback.format_exc())
435 raise
435 raise
436
436
437 def _handle_user_repos(self, username, repositories, handle_mode=None):
437 def _handle_user_repos(self, username, repositories, handle_mode=None):
438 _superadmin = self.cls.get_first_super_admin()
438 _superadmin = self.cls.get_first_super_admin()
439 left_overs = True
439 left_overs = True
440
440
441 from rhodecode.model.repo import RepoModel
441 from rhodecode.model.repo import RepoModel
442
442
443 if handle_mode == 'detach':
443 if handle_mode == 'detach':
444 for obj in repositories:
444 for obj in repositories:
445 obj.user = _superadmin
445 obj.user = _superadmin
446 # set description we know why we super admin now owns
446 # set description we know why we super admin now owns
447 # additional repositories that were orphaned !
447 # additional repositories that were orphaned !
448 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
448 obj.description += ' \n::detached repository from deleted user: %s' % (username,)
449 self.sa.add(obj)
449 self.sa.add(obj)
450 left_overs = False
450 left_overs = False
451 elif handle_mode == 'delete':
451 elif handle_mode == 'delete':
452 for obj in repositories:
452 for obj in repositories:
453 RepoModel().delete(obj, forks='detach')
453 RepoModel().delete(obj, forks='detach')
454 left_overs = False
454 left_overs = False
455
455
456 # if nothing is done we have left overs left
456 # if nothing is done we have left overs left
457 return left_overs
457 return left_overs
458
458
459 def _handle_user_repo_groups(self, username, repository_groups,
459 def _handle_user_repo_groups(self, username, repository_groups,
460 handle_mode=None):
460 handle_mode=None):
461 _superadmin = self.cls.get_first_super_admin()
461 _superadmin = self.cls.get_first_super_admin()
462 left_overs = True
462 left_overs = True
463
463
464 from rhodecode.model.repo_group import RepoGroupModel
464 from rhodecode.model.repo_group import RepoGroupModel
465
465
466 if handle_mode == 'detach':
466 if handle_mode == 'detach':
467 for r in repository_groups:
467 for r in repository_groups:
468 r.user = _superadmin
468 r.user = _superadmin
469 # set description we know why we super admin now owns
469 # set description we know why we super admin now owns
470 # additional repositories that were orphaned !
470 # additional repositories that were orphaned !
471 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
471 r.group_description += ' \n::detached repository group from deleted user: %s' % (username,)
472 r.personal = False
472 r.personal = False
473 self.sa.add(r)
473 self.sa.add(r)
474 left_overs = False
474 left_overs = False
475 elif handle_mode == 'delete':
475 elif handle_mode == 'delete':
476 for r in repository_groups:
476 for r in repository_groups:
477 RepoGroupModel().delete(r)
477 RepoGroupModel().delete(r)
478 left_overs = False
478 left_overs = False
479
479
480 # if nothing is done we have left overs left
480 # if nothing is done we have left overs left
481 return left_overs
481 return left_overs
482
482
483 def _handle_user_user_groups(self, username, user_groups, handle_mode=None):
483 def _handle_user_user_groups(self, username, user_groups, handle_mode=None):
484 _superadmin = self.cls.get_first_super_admin()
484 _superadmin = self.cls.get_first_super_admin()
485 left_overs = True
485 left_overs = True
486
486
487 from rhodecode.model.user_group import UserGroupModel
487 from rhodecode.model.user_group import UserGroupModel
488
488
489 if handle_mode == 'detach':
489 if handle_mode == 'detach':
490 for r in user_groups:
490 for r in user_groups:
491 for user_user_group_to_perm in r.user_user_group_to_perm:
491 for user_user_group_to_perm in r.user_user_group_to_perm:
492 if user_user_group_to_perm.user.username == username:
492 if user_user_group_to_perm.user.username == username:
493 user_user_group_to_perm.user = _superadmin
493 user_user_group_to_perm.user = _superadmin
494 r.user = _superadmin
494 r.user = _superadmin
495 # set description we know why we super admin now owns
495 # set description we know why we super admin now owns
496 # additional repositories that were orphaned !
496 # additional repositories that were orphaned !
497 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
497 r.user_group_description += ' \n::detached user group from deleted user: %s' % (username,)
498 self.sa.add(r)
498 self.sa.add(r)
499 left_overs = False
499 left_overs = False
500 elif handle_mode == 'delete':
500 elif handle_mode == 'delete':
501 for r in user_groups:
501 for r in user_groups:
502 UserGroupModel().delete(r)
502 UserGroupModel().delete(r)
503 left_overs = False
503 left_overs = False
504
504
505 # if nothing is done we have left overs left
505 # if nothing is done we have left overs left
506 return left_overs
506 return left_overs
507
507
508 def delete(self, user, cur_user=None, handle_repos=None,
508 def delete(self, user, cur_user=None, handle_repos=None,
509 handle_repo_groups=None, handle_user_groups=None):
509 handle_repo_groups=None, handle_user_groups=None):
510 from rhodecode.lib.hooks_base import log_delete_user
510 from rhodecode.lib.hooks_base import log_delete_user
511
511
512 if not cur_user:
512 if not cur_user:
513 cur_user = getattr(
513 cur_user = getattr(get_current_rhodecode_user(), 'username', None)
514 get_current_rhodecode_user(), 'username', None)
515 user = self._get_user(user)
514 user = self._get_user(user)
516
515
517 try:
516 try:
518 if user.username == User.DEFAULT_USER:
517 if user.username == User.DEFAULT_USER:
519 raise DefaultUserException(
518 raise DefaultUserException(
520 u"You can't remove this user since it's"
519 u"You can't remove this user since it's"
521 u" crucial for entire application")
520 u" crucial for entire application")
522
521
523 left_overs = self._handle_user_repos(
522 left_overs = self._handle_user_repos(
524 user.username, user.repositories, handle_repos)
523 user.username, user.repositories, handle_repos)
525 if left_overs and user.repositories:
524 if left_overs and user.repositories:
526 repos = [x.repo_name for x in user.repositories]
525 repos = [x.repo_name for x in user.repositories]
527 raise UserOwnsReposException(
526 raise UserOwnsReposException(
528 u'user "%(username)s" still owns %(len_repos)s repositories and cannot be '
527 u'user "%(username)s" still owns %(len_repos)s repositories and cannot be '
529 u'removed. Switch owners or remove those repositories:%(list_repos)s'
528 u'removed. Switch owners or remove those repositories:%(list_repos)s'
530 % {'username': user.username, 'len_repos': len(repos),
529 % {'username': user.username, 'len_repos': len(repos),
531 'list_repos': ', '.join(repos)})
530 'list_repos': ', '.join(repos)})
532
531
533 left_overs = self._handle_user_repo_groups(
532 left_overs = self._handle_user_repo_groups(
534 user.username, user.repository_groups, handle_repo_groups)
533 user.username, user.repository_groups, handle_repo_groups)
535 if left_overs and user.repository_groups:
534 if left_overs and user.repository_groups:
536 repo_groups = [x.group_name for x in user.repository_groups]
535 repo_groups = [x.group_name for x in user.repository_groups]
537 raise UserOwnsRepoGroupsException(
536 raise UserOwnsRepoGroupsException(
538 u'user "%(username)s" still owns %(len_repo_groups)s repository groups and cannot be '
537 u'user "%(username)s" still owns %(len_repo_groups)s repository groups and cannot be '
539 u'removed. Switch owners or remove those repository groups:%(list_repo_groups)s'
538 u'removed. Switch owners or remove those repository groups:%(list_repo_groups)s'
540 % {'username': user.username, 'len_repo_groups': len(repo_groups),
539 % {'username': user.username, 'len_repo_groups': len(repo_groups),
541 'list_repo_groups': ', '.join(repo_groups)})
540 'list_repo_groups': ', '.join(repo_groups)})
542
541
543 left_overs = self._handle_user_user_groups(
542 left_overs = self._handle_user_user_groups(
544 user.username, user.user_groups, handle_user_groups)
543 user.username, user.user_groups, handle_user_groups)
545 if left_overs and user.user_groups:
544 if left_overs and user.user_groups:
546 user_groups = [x.users_group_name for x in user.user_groups]
545 user_groups = [x.users_group_name for x in user.user_groups]
547 raise UserOwnsUserGroupsException(
546 raise UserOwnsUserGroupsException(
548 u'user "%s" still owns %s user groups and cannot be '
547 u'user "%s" still owns %s user groups and cannot be '
549 u'removed. Switch owners or remove those user groups:%s'
548 u'removed. Switch owners or remove those user groups:%s'
550 % (user.username, len(user_groups), ', '.join(user_groups)))
549 % (user.username, len(user_groups), ', '.join(user_groups)))
551
550
552 user_data = user.get_dict() # fetch user data before expire
551 user_data = user.get_dict() # fetch user data before expire
553
552
554 # we might change the user data with detach/delete, make sure
553 # we might change the user data with detach/delete, make sure
555 # the object is marked as expired before actually deleting !
554 # the object is marked as expired before actually deleting !
556 self.sa.expire(user)
555 self.sa.expire(user)
557 self.sa.delete(user)
556 self.sa.delete(user)
558
557
559 log_delete_user(deleted_by=cur_user, **user_data)
558 log_delete_user(deleted_by=cur_user, **user_data)
560 except Exception:
559 except Exception:
561 log.error(traceback.format_exc())
560 log.error(traceback.format_exc())
562 raise
561 raise
563
562
564 def reset_password_link(self, data, pwd_reset_url):
563 def reset_password_link(self, data, pwd_reset_url):
565 from rhodecode.lib.celerylib import tasks, run_task
564 from rhodecode.lib.celerylib import tasks, run_task
566 from rhodecode.model.notification import EmailNotificationModel
565 from rhodecode.model.notification import EmailNotificationModel
567 user_email = data['email']
566 user_email = data['email']
568 try:
567 try:
569 user = User.get_by_email(user_email)
568 user = User.get_by_email(user_email)
570 if user:
569 if user:
571 log.debug('password reset user found %s', user)
570 log.debug('password reset user found %s', user)
572
571
573 email_kwargs = {
572 email_kwargs = {
574 'password_reset_url': pwd_reset_url,
573 'password_reset_url': pwd_reset_url,
575 'user': user,
574 'user': user,
576 'email': user_email,
575 'email': user_email,
577 'date': datetime.datetime.now()
576 'date': datetime.datetime.now()
578 }
577 }
579
578
580 (subject, headers, email_body,
579 (subject, headers, email_body,
581 email_body_plaintext) = EmailNotificationModel().render_email(
580 email_body_plaintext) = EmailNotificationModel().render_email(
582 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
581 EmailNotificationModel.TYPE_PASSWORD_RESET, **email_kwargs)
583
582
584 recipients = [user_email]
583 recipients = [user_email]
585
584
586 action_logger_generic(
585 action_logger_generic(
587 'sending password reset email to user: {}'.format(
586 'sending password reset email to user: {}'.format(
588 user), namespace='security.password_reset')
587 user), namespace='security.password_reset')
589
588
590 run_task(tasks.send_email, recipients, subject,
589 run_task(tasks.send_email, recipients, subject,
591 email_body_plaintext, email_body)
590 email_body_plaintext, email_body)
592
591
593 else:
592 else:
594 log.debug("password reset email %s not found", user_email)
593 log.debug("password reset email %s not found", user_email)
595 except Exception:
594 except Exception:
596 log.error(traceback.format_exc())
595 log.error(traceback.format_exc())
597 return False
596 return False
598
597
599 return True
598 return True
600
599
601 def reset_password(self, data):
600 def reset_password(self, data):
602 from rhodecode.lib.celerylib import tasks, run_task
601 from rhodecode.lib.celerylib import tasks, run_task
603 from rhodecode.model.notification import EmailNotificationModel
602 from rhodecode.model.notification import EmailNotificationModel
604 from rhodecode.lib import auth
603 from rhodecode.lib import auth
605 user_email = data['email']
604 user_email = data['email']
606 pre_db = True
605 pre_db = True
607 try:
606 try:
608 user = User.get_by_email(user_email)
607 user = User.get_by_email(user_email)
609 new_passwd = auth.PasswordGenerator().gen_password(
608 new_passwd = auth.PasswordGenerator().gen_password(
610 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
609 12, auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
611 if user:
610 if user:
612 user.password = auth.get_crypt_password(new_passwd)
611 user.password = auth.get_crypt_password(new_passwd)
613 # also force this user to reset his password !
612 # also force this user to reset his password !
614 user.update_userdata(force_password_change=True)
613 user.update_userdata(force_password_change=True)
615
614
616 Session().add(user)
615 Session().add(user)
617
616
618 # now delete the token in question
617 # now delete the token in question
619 UserApiKeys = AuthTokenModel.cls
618 UserApiKeys = AuthTokenModel.cls
620 UserApiKeys().query().filter(
619 UserApiKeys().query().filter(
621 UserApiKeys.api_key == data['token']).delete()
620 UserApiKeys.api_key == data['token']).delete()
622
621
623 Session().commit()
622 Session().commit()
624 log.info('successfully reset password for `%s`', user_email)
623 log.info('successfully reset password for `%s`', user_email)
625
624
626 if new_passwd is None:
625 if new_passwd is None:
627 raise Exception('unable to generate new password')
626 raise Exception('unable to generate new password')
628
627
629 pre_db = False
628 pre_db = False
630
629
631 email_kwargs = {
630 email_kwargs = {
632 'new_password': new_passwd,
631 'new_password': new_passwd,
633 'user': user,
632 'user': user,
634 'email': user_email,
633 'email': user_email,
635 'date': datetime.datetime.now()
634 'date': datetime.datetime.now()
636 }
635 }
637
636
638 (subject, headers, email_body,
637 (subject, headers, email_body,
639 email_body_plaintext) = EmailNotificationModel().render_email(
638 email_body_plaintext) = EmailNotificationModel().render_email(
640 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION,
639 EmailNotificationModel.TYPE_PASSWORD_RESET_CONFIRMATION,
641 **email_kwargs)
640 **email_kwargs)
642
641
643 recipients = [user_email]
642 recipients = [user_email]
644
643
645 action_logger_generic(
644 action_logger_generic(
646 'sent new password to user: {} with email: {}'.format(
645 'sent new password to user: {} with email: {}'.format(
647 user, user_email), namespace='security.password_reset')
646 user, user_email), namespace='security.password_reset')
648
647
649 run_task(tasks.send_email, recipients, subject,
648 run_task(tasks.send_email, recipients, subject,
650 email_body_plaintext, email_body)
649 email_body_plaintext, email_body)
651
650
652 except Exception:
651 except Exception:
653 log.error('Failed to update user password')
652 log.error('Failed to update user password')
654 log.error(traceback.format_exc())
653 log.error(traceback.format_exc())
655 if pre_db:
654 if pre_db:
656 # we rollback only if local db stuff fails. If it goes into
655 # we rollback only if local db stuff fails. If it goes into
657 # run_task, we're pass rollback state this wouldn't work then
656 # run_task, we're pass rollback state this wouldn't work then
658 Session().rollback()
657 Session().rollback()
659
658
660 return True
659 return True
661
660
662 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
661 def fill_data(self, auth_user, user_id=None, api_key=None, username=None):
663 """
662 """
664 Fetches auth_user by user_id,or api_key if present.
663 Fetches auth_user by user_id,or api_key if present.
665 Fills auth_user attributes with those taken from database.
664 Fills auth_user attributes with those taken from database.
666 Additionally set's is_authenitated if lookup fails
665 Additionally set's is_authenitated if lookup fails
667 present in database
666 present in database
668
667
669 :param auth_user: instance of user to set attributes
668 :param auth_user: instance of user to set attributes
670 :param user_id: user id to fetch by
669 :param user_id: user id to fetch by
671 :param api_key: api key to fetch by
670 :param api_key: api key to fetch by
672 :param username: username to fetch by
671 :param username: username to fetch by
673 """
672 """
674 def token_obfuscate(token):
673 def token_obfuscate(token):
675 if token:
674 if token:
676 return token[:4] + "****"
675 return token[:4] + "****"
677
676
678 if user_id is None and api_key is None and username is None:
677 if user_id is None and api_key is None and username is None:
679 raise Exception('You need to pass user_id, api_key or username')
678 raise Exception('You need to pass user_id, api_key or username')
680
679
681 log.debug(
680 log.debug(
682 'AuthUser: fill data execution based on: '
681 'AuthUser: fill data execution based on: '
683 'user_id:%s api_key:%s username:%s', user_id, api_key, username)
682 'user_id:%s api_key:%s username:%s', user_id, api_key, username)
684 try:
683 try:
685 dbuser = None
684 dbuser = None
686 if user_id:
685 if user_id:
687 dbuser = self.get(user_id)
686 dbuser = self.get(user_id)
688 elif api_key:
687 elif api_key:
689 dbuser = self.get_by_auth_token(api_key)
688 dbuser = self.get_by_auth_token(api_key)
690 elif username:
689 elif username:
691 dbuser = self.get_by_username(username)
690 dbuser = self.get_by_username(username)
692
691
693 if not dbuser:
692 if not dbuser:
694 log.warning(
693 log.warning(
695 'Unable to lookup user by id:%s api_key:%s username:%s',
694 'Unable to lookup user by id:%s api_key:%s username:%s',
696 user_id, token_obfuscate(api_key), username)
695 user_id, token_obfuscate(api_key), username)
697 return False
696 return False
698 if not dbuser.active:
697 if not dbuser.active:
699 log.debug('User `%s:%s` is inactive, skipping fill data',
698 log.debug('User `%s:%s` is inactive, skipping fill data',
700 username, user_id)
699 username, user_id)
701 return False
700 return False
702
701
703 log.debug('AuthUser: filling found user:%s data', dbuser)
702 log.debug('AuthUser: filling found user:%s data', dbuser)
704 user_data = dbuser.get_dict()
703 user_data = dbuser.get_dict()
705
704
706 user_data.update({
705 user_data.update({
707 # set explicit the safe escaped values
706 # set explicit the safe escaped values
708 'first_name': dbuser.first_name,
707 'first_name': dbuser.first_name,
709 'last_name': dbuser.last_name,
708 'last_name': dbuser.last_name,
710 })
709 })
711
710
712 for k, v in user_data.items():
711 for k, v in user_data.items():
713 # properties of auth user we dont update
712 # properties of auth user we dont update
714 if k not in ['auth_tokens', 'permissions']:
713 if k not in ['auth_tokens', 'permissions']:
715 setattr(auth_user, k, v)
714 setattr(auth_user, k, v)
716
715
717 except Exception:
716 except Exception:
718 log.error(traceback.format_exc())
717 log.error(traceback.format_exc())
719 auth_user.is_authenticated = False
718 auth_user.is_authenticated = False
720 return False
719 return False
721
720
722 return True
721 return True
723
722
724 def has_perm(self, user, perm):
723 def has_perm(self, user, perm):
725 perm = self._get_perm(perm)
724 perm = self._get_perm(perm)
726 user = self._get_user(user)
725 user = self._get_user(user)
727
726
728 return UserToPerm.query().filter(UserToPerm.user == user)\
727 return UserToPerm.query().filter(UserToPerm.user == user)\
729 .filter(UserToPerm.permission == perm).scalar() is not None
728 .filter(UserToPerm.permission == perm).scalar() is not None
730
729
731 def grant_perm(self, user, perm):
730 def grant_perm(self, user, perm):
732 """
731 """
733 Grant user global permissions
732 Grant user global permissions
734
733
735 :param user:
734 :param user:
736 :param perm:
735 :param perm:
737 """
736 """
738 user = self._get_user(user)
737 user = self._get_user(user)
739 perm = self._get_perm(perm)
738 perm = self._get_perm(perm)
740 # if this permission is already granted skip it
739 # if this permission is already granted skip it
741 _perm = UserToPerm.query()\
740 _perm = UserToPerm.query()\
742 .filter(UserToPerm.user == user)\
741 .filter(UserToPerm.user == user)\
743 .filter(UserToPerm.permission == perm)\
742 .filter(UserToPerm.permission == perm)\
744 .scalar()
743 .scalar()
745 if _perm:
744 if _perm:
746 return
745 return
747 new = UserToPerm()
746 new = UserToPerm()
748 new.user = user
747 new.user = user
749 new.permission = perm
748 new.permission = perm
750 self.sa.add(new)
749 self.sa.add(new)
751 return new
750 return new
752
751
753 def revoke_perm(self, user, perm):
752 def revoke_perm(self, user, perm):
754 """
753 """
755 Revoke users global permissions
754 Revoke users global permissions
756
755
757 :param user:
756 :param user:
758 :param perm:
757 :param perm:
759 """
758 """
760 user = self._get_user(user)
759 user = self._get_user(user)
761 perm = self._get_perm(perm)
760 perm = self._get_perm(perm)
762
761
763 obj = UserToPerm.query()\
762 obj = UserToPerm.query()\
764 .filter(UserToPerm.user == user)\
763 .filter(UserToPerm.user == user)\
765 .filter(UserToPerm.permission == perm)\
764 .filter(UserToPerm.permission == perm)\
766 .scalar()
765 .scalar()
767 if obj:
766 if obj:
768 self.sa.delete(obj)
767 self.sa.delete(obj)
769
768
770 def add_extra_email(self, user, email):
769 def add_extra_email(self, user, email):
771 """
770 """
772 Adds email address to UserEmailMap
771 Adds email address to UserEmailMap
773
772
774 :param user:
773 :param user:
775 :param email:
774 :param email:
776 """
775 """
777
776
778 user = self._get_user(user)
777 user = self._get_user(user)
779
778
780 obj = UserEmailMap()
779 obj = UserEmailMap()
781 obj.user = user
780 obj.user = user
782 obj.email = email
781 obj.email = email
783 self.sa.add(obj)
782 self.sa.add(obj)
784 return obj
783 return obj
785
784
786 def delete_extra_email(self, user, email_id):
785 def delete_extra_email(self, user, email_id):
787 """
786 """
788 Removes email address from UserEmailMap
787 Removes email address from UserEmailMap
789
788
790 :param user:
789 :param user:
791 :param email_id:
790 :param email_id:
792 """
791 """
793 user = self._get_user(user)
792 user = self._get_user(user)
794 obj = UserEmailMap.query().get(email_id)
793 obj = UserEmailMap.query().get(email_id)
795 if obj and obj.user_id == user.user_id:
794 if obj and obj.user_id == user.user_id:
796 self.sa.delete(obj)
795 self.sa.delete(obj)
797
796
798 def parse_ip_range(self, ip_range):
797 def parse_ip_range(self, ip_range):
799 ip_list = []
798 ip_list = []
800
799
801 def make_unique(value):
800 def make_unique(value):
802 seen = []
801 seen = []
803 return [c for c in value if not (c in seen or seen.append(c))]
802 return [c for c in value if not (c in seen or seen.append(c))]
804
803
805 # firsts split by commas
804 # firsts split by commas
806 for ip_range in ip_range.split(','):
805 for ip_range in ip_range.split(','):
807 if not ip_range:
806 if not ip_range:
808 continue
807 continue
809 ip_range = ip_range.strip()
808 ip_range = ip_range.strip()
810 if '-' in ip_range:
809 if '-' in ip_range:
811 start_ip, end_ip = ip_range.split('-', 1)
810 start_ip, end_ip = ip_range.split('-', 1)
812 start_ip = ipaddress.ip_address(safe_unicode(start_ip.strip()))
811 start_ip = ipaddress.ip_address(safe_unicode(start_ip.strip()))
813 end_ip = ipaddress.ip_address(safe_unicode(end_ip.strip()))
812 end_ip = ipaddress.ip_address(safe_unicode(end_ip.strip()))
814 parsed_ip_range = []
813 parsed_ip_range = []
815
814
816 for index in xrange(int(start_ip), int(end_ip) + 1):
815 for index in xrange(int(start_ip), int(end_ip) + 1):
817 new_ip = ipaddress.ip_address(index)
816 new_ip = ipaddress.ip_address(index)
818 parsed_ip_range.append(str(new_ip))
817 parsed_ip_range.append(str(new_ip))
819 ip_list.extend(parsed_ip_range)
818 ip_list.extend(parsed_ip_range)
820 else:
819 else:
821 ip_list.append(ip_range)
820 ip_list.append(ip_range)
822
821
823 return make_unique(ip_list)
822 return make_unique(ip_list)
824
823
825 def add_extra_ip(self, user, ip, description=None):
824 def add_extra_ip(self, user, ip, description=None):
826 """
825 """
827 Adds ip address to UserIpMap
826 Adds ip address to UserIpMap
828
827
829 :param user:
828 :param user:
830 :param ip:
829 :param ip:
831 """
830 """
832
831
833 user = self._get_user(user)
832 user = self._get_user(user)
834 obj = UserIpMap()
833 obj = UserIpMap()
835 obj.user = user
834 obj.user = user
836 obj.ip_addr = ip
835 obj.ip_addr = ip
837 obj.description = description
836 obj.description = description
838 self.sa.add(obj)
837 self.sa.add(obj)
839 return obj
838 return obj
840
839
841 auth_token_role = AuthTokenModel.cls
840 auth_token_role = AuthTokenModel.cls
842
841
843 def add_auth_token(self, user, lifetime_minutes, role, description=u'',
842 def add_auth_token(self, user, lifetime_minutes, role, description=u'',
844 scope_callback=None):
843 scope_callback=None):
845 """
844 """
846 Add AuthToken for user.
845 Add AuthToken for user.
847
846
848 :param user: username/user_id
847 :param user: username/user_id
849 :param lifetime_minutes: in minutes the lifetime for token, -1 equals no limit
848 :param lifetime_minutes: in minutes the lifetime for token, -1 equals no limit
850 :param role: one of AuthTokenModel.cls.ROLE_*
849 :param role: one of AuthTokenModel.cls.ROLE_*
851 :param description: optional string description
850 :param description: optional string description
852 """
851 """
853
852
854 token = AuthTokenModel().create(
853 token = AuthTokenModel().create(
855 user, description, lifetime_minutes, role)
854 user, description, lifetime_minutes, role)
856 if scope_callback and callable(scope_callback):
855 if scope_callback and callable(scope_callback):
857 # call the callback if we provide, used to attach scope for EE edition
856 # call the callback if we provide, used to attach scope for EE edition
858 scope_callback(token)
857 scope_callback(token)
859 return token
858 return token
860
859
861 def delete_extra_ip(self, user, ip_id):
860 def delete_extra_ip(self, user, ip_id):
862 """
861 """
863 Removes ip address from UserIpMap
862 Removes ip address from UserIpMap
864
863
865 :param user:
864 :param user:
866 :param ip_id:
865 :param ip_id:
867 """
866 """
868 user = self._get_user(user)
867 user = self._get_user(user)
869 obj = UserIpMap.query().get(ip_id)
868 obj = UserIpMap.query().get(ip_id)
870 if obj and obj.user_id == user.user_id:
869 if obj and obj.user_id == user.user_id:
871 self.sa.delete(obj)
870 self.sa.delete(obj)
872
871
873 def get_accounts_in_creation_order(self, current_user=None):
872 def get_accounts_in_creation_order(self, current_user=None):
874 """
873 """
875 Get accounts in order of creation for deactivation for license limits
874 Get accounts in order of creation for deactivation for license limits
876
875
877 pick currently logged in user, and append to the list in position 0
876 pick currently logged in user, and append to the list in position 0
878 pick all super-admins in order of creation date and add it to the list
877 pick all super-admins in order of creation date and add it to the list
879 pick all other accounts in order of creation and add it to the list.
878 pick all other accounts in order of creation and add it to the list.
880
879
881 Based on that list, the last accounts can be disabled as they are
880 Based on that list, the last accounts can be disabled as they are
882 created at the end and don't include any of the super admins as well
881 created at the end and don't include any of the super admins as well
883 as the current user.
882 as the current user.
884
883
885 :param current_user: optionally current user running this operation
884 :param current_user: optionally current user running this operation
886 """
885 """
887
886
888 if not current_user:
887 if not current_user:
889 current_user = get_current_rhodecode_user()
888 current_user = get_current_rhodecode_user()
890 active_super_admins = [
889 active_super_admins = [
891 x.user_id for x in User.query()
890 x.user_id for x in User.query()
892 .filter(User.user_id != current_user.user_id)
891 .filter(User.user_id != current_user.user_id)
893 .filter(User.active == true())
892 .filter(User.active == true())
894 .filter(User.admin == true())
893 .filter(User.admin == true())
895 .order_by(User.created_on.asc())]
894 .order_by(User.created_on.asc())]
896
895
897 active_regular_users = [
896 active_regular_users = [
898 x.user_id for x in User.query()
897 x.user_id for x in User.query()
899 .filter(User.user_id != current_user.user_id)
898 .filter(User.user_id != current_user.user_id)
900 .filter(User.active == true())
899 .filter(User.active == true())
901 .filter(User.admin == false())
900 .filter(User.admin == false())
902 .order_by(User.created_on.asc())]
901 .order_by(User.created_on.asc())]
903
902
904 list_of_accounts = [current_user.user_id]
903 list_of_accounts = [current_user.user_id]
905 list_of_accounts += active_super_admins
904 list_of_accounts += active_super_admins
906 list_of_accounts += active_regular_users
905 list_of_accounts += active_regular_users
907
906
908 return list_of_accounts
907 return list_of_accounts
909
908
910 def deactivate_last_users(self, expected_users, current_user=None):
909 def deactivate_last_users(self, expected_users, current_user=None):
911 """
910 """
912 Deactivate accounts that are over the license limits.
911 Deactivate accounts that are over the license limits.
913 Algorithm of which accounts to disabled is based on the formula:
912 Algorithm of which accounts to disabled is based on the formula:
914
913
915 Get current user, then super admins in creation order, then regular
914 Get current user, then super admins in creation order, then regular
916 active users in creation order.
915 active users in creation order.
917
916
918 Using that list we mark all accounts from the end of it as inactive.
917 Using that list we mark all accounts from the end of it as inactive.
919 This way we block only latest created accounts.
918 This way we block only latest created accounts.
920
919
921 :param expected_users: list of users in special order, we deactivate
920 :param expected_users: list of users in special order, we deactivate
922 the end N amount of users from that list
921 the end N amount of users from that list
923 """
922 """
924
923
925 list_of_accounts = self.get_accounts_in_creation_order(
924 list_of_accounts = self.get_accounts_in_creation_order(
926 current_user=current_user)
925 current_user=current_user)
927
926
928 for acc_id in list_of_accounts[expected_users + 1:]:
927 for acc_id in list_of_accounts[expected_users + 1:]:
929 user = User.get(acc_id)
928 user = User.get(acc_id)
930 log.info('Deactivating account %s for license unlock', user)
929 log.info('Deactivating account %s for license unlock', user)
931 user.active = False
930 user.active = False
932 Session().add(user)
931 Session().add(user)
933 Session().commit()
932 Session().commit()
934
933
935 return
934 return
936
935
937 def get_user_log(self, user, filter_term):
936 def get_user_log(self, user, filter_term):
938 user_log = UserLog.query()\
937 user_log = UserLog.query()\
939 .filter(or_(UserLog.user_id == user.user_id,
938 .filter(or_(UserLog.user_id == user.user_id,
940 UserLog.username == user.username))\
939 UserLog.username == user.username))\
941 .options(joinedload(UserLog.user))\
940 .options(joinedload(UserLog.user))\
942 .options(joinedload(UserLog.repository))\
941 .options(joinedload(UserLog.repository))\
943 .order_by(UserLog.action_date.desc())
942 .order_by(UserLog.action_date.desc())
944
943
945 user_log = user_log_filter(user_log, filter_term)
944 user_log = user_log_filter(user_log, filter_term)
946 return user_log
945 return user_log
@@ -1,617 +1,632 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 Session, 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 Session().commit()
96
97
97 permissions = get_permissions(user)
98 permissions = get_permissions(user)
98 assert granted_permission in permissions['global']
99 assert granted_permission in permissions['global']
99
100
100
101
101 @pytest.mark.xfail(reason="Not implemented, see TODO note")
102 @pytest.mark.xfail(reason="Not implemented, see TODO note")
102 def test_cached_perms_data_user_group_global_permissions_(user_util):
103 def test_cached_perms_data_user_group_global_permissions_(user_util):
103 user, user_group = user_util.create_user_with_group()
104 user, user_group = user_util.create_user_with_group()
104
105
105 granted_permission = 'repository.write'
106 granted_permission = 'repository.write'
106 UserGroupModel().grant_perm(user_group, granted_permission)
107 UserGroupModel().grant_perm(user_group, granted_permission)
108 Session().commit()
107
109
108 permissions = get_permissions(user)
110 permissions = get_permissions(user)
109 assert granted_permission in permissions['global']
111 assert granted_permission in permissions['global']
110
112
111
113
112 def test_cached_perms_data_user_global_permissions(user_util):
114 def test_cached_perms_data_user_global_permissions(user_util):
113 user = user_util.create_user()
115 user = user_util.create_user()
114 UserModel().grant_perm(user, 'repository.none')
116 UserModel().grant_perm(user, 'repository.none')
117 Session().commit()
115
118
116 permissions = get_permissions(user, user_inherit_default_permissions=True)
119 permissions = get_permissions(user, user_inherit_default_permissions=True)
117 assert 'repository.read' in permissions['global']
120 assert 'repository.read' in permissions['global']
118
121
119
122
120 def test_cached_perms_data_repository_permissions_on_private_repository(
123 def test_cached_perms_data_repository_permissions_on_private_repository(
121 backend_random, user_util):
124 backend_random, user_util):
122 user, user_group = user_util.create_user_with_group()
125 user, user_group = user_util.create_user_with_group()
123
126
124 repo = backend_random.create_repo()
127 repo = backend_random.create_repo()
125 repo.private = True
128 repo.private = True
126
129
127 granted_permission = 'repository.write'
130 granted_permission = 'repository.write'
128 RepoModel().grant_user_group_permission(
131 RepoModel().grant_user_group_permission(
129 repo, user_group.users_group_name, granted_permission)
132 repo, user_group.users_group_name, granted_permission)
133 Session().commit()
130
134
131 permissions = get_permissions(user)
135 permissions = get_permissions(user)
132 assert permissions['repositories'][repo.repo_name] == granted_permission
136 assert permissions['repositories'][repo.repo_name] == granted_permission
133
137
134
138
135 def test_cached_perms_data_repository_permissions_for_owner(
139 def test_cached_perms_data_repository_permissions_for_owner(
136 backend_random, user_util):
140 backend_random, user_util):
137 user = user_util.create_user()
141 user = user_util.create_user()
138
142
139 repo = backend_random.create_repo()
143 repo = backend_random.create_repo()
140 repo.user_id = user.user_id
144 repo.user_id = user.user_id
141
145
142 permissions = get_permissions(user)
146 permissions = get_permissions(user)
143 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
147 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
144
148
145 # TODO: johbo: Make cleanup in UserUtility smarter, then remove this hack
149 # TODO: johbo: Make cleanup in UserUtility smarter, then remove this hack
146 repo.user_id = User.get_default_user().user_id
150 repo.user_id = User.get_default_user().user_id
147
151
148
152
149 def test_cached_perms_data_repository_permissions_not_inheriting_defaults(
153 def test_cached_perms_data_repository_permissions_not_inheriting_defaults(
150 backend_random, user_util):
154 backend_random, user_util):
151 user = user_util.create_user()
155 user = user_util.create_user()
152 repo = backend_random.create_repo()
156 repo = backend_random.create_repo()
153
157
154 # Don't inherit default object permissions
158 # Don't inherit default object permissions
155 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
159 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
160 Session().commit()
156
161
157 permissions = get_permissions(user)
162 permissions = get_permissions(user)
158 assert permissions['repositories'][repo.repo_name] == 'repository.none'
163 assert permissions['repositories'][repo.repo_name] == 'repository.none'
159
164
160
165
161 def test_cached_perms_data_default_permissions_on_repository_group(user_util):
166 def test_cached_perms_data_default_permissions_on_repository_group(user_util):
162 # Have a repository group with default permissions set
167 # Have a repository group with default permissions set
163 repo_group = user_util.create_repo_group()
168 repo_group = user_util.create_repo_group()
164 default_user = User.get_default_user()
169 default_user = User.get_default_user()
165 user_util.grant_user_permission_to_repo_group(
170 user_util.grant_user_permission_to_repo_group(
166 repo_group, default_user, 'repository.write')
171 repo_group, default_user, 'repository.write')
167 user = user_util.create_user()
172 user = user_util.create_user()
168
173
169 permissions = get_permissions(user)
174 permissions = get_permissions(user)
170 assert permissions['repositories_groups'][repo_group.group_name] == \
175 assert permissions['repositories_groups'][repo_group.group_name] == \
171 'repository.write'
176 'repository.write'
172
177
173
178
174 def test_cached_perms_data_default_permissions_on_repository_group_owner(
179 def test_cached_perms_data_default_permissions_on_repository_group_owner(
175 user_util):
180 user_util):
176 # Have a repository group
181 # Have a repository group
177 repo_group = user_util.create_repo_group()
182 repo_group = user_util.create_repo_group()
178 default_user = User.get_default_user()
183 default_user = User.get_default_user()
179
184
180 # Add a permission for the default user to hit the code path
185 # Add a permission for the default user to hit the code path
181 user_util.grant_user_permission_to_repo_group(
186 user_util.grant_user_permission_to_repo_group(
182 repo_group, default_user, 'repository.write')
187 repo_group, default_user, 'repository.write')
183
188
184 # Have an owner of the group
189 # Have an owner of the group
185 user = user_util.create_user()
190 user = user_util.create_user()
186 repo_group.user_id = user.user_id
191 repo_group.user_id = user.user_id
187
192
188 permissions = get_permissions(user)
193 permissions = get_permissions(user)
189 assert permissions['repositories_groups'][repo_group.group_name] == \
194 assert permissions['repositories_groups'][repo_group.group_name] == \
190 'group.admin'
195 'group.admin'
191
196
192
197
193 def test_cached_perms_data_default_permissions_on_repository_group_no_inherit(
198 def test_cached_perms_data_default_permissions_on_repository_group_no_inherit(
194 user_util):
199 user_util):
195 # Have a repository group
200 # Have a repository group
196 repo_group = user_util.create_repo_group()
201 repo_group = user_util.create_repo_group()
197 default_user = User.get_default_user()
202 default_user = User.get_default_user()
198
203
199 # Add a permission for the default user to hit the code path
204 # Add a permission for the default user to hit the code path
200 user_util.grant_user_permission_to_repo_group(
205 user_util.grant_user_permission_to_repo_group(
201 repo_group, default_user, 'repository.write')
206 repo_group, default_user, 'repository.write')
202
207
203 # Don't inherit default object permissions
208 # Don't inherit default object permissions
204 user = user_util.create_user()
209 user = user_util.create_user()
205 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
210 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
211 Session().commit()
206
212
207 permissions = get_permissions(user)
213 permissions = get_permissions(user)
208 assert permissions['repositories_groups'][repo_group.group_name] == \
214 assert permissions['repositories_groups'][repo_group.group_name] == \
209 'group.none'
215 'group.none'
210
216
211
217
212 def test_cached_perms_data_repository_permissions_from_user_group(
218 def test_cached_perms_data_repository_permissions_from_user_group(
213 user_util, backend_random):
219 user_util, backend_random):
214 user, user_group = user_util.create_user_with_group()
220 user, user_group = user_util.create_user_with_group()
215
221
216 # Needs a second user group to make sure that we select the right
222 # Needs a second user group to make sure that we select the right
217 # permissions.
223 # permissions.
218 user_group2 = user_util.create_user_group()
224 user_group2 = user_util.create_user_group()
219 UserGroupModel().add_user_to_group(user_group2, user)
225 UserGroupModel().add_user_to_group(user_group2, user)
220
226
221 repo = backend_random.create_repo()
227 repo = backend_random.create_repo()
222
228
223 RepoModel().grant_user_group_permission(
229 RepoModel().grant_user_group_permission(
224 repo, user_group.users_group_name, 'repository.read')
230 repo, user_group.users_group_name, 'repository.read')
225 RepoModel().grant_user_group_permission(
231 RepoModel().grant_user_group_permission(
226 repo, user_group2.users_group_name, 'repository.write')
232 repo, user_group2.users_group_name, 'repository.write')
233 Session().commit()
227
234
228 permissions = get_permissions(user)
235 permissions = get_permissions(user)
229 assert permissions['repositories'][repo.repo_name] == 'repository.write'
236 assert permissions['repositories'][repo.repo_name] == 'repository.write'
230
237
231
238
232 def test_cached_perms_data_repository_permissions_from_user_group_owner(
239 def test_cached_perms_data_repository_permissions_from_user_group_owner(
233 user_util, backend_random):
240 user_util, backend_random):
234 user, user_group = user_util.create_user_with_group()
241 user, user_group = user_util.create_user_with_group()
235
242
236 repo = backend_random.create_repo()
243 repo = backend_random.create_repo()
237 repo.user_id = user.user_id
244 repo.user_id = user.user_id
238
245
239 RepoModel().grant_user_group_permission(
246 RepoModel().grant_user_group_permission(
240 repo, user_group.users_group_name, 'repository.write')
247 repo, user_group.users_group_name, 'repository.write')
248 Session().commit()
241
249
242 permissions = get_permissions(user)
250 permissions = get_permissions(user)
243 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
251 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
244
252
245
253
246 def test_cached_perms_data_user_repository_permissions(
254 def test_cached_perms_data_user_repository_permissions(
247 user_util, backend_random):
255 user_util, backend_random):
248 user = user_util.create_user()
256 user = user_util.create_user()
249 repo = backend_random.create_repo()
257 repo = backend_random.create_repo()
250 granted_permission = 'repository.write'
258 granted_permission = 'repository.write'
251 RepoModel().grant_user_permission(repo, user, granted_permission)
259 RepoModel().grant_user_permission(repo, user, granted_permission)
260 Session().commit()
252
261
253 permissions = get_permissions(user)
262 permissions = get_permissions(user)
254 assert permissions['repositories'][repo.repo_name] == granted_permission
263 assert permissions['repositories'][repo.repo_name] == granted_permission
255
264
256
265
257 def test_cached_perms_data_user_repository_permissions_explicit(
266 def test_cached_perms_data_user_repository_permissions_explicit(
258 user_util, backend_random):
267 user_util, backend_random):
259 user = user_util.create_user()
268 user = user_util.create_user()
260 repo = backend_random.create_repo()
269 repo = backend_random.create_repo()
261 granted_permission = 'repository.none'
270 granted_permission = 'repository.none'
262 RepoModel().grant_user_permission(repo, user, granted_permission)
271 RepoModel().grant_user_permission(repo, user, granted_permission)
272 Session().commit()
263
273
264 permissions = get_permissions(user, explicit=True)
274 permissions = get_permissions(user, explicit=True)
265 assert permissions['repositories'][repo.repo_name] == granted_permission
275 assert permissions['repositories'][repo.repo_name] == granted_permission
266
276
267
277
268 def test_cached_perms_data_user_repository_permissions_owner(
278 def test_cached_perms_data_user_repository_permissions_owner(
269 user_util, backend_random):
279 user_util, backend_random):
270 user = user_util.create_user()
280 user = user_util.create_user()
271 repo = backend_random.create_repo()
281 repo = backend_random.create_repo()
272 repo.user_id = user.user_id
282 repo.user_id = user.user_id
273 RepoModel().grant_user_permission(repo, user, 'repository.write')
283 RepoModel().grant_user_permission(repo, user, 'repository.write')
284 Session().commit()
274
285
275 permissions = get_permissions(user)
286 permissions = get_permissions(user)
276 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
287 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
277
288
278
289
279 def test_cached_perms_data_repository_groups_permissions_inherited(
290 def test_cached_perms_data_repository_groups_permissions_inherited(
280 user_util, backend_random):
291 user_util, backend_random):
281 user, user_group = user_util.create_user_with_group()
292 user, user_group = user_util.create_user_with_group()
282
293
283 # Needs a second group to hit the last condition
294 # Needs a second group to hit the last condition
284 user_group2 = user_util.create_user_group()
295 user_group2 = user_util.create_user_group()
285 UserGroupModel().add_user_to_group(user_group2, user)
296 UserGroupModel().add_user_to_group(user_group2, user)
286
297
287 repo_group = user_util.create_repo_group()
298 repo_group = user_util.create_repo_group()
288
299
289 user_util.grant_user_group_permission_to_repo_group(
300 user_util.grant_user_group_permission_to_repo_group(
290 repo_group, user_group, 'group.read')
301 repo_group, user_group, 'group.read')
291 user_util.grant_user_group_permission_to_repo_group(
302 user_util.grant_user_group_permission_to_repo_group(
292 repo_group, user_group2, 'group.write')
303 repo_group, user_group2, 'group.write')
293
304
294 permissions = get_permissions(user)
305 permissions = get_permissions(user)
295 assert permissions['repositories_groups'][repo_group.group_name] == \
306 assert permissions['repositories_groups'][repo_group.group_name] == \
296 'group.write'
307 'group.write'
297
308
298
309
299 def test_cached_perms_data_repository_groups_permissions_inherited_owner(
310 def test_cached_perms_data_repository_groups_permissions_inherited_owner(
300 user_util, backend_random):
311 user_util, backend_random):
301 user, user_group = user_util.create_user_with_group()
312 user, user_group = user_util.create_user_with_group()
302 repo_group = user_util.create_repo_group()
313 repo_group = user_util.create_repo_group()
303 repo_group.user_id = user.user_id
314 repo_group.user_id = user.user_id
304
315
305 granted_permission = 'group.write'
316 granted_permission = 'group.write'
306 user_util.grant_user_group_permission_to_repo_group(
317 user_util.grant_user_group_permission_to_repo_group(
307 repo_group, user_group, granted_permission)
318 repo_group, user_group, granted_permission)
308
319
309 permissions = get_permissions(user)
320 permissions = get_permissions(user)
310 assert permissions['repositories_groups'][repo_group.group_name] == \
321 assert permissions['repositories_groups'][repo_group.group_name] == \
311 'group.admin'
322 'group.admin'
312
323
313
324
314 def test_cached_perms_data_repository_groups_permissions(
325 def test_cached_perms_data_repository_groups_permissions(
315 user_util, backend_random):
326 user_util, backend_random):
316 user = user_util.create_user()
327 user = user_util.create_user()
317
328
318 repo_group = user_util.create_repo_group()
329 repo_group = user_util.create_repo_group()
319
330
320 granted_permission = 'group.write'
331 granted_permission = 'group.write'
321 user_util.grant_user_permission_to_repo_group(
332 user_util.grant_user_permission_to_repo_group(
322 repo_group, user, granted_permission)
333 repo_group, user, granted_permission)
323
334
324 permissions = get_permissions(user)
335 permissions = get_permissions(user)
325 assert permissions['repositories_groups'][repo_group.group_name] == \
336 assert permissions['repositories_groups'][repo_group.group_name] == \
326 'group.write'
337 'group.write'
327
338
328
339
329 def test_cached_perms_data_repository_groups_permissions_explicit(
340 def test_cached_perms_data_repository_groups_permissions_explicit(
330 user_util, backend_random):
341 user_util, backend_random):
331 user = user_util.create_user()
342 user = user_util.create_user()
332
343
333 repo_group = user_util.create_repo_group()
344 repo_group = user_util.create_repo_group()
334
345
335 granted_permission = 'group.none'
346 granted_permission = 'group.none'
336 user_util.grant_user_permission_to_repo_group(
347 user_util.grant_user_permission_to_repo_group(
337 repo_group, user, granted_permission)
348 repo_group, user, granted_permission)
338
349
339 permissions = get_permissions(user, explicit=True)
350 permissions = get_permissions(user, explicit=True)
340 assert permissions['repositories_groups'][repo_group.group_name] == \
351 assert permissions['repositories_groups'][repo_group.group_name] == \
341 'group.none'
352 'group.none'
342
353
343
354
344 def test_cached_perms_data_repository_groups_permissions_owner(
355 def test_cached_perms_data_repository_groups_permissions_owner(
345 user_util, backend_random):
356 user_util, backend_random):
346 user = user_util.create_user()
357 user = user_util.create_user()
347
358
348 repo_group = user_util.create_repo_group()
359 repo_group = user_util.create_repo_group()
349 repo_group.user_id = user.user_id
360 repo_group.user_id = user.user_id
350
361
351 granted_permission = 'group.write'
362 granted_permission = 'group.write'
352 user_util.grant_user_permission_to_repo_group(
363 user_util.grant_user_permission_to_repo_group(
353 repo_group, user, granted_permission)
364 repo_group, user, granted_permission)
354
365
355 permissions = get_permissions(user)
366 permissions = get_permissions(user)
356 assert permissions['repositories_groups'][repo_group.group_name] == \
367 assert permissions['repositories_groups'][repo_group.group_name] == \
357 'group.admin'
368 'group.admin'
358
369
359
370
360 def test_cached_perms_data_user_group_permissions_inherited(
371 def test_cached_perms_data_user_group_permissions_inherited(
361 user_util, backend_random):
372 user_util, backend_random):
362 user, user_group = user_util.create_user_with_group()
373 user, user_group = user_util.create_user_with_group()
363 user_group2 = user_util.create_user_group()
374 user_group2 = user_util.create_user_group()
364 UserGroupModel().add_user_to_group(user_group2, user)
375 UserGroupModel().add_user_to_group(user_group2, user)
365
376
366 target_user_group = user_util.create_user_group()
377 target_user_group = user_util.create_user_group()
367
378
368 user_util.grant_user_group_permission_to_user_group(
379 user_util.grant_user_group_permission_to_user_group(
369 target_user_group, user_group, 'usergroup.read')
380 target_user_group, user_group, 'usergroup.read')
370 user_util.grant_user_group_permission_to_user_group(
381 user_util.grant_user_group_permission_to_user_group(
371 target_user_group, user_group2, 'usergroup.write')
382 target_user_group, user_group2, 'usergroup.write')
372
383
373 permissions = get_permissions(user)
384 permissions = get_permissions(user)
374 assert permissions['user_groups'][target_user_group.users_group_name] == \
385 assert permissions['user_groups'][target_user_group.users_group_name] == \
375 'usergroup.write'
386 'usergroup.write'
376
387
377
388
378 def test_cached_perms_data_user_group_permissions(
389 def test_cached_perms_data_user_group_permissions(
379 user_util, backend_random):
390 user_util, backend_random):
380 user = user_util.create_user()
391 user = user_util.create_user()
381 user_group = user_util.create_user_group()
392 user_group = user_util.create_user_group()
382 UserGroupModel().grant_user_permission(user_group, user, 'usergroup.write')
393 UserGroupModel().grant_user_permission(user_group, user, 'usergroup.write')
394 Session().commit()
383
395
384 permissions = get_permissions(user)
396 permissions = get_permissions(user)
385 assert permissions['user_groups'][user_group.users_group_name] == \
397 assert permissions['user_groups'][user_group.users_group_name] == \
386 'usergroup.write'
398 'usergroup.write'
387
399
388
400
389 def test_cached_perms_data_user_group_permissions_explicit(
401 def test_cached_perms_data_user_group_permissions_explicit(
390 user_util, backend_random):
402 user_util, backend_random):
391 user = user_util.create_user()
403 user = user_util.create_user()
392 user_group = user_util.create_user_group()
404 user_group = user_util.create_user_group()
393 UserGroupModel().grant_user_permission(user_group, user, 'usergroup.none')
405 UserGroupModel().grant_user_permission(user_group, user, 'usergroup.none')
406 Session().commit()
394
407
395 permissions = get_permissions(user, explicit=True)
408 permissions = get_permissions(user, explicit=True)
396 assert permissions['user_groups'][user_group.users_group_name] == \
409 assert permissions['user_groups'][user_group.users_group_name] == \
397 'usergroup.none'
410 'usergroup.none'
398
411
399
412
400 def test_cached_perms_data_user_group_permissions_not_inheriting_defaults(
413 def test_cached_perms_data_user_group_permissions_not_inheriting_defaults(
401 user_util, backend_random):
414 user_util, backend_random):
402 user = user_util.create_user()
415 user = user_util.create_user()
403 user_group = user_util.create_user_group()
416 user_group = user_util.create_user_group()
404
417
405 # Don't inherit default object permissions
418 # Don't inherit default object permissions
406 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
419 UserModel().grant_perm(user, 'hg.inherit_default_perms.false')
420 Session().commit()
407
421
408 permissions = get_permissions(user)
422 permissions = get_permissions(user)
409 assert permissions['user_groups'][user_group.users_group_name] == \
423 assert permissions['user_groups'][user_group.users_group_name] == \
410 'usergroup.none'
424 'usergroup.none'
411
425
412
426
413 def test_permission_calculator_admin_permissions(
427 def test_permission_calculator_admin_permissions(
414 user_util, backend_random):
428 user_util, backend_random):
415 user = user_util.create_user()
429 user = user_util.create_user()
416 user_group = user_util.create_user_group()
430 user_group = user_util.create_user_group()
417 repo = backend_random.repo
431 repo = backend_random.repo
418 repo_group = user_util.create_repo_group()
432 repo_group = user_util.create_repo_group()
419
433
420 calculator = auth.PermissionCalculator(
434 calculator = auth.PermissionCalculator(
421 user.user_id, {}, False, False, True, 'higherwin')
435 user.user_id, {}, False, False, True, 'higherwin')
422 permissions = calculator._calculate_admin_permissions()
436 permissions = calculator._calculate_admin_permissions()
423
437
424 assert permissions['repositories_groups'][repo_group.group_name] == \
438 assert permissions['repositories_groups'][repo_group.group_name] == \
425 'group.admin'
439 'group.admin'
426 assert permissions['user_groups'][user_group.users_group_name] == \
440 assert permissions['user_groups'][user_group.users_group_name] == \
427 'usergroup.admin'
441 'usergroup.admin'
428 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
442 assert permissions['repositories'][repo.repo_name] == 'repository.admin'
429 assert 'hg.admin' in permissions['global']
443 assert 'hg.admin' in permissions['global']
430
444
431
445
432 def test_permission_calculator_repository_permissions_robustness_from_group(
446 def test_permission_calculator_repository_permissions_robustness_from_group(
433 user_util, backend_random):
447 user_util, backend_random):
434 user, user_group = user_util.create_user_with_group()
448 user, user_group = user_util.create_user_with_group()
435
449
436 RepoModel().grant_user_group_permission(
450 RepoModel().grant_user_group_permission(
437 backend_random.repo, user_group.users_group_name, 'repository.write')
451 backend_random.repo, user_group.users_group_name, 'repository.write')
438
452
439 calculator = auth.PermissionCalculator(
453 calculator = auth.PermissionCalculator(
440 user.user_id, {}, False, False, False, 'higherwin')
454 user.user_id, {}, False, False, False, 'higherwin')
441 calculator._calculate_repository_permissions()
455 calculator._calculate_repository_permissions()
442
456
443
457
444 def test_permission_calculator_repository_permissions_robustness_from_user(
458 def test_permission_calculator_repository_permissions_robustness_from_user(
445 user_util, backend_random):
459 user_util, backend_random):
446 user = user_util.create_user()
460 user = user_util.create_user()
447
461
448 RepoModel().grant_user_permission(
462 RepoModel().grant_user_permission(
449 backend_random.repo, user, 'repository.write')
463 backend_random.repo, user, 'repository.write')
464 Session().commit()
450
465
451 calculator = auth.PermissionCalculator(
466 calculator = auth.PermissionCalculator(
452 user.user_id, {}, False, False, False, 'higherwin')
467 user.user_id, {}, False, False, False, 'higherwin')
453 calculator._calculate_repository_permissions()
468 calculator._calculate_repository_permissions()
454
469
455
470
456 def test_permission_calculator_repo_group_permissions_robustness_from_group(
471 def test_permission_calculator_repo_group_permissions_robustness_from_group(
457 user_util, backend_random):
472 user_util, backend_random):
458 user, user_group = user_util.create_user_with_group()
473 user, user_group = user_util.create_user_with_group()
459 repo_group = user_util.create_repo_group()
474 repo_group = user_util.create_repo_group()
460
475
461 user_util.grant_user_group_permission_to_repo_group(
476 user_util.grant_user_group_permission_to_repo_group(
462 repo_group, user_group, 'group.write')
477 repo_group, user_group, 'group.write')
463
478
464 calculator = auth.PermissionCalculator(
479 calculator = auth.PermissionCalculator(
465 user.user_id, {}, False, False, False, 'higherwin')
480 user.user_id, {}, False, False, False, 'higherwin')
466 calculator._calculate_repository_group_permissions()
481 calculator._calculate_repository_group_permissions()
467
482
468
483
469 def test_permission_calculator_repo_group_permissions_robustness_from_user(
484 def test_permission_calculator_repo_group_permissions_robustness_from_user(
470 user_util, backend_random):
485 user_util, backend_random):
471 user = user_util.create_user()
486 user = user_util.create_user()
472 repo_group = user_util.create_repo_group()
487 repo_group = user_util.create_repo_group()
473
488
474 user_util.grant_user_permission_to_repo_group(
489 user_util.grant_user_permission_to_repo_group(
475 repo_group, user, 'group.write')
490 repo_group, user, 'group.write')
476
491
477 calculator = auth.PermissionCalculator(
492 calculator = auth.PermissionCalculator(
478 user.user_id, {}, False, False, False, 'higherwin')
493 user.user_id, {}, False, False, False, 'higherwin')
479 calculator._calculate_repository_group_permissions()
494 calculator._calculate_repository_group_permissions()
480
495
481
496
482 def test_permission_calculator_user_group_permissions_robustness_from_group(
497 def test_permission_calculator_user_group_permissions_robustness_from_group(
483 user_util, backend_random):
498 user_util, backend_random):
484 user, user_group = user_util.create_user_with_group()
499 user, user_group = user_util.create_user_with_group()
485 target_user_group = user_util.create_user_group()
500 target_user_group = user_util.create_user_group()
486
501
487 user_util.grant_user_group_permission_to_user_group(
502 user_util.grant_user_group_permission_to_user_group(
488 target_user_group, user_group, 'usergroup.write')
503 target_user_group, user_group, 'usergroup.write')
489
504
490 calculator = auth.PermissionCalculator(
505 calculator = auth.PermissionCalculator(
491 user.user_id, {}, False, False, False, 'higherwin')
506 user.user_id, {}, False, False, False, 'higherwin')
492 calculator._calculate_user_group_permissions()
507 calculator._calculate_user_group_permissions()
493
508
494
509
495 def test_permission_calculator_user_group_permissions_robustness_from_user(
510 def test_permission_calculator_user_group_permissions_robustness_from_user(
496 user_util, backend_random):
511 user_util, backend_random):
497 user = user_util.create_user()
512 user = user_util.create_user()
498 target_user_group = user_util.create_user_group()
513 target_user_group = user_util.create_user_group()
499
514
500 user_util.grant_user_permission_to_user_group(
515 user_util.grant_user_permission_to_user_group(
501 target_user_group, user, 'usergroup.write')
516 target_user_group, user, 'usergroup.write')
502
517
503 calculator = auth.PermissionCalculator(
518 calculator = auth.PermissionCalculator(
504 user.user_id, {}, False, False, False, 'higherwin')
519 user.user_id, {}, False, False, False, 'higherwin')
505 calculator._calculate_user_group_permissions()
520 calculator._calculate_user_group_permissions()
506
521
507
522
508 @pytest.mark.parametrize("algo, new_permission, old_permission, expected", [
523 @pytest.mark.parametrize("algo, new_permission, old_permission, expected", [
509 ('higherwin', 'repository.none', 'repository.none', 'repository.none'),
524 ('higherwin', 'repository.none', 'repository.none', 'repository.none'),
510 ('higherwin', 'repository.read', 'repository.none', 'repository.read'),
525 ('higherwin', 'repository.read', 'repository.none', 'repository.read'),
511 ('lowerwin', 'repository.write', 'repository.write', 'repository.write'),
526 ('lowerwin', 'repository.write', 'repository.write', 'repository.write'),
512 ('lowerwin', 'repository.read', 'repository.write', 'repository.read'),
527 ('lowerwin', 'repository.read', 'repository.write', 'repository.read'),
513 ])
528 ])
514 def test_permission_calculator_choose_permission(
529 def test_permission_calculator_choose_permission(
515 user_regular, algo, new_permission, old_permission, expected):
530 user_regular, algo, new_permission, old_permission, expected):
516 calculator = auth.PermissionCalculator(
531 calculator = auth.PermissionCalculator(
517 user_regular.user_id, {}, False, False, False, algo)
532 user_regular.user_id, {}, False, False, False, algo)
518 result = calculator._choose_permission(new_permission, old_permission)
533 result = calculator._choose_permission(new_permission, old_permission)
519 assert result == expected
534 assert result == expected
520
535
521
536
522 def test_permission_calculator_choose_permission_raises_on_wrong_algo(
537 def test_permission_calculator_choose_permission_raises_on_wrong_algo(
523 user_regular):
538 user_regular):
524 calculator = auth.PermissionCalculator(
539 calculator = auth.PermissionCalculator(
525 user_regular.user_id, {}, False, False, False, 'invalid')
540 user_regular.user_id, {}, False, False, False, 'invalid')
526 result = calculator._choose_permission(
541 result = calculator._choose_permission(
527 'repository.read', 'repository.read')
542 'repository.read', 'repository.read')
528 # TODO: johbo: This documents the existing behavior. Think of an
543 # TODO: johbo: This documents the existing behavior. Think of an
529 # improvement.
544 # improvement.
530 assert result is None
545 assert result is None
531
546
532
547
533 def test_auth_user_get_cookie_store_for_normal_user(user_util):
548 def test_auth_user_get_cookie_store_for_normal_user(user_util):
534 user = user_util.create_user()
549 user = user_util.create_user()
535 auth_user = auth.AuthUser(user_id=user.user_id)
550 auth_user = auth.AuthUser(user_id=user.user_id)
536 expected_data = {
551 expected_data = {
537 'username': user.username,
552 'username': user.username,
538 'user_id': user.user_id,
553 'user_id': user.user_id,
539 'password': md5(user.password),
554 'password': md5(user.password),
540 'is_authenticated': False
555 'is_authenticated': False
541 }
556 }
542 assert auth_user.get_cookie_store() == expected_data
557 assert auth_user.get_cookie_store() == expected_data
543
558
544
559
545 def test_auth_user_get_cookie_store_for_default_user():
560 def test_auth_user_get_cookie_store_for_default_user():
546 default_user = User.get_default_user()
561 default_user = User.get_default_user()
547 auth_user = auth.AuthUser()
562 auth_user = auth.AuthUser()
548 expected_data = {
563 expected_data = {
549 'username': User.DEFAULT_USER,
564 'username': User.DEFAULT_USER,
550 'user_id': default_user.user_id,
565 'user_id': default_user.user_id,
551 'password': md5(default_user.password),
566 'password': md5(default_user.password),
552 'is_authenticated': True
567 'is_authenticated': True
553 }
568 }
554 assert auth_user.get_cookie_store() == expected_data
569 assert auth_user.get_cookie_store() == expected_data
555
570
556
571
557 def get_permissions(user, **kwargs):
572 def get_permissions(user, **kwargs):
558 """
573 """
559 Utility filling in useful defaults into the call to `_cached_perms_data`.
574 Utility filling in useful defaults into the call to `_cached_perms_data`.
560
575
561 Fill in `**kwargs` if specific values are needed for a test.
576 Fill in `**kwargs` if specific values are needed for a test.
562 """
577 """
563 call_args = {
578 call_args = {
564 'user_id': user.user_id,
579 'user_id': user.user_id,
565 'scope': {},
580 'scope': {},
566 'user_is_admin': False,
581 'user_is_admin': False,
567 'user_inherit_default_permissions': False,
582 'user_inherit_default_permissions': False,
568 'explicit': False,
583 'explicit': False,
569 'algo': 'higherwin',
584 'algo': 'higherwin',
570 'calculate_super_admin': False,
585 'calculate_super_admin': False,
571 }
586 }
572 call_args.update(kwargs)
587 call_args.update(kwargs)
573 permissions = auth._cached_perms_data(**call_args)
588 permissions = auth._cached_perms_data(**call_args)
574 return permissions
589 return permissions
575
590
576
591
577 class TestGenerateAuthToken(object):
592 class TestGenerateAuthToken(object):
578 def test_salt_is_used_when_specified(self):
593 def test_salt_is_used_when_specified(self):
579 salt = 'abcde'
594 salt = 'abcde'
580 user_name = 'test_user'
595 user_name = 'test_user'
581 result = auth.generate_auth_token(user_name, salt)
596 result = auth.generate_auth_token(user_name, salt)
582 expected_result = sha1(user_name + salt).hexdigest()
597 expected_result = sha1(user_name + salt).hexdigest()
583 assert result == expected_result
598 assert result == expected_result
584
599
585 def test_salt_is_geneated_when_not_specified(self):
600 def test_salt_is_geneated_when_not_specified(self):
586 user_name = 'test_user'
601 user_name = 'test_user'
587 random_salt = os.urandom(16)
602 random_salt = os.urandom(16)
588 with patch.object(auth, 'os') as os_mock:
603 with patch.object(auth, 'os') as os_mock:
589 os_mock.urandom.return_value = random_salt
604 os_mock.urandom.return_value = random_salt
590 result = auth.generate_auth_token(user_name)
605 result = auth.generate_auth_token(user_name)
591 expected_result = sha1(user_name + random_salt).hexdigest()
606 expected_result = sha1(user_name + random_salt).hexdigest()
592 assert result == expected_result
607 assert result == expected_result
593
608
594
609
595 @pytest.mark.parametrize("test_token, test_roles, auth_result, expected_tokens", [
610 @pytest.mark.parametrize("test_token, test_roles, auth_result, expected_tokens", [
596 ('', None, False,
611 ('', None, False,
597 []),
612 []),
598 ('wrongtoken', None, False,
613 ('wrongtoken', None, False,
599 []),
614 []),
600 ('abracadabra_vcs', [AuthTokenModel.cls.ROLE_API], False,
615 ('abracadabra_vcs', [AuthTokenModel.cls.ROLE_API], False,
601 [('abracadabra_api', AuthTokenModel.cls.ROLE_API, -1)]),
616 [('abracadabra_api', AuthTokenModel.cls.ROLE_API, -1)]),
602 ('abracadabra_api', [AuthTokenModel.cls.ROLE_API], True,
617 ('abracadabra_api', [AuthTokenModel.cls.ROLE_API], True,
603 [('abracadabra_api', AuthTokenModel.cls.ROLE_API, -1)]),
618 [('abracadabra_api', AuthTokenModel.cls.ROLE_API, -1)]),
604 ('abracadabra_api', [AuthTokenModel.cls.ROLE_API], True,
619 ('abracadabra_api', [AuthTokenModel.cls.ROLE_API], True,
605 [('abracadabra_api', AuthTokenModel.cls.ROLE_API, -1),
620 [('abracadabra_api', AuthTokenModel.cls.ROLE_API, -1),
606 ('abracadabra_http', AuthTokenModel.cls.ROLE_HTTP, -1)]),
621 ('abracadabra_http', AuthTokenModel.cls.ROLE_HTTP, -1)]),
607 ])
622 ])
608 def test_auth_by_token(test_token, test_roles, auth_result, expected_tokens,
623 def test_auth_by_token(test_token, test_roles, auth_result, expected_tokens,
609 user_util):
624 user_util):
610 user = user_util.create_user()
625 user = user_util.create_user()
611 user_id = user.user_id
626 user_id = user.user_id
612 for token, role, expires in expected_tokens:
627 for token, role, expires in expected_tokens:
613 new_token = AuthTokenModel().create(user_id, 'test-token', expires, role)
628 new_token = AuthTokenModel().create(user_id, u'test-token', expires, role)
614 new_token.api_key = token # inject known name for testing...
629 new_token.api_key = token # inject known name for testing...
615
630
616 assert auth_result == user.authenticate_by_token(
631 assert auth_result == user.authenticate_by_token(
617 test_token, roles=test_roles)
632 test_token, roles=test_roles)
@@ -1,1080 +1,1143 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.lib.utils2 import str2bool
24 from rhodecode.lib.utils2 import str2bool
25 from rhodecode.model.meta import Session
25 from rhodecode.model.meta import Session
26 from rhodecode.model.settings import VcsSettingsModel, UiSetting
26 from rhodecode.model.settings import VcsSettingsModel, UiSetting
27
27
28
28
29 HOOKS_FORM_DATA = {
29 HOOKS_FORM_DATA = {
30 'hooks_changegroup_repo_size': True,
30 'hooks_changegroup_repo_size': True,
31 'hooks_changegroup_push_logger': True,
31 'hooks_changegroup_push_logger': True,
32 'hooks_outgoing_pull_logger': True
32 'hooks_outgoing_pull_logger': True
33 }
33 }
34
34
35 SVN_FORM_DATA = {
35 SVN_FORM_DATA = {
36 'new_svn_branch': 'test-branch',
36 'new_svn_branch': 'test-branch',
37 'new_svn_tag': 'test-tag'
37 'new_svn_tag': 'test-tag'
38 }
38 }
39
39
40 GENERAL_FORM_DATA = {
40 GENERAL_FORM_DATA = {
41 'rhodecode_pr_merge_enabled': True,
41 'rhodecode_pr_merge_enabled': True,
42 'rhodecode_use_outdated_comments': True,
42 'rhodecode_use_outdated_comments': True,
43 'rhodecode_hg_use_rebase_for_merging': True,
43 'rhodecode_hg_use_rebase_for_merging': True,
44 'rhodecode_hg_close_branch_before_merging': True,
44 'rhodecode_hg_close_branch_before_merging': True,
45 'rhodecode_git_use_rebase_for_merging': True,
45 'rhodecode_git_use_rebase_for_merging': True,
46 'rhodecode_git_close_branch_before_merging': True,
46 'rhodecode_git_close_branch_before_merging': True,
47 'rhodecode_diff_cache': True,
47 'rhodecode_diff_cache': True,
48 }
48 }
49
49
50
50
51 class TestInheritGlobalSettingsProperty(object):
51 class TestInheritGlobalSettingsProperty(object):
52 def test_get_raises_exception_when_repository_not_specified(self):
52 def test_get_raises_exception_when_repository_not_specified(self):
53 model = VcsSettingsModel()
53 model = VcsSettingsModel()
54 with pytest.raises(Exception) as exc_info:
54 with pytest.raises(Exception) as exc_info:
55 model.inherit_global_settings
55 model.inherit_global_settings
56 assert str(exc_info.value) == 'Repository is not specified'
56 assert str(exc_info.value) == 'Repository is not specified'
57
57
58 def test_true_is_returned_when_value_is_not_found(self, repo_stub):
58 def test_true_is_returned_when_value_is_not_found(self, repo_stub):
59 model = VcsSettingsModel(repo=repo_stub.repo_name)
59 model = VcsSettingsModel(repo=repo_stub.repo_name)
60 assert model.inherit_global_settings is True
60 assert model.inherit_global_settings is True
61
61
62 def test_value_is_returned(self, repo_stub, settings_util):
62 def test_value_is_returned(self, repo_stub, settings_util):
63 model = VcsSettingsModel(repo=repo_stub.repo_name)
63 model = VcsSettingsModel(repo=repo_stub.repo_name)
64 settings_util.create_repo_rhodecode_setting(
64 settings_util.create_repo_rhodecode_setting(
65 repo_stub, VcsSettingsModel.INHERIT_SETTINGS, False, 'bool')
65 repo_stub, VcsSettingsModel.INHERIT_SETTINGS, False, 'bool')
66 assert model.inherit_global_settings is False
66 assert model.inherit_global_settings is False
67
67
68 def test_value_is_set(self, repo_stub):
68 def test_value_is_set(self, repo_stub):
69 model = VcsSettingsModel(repo=repo_stub.repo_name)
69 model = VcsSettingsModel(repo=repo_stub.repo_name)
70 model.inherit_global_settings = False
70 model.inherit_global_settings = False
71 setting = model.repo_settings.get_setting_by_name(
71 setting = model.repo_settings.get_setting_by_name(
72 VcsSettingsModel.INHERIT_SETTINGS)
72 VcsSettingsModel.INHERIT_SETTINGS)
73 try:
73 try:
74 assert setting.app_settings_type == 'bool'
74 assert setting.app_settings_type == 'bool'
75 assert setting.app_settings_value is False
75 assert setting.app_settings_value is False
76 finally:
76 finally:
77 Session().delete(setting)
77 Session().delete(setting)
78 Session().commit()
78 Session().commit()
79
79
80 def test_set_raises_exception_when_repository_not_specified(self):
80 def test_set_raises_exception_when_repository_not_specified(self):
81 model = VcsSettingsModel()
81 model = VcsSettingsModel()
82 with pytest.raises(Exception) as exc_info:
82 with pytest.raises(Exception) as exc_info:
83 model.inherit_global_settings = False
83 model.inherit_global_settings = False
84 assert str(exc_info.value) == 'Repository is not specified'
84 assert str(exc_info.value) == 'Repository is not specified'
85
85
86
86
87 class TestVcsSettingsModel(object):
87 class TestVcsSettingsModel(object):
88 def test_global_svn_branch_patterns(self):
88 def test_global_svn_branch_patterns(self):
89 model = VcsSettingsModel()
89 model = VcsSettingsModel()
90 expected_result = {'test': 'test'}
90 expected_result = {'test': 'test'}
91 with mock.patch.object(model, 'global_settings') as settings_mock:
91 with mock.patch.object(model, 'global_settings') as settings_mock:
92 get_settings = settings_mock.get_ui_by_section
92 get_settings = settings_mock.get_ui_by_section
93 get_settings.return_value = expected_result
93 get_settings.return_value = expected_result
94 settings_mock.return_value = expected_result
94 settings_mock.return_value = expected_result
95 result = model.get_global_svn_branch_patterns()
95 result = model.get_global_svn_branch_patterns()
96
96
97 get_settings.assert_called_once_with(model.SVN_BRANCH_SECTION)
97 get_settings.assert_called_once_with(model.SVN_BRANCH_SECTION)
98 assert expected_result == result
98 assert expected_result == result
99
99
100 def test_repo_svn_branch_patterns(self):
100 def test_repo_svn_branch_patterns(self):
101 model = VcsSettingsModel()
101 model = VcsSettingsModel()
102 expected_result = {'test': 'test'}
102 expected_result = {'test': 'test'}
103 with mock.patch.object(model, 'repo_settings') as settings_mock:
103 with mock.patch.object(model, 'repo_settings') as settings_mock:
104 get_settings = settings_mock.get_ui_by_section
104 get_settings = settings_mock.get_ui_by_section
105 get_settings.return_value = expected_result
105 get_settings.return_value = expected_result
106 settings_mock.return_value = expected_result
106 settings_mock.return_value = expected_result
107 result = model.get_repo_svn_branch_patterns()
107 result = model.get_repo_svn_branch_patterns()
108
108
109 get_settings.assert_called_once_with(model.SVN_BRANCH_SECTION)
109 get_settings.assert_called_once_with(model.SVN_BRANCH_SECTION)
110 assert expected_result == result
110 assert expected_result == result
111
111
112 def test_repo_svn_branch_patterns_raises_exception_when_repo_is_not_set(
112 def test_repo_svn_branch_patterns_raises_exception_when_repo_is_not_set(
113 self):
113 self):
114 model = VcsSettingsModel()
114 model = VcsSettingsModel()
115 with pytest.raises(Exception) as exc_info:
115 with pytest.raises(Exception) as exc_info:
116 model.get_repo_svn_branch_patterns()
116 model.get_repo_svn_branch_patterns()
117 assert str(exc_info.value) == 'Repository is not specified'
117 assert str(exc_info.value) == 'Repository is not specified'
118
118
119 def test_global_svn_tag_patterns(self):
119 def test_global_svn_tag_patterns(self):
120 model = VcsSettingsModel()
120 model = VcsSettingsModel()
121 expected_result = {'test': 'test'}
121 expected_result = {'test': 'test'}
122 with mock.patch.object(model, 'global_settings') as settings_mock:
122 with mock.patch.object(model, 'global_settings') as settings_mock:
123 get_settings = settings_mock.get_ui_by_section
123 get_settings = settings_mock.get_ui_by_section
124 get_settings.return_value = expected_result
124 get_settings.return_value = expected_result
125 settings_mock.return_value = expected_result
125 settings_mock.return_value = expected_result
126 result = model.get_global_svn_tag_patterns()
126 result = model.get_global_svn_tag_patterns()
127
127
128 get_settings.assert_called_once_with(model.SVN_TAG_SECTION)
128 get_settings.assert_called_once_with(model.SVN_TAG_SECTION)
129 assert expected_result == result
129 assert expected_result == result
130
130
131 def test_repo_svn_tag_patterns(self):
131 def test_repo_svn_tag_patterns(self):
132 model = VcsSettingsModel()
132 model = VcsSettingsModel()
133 expected_result = {'test': 'test'}
133 expected_result = {'test': 'test'}
134 with mock.patch.object(model, 'repo_settings') as settings_mock:
134 with mock.patch.object(model, 'repo_settings') as settings_mock:
135 get_settings = settings_mock.get_ui_by_section
135 get_settings = settings_mock.get_ui_by_section
136 get_settings.return_value = expected_result
136 get_settings.return_value = expected_result
137 settings_mock.return_value = expected_result
137 settings_mock.return_value = expected_result
138 result = model.get_repo_svn_tag_patterns()
138 result = model.get_repo_svn_tag_patterns()
139
139
140 get_settings.assert_called_once_with(model.SVN_TAG_SECTION)
140 get_settings.assert_called_once_with(model.SVN_TAG_SECTION)
141 assert expected_result == result
141 assert expected_result == result
142
142
143 def test_repo_svn_tag_patterns_raises_exception_when_repo_is_not_set(self):
143 def test_repo_svn_tag_patterns_raises_exception_when_repo_is_not_set(self):
144 model = VcsSettingsModel()
144 model = VcsSettingsModel()
145 with pytest.raises(Exception) as exc_info:
145 with pytest.raises(Exception) as exc_info:
146 model.get_repo_svn_tag_patterns()
146 model.get_repo_svn_tag_patterns()
147 assert str(exc_info.value) == 'Repository is not specified'
147 assert str(exc_info.value) == 'Repository is not specified'
148
148
149 def test_get_global_settings(self):
149 def test_get_global_settings(self):
150 expected_result = {'test': 'test'}
150 expected_result = {'test': 'test'}
151 model = VcsSettingsModel()
151 model = VcsSettingsModel()
152 with mock.patch.object(model, '_collect_all_settings') as collect_mock:
152 with mock.patch.object(model, '_collect_all_settings') as collect_mock:
153 collect_mock.return_value = expected_result
153 collect_mock.return_value = expected_result
154 result = model.get_global_settings()
154 result = model.get_global_settings()
155
155
156 collect_mock.assert_called_once_with(global_=True)
156 collect_mock.assert_called_once_with(global_=True)
157 assert result == expected_result
157 assert result == expected_result
158
158
159 def test_get_repo_settings(self, repo_stub):
159 def test_get_repo_settings(self, repo_stub):
160 model = VcsSettingsModel(repo=repo_stub.repo_name)
160 model = VcsSettingsModel(repo=repo_stub.repo_name)
161 expected_result = {'test': 'test'}
161 expected_result = {'test': 'test'}
162 with mock.patch.object(model, '_collect_all_settings') as collect_mock:
162 with mock.patch.object(model, '_collect_all_settings') as collect_mock:
163 collect_mock.return_value = expected_result
163 collect_mock.return_value = expected_result
164 result = model.get_repo_settings()
164 result = model.get_repo_settings()
165
165
166 collect_mock.assert_called_once_with(global_=False)
166 collect_mock.assert_called_once_with(global_=False)
167 assert result == expected_result
167 assert result == expected_result
168
168
169 @pytest.mark.parametrize('settings, global_', [
169 @pytest.mark.parametrize('settings, global_', [
170 ('global_settings', True),
170 ('global_settings', True),
171 ('repo_settings', False)
171 ('repo_settings', False)
172 ])
172 ])
173 def test_collect_all_settings(self, settings, global_):
173 def test_collect_all_settings(self, settings, global_):
174 model = VcsSettingsModel()
174 model = VcsSettingsModel()
175 result_mock = self._mock_result()
175 result_mock = self._mock_result()
176
176
177 settings_patch = mock.patch.object(model, settings)
177 settings_patch = mock.patch.object(model, settings)
178 with settings_patch as settings_mock:
178 with settings_patch as settings_mock:
179 settings_mock.get_ui_by_section_and_key.return_value = result_mock
179 settings_mock.get_ui_by_section_and_key.return_value = result_mock
180 settings_mock.get_setting_by_name.return_value = result_mock
180 settings_mock.get_setting_by_name.return_value = result_mock
181 result = model._collect_all_settings(global_=global_)
181 result = model._collect_all_settings(global_=global_)
182
182
183 ui_settings = model.HG_SETTINGS + model.GIT_SETTINGS + model.HOOKS_SETTINGS
183 ui_settings = model.HG_SETTINGS + model.GIT_SETTINGS + model.HOOKS_SETTINGS
184 self._assert_get_settings_calls(
184 self._assert_get_settings_calls(
185 settings_mock, ui_settings, model.GENERAL_SETTINGS)
185 settings_mock, ui_settings, model.GENERAL_SETTINGS)
186 self._assert_collect_all_settings_result(
186 self._assert_collect_all_settings_result(
187 ui_settings, model.GENERAL_SETTINGS, result)
187 ui_settings, model.GENERAL_SETTINGS, result)
188
188
189 @pytest.mark.parametrize('settings, global_', [
189 @pytest.mark.parametrize('settings, global_', [
190 ('global_settings', True),
190 ('global_settings', True),
191 ('repo_settings', False)
191 ('repo_settings', False)
192 ])
192 ])
193 def test_collect_all_settings_without_empty_value(self, settings, global_):
193 def test_collect_all_settings_without_empty_value(self, settings, global_):
194 model = VcsSettingsModel()
194 model = VcsSettingsModel()
195
195
196 settings_patch = mock.patch.object(model, settings)
196 settings_patch = mock.patch.object(model, settings)
197 with settings_patch as settings_mock:
197 with settings_patch as settings_mock:
198 settings_mock.get_ui_by_section_and_key.return_value = None
198 settings_mock.get_ui_by_section_and_key.return_value = None
199 settings_mock.get_setting_by_name.return_value = None
199 settings_mock.get_setting_by_name.return_value = None
200 result = model._collect_all_settings(global_=global_)
200 result = model._collect_all_settings(global_=global_)
201
201
202 assert result == {}
202 assert result == {}
203
203
204 def _mock_result(self):
204 def _mock_result(self):
205 result_mock = mock.Mock()
205 result_mock = mock.Mock()
206 result_mock.ui_value = 'ui_value'
206 result_mock.ui_value = 'ui_value'
207 result_mock.ui_active = True
207 result_mock.ui_active = True
208 result_mock.app_settings_value = 'setting_value'
208 result_mock.app_settings_value = 'setting_value'
209 return result_mock
209 return result_mock
210
210
211 def _assert_get_settings_calls(
211 def _assert_get_settings_calls(
212 self, settings_mock, ui_settings, general_settings):
212 self, settings_mock, ui_settings, general_settings):
213 assert (
213 assert (
214 settings_mock.get_ui_by_section_and_key.call_count ==
214 settings_mock.get_ui_by_section_and_key.call_count ==
215 len(ui_settings))
215 len(ui_settings))
216 assert (
216 assert (
217 settings_mock.get_setting_by_name.call_count ==
217 settings_mock.get_setting_by_name.call_count ==
218 len(general_settings))
218 len(general_settings))
219
219
220 for section, key in ui_settings:
220 for section, key in ui_settings:
221 expected_call = mock.call(section, key)
221 expected_call = mock.call(section, key)
222 assert (
222 assert (
223 expected_call in
223 expected_call in
224 settings_mock.get_ui_by_section_and_key.call_args_list)
224 settings_mock.get_ui_by_section_and_key.call_args_list)
225
225
226 for name in general_settings:
226 for name in general_settings:
227 expected_call = mock.call(name)
227 expected_call = mock.call(name)
228 assert (
228 assert (
229 expected_call in
229 expected_call in
230 settings_mock.get_setting_by_name.call_args_list)
230 settings_mock.get_setting_by_name.call_args_list)
231
231
232 def _assert_collect_all_settings_result(
232 def _assert_collect_all_settings_result(
233 self, ui_settings, general_settings, result):
233 self, ui_settings, general_settings, result):
234 expected_result = {}
234 expected_result = {}
235 for section, key in ui_settings:
235 for section, key in ui_settings:
236 key = '{}_{}'.format(section, key.replace('.', '_'))
236 key = '{}_{}'.format(section, key.replace('.', '_'))
237
237
238 if section in ('extensions', 'hooks'):
238 if section in ('extensions', 'hooks'):
239 value = True
239 value = True
240 elif key in ['vcs_git_lfs_enabled']:
240 elif key in ['vcs_git_lfs_enabled']:
241 value = True
241 value = True
242 else:
242 else:
243 value = 'ui_value'
243 value = 'ui_value'
244 expected_result[key] = value
244 expected_result[key] = value
245
245
246 for name in general_settings:
246 for name in general_settings:
247 key = 'rhodecode_' + name
247 key = 'rhodecode_' + name
248 expected_result[key] = 'setting_value'
248 expected_result[key] = 'setting_value'
249
249
250 assert expected_result == result
250 assert expected_result == result
251
251
252
252
253 class TestCreateOrUpdateRepoHookSettings(object):
253 class TestCreateOrUpdateRepoHookSettings(object):
254 def test_create_when_no_repo_object_found(self, repo_stub):
254 def test_create_when_no_repo_object_found(self, repo_stub):
255 model = VcsSettingsModel(repo=repo_stub.repo_name)
255 model = VcsSettingsModel(repo=repo_stub.repo_name)
256
256
257 self._create_settings(model, HOOKS_FORM_DATA)
257 self._create_settings(model, HOOKS_FORM_DATA)
258
258
259 cleanup = []
259 cleanup = []
260 try:
260 try:
261 for section, key in model.HOOKS_SETTINGS:
261 for section, key in model.HOOKS_SETTINGS:
262 ui = model.repo_settings.get_ui_by_section_and_key(
262 ui = model.repo_settings.get_ui_by_section_and_key(
263 section, key)
263 section, key)
264 assert ui.ui_active is True
264 assert ui.ui_active is True
265 cleanup.append(ui)
265 cleanup.append(ui)
266 finally:
266 finally:
267 for ui in cleanup:
267 for ui in cleanup:
268 Session().delete(ui)
268 Session().delete(ui)
269 Session().commit()
269 Session().commit()
270
270
271 def test_create_raises_exception_when_data_incomplete(self, repo_stub):
271 def test_create_raises_exception_when_data_incomplete(self, repo_stub):
272 model = VcsSettingsModel(repo=repo_stub.repo_name)
272 model = VcsSettingsModel(repo=repo_stub.repo_name)
273
273
274 deleted_key = 'hooks_changegroup_repo_size'
274 deleted_key = 'hooks_changegroup_repo_size'
275 data = HOOKS_FORM_DATA.copy()
275 data = HOOKS_FORM_DATA.copy()
276 data.pop(deleted_key)
276 data.pop(deleted_key)
277
277
278 with pytest.raises(ValueError) as exc_info:
278 with pytest.raises(ValueError) as exc_info:
279 model.create_or_update_repo_hook_settings(data)
279 model.create_or_update_repo_hook_settings(data)
280 Session().commit()
281
280 msg = 'The given data does not contain {} key'.format(deleted_key)
282 msg = 'The given data does not contain {} key'.format(deleted_key)
281 assert str(exc_info.value) == msg
283 assert str(exc_info.value) == msg
282
284
283 def test_update_when_repo_object_found(self, repo_stub, settings_util):
285 def test_update_when_repo_object_found(self, repo_stub, settings_util):
284 model = VcsSettingsModel(repo=repo_stub.repo_name)
286 model = VcsSettingsModel(repo=repo_stub.repo_name)
285 for section, key in model.HOOKS_SETTINGS:
287 for section, key in model.HOOKS_SETTINGS:
286 settings_util.create_repo_rhodecode_ui(
288 settings_util.create_repo_rhodecode_ui(
287 repo_stub, section, None, key=key, active=False)
289 repo_stub, section, None, key=key, active=False)
288 model.create_or_update_repo_hook_settings(HOOKS_FORM_DATA)
290 model.create_or_update_repo_hook_settings(HOOKS_FORM_DATA)
291 Session().commit()
292
289 for section, key in model.HOOKS_SETTINGS:
293 for section, key in model.HOOKS_SETTINGS:
290 ui = model.repo_settings.get_ui_by_section_and_key(section, key)
294 ui = model.repo_settings.get_ui_by_section_and_key(section, key)
291 assert ui.ui_active is True
295 assert ui.ui_active is True
292
296
293 def _create_settings(self, model, data):
297 def _create_settings(self, model, data):
294 global_patch = mock.patch.object(model, 'global_settings')
298 global_patch = mock.patch.object(model, 'global_settings')
295 global_setting = mock.Mock()
299 global_setting = mock.Mock()
296 global_setting.ui_value = 'Test value'
300 global_setting.ui_value = 'Test value'
297 with global_patch as global_mock:
301 with global_patch as global_mock:
298 global_mock.get_ui_by_section_and_key.return_value = global_setting
302 global_mock.get_ui_by_section_and_key.return_value = global_setting
299 model.create_or_update_repo_hook_settings(HOOKS_FORM_DATA)
303 model.create_or_update_repo_hook_settings(HOOKS_FORM_DATA)
304 Session().commit()
300
305
301
306
302 class TestUpdateGlobalHookSettings(object):
307 class TestUpdateGlobalHookSettings(object):
303 def test_update_raises_exception_when_data_incomplete(self):
308 def test_update_raises_exception_when_data_incomplete(self):
304 model = VcsSettingsModel()
309 model = VcsSettingsModel()
305
310
306 deleted_key = 'hooks_changegroup_repo_size'
311 deleted_key = 'hooks_changegroup_repo_size'
307 data = HOOKS_FORM_DATA.copy()
312 data = HOOKS_FORM_DATA.copy()
308 data.pop(deleted_key)
313 data.pop(deleted_key)
309
314
310 with pytest.raises(ValueError) as exc_info:
315 with pytest.raises(ValueError) as exc_info:
311 model.update_global_hook_settings(data)
316 model.update_global_hook_settings(data)
317 Session().commit()
318
312 msg = 'The given data does not contain {} key'.format(deleted_key)
319 msg = 'The given data does not contain {} key'.format(deleted_key)
313 assert str(exc_info.value) == msg
320 assert str(exc_info.value) == msg
314
321
315 def test_update_global_hook_settings(self, settings_util):
322 def test_update_global_hook_settings(self, settings_util):
316 model = VcsSettingsModel()
323 model = VcsSettingsModel()
317 setting_mock = mock.MagicMock()
324 setting_mock = mock.MagicMock()
318 setting_mock.ui_active = False
325 setting_mock.ui_active = False
319 get_settings_patcher = mock.patch.object(
326 get_settings_patcher = mock.patch.object(
320 model.global_settings, 'get_ui_by_section_and_key',
327 model.global_settings, 'get_ui_by_section_and_key',
321 return_value=setting_mock)
328 return_value=setting_mock)
322 session_patcher = mock.patch('rhodecode.model.settings.Session')
329 session_patcher = mock.patch('rhodecode.model.settings.Session')
323 with get_settings_patcher as get_settings_mock, session_patcher:
330 with get_settings_patcher as get_settings_mock, session_patcher:
324 model.update_global_hook_settings(HOOKS_FORM_DATA)
331 model.update_global_hook_settings(HOOKS_FORM_DATA)
332 Session().commit()
333
325 assert setting_mock.ui_active is True
334 assert setting_mock.ui_active is True
326 assert get_settings_mock.call_count == 3
335 assert get_settings_mock.call_count == 3
327
336
328
337
329 class TestCreateOrUpdateRepoGeneralSettings(object):
338 class TestCreateOrUpdateRepoGeneralSettings(object):
330 def test_calls_create_or_update_general_settings(self, repo_stub):
339 def test_calls_create_or_update_general_settings(self, repo_stub):
331 model = VcsSettingsModel(repo=repo_stub.repo_name)
340 model = VcsSettingsModel(repo=repo_stub.repo_name)
332 create_patch = mock.patch.object(
341 create_patch = mock.patch.object(
333 model, '_create_or_update_general_settings')
342 model, '_create_or_update_general_settings')
334 with create_patch as create_mock:
343 with create_patch as create_mock:
335 model.create_or_update_repo_pr_settings(GENERAL_FORM_DATA)
344 model.create_or_update_repo_pr_settings(GENERAL_FORM_DATA)
345 Session().commit()
346
336 create_mock.assert_called_once_with(
347 create_mock.assert_called_once_with(
337 model.repo_settings, GENERAL_FORM_DATA)
348 model.repo_settings, GENERAL_FORM_DATA)
338
349
339 def test_raises_exception_when_repository_is_not_specified(self):
350 def test_raises_exception_when_repository_is_not_specified(self):
340 model = VcsSettingsModel()
351 model = VcsSettingsModel()
341 with pytest.raises(Exception) as exc_info:
352 with pytest.raises(Exception) as exc_info:
342 model.create_or_update_repo_pr_settings(GENERAL_FORM_DATA)
353 model.create_or_update_repo_pr_settings(GENERAL_FORM_DATA)
343 assert str(exc_info.value) == 'Repository is not specified'
354 assert str(exc_info.value) == 'Repository is not specified'
344
355
345
356
346 class TestCreateOrUpdatGlobalGeneralSettings(object):
357 class TestCreateOrUpdatGlobalGeneralSettings(object):
347 def test_calls_create_or_update_general_settings(self):
358 def test_calls_create_or_update_general_settings(self):
348 model = VcsSettingsModel()
359 model = VcsSettingsModel()
349 create_patch = mock.patch.object(
360 create_patch = mock.patch.object(
350 model, '_create_or_update_general_settings')
361 model, '_create_or_update_general_settings')
351 with create_patch as create_mock:
362 with create_patch as create_mock:
352 model.create_or_update_global_pr_settings(GENERAL_FORM_DATA)
363 model.create_or_update_global_pr_settings(GENERAL_FORM_DATA)
353 create_mock.assert_called_once_with(
364 create_mock.assert_called_once_with(
354 model.global_settings, GENERAL_FORM_DATA)
365 model.global_settings, GENERAL_FORM_DATA)
355
366
356
367
357 class TestCreateOrUpdateGeneralSettings(object):
368 class TestCreateOrUpdateGeneralSettings(object):
358 def test_create_when_no_repo_settings_found(self, repo_stub):
369 def test_create_when_no_repo_settings_found(self, repo_stub):
359 model = VcsSettingsModel(repo=repo_stub.repo_name)
370 model = VcsSettingsModel(repo=repo_stub.repo_name)
360 model._create_or_update_general_settings(
371 model._create_or_update_general_settings(
361 model.repo_settings, GENERAL_FORM_DATA)
372 model.repo_settings, GENERAL_FORM_DATA)
362
373
363 cleanup = []
374 cleanup = []
364 try:
375 try:
365 for name in model.GENERAL_SETTINGS:
376 for name in model.GENERAL_SETTINGS:
366 setting = model.repo_settings.get_setting_by_name(name)
377 setting = model.repo_settings.get_setting_by_name(name)
367 assert setting.app_settings_value is True
378 assert setting.app_settings_value is True
368 cleanup.append(setting)
379 cleanup.append(setting)
369 finally:
380 finally:
370 for setting in cleanup:
381 for setting in cleanup:
371 Session().delete(setting)
382 Session().delete(setting)
372 Session().commit()
383 Session().commit()
373
384
374 def test_create_raises_exception_when_data_incomplete(self, repo_stub):
385 def test_create_raises_exception_when_data_incomplete(self, repo_stub):
375 model = VcsSettingsModel(repo=repo_stub.repo_name)
386 model = VcsSettingsModel(repo=repo_stub.repo_name)
376
387
377 deleted_key = 'rhodecode_pr_merge_enabled'
388 deleted_key = 'rhodecode_pr_merge_enabled'
378 data = GENERAL_FORM_DATA.copy()
389 data = GENERAL_FORM_DATA.copy()
379 data.pop(deleted_key)
390 data.pop(deleted_key)
380
391
381 with pytest.raises(ValueError) as exc_info:
392 with pytest.raises(ValueError) as exc_info:
382 model._create_or_update_general_settings(model.repo_settings, data)
393 model._create_or_update_general_settings(model.repo_settings, data)
394 Session().commit()
383
395
384 msg = 'The given data does not contain {} key'.format(deleted_key)
396 msg = 'The given data does not contain {} key'.format(deleted_key)
385 assert str(exc_info.value) == msg
397 assert str(exc_info.value) == msg
386
398
387 def test_update_when_repo_setting_found(self, repo_stub, settings_util):
399 def test_update_when_repo_setting_found(self, repo_stub, settings_util):
388 model = VcsSettingsModel(repo=repo_stub.repo_name)
400 model = VcsSettingsModel(repo=repo_stub.repo_name)
389 for name in model.GENERAL_SETTINGS:
401 for name in model.GENERAL_SETTINGS:
390 settings_util.create_repo_rhodecode_setting(
402 settings_util.create_repo_rhodecode_setting(
391 repo_stub, name, False, 'bool')
403 repo_stub, name, False, 'bool')
392
404
393 model._create_or_update_general_settings(
405 model._create_or_update_general_settings(
394 model.repo_settings, GENERAL_FORM_DATA)
406 model.repo_settings, GENERAL_FORM_DATA)
407 Session().commit()
395
408
396 for name in model.GENERAL_SETTINGS:
409 for name in model.GENERAL_SETTINGS:
397 setting = model.repo_settings.get_setting_by_name(name)
410 setting = model.repo_settings.get_setting_by_name(name)
398 assert setting.app_settings_value is True
411 assert setting.app_settings_value is True
399
412
400
413
401 class TestCreateRepoSvnSettings(object):
414 class TestCreateRepoSvnSettings(object):
402 def test_calls_create_svn_settings(self, repo_stub):
415 def test_calls_create_svn_settings(self, repo_stub):
403 model = VcsSettingsModel(repo=repo_stub.repo_name)
416 model = VcsSettingsModel(repo=repo_stub.repo_name)
404 with mock.patch.object(model, '_create_svn_settings') as create_mock:
417 with mock.patch.object(model, '_create_svn_settings') as create_mock:
405 model.create_repo_svn_settings(SVN_FORM_DATA)
418 model.create_repo_svn_settings(SVN_FORM_DATA)
419 Session().commit()
420
406 create_mock.assert_called_once_with(model.repo_settings, SVN_FORM_DATA)
421 create_mock.assert_called_once_with(model.repo_settings, SVN_FORM_DATA)
407
422
408 def test_raises_exception_when_repository_is_not_specified(self):
423 def test_raises_exception_when_repository_is_not_specified(self):
409 model = VcsSettingsModel()
424 model = VcsSettingsModel()
410 with pytest.raises(Exception) as exc_info:
425 with pytest.raises(Exception) as exc_info:
411 model.create_repo_svn_settings(SVN_FORM_DATA)
426 model.create_repo_svn_settings(SVN_FORM_DATA)
427 Session().commit()
428
412 assert str(exc_info.value) == 'Repository is not specified'
429 assert str(exc_info.value) == 'Repository is not specified'
413
430
414
431
415 class TestCreateSvnSettings(object):
432 class TestCreateSvnSettings(object):
416 def test_create(self, repo_stub):
433 def test_create(self, repo_stub):
417 model = VcsSettingsModel(repo=repo_stub.repo_name)
434 model = VcsSettingsModel(repo=repo_stub.repo_name)
418 model._create_svn_settings(model.repo_settings, SVN_FORM_DATA)
435 model._create_svn_settings(model.repo_settings, SVN_FORM_DATA)
419 Session().commit()
436 Session().commit()
420
437
421 branch_ui = model.repo_settings.get_ui_by_section(
438 branch_ui = model.repo_settings.get_ui_by_section(
422 model.SVN_BRANCH_SECTION)
439 model.SVN_BRANCH_SECTION)
423 tag_ui = model.repo_settings.get_ui_by_section(
440 tag_ui = model.repo_settings.get_ui_by_section(
424 model.SVN_TAG_SECTION)
441 model.SVN_TAG_SECTION)
425
442
426 try:
443 try:
427 assert len(branch_ui) == 1
444 assert len(branch_ui) == 1
428 assert len(tag_ui) == 1
445 assert len(tag_ui) == 1
429 finally:
446 finally:
430 Session().delete(branch_ui[0])
447 Session().delete(branch_ui[0])
431 Session().delete(tag_ui[0])
448 Session().delete(tag_ui[0])
432 Session().commit()
449 Session().commit()
433
450
434 def test_create_tag(self, repo_stub):
451 def test_create_tag(self, repo_stub):
435 model = VcsSettingsModel(repo=repo_stub.repo_name)
452 model = VcsSettingsModel(repo=repo_stub.repo_name)
436 data = SVN_FORM_DATA.copy()
453 data = SVN_FORM_DATA.copy()
437 data.pop('new_svn_branch')
454 data.pop('new_svn_branch')
438 model._create_svn_settings(model.repo_settings, data)
455 model._create_svn_settings(model.repo_settings, data)
439 Session().commit()
456 Session().commit()
440
457
441 branch_ui = model.repo_settings.get_ui_by_section(
458 branch_ui = model.repo_settings.get_ui_by_section(
442 model.SVN_BRANCH_SECTION)
459 model.SVN_BRANCH_SECTION)
443 tag_ui = model.repo_settings.get_ui_by_section(
460 tag_ui = model.repo_settings.get_ui_by_section(
444 model.SVN_TAG_SECTION)
461 model.SVN_TAG_SECTION)
445
462
446 try:
463 try:
447 assert len(branch_ui) == 0
464 assert len(branch_ui) == 0
448 assert len(tag_ui) == 1
465 assert len(tag_ui) == 1
449 finally:
466 finally:
450 Session().delete(tag_ui[0])
467 Session().delete(tag_ui[0])
451 Session().commit()
468 Session().commit()
452
469
453 def test_create_nothing_when_no_svn_settings_specified(self, repo_stub):
470 def test_create_nothing_when_no_svn_settings_specified(self, repo_stub):
454 model = VcsSettingsModel(repo=repo_stub.repo_name)
471 model = VcsSettingsModel(repo=repo_stub.repo_name)
455 model._create_svn_settings(model.repo_settings, {})
472 model._create_svn_settings(model.repo_settings, {})
456 Session().commit()
473 Session().commit()
457
474
458 branch_ui = model.repo_settings.get_ui_by_section(
475 branch_ui = model.repo_settings.get_ui_by_section(
459 model.SVN_BRANCH_SECTION)
476 model.SVN_BRANCH_SECTION)
460 tag_ui = model.repo_settings.get_ui_by_section(
477 tag_ui = model.repo_settings.get_ui_by_section(
461 model.SVN_TAG_SECTION)
478 model.SVN_TAG_SECTION)
462
479
463 assert len(branch_ui) == 0
480 assert len(branch_ui) == 0
464 assert len(tag_ui) == 0
481 assert len(tag_ui) == 0
465
482
466 def test_create_nothing_when_empty_settings_specified(self, repo_stub):
483 def test_create_nothing_when_empty_settings_specified(self, repo_stub):
467 model = VcsSettingsModel(repo=repo_stub.repo_name)
484 model = VcsSettingsModel(repo=repo_stub.repo_name)
468 data = {
485 data = {
469 'new_svn_branch': '',
486 'new_svn_branch': '',
470 'new_svn_tag': ''
487 'new_svn_tag': ''
471 }
488 }
472 model._create_svn_settings(model.repo_settings, data)
489 model._create_svn_settings(model.repo_settings, data)
473 Session().commit()
490 Session().commit()
474
491
475 branch_ui = model.repo_settings.get_ui_by_section(
492 branch_ui = model.repo_settings.get_ui_by_section(
476 model.SVN_BRANCH_SECTION)
493 model.SVN_BRANCH_SECTION)
477 tag_ui = model.repo_settings.get_ui_by_section(
494 tag_ui = model.repo_settings.get_ui_by_section(
478 model.SVN_TAG_SECTION)
495 model.SVN_TAG_SECTION)
479
496
480 assert len(branch_ui) == 0
497 assert len(branch_ui) == 0
481 assert len(tag_ui) == 0
498 assert len(tag_ui) == 0
482
499
483
500
484 class TestCreateOrUpdateUi(object):
501 class TestCreateOrUpdateUi(object):
485 def test_create(self, repo_stub):
502 def test_create(self, repo_stub):
486 model = VcsSettingsModel(repo=repo_stub.repo_name)
503 model = VcsSettingsModel(repo=repo_stub.repo_name)
487 model._create_or_update_ui(
504 model._create_or_update_ui(
488 model.repo_settings, 'test-section', 'test-key', active=False,
505 model.repo_settings, 'test-section', 'test-key', active=False,
489 value='False')
506 value='False')
490 Session().commit()
507 Session().commit()
491
508
492 created_ui = model.repo_settings.get_ui_by_section_and_key(
509 created_ui = model.repo_settings.get_ui_by_section_and_key(
493 'test-section', 'test-key')
510 'test-section', 'test-key')
494
511
495 try:
512 try:
496 assert created_ui.ui_active is False
513 assert created_ui.ui_active is False
497 assert str2bool(created_ui.ui_value) is False
514 assert str2bool(created_ui.ui_value) is False
498 finally:
515 finally:
499 Session().delete(created_ui)
516 Session().delete(created_ui)
500 Session().commit()
517 Session().commit()
501
518
502 def test_update(self, repo_stub, settings_util):
519 def test_update(self, repo_stub, settings_util):
503 model = VcsSettingsModel(repo=repo_stub.repo_name)
520 model = VcsSettingsModel(repo=repo_stub.repo_name)
504 # care about only 3 first settings
521 # care about only 3 first settings
505 largefiles, phases, evolve = model.HG_SETTINGS[:3]
522 largefiles, phases, evolve = model.HG_SETTINGS[:3]
506
523
507 section = 'test-section'
524 section = 'test-section'
508 key = 'test-key'
525 key = 'test-key'
509 settings_util.create_repo_rhodecode_ui(
526 settings_util.create_repo_rhodecode_ui(
510 repo_stub, section, 'True', key=key, active=True)
527 repo_stub, section, 'True', key=key, active=True)
511
528
512 model._create_or_update_ui(
529 model._create_or_update_ui(
513 model.repo_settings, section, key, active=False, value='False')
530 model.repo_settings, section, key, active=False, value='False')
514 Session().commit()
531 Session().commit()
515
532
516 created_ui = model.repo_settings.get_ui_by_section_and_key(
533 created_ui = model.repo_settings.get_ui_by_section_and_key(
517 section, key)
534 section, key)
518 assert created_ui.ui_active is False
535 assert created_ui.ui_active is False
519 assert str2bool(created_ui.ui_value) is False
536 assert str2bool(created_ui.ui_value) is False
520
537
521
538
522 class TestCreateOrUpdateRepoHgSettings(object):
539 class TestCreateOrUpdateRepoHgSettings(object):
523 FORM_DATA = {
540 FORM_DATA = {
524 'extensions_largefiles': False,
541 'extensions_largefiles': False,
525 'extensions_evolve': False,
542 'extensions_evolve': False,
526 'phases_publish': False
543 'phases_publish': False
527 }
544 }
528
545
529 def test_creates_repo_hg_settings_when_data_is_correct(self, repo_stub):
546 def test_creates_repo_hg_settings_when_data_is_correct(self, repo_stub):
530 model = VcsSettingsModel(repo=repo_stub.repo_name)
547 model = VcsSettingsModel(repo=repo_stub.repo_name)
531 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
548 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
532 model.create_or_update_repo_hg_settings(self.FORM_DATA)
549 model.create_or_update_repo_hg_settings(self.FORM_DATA)
533 expected_calls = [
550 expected_calls = [
534 mock.call(model.repo_settings, 'extensions', 'largefiles', active=False, value=''),
551 mock.call(model.repo_settings, 'extensions', 'largefiles', active=False, value=''),
535 mock.call(model.repo_settings, 'extensions', 'evolve', active=False, value=''),
552 mock.call(model.repo_settings, 'extensions', 'evolve', active=False, value=''),
536 mock.call(model.repo_settings, 'experimental', 'evolution', active=False, value=''),
553 mock.call(model.repo_settings, 'experimental', 'evolution', active=False, value=''),
537 mock.call(model.repo_settings, 'experimental', 'evolution.exchange', active=False, value='no'),
554 mock.call(model.repo_settings, 'experimental', 'evolution.exchange', active=False, value='no'),
538 mock.call(model.repo_settings, 'extensions', 'topic', active=False, value=''),
555 mock.call(model.repo_settings, 'extensions', 'topic', active=False, value=''),
539 mock.call(model.repo_settings, 'phases', 'publish', value='False'),
556 mock.call(model.repo_settings, 'phases', 'publish', value='False'),
540 ]
557 ]
541 assert expected_calls == create_mock.call_args_list
558 assert expected_calls == create_mock.call_args_list
542
559
543 @pytest.mark.parametrize('field_to_remove', FORM_DATA.keys())
560 @pytest.mark.parametrize('field_to_remove', FORM_DATA.keys())
544 def test_key_is_not_found(self, repo_stub, field_to_remove):
561 def test_key_is_not_found(self, repo_stub, field_to_remove):
545 model = VcsSettingsModel(repo=repo_stub.repo_name)
562 model = VcsSettingsModel(repo=repo_stub.repo_name)
546 data = self.FORM_DATA.copy()
563 data = self.FORM_DATA.copy()
547 data.pop(field_to_remove)
564 data.pop(field_to_remove)
548 with pytest.raises(ValueError) as exc_info:
565 with pytest.raises(ValueError) as exc_info:
549 model.create_or_update_repo_hg_settings(data)
566 model.create_or_update_repo_hg_settings(data)
567 Session().commit()
568
550 expected_message = 'The given data does not contain {} key'.format(
569 expected_message = 'The given data does not contain {} key'.format(
551 field_to_remove)
570 field_to_remove)
552 assert str(exc_info.value) == expected_message
571 assert str(exc_info.value) == expected_message
553
572
554 def test_create_raises_exception_when_repository_not_specified(self):
573 def test_create_raises_exception_when_repository_not_specified(self):
555 model = VcsSettingsModel()
574 model = VcsSettingsModel()
556 with pytest.raises(Exception) as exc_info:
575 with pytest.raises(Exception) as exc_info:
557 model.create_or_update_repo_hg_settings(self.FORM_DATA)
576 model.create_or_update_repo_hg_settings(self.FORM_DATA)
577 Session().commit()
578
558 assert str(exc_info.value) == 'Repository is not specified'
579 assert str(exc_info.value) == 'Repository is not specified'
559
580
560
581
561 class TestUpdateGlobalSslSetting(object):
582 class TestUpdateGlobalSslSetting(object):
562 def test_updates_global_hg_settings(self):
583 def test_updates_global_hg_settings(self):
563 model = VcsSettingsModel()
584 model = VcsSettingsModel()
564 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
585 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
565 model.update_global_ssl_setting('False')
586 model.update_global_ssl_setting('False')
587 Session().commit()
588
566 create_mock.assert_called_once_with(
589 create_mock.assert_called_once_with(
567 model.global_settings, 'web', 'push_ssl', value='False')
590 model.global_settings, 'web', 'push_ssl', value='False')
568
591
569
592
570 class TestUpdateGlobalPathSetting(object):
593 class TestUpdateGlobalPathSetting(object):
571 def test_updates_global_path_settings(self):
594 def test_updates_global_path_settings(self):
572 model = VcsSettingsModel()
595 model = VcsSettingsModel()
573 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
596 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
574 model.update_global_path_setting('False')
597 model.update_global_path_setting('False')
598 Session().commit()
599
575 create_mock.assert_called_once_with(
600 create_mock.assert_called_once_with(
576 model.global_settings, 'paths', '/', value='False')
601 model.global_settings, 'paths', '/', value='False')
577
602
578
603
579 class TestCreateOrUpdateGlobalHgSettings(object):
604 class TestCreateOrUpdateGlobalHgSettings(object):
580 FORM_DATA = {
605 FORM_DATA = {
581 'extensions_largefiles': False,
606 'extensions_largefiles': False,
582 'largefiles_usercache': '/example/largefiles-store',
607 'largefiles_usercache': '/example/largefiles-store',
583 'phases_publish': False,
608 'phases_publish': False,
584 'extensions_hgsubversion': False,
609 'extensions_hgsubversion': False,
585 'extensions_evolve': False
610 'extensions_evolve': False
586 }
611 }
587
612
588 def test_creates_repo_hg_settings_when_data_is_correct(self):
613 def test_creates_repo_hg_settings_when_data_is_correct(self):
589 model = VcsSettingsModel()
614 model = VcsSettingsModel()
590 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
615 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
591 model.create_or_update_global_hg_settings(self.FORM_DATA)
616 model.create_or_update_global_hg_settings(self.FORM_DATA)
617 Session().commit()
618
592 expected_calls = [
619 expected_calls = [
593 mock.call(model.global_settings, 'extensions', 'largefiles', active=False, value=''),
620 mock.call(model.global_settings, 'extensions', 'largefiles', active=False, value=''),
594 mock.call(model.global_settings, 'largefiles', 'usercache', value='/example/largefiles-store'),
621 mock.call(model.global_settings, 'largefiles', 'usercache', value='/example/largefiles-store'),
595 mock.call(model.global_settings, 'phases', 'publish', value='False'),
622 mock.call(model.global_settings, 'phases', 'publish', value='False'),
596 mock.call(model.global_settings, 'extensions', 'hgsubversion', active=False),
623 mock.call(model.global_settings, 'extensions', 'hgsubversion', active=False),
597 mock.call(model.global_settings, 'extensions', 'evolve', active=False, value=''),
624 mock.call(model.global_settings, 'extensions', 'evolve', active=False, value=''),
598 mock.call(model.global_settings, 'experimental', 'evolution', active=False, value=''),
625 mock.call(model.global_settings, 'experimental', 'evolution', active=False, value=''),
599 mock.call(model.global_settings, 'experimental', 'evolution.exchange', active=False, value='no'),
626 mock.call(model.global_settings, 'experimental', 'evolution.exchange', active=False, value='no'),
600 mock.call(model.global_settings, 'extensions', 'topic', active=False, value=''),
627 mock.call(model.global_settings, 'extensions', 'topic', active=False, value=''),
601 ]
628 ]
602
629
603 assert expected_calls == create_mock.call_args_list
630 assert expected_calls == create_mock.call_args_list
604
631
605 @pytest.mark.parametrize('field_to_remove', FORM_DATA.keys())
632 @pytest.mark.parametrize('field_to_remove', FORM_DATA.keys())
606 def test_key_is_not_found(self, repo_stub, field_to_remove):
633 def test_key_is_not_found(self, repo_stub, field_to_remove):
607 model = VcsSettingsModel(repo=repo_stub.repo_name)
634 model = VcsSettingsModel(repo=repo_stub.repo_name)
608 data = self.FORM_DATA.copy()
635 data = self.FORM_DATA.copy()
609 data.pop(field_to_remove)
636 data.pop(field_to_remove)
610 with pytest.raises(Exception) as exc_info:
637 with pytest.raises(Exception) as exc_info:
611 model.create_or_update_global_hg_settings(data)
638 model.create_or_update_global_hg_settings(data)
639 Session().commit()
640
612 expected_message = 'The given data does not contain {} key'.format(
641 expected_message = 'The given data does not contain {} key'.format(
613 field_to_remove)
642 field_to_remove)
614 assert str(exc_info.value) == expected_message
643 assert str(exc_info.value) == expected_message
615
644
616
645
617 class TestCreateOrUpdateGlobalGitSettings(object):
646 class TestCreateOrUpdateGlobalGitSettings(object):
618 FORM_DATA = {
647 FORM_DATA = {
619 'vcs_git_lfs_enabled': False,
648 'vcs_git_lfs_enabled': False,
620 'vcs_git_lfs_store_location': '/example/lfs-store',
649 'vcs_git_lfs_store_location': '/example/lfs-store',
621 }
650 }
622
651
623 def test_creates_repo_hg_settings_when_data_is_correct(self):
652 def test_creates_repo_hg_settings_when_data_is_correct(self):
624 model = VcsSettingsModel()
653 model = VcsSettingsModel()
625 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
654 with mock.patch.object(model, '_create_or_update_ui') as create_mock:
626 model.create_or_update_global_git_settings(self.FORM_DATA)
655 model.create_or_update_global_git_settings(self.FORM_DATA)
656 Session().commit()
657
627 expected_calls = [
658 expected_calls = [
628 mock.call(model.global_settings, 'vcs_git_lfs', 'enabled', active=False, value=False),
659 mock.call(model.global_settings, 'vcs_git_lfs', 'enabled', active=False, value=False),
629 mock.call(model.global_settings, 'vcs_git_lfs', 'store_location', value='/example/lfs-store'),
660 mock.call(model.global_settings, 'vcs_git_lfs', 'store_location', value='/example/lfs-store'),
630 ]
661 ]
631 assert expected_calls == create_mock.call_args_list
662 assert expected_calls == create_mock.call_args_list
632
663
633
664
634 class TestDeleteRepoSvnPattern(object):
665 class TestDeleteRepoSvnPattern(object):
635 def test_success_when_repo_is_set(self, backend_svn, settings_util):
666 def test_success_when_repo_is_set(self, backend_svn, settings_util):
636 repo = backend_svn.create_repo()
667 repo = backend_svn.create_repo()
637 repo_name = repo.repo_name
668 repo_name = repo.repo_name
638
669
639 model = VcsSettingsModel(repo=repo_name)
670 model = VcsSettingsModel(repo=repo_name)
640 entry = settings_util.create_repo_rhodecode_ui(
671 entry = settings_util.create_repo_rhodecode_ui(
641 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'svn-branch')
672 repo, VcsSettingsModel.SVN_BRANCH_SECTION, 'svn-branch')
642 Session().commit()
673 Session().commit()
643
674
644 model.delete_repo_svn_pattern(entry.ui_id)
675 model.delete_repo_svn_pattern(entry.ui_id)
645
676
646 def test_fail_when_delete_id_from_other_repo(self, backend_svn):
677 def test_fail_when_delete_id_from_other_repo(self, backend_svn):
647 repo_name = backend_svn.repo_name
678 repo_name = backend_svn.repo_name
648 model = VcsSettingsModel(repo=repo_name)
679 model = VcsSettingsModel(repo=repo_name)
649 delete_ui_patch = mock.patch.object(model.repo_settings, 'delete_ui')
680 delete_ui_patch = mock.patch.object(model.repo_settings, 'delete_ui')
650 with delete_ui_patch as delete_ui_mock:
681 with delete_ui_patch as delete_ui_mock:
651 model.delete_repo_svn_pattern(123)
682 model.delete_repo_svn_pattern(123)
683 Session().commit()
684
652 delete_ui_mock.assert_called_once_with(-1)
685 delete_ui_mock.assert_called_once_with(-1)
653
686
654 def test_raises_exception_when_repository_is_not_specified(self):
687 def test_raises_exception_when_repository_is_not_specified(self):
655 model = VcsSettingsModel()
688 model = VcsSettingsModel()
656 with pytest.raises(Exception) as exc_info:
689 with pytest.raises(Exception) as exc_info:
657 model.delete_repo_svn_pattern(123)
690 model.delete_repo_svn_pattern(123)
658 assert str(exc_info.value) == 'Repository is not specified'
691 assert str(exc_info.value) == 'Repository is not specified'
659
692
660
693
661 class TestDeleteGlobalSvnPattern(object):
694 class TestDeleteGlobalSvnPattern(object):
662 def test_delete_global_svn_pattern_calls_delete_ui(self):
695 def test_delete_global_svn_pattern_calls_delete_ui(self):
663 model = VcsSettingsModel()
696 model = VcsSettingsModel()
664 delete_ui_patch = mock.patch.object(model.global_settings, 'delete_ui')
697 delete_ui_patch = mock.patch.object(model.global_settings, 'delete_ui')
665 with delete_ui_patch as delete_ui_mock:
698 with delete_ui_patch as delete_ui_mock:
666 model.delete_global_svn_pattern(123)
699 model.delete_global_svn_pattern(123)
667 delete_ui_mock.assert_called_once_with(123)
700 delete_ui_mock.assert_called_once_with(123)
668
701
669
702
670 class TestFilterUiSettings(object):
703 class TestFilterUiSettings(object):
671 def test_settings_are_filtered(self):
704 def test_settings_are_filtered(self):
672 model = VcsSettingsModel()
705 model = VcsSettingsModel()
673 repo_settings = [
706 repo_settings = [
674 UiSetting('extensions', 'largefiles', '', True),
707 UiSetting('extensions', 'largefiles', '', True),
675 UiSetting('phases', 'publish', 'True', True),
708 UiSetting('phases', 'publish', 'True', True),
676 UiSetting('hooks', 'changegroup.repo_size', 'hook', True),
709 UiSetting('hooks', 'changegroup.repo_size', 'hook', True),
677 UiSetting('hooks', 'changegroup.push_logger', 'hook', True),
710 UiSetting('hooks', 'changegroup.push_logger', 'hook', True),
678 UiSetting('hooks', 'outgoing.pull_logger', 'hook', True),
711 UiSetting('hooks', 'outgoing.pull_logger', 'hook', True),
679 UiSetting(
712 UiSetting(
680 'vcs_svn_branch', '84223c972204fa545ca1b22dac7bef5b68d7442d',
713 'vcs_svn_branch', '84223c972204fa545ca1b22dac7bef5b68d7442d',
681 'test_branch', True),
714 'test_branch', True),
682 UiSetting(
715 UiSetting(
683 'vcs_svn_tag', '84229c972204fa545ca1b22dac7bef5b68d7442d',
716 'vcs_svn_tag', '84229c972204fa545ca1b22dac7bef5b68d7442d',
684 'test_tag', True),
717 'test_tag', True),
685 ]
718 ]
686 non_repo_settings = [
719 non_repo_settings = [
687 UiSetting('largefiles', 'usercache', '/example/largefiles-store', True),
720 UiSetting('largefiles', 'usercache', '/example/largefiles-store', True),
688 UiSetting('test', 'outgoing.pull_logger', 'hook', True),
721 UiSetting('test', 'outgoing.pull_logger', 'hook', True),
689 UiSetting('hooks', 'test2', 'hook', True),
722 UiSetting('hooks', 'test2', 'hook', True),
690 UiSetting(
723 UiSetting(
691 'vcs_svn_repo', '84229c972204fa545ca1b22dac7bef5b68d7442d',
724 'vcs_svn_repo', '84229c972204fa545ca1b22dac7bef5b68d7442d',
692 'test_tag', True),
725 'test_tag', True),
693 ]
726 ]
694 settings = repo_settings + non_repo_settings
727 settings = repo_settings + non_repo_settings
695 filtered_settings = model._filter_ui_settings(settings)
728 filtered_settings = model._filter_ui_settings(settings)
696 assert sorted(filtered_settings) == sorted(repo_settings)
729 assert sorted(filtered_settings) == sorted(repo_settings)
697
730
698
731
699 class TestFilterGeneralSettings(object):
732 class TestFilterGeneralSettings(object):
700 def test_settings_are_filtered(self):
733 def test_settings_are_filtered(self):
701 model = VcsSettingsModel()
734 model = VcsSettingsModel()
702 settings = {
735 settings = {
703 'rhodecode_abcde': 'value1',
736 'rhodecode_abcde': 'value1',
704 'rhodecode_vwxyz': 'value2',
737 'rhodecode_vwxyz': 'value2',
705 }
738 }
706 general_settings = {
739 general_settings = {
707 'rhodecode_{}'.format(key): 'value'
740 'rhodecode_{}'.format(key): 'value'
708 for key in VcsSettingsModel.GENERAL_SETTINGS
741 for key in VcsSettingsModel.GENERAL_SETTINGS
709 }
742 }
710 settings.update(general_settings)
743 settings.update(general_settings)
711
744
712 filtered_settings = model._filter_general_settings(general_settings)
745 filtered_settings = model._filter_general_settings(general_settings)
713 assert sorted(filtered_settings) == sorted(general_settings)
746 assert sorted(filtered_settings) == sorted(general_settings)
714
747
715
748
716 class TestGetRepoUiSettings(object):
749 class TestGetRepoUiSettings(object):
717 def test_global_uis_are_returned_when_no_repo_uis_found(
750 def test_global_uis_are_returned_when_no_repo_uis_found(
718 self, repo_stub):
751 self, repo_stub):
719 model = VcsSettingsModel(repo=repo_stub.repo_name)
752 model = VcsSettingsModel(repo=repo_stub.repo_name)
720 result = model.get_repo_ui_settings()
753 result = model.get_repo_ui_settings()
721 svn_sections = (
754 svn_sections = (
722 VcsSettingsModel.SVN_TAG_SECTION,
755 VcsSettingsModel.SVN_TAG_SECTION,
723 VcsSettingsModel.SVN_BRANCH_SECTION)
756 VcsSettingsModel.SVN_BRANCH_SECTION)
724 expected_result = [
757 expected_result = [
725 s for s in model.global_settings.get_ui()
758 s for s in model.global_settings.get_ui()
726 if s.section not in svn_sections]
759 if s.section not in svn_sections]
727 assert sorted(result) == sorted(expected_result)
760 assert sorted(result) == sorted(expected_result)
728
761
729 def test_repo_uis_are_overriding_global_uis(
762 def test_repo_uis_are_overriding_global_uis(
730 self, repo_stub, settings_util):
763 self, repo_stub, settings_util):
731 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
764 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
732 settings_util.create_repo_rhodecode_ui(
765 settings_util.create_repo_rhodecode_ui(
733 repo_stub, section, 'repo', key=key, active=False)
766 repo_stub, section, 'repo', key=key, active=False)
734 model = VcsSettingsModel(repo=repo_stub.repo_name)
767 model = VcsSettingsModel(repo=repo_stub.repo_name)
735 result = model.get_repo_ui_settings()
768 result = model.get_repo_ui_settings()
736 for setting in result:
769 for setting in result:
737 locator = (setting.section, setting.key)
770 locator = (setting.section, setting.key)
738 if locator in VcsSettingsModel.HOOKS_SETTINGS:
771 if locator in VcsSettingsModel.HOOKS_SETTINGS:
739 assert setting.value == 'repo'
772 assert setting.value == 'repo'
740
773
741 assert setting.active is False
774 assert setting.active is False
742
775
743 def test_global_svn_patterns_are_not_in_list(
776 def test_global_svn_patterns_are_not_in_list(
744 self, repo_stub, settings_util):
777 self, repo_stub, settings_util):
745 svn_sections = (
778 svn_sections = (
746 VcsSettingsModel.SVN_TAG_SECTION,
779 VcsSettingsModel.SVN_TAG_SECTION,
747 VcsSettingsModel.SVN_BRANCH_SECTION)
780 VcsSettingsModel.SVN_BRANCH_SECTION)
748 for section in svn_sections:
781 for section in svn_sections:
749 settings_util.create_rhodecode_ui(
782 settings_util.create_rhodecode_ui(
750 section, 'repo', key='deadbeef' + section, active=False)
783 section, 'repo', key='deadbeef' + section, active=False)
784 Session().commit()
785
751 model = VcsSettingsModel(repo=repo_stub.repo_name)
786 model = VcsSettingsModel(repo=repo_stub.repo_name)
752 result = model.get_repo_ui_settings()
787 result = model.get_repo_ui_settings()
753 for setting in result:
788 for setting in result:
754 assert setting.section not in svn_sections
789 assert setting.section not in svn_sections
755
790
756 def test_repo_uis_filtered_by_section_are_returned(
791 def test_repo_uis_filtered_by_section_are_returned(
757 self, repo_stub, settings_util):
792 self, repo_stub, settings_util):
758 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
793 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
759 settings_util.create_repo_rhodecode_ui(
794 settings_util.create_repo_rhodecode_ui(
760 repo_stub, section, 'repo', key=key, active=False)
795 repo_stub, section, 'repo', key=key, active=False)
761 model = VcsSettingsModel(repo=repo_stub.repo_name)
796 model = VcsSettingsModel(repo=repo_stub.repo_name)
762 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
797 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
763 result = model.get_repo_ui_settings(section=section)
798 result = model.get_repo_ui_settings(section=section)
764 for setting in result:
799 for setting in result:
765 assert setting.section == section
800 assert setting.section == section
766
801
767 def test_repo_uis_filtered_by_key_are_returned(
802 def test_repo_uis_filtered_by_key_are_returned(
768 self, repo_stub, settings_util):
803 self, repo_stub, settings_util):
769 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
804 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
770 settings_util.create_repo_rhodecode_ui(
805 settings_util.create_repo_rhodecode_ui(
771 repo_stub, section, 'repo', key=key, active=False)
806 repo_stub, section, 'repo', key=key, active=False)
772 model = VcsSettingsModel(repo=repo_stub.repo_name)
807 model = VcsSettingsModel(repo=repo_stub.repo_name)
773 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
808 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
774 result = model.get_repo_ui_settings(key=key)
809 result = model.get_repo_ui_settings(key=key)
775 for setting in result:
810 for setting in result:
776 assert setting.key == key
811 assert setting.key == key
777
812
778 def test_raises_exception_when_repository_is_not_specified(self):
813 def test_raises_exception_when_repository_is_not_specified(self):
779 model = VcsSettingsModel()
814 model = VcsSettingsModel()
780 with pytest.raises(Exception) as exc_info:
815 with pytest.raises(Exception) as exc_info:
781 model.get_repo_ui_settings()
816 model.get_repo_ui_settings()
782 assert str(exc_info.value) == 'Repository is not specified'
817 assert str(exc_info.value) == 'Repository is not specified'
783
818
784
819
785 class TestGetRepoGeneralSettings(object):
820 class TestGetRepoGeneralSettings(object):
786 def test_global_settings_are_returned_when_no_repo_settings_found(
821 def test_global_settings_are_returned_when_no_repo_settings_found(
787 self, repo_stub):
822 self, repo_stub):
788 model = VcsSettingsModel(repo=repo_stub.repo_name)
823 model = VcsSettingsModel(repo=repo_stub.repo_name)
789 result = model.get_repo_general_settings()
824 result = model.get_repo_general_settings()
790 expected_result = model.global_settings.get_all_settings()
825 expected_result = model.global_settings.get_all_settings()
791 assert sorted(result) == sorted(expected_result)
826 assert sorted(result) == sorted(expected_result)
792
827
793 def test_repo_uis_are_overriding_global_uis(
828 def test_repo_uis_are_overriding_global_uis(
794 self, repo_stub, settings_util):
829 self, repo_stub, settings_util):
795 for key in VcsSettingsModel.GENERAL_SETTINGS:
830 for key in VcsSettingsModel.GENERAL_SETTINGS:
796 settings_util.create_repo_rhodecode_setting(
831 settings_util.create_repo_rhodecode_setting(
797 repo_stub, key, 'abcde', type_='unicode')
832 repo_stub, key, 'abcde', type_='unicode')
833 Session().commit()
834
798 model = VcsSettingsModel(repo=repo_stub.repo_name)
835 model = VcsSettingsModel(repo=repo_stub.repo_name)
799 result = model.get_repo_ui_settings()
836 result = model.get_repo_ui_settings()
800 for key in result:
837 for key in result:
801 if key in VcsSettingsModel.GENERAL_SETTINGS:
838 if key in VcsSettingsModel.GENERAL_SETTINGS:
802 assert result[key] == 'abcde'
839 assert result[key] == 'abcde'
803
840
804 def test_raises_exception_when_repository_is_not_specified(self):
841 def test_raises_exception_when_repository_is_not_specified(self):
805 model = VcsSettingsModel()
842 model = VcsSettingsModel()
806 with pytest.raises(Exception) as exc_info:
843 with pytest.raises(Exception) as exc_info:
807 model.get_repo_general_settings()
844 model.get_repo_general_settings()
808 assert str(exc_info.value) == 'Repository is not specified'
845 assert str(exc_info.value) == 'Repository is not specified'
809
846
810
847
811 class TestGetGlobalGeneralSettings(object):
848 class TestGetGlobalGeneralSettings(object):
812 def test_global_settings_are_returned(self, repo_stub):
849 def test_global_settings_are_returned(self, repo_stub):
813 model = VcsSettingsModel()
850 model = VcsSettingsModel()
814 result = model.get_global_general_settings()
851 result = model.get_global_general_settings()
815 expected_result = model.global_settings.get_all_settings()
852 expected_result = model.global_settings.get_all_settings()
816 assert sorted(result) == sorted(expected_result)
853 assert sorted(result) == sorted(expected_result)
817
854
818 def test_repo_uis_are_not_overriding_global_uis(
855 def test_repo_uis_are_not_overriding_global_uis(
819 self, repo_stub, settings_util):
856 self, repo_stub, settings_util):
820 for key in VcsSettingsModel.GENERAL_SETTINGS:
857 for key in VcsSettingsModel.GENERAL_SETTINGS:
821 settings_util.create_repo_rhodecode_setting(
858 settings_util.create_repo_rhodecode_setting(
822 repo_stub, key, 'abcde', type_='unicode')
859 repo_stub, key, 'abcde', type_='unicode')
860 Session().commit()
861
823 model = VcsSettingsModel(repo=repo_stub.repo_name)
862 model = VcsSettingsModel(repo=repo_stub.repo_name)
824 result = model.get_global_general_settings()
863 result = model.get_global_general_settings()
825 expected_result = model.global_settings.get_all_settings()
864 expected_result = model.global_settings.get_all_settings()
826 assert sorted(result) == sorted(expected_result)
865 assert sorted(result) == sorted(expected_result)
827
866
828
867
829 class TestGetGlobalUiSettings(object):
868 class TestGetGlobalUiSettings(object):
830 def test_global_uis_are_returned(self, repo_stub):
869 def test_global_uis_are_returned(self, repo_stub):
831 model = VcsSettingsModel()
870 model = VcsSettingsModel()
832 result = model.get_global_ui_settings()
871 result = model.get_global_ui_settings()
833 expected_result = model.global_settings.get_ui()
872 expected_result = model.global_settings.get_ui()
834 assert sorted(result) == sorted(expected_result)
873 assert sorted(result) == sorted(expected_result)
835
874
836 def test_repo_uis_are_not_overriding_global_uis(
875 def test_repo_uis_are_not_overriding_global_uis(
837 self, repo_stub, settings_util):
876 self, repo_stub, settings_util):
838 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
877 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
839 settings_util.create_repo_rhodecode_ui(
878 settings_util.create_repo_rhodecode_ui(
840 repo_stub, section, 'repo', key=key, active=False)
879 repo_stub, section, 'repo', key=key, active=False)
880 Session().commit()
881
841 model = VcsSettingsModel(repo=repo_stub.repo_name)
882 model = VcsSettingsModel(repo=repo_stub.repo_name)
842 result = model.get_global_ui_settings()
883 result = model.get_global_ui_settings()
843 expected_result = model.global_settings.get_ui()
884 expected_result = model.global_settings.get_ui()
844 assert sorted(result) == sorted(expected_result)
885 assert sorted(result) == sorted(expected_result)
845
886
846 def test_ui_settings_filtered_by_section(
887 def test_ui_settings_filtered_by_section(
847 self, repo_stub, settings_util):
888 self, repo_stub, settings_util):
848 model = VcsSettingsModel(repo=repo_stub.repo_name)
889 model = VcsSettingsModel(repo=repo_stub.repo_name)
849 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
890 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
850 result = model.get_global_ui_settings(section=section)
891 result = model.get_global_ui_settings(section=section)
851 expected_result = model.global_settings.get_ui(section=section)
892 expected_result = model.global_settings.get_ui(section=section)
852 assert sorted(result) == sorted(expected_result)
893 assert sorted(result) == sorted(expected_result)
853
894
854 def test_ui_settings_filtered_by_key(
895 def test_ui_settings_filtered_by_key(
855 self, repo_stub, settings_util):
896 self, repo_stub, settings_util):
856 model = VcsSettingsModel(repo=repo_stub.repo_name)
897 model = VcsSettingsModel(repo=repo_stub.repo_name)
857 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
898 section, key = VcsSettingsModel.HOOKS_SETTINGS[0]
858 result = model.get_global_ui_settings(key=key)
899 result = model.get_global_ui_settings(key=key)
859 expected_result = model.global_settings.get_ui(key=key)
900 expected_result = model.global_settings.get_ui(key=key)
860 assert sorted(result) == sorted(expected_result)
901 assert sorted(result) == sorted(expected_result)
861
902
862
903
863 class TestGetGeneralSettings(object):
904 class TestGetGeneralSettings(object):
864 def test_global_settings_are_returned_when_inherited_is_true(
905 def test_global_settings_are_returned_when_inherited_is_true(
865 self, repo_stub, settings_util):
906 self, repo_stub, settings_util):
866 model = VcsSettingsModel(repo=repo_stub.repo_name)
907 model = VcsSettingsModel(repo=repo_stub.repo_name)
867 model.inherit_global_settings = True
908 model.inherit_global_settings = True
868 for key in VcsSettingsModel.GENERAL_SETTINGS:
909 for key in VcsSettingsModel.GENERAL_SETTINGS:
869 settings_util.create_repo_rhodecode_setting(
910 settings_util.create_repo_rhodecode_setting(
870 repo_stub, key, 'abcde', type_='unicode')
911 repo_stub, key, 'abcde', type_='unicode')
912 Session().commit()
913
871 result = model.get_general_settings()
914 result = model.get_general_settings()
872 expected_result = model.get_global_general_settings()
915 expected_result = model.get_global_general_settings()
873 assert sorted(result) == sorted(expected_result)
916 assert sorted(result) == sorted(expected_result)
874
917
875 def test_repo_settings_are_returned_when_inherited_is_false(
918 def test_repo_settings_are_returned_when_inherited_is_false(
876 self, repo_stub, settings_util):
919 self, repo_stub, settings_util):
877 model = VcsSettingsModel(repo=repo_stub.repo_name)
920 model = VcsSettingsModel(repo=repo_stub.repo_name)
878 model.inherit_global_settings = False
921 model.inherit_global_settings = False
879 for key in VcsSettingsModel.GENERAL_SETTINGS:
922 for key in VcsSettingsModel.GENERAL_SETTINGS:
880 settings_util.create_repo_rhodecode_setting(
923 settings_util.create_repo_rhodecode_setting(
881 repo_stub, key, 'abcde', type_='unicode')
924 repo_stub, key, 'abcde', type_='unicode')
925 Session().commit()
926
882 result = model.get_general_settings()
927 result = model.get_general_settings()
883 expected_result = model.get_repo_general_settings()
928 expected_result = model.get_repo_general_settings()
884 assert sorted(result) == sorted(expected_result)
929 assert sorted(result) == sorted(expected_result)
885
930
886 def test_global_settings_are_returned_when_no_repository_specified(self):
931 def test_global_settings_are_returned_when_no_repository_specified(self):
887 model = VcsSettingsModel()
932 model = VcsSettingsModel()
888 result = model.get_general_settings()
933 result = model.get_general_settings()
889 expected_result = model.get_global_general_settings()
934 expected_result = model.get_global_general_settings()
890 assert sorted(result) == sorted(expected_result)
935 assert sorted(result) == sorted(expected_result)
891
936
892
937
893 class TestGetUiSettings(object):
938 class TestGetUiSettings(object):
894 def test_global_settings_are_returned_when_inherited_is_true(
939 def test_global_settings_are_returned_when_inherited_is_true(
895 self, repo_stub, settings_util):
940 self, repo_stub, settings_util):
896 model = VcsSettingsModel(repo=repo_stub.repo_name)
941 model = VcsSettingsModel(repo=repo_stub.repo_name)
897 model.inherit_global_settings = True
942 model.inherit_global_settings = True
898 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
943 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
899 settings_util.create_repo_rhodecode_ui(
944 settings_util.create_repo_rhodecode_ui(
900 repo_stub, section, 'repo', key=key, active=True)
945 repo_stub, section, 'repo', key=key, active=True)
946 Session().commit()
947
901 result = model.get_ui_settings()
948 result = model.get_ui_settings()
902 expected_result = model.get_global_ui_settings()
949 expected_result = model.get_global_ui_settings()
903 assert sorted(result) == sorted(expected_result)
950 assert sorted(result) == sorted(expected_result)
904
951
905 def test_repo_settings_are_returned_when_inherited_is_false(
952 def test_repo_settings_are_returned_when_inherited_is_false(
906 self, repo_stub, settings_util):
953 self, repo_stub, settings_util):
907 model = VcsSettingsModel(repo=repo_stub.repo_name)
954 model = VcsSettingsModel(repo=repo_stub.repo_name)
908 model.inherit_global_settings = False
955 model.inherit_global_settings = False
909 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
956 for section, key in VcsSettingsModel.HOOKS_SETTINGS:
910 settings_util.create_repo_rhodecode_ui(
957 settings_util.create_repo_rhodecode_ui(
911 repo_stub, section, 'repo', key=key, active=True)
958 repo_stub, section, 'repo', key=key, active=True)
959 Session().commit()
960
912 result = model.get_ui_settings()
961 result = model.get_ui_settings()
913 expected_result = model.get_repo_ui_settings()
962 expected_result = model.get_repo_ui_settings()
914 assert sorted(result) == sorted(expected_result)
963 assert sorted(result) == sorted(expected_result)
915
964
916 def test_repo_settings_filtered_by_section_and_key(self, repo_stub):
965 def test_repo_settings_filtered_by_section_and_key(self, repo_stub):
917 model = VcsSettingsModel(repo=repo_stub.repo_name)
966 model = VcsSettingsModel(repo=repo_stub.repo_name)
918 model.inherit_global_settings = False
967 model.inherit_global_settings = False
968
919 args = ('section', 'key')
969 args = ('section', 'key')
920 with mock.patch.object(model, 'get_repo_ui_settings') as settings_mock:
970 with mock.patch.object(model, 'get_repo_ui_settings') as settings_mock:
921 model.get_ui_settings(*args)
971 model.get_ui_settings(*args)
972 Session().commit()
973
922 settings_mock.assert_called_once_with(*args)
974 settings_mock.assert_called_once_with(*args)
923
975
924 def test_global_settings_filtered_by_section_and_key(self):
976 def test_global_settings_filtered_by_section_and_key(self):
925 model = VcsSettingsModel()
977 model = VcsSettingsModel()
926 args = ('section', 'key')
978 args = ('section', 'key')
927 with mock.patch.object(model, 'get_global_ui_settings') as (
979 with mock.patch.object(model, 'get_global_ui_settings') as (
928 settings_mock):
980 settings_mock):
929 model.get_ui_settings(*args)
981 model.get_ui_settings(*args)
930 settings_mock.assert_called_once_with(*args)
982 settings_mock.assert_called_once_with(*args)
931
983
932 def test_global_settings_are_returned_when_no_repository_specified(self):
984 def test_global_settings_are_returned_when_no_repository_specified(self):
933 model = VcsSettingsModel()
985 model = VcsSettingsModel()
934 result = model.get_ui_settings()
986 result = model.get_ui_settings()
935 expected_result = model.get_global_ui_settings()
987 expected_result = model.get_global_ui_settings()
936 assert sorted(result) == sorted(expected_result)
988 assert sorted(result) == sorted(expected_result)
937
989
938
990
939 class TestGetSvnPatterns(object):
991 class TestGetSvnPatterns(object):
940 def test_repo_settings_filtered_by_section_and_key(self, repo_stub):
992 def test_repo_settings_filtered_by_section_and_key(self, repo_stub):
941 model = VcsSettingsModel(repo=repo_stub.repo_name)
993 model = VcsSettingsModel(repo=repo_stub.repo_name)
942 args = ('section', )
994 args = ('section', )
943 with mock.patch.object(model, 'get_repo_ui_settings') as settings_mock:
995 with mock.patch.object(model, 'get_repo_ui_settings') as settings_mock:
944 model.get_svn_patterns(*args)
996 model.get_svn_patterns(*args)
997
998 Session().commit()
945 settings_mock.assert_called_once_with(*args)
999 settings_mock.assert_called_once_with(*args)
946
1000
947 def test_global_settings_filtered_by_section_and_key(self):
1001 def test_global_settings_filtered_by_section_and_key(self):
948 model = VcsSettingsModel()
1002 model = VcsSettingsModel()
949 args = ('section', )
1003 args = ('section', )
950 with mock.patch.object(model, 'get_global_ui_settings') as (
1004 with mock.patch.object(model, 'get_global_ui_settings') as (
951 settings_mock):
1005 settings_mock):
952 model.get_svn_patterns(*args)
1006 model.get_svn_patterns(*args)
953 settings_mock.assert_called_once_with(*args)
1007 settings_mock.assert_called_once_with(*args)
954
1008
955
1009
956 class TestGetReposLocation(object):
1010 class TestGetReposLocation(object):
957 def test_returns_repos_location(self, repo_stub):
1011 def test_returns_repos_location(self, repo_stub):
958 model = VcsSettingsModel()
1012 model = VcsSettingsModel()
959
1013
960 result_mock = mock.Mock()
1014 result_mock = mock.Mock()
961 result_mock.ui_value = '/tmp'
1015 result_mock.ui_value = '/tmp'
962
1016
963 with mock.patch.object(model, 'global_settings') as settings_mock:
1017 with mock.patch.object(model, 'global_settings') as settings_mock:
964 settings_mock.get_ui_by_key.return_value = result_mock
1018 settings_mock.get_ui_by_key.return_value = result_mock
965 result = model.get_repos_location()
1019 result = model.get_repos_location()
966
1020
967 settings_mock.get_ui_by_key.assert_called_once_with('/')
1021 settings_mock.get_ui_by_key.assert_called_once_with('/')
968 assert result == '/tmp'
1022 assert result == '/tmp'
969
1023
970
1024
971 class TestCreateOrUpdateRepoSettings(object):
1025 class TestCreateOrUpdateRepoSettings(object):
972 FORM_DATA = {
1026 FORM_DATA = {
973 'inherit_global_settings': False,
1027 'inherit_global_settings': False,
974 'hooks_changegroup_repo_size': False,
1028 'hooks_changegroup_repo_size': False,
975 'hooks_changegroup_push_logger': False,
1029 'hooks_changegroup_push_logger': False,
976 'hooks_outgoing_pull_logger': False,
1030 'hooks_outgoing_pull_logger': False,
977 'extensions_largefiles': False,
1031 'extensions_largefiles': False,
978 'extensions_evolve': False,
1032 'extensions_evolve': False,
979 'largefiles_usercache': '/example/largefiles-store',
1033 'largefiles_usercache': '/example/largefiles-store',
980 'vcs_git_lfs_enabled': False,
1034 'vcs_git_lfs_enabled': False,
981 'vcs_git_lfs_store_location': '/',
1035 'vcs_git_lfs_store_location': '/',
982 'phases_publish': 'False',
1036 'phases_publish': 'False',
983 'rhodecode_pr_merge_enabled': False,
1037 'rhodecode_pr_merge_enabled': False,
984 'rhodecode_use_outdated_comments': False,
1038 'rhodecode_use_outdated_comments': False,
985 'new_svn_branch': '',
1039 'new_svn_branch': '',
986 'new_svn_tag': ''
1040 'new_svn_tag': ''
987 }
1041 }
988
1042
989 def test_get_raises_exception_when_repository_not_specified(self):
1043 def test_get_raises_exception_when_repository_not_specified(self):
990 model = VcsSettingsModel()
1044 model = VcsSettingsModel()
991 with pytest.raises(Exception) as exc_info:
1045 with pytest.raises(Exception) as exc_info:
992 model.create_or_update_repo_settings(data=self.FORM_DATA)
1046 model.create_or_update_repo_settings(data=self.FORM_DATA)
1047 Session().commit()
1048
993 assert str(exc_info.value) == 'Repository is not specified'
1049 assert str(exc_info.value) == 'Repository is not specified'
994
1050
995 def test_only_svn_settings_are_updated_when_type_is_svn(self, backend_svn):
1051 def test_only_svn_settings_are_updated_when_type_is_svn(self, backend_svn):
996 repo = backend_svn.create_repo()
1052 repo = backend_svn.create_repo()
997 model = VcsSettingsModel(repo=repo)
1053 model = VcsSettingsModel(repo=repo)
998 with self._patch_model(model) as mocks:
1054 with self._patch_model(model) as mocks:
999 model.create_or_update_repo_settings(
1055 model.create_or_update_repo_settings(
1000 data=self.FORM_DATA, inherit_global_settings=False)
1056 data=self.FORM_DATA, inherit_global_settings=False)
1057 Session().commit()
1058
1001 mocks['create_repo_svn_settings'].assert_called_once_with(
1059 mocks['create_repo_svn_settings'].assert_called_once_with(
1002 self.FORM_DATA)
1060 self.FORM_DATA)
1003 non_called_methods = (
1061 non_called_methods = (
1004 'create_or_update_repo_hook_settings',
1062 'create_or_update_repo_hook_settings',
1005 'create_or_update_repo_pr_settings',
1063 'create_or_update_repo_pr_settings',
1006 'create_or_update_repo_hg_settings')
1064 'create_or_update_repo_hg_settings')
1007 for method in non_called_methods:
1065 for method in non_called_methods:
1008 assert mocks[method].call_count == 0
1066 assert mocks[method].call_count == 0
1009
1067
1010 def test_non_svn_settings_are_updated_when_type_is_hg(self, backend_hg):
1068 def test_non_svn_settings_are_updated_when_type_is_hg(self, backend_hg):
1011 repo = backend_hg.create_repo()
1069 repo = backend_hg.create_repo()
1012 model = VcsSettingsModel(repo=repo)
1070 model = VcsSettingsModel(repo=repo)
1013 with self._patch_model(model) as mocks:
1071 with self._patch_model(model) as mocks:
1014 model.create_or_update_repo_settings(
1072 model.create_or_update_repo_settings(
1015 data=self.FORM_DATA, inherit_global_settings=False)
1073 data=self.FORM_DATA, inherit_global_settings=False)
1074 Session().commit()
1016
1075
1017 assert mocks['create_repo_svn_settings'].call_count == 0
1076 assert mocks['create_repo_svn_settings'].call_count == 0
1018 called_methods = (
1077 called_methods = (
1019 'create_or_update_repo_hook_settings',
1078 'create_or_update_repo_hook_settings',
1020 'create_or_update_repo_pr_settings',
1079 'create_or_update_repo_pr_settings',
1021 'create_or_update_repo_hg_settings')
1080 'create_or_update_repo_hg_settings')
1022 for method in called_methods:
1081 for method in called_methods:
1023 mocks[method].assert_called_once_with(self.FORM_DATA)
1082 mocks[method].assert_called_once_with(self.FORM_DATA)
1024
1083
1025 def test_non_svn_and_hg_settings_are_updated_when_type_is_git(
1084 def test_non_svn_and_hg_settings_are_updated_when_type_is_git(
1026 self, backend_git):
1085 self, backend_git):
1027 repo = backend_git.create_repo()
1086 repo = backend_git.create_repo()
1028 model = VcsSettingsModel(repo=repo)
1087 model = VcsSettingsModel(repo=repo)
1029 with self._patch_model(model) as mocks:
1088 with self._patch_model(model) as mocks:
1030 model.create_or_update_repo_settings(
1089 model.create_or_update_repo_settings(
1031 data=self.FORM_DATA, inherit_global_settings=False)
1090 data=self.FORM_DATA, inherit_global_settings=False)
1032
1091
1033 assert mocks['create_repo_svn_settings'].call_count == 0
1092 assert mocks['create_repo_svn_settings'].call_count == 0
1034 called_methods = (
1093 called_methods = (
1035 'create_or_update_repo_hook_settings',
1094 'create_or_update_repo_hook_settings',
1036 'create_or_update_repo_pr_settings')
1095 'create_or_update_repo_pr_settings')
1037 non_called_methods = (
1096 non_called_methods = (
1038 'create_repo_svn_settings',
1097 'create_repo_svn_settings',
1039 'create_or_update_repo_hg_settings'
1098 'create_or_update_repo_hg_settings'
1040 )
1099 )
1041 for method in called_methods:
1100 for method in called_methods:
1042 mocks[method].assert_called_once_with(self.FORM_DATA)
1101 mocks[method].assert_called_once_with(self.FORM_DATA)
1043 for method in non_called_methods:
1102 for method in non_called_methods:
1044 assert mocks[method].call_count == 0
1103 assert mocks[method].call_count == 0
1045
1104
1046 def test_no_methods_are_called_when_settings_are_inherited(
1105 def test_no_methods_are_called_when_settings_are_inherited(
1047 self, backend):
1106 self, backend):
1048 repo = backend.create_repo()
1107 repo = backend.create_repo()
1049 model = VcsSettingsModel(repo=repo)
1108 model = VcsSettingsModel(repo=repo)
1050 with self._patch_model(model) as mocks:
1109 with self._patch_model(model) as mocks:
1051 model.create_or_update_repo_settings(
1110 model.create_or_update_repo_settings(
1052 data=self.FORM_DATA, inherit_global_settings=True)
1111 data=self.FORM_DATA, inherit_global_settings=True)
1053 for method_name in mocks:
1112 for method_name in mocks:
1054 assert mocks[method_name].call_count == 0
1113 assert mocks[method_name].call_count == 0
1055
1114
1056 def test_cache_is_marked_for_invalidation(self, repo_stub):
1115 def test_cache_is_marked_for_invalidation(self, repo_stub):
1057 model = VcsSettingsModel(repo=repo_stub)
1116 model = VcsSettingsModel(repo=repo_stub)
1058 invalidation_patcher = mock.patch(
1117 invalidation_patcher = mock.patch(
1059 'rhodecode.model.scm.ScmModel.mark_for_invalidation')
1118 'rhodecode.model.scm.ScmModel.mark_for_invalidation')
1060 with invalidation_patcher as invalidation_mock:
1119 with invalidation_patcher as invalidation_mock:
1061 model.create_or_update_repo_settings(
1120 model.create_or_update_repo_settings(
1062 data=self.FORM_DATA, inherit_global_settings=True)
1121 data=self.FORM_DATA, inherit_global_settings=True)
1122 Session().commit()
1123
1063 invalidation_mock.assert_called_once_with(
1124 invalidation_mock.assert_called_once_with(
1064 repo_stub.repo_name, delete=True)
1125 repo_stub.repo_name, delete=True)
1065
1126
1066 def test_inherit_flag_is_saved(self, repo_stub):
1127 def test_inherit_flag_is_saved(self, repo_stub):
1067 model = VcsSettingsModel(repo=repo_stub)
1128 model = VcsSettingsModel(repo=repo_stub)
1068 model.inherit_global_settings = True
1129 model.inherit_global_settings = True
1069 with self._patch_model(model):
1130 with self._patch_model(model):
1070 model.create_or_update_repo_settings(
1131 model.create_or_update_repo_settings(
1071 data=self.FORM_DATA, inherit_global_settings=False)
1132 data=self.FORM_DATA, inherit_global_settings=False)
1133 Session().commit()
1134
1072 assert model.inherit_global_settings is False
1135 assert model.inherit_global_settings is False
1073
1136
1074 def _patch_model(self, model):
1137 def _patch_model(self, model):
1075 return mock.patch.multiple(
1138 return mock.patch.multiple(
1076 model,
1139 model,
1077 create_repo_svn_settings=mock.DEFAULT,
1140 create_repo_svn_settings=mock.DEFAULT,
1078 create_or_update_repo_hook_settings=mock.DEFAULT,
1141 create_or_update_repo_hook_settings=mock.DEFAULT,
1079 create_or_update_repo_pr_settings=mock.DEFAULT,
1142 create_or_update_repo_pr_settings=mock.DEFAULT,
1080 create_or_update_repo_hg_settings=mock.DEFAULT)
1143 create_or_update_repo_hg_settings=mock.DEFAULT)
@@ -1,735 +1,744 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 pytest
21 import pytest
22
22
23 from rhodecode.lib.auth import AuthUser
23 from rhodecode.lib.auth import AuthUser
24 from rhodecode.model.db import (
24 from rhodecode.model.db import (
25 RepoGroup, User, UserGroupRepoGroupToPerm, Permission, UserToPerm,
25 RepoGroup, User, UserGroupRepoGroupToPerm, Permission, UserToPerm,
26 UserGroupToPerm)
26 UserGroupToPerm)
27 from rhodecode.model.meta import Session
27 from rhodecode.model.meta import Session
28 from rhodecode.model.permission import PermissionModel
28 from rhodecode.model.permission import PermissionModel
29 from rhodecode.model.repo import RepoModel
29 from rhodecode.model.repo import RepoModel
30 from rhodecode.model.repo_group import RepoGroupModel
30 from rhodecode.model.repo_group import RepoGroupModel
31 from rhodecode.model.user import UserModel
31 from rhodecode.model.user import UserModel
32 from rhodecode.model.user_group import UserGroupModel
32 from rhodecode.model.user_group import UserGroupModel
33 from rhodecode.tests.fixture import Fixture
33 from rhodecode.tests.fixture import Fixture
34
34
35
35
36 fixture = Fixture()
36 fixture = Fixture()
37
37
38
38
39 @pytest.fixture()
39 @pytest.fixture()
40 def repo_name(backend_hg):
40 def repo_name(backend_hg):
41 return backend_hg.repo_name
41 return backend_hg.repo_name
42
42
43
43
44 class TestPermissions(object):
44 class TestPermissions(object):
45
45
46 @pytest.fixture(scope='class', autouse=True)
46 @pytest.fixture(scope='class', autouse=True)
47 def default_permissions(self, request, baseapp):
47 def default_permissions(self, request, baseapp):
48 # recreate default user to get a clean start
48 # recreate default user to get a clean start
49 PermissionModel().create_default_user_permissions(
49 PermissionModel().create_default_user_permissions(
50 user=User.DEFAULT_USER, force=True)
50 user=User.DEFAULT_USER, force=True)
51 Session().commit()
51 Session().commit()
52
52
53 @pytest.fixture(autouse=True)
53 @pytest.fixture(autouse=True)
54 def prepare_users(self, request):
54 def prepare_users(self, request):
55 # TODO: User creation is a duplicate of test_nofitications, check
55 # TODO: User creation is a duplicate of test_nofitications, check
56 # if that can be unified
56 # if that can be unified
57 self.u1 = UserModel().create_or_update(
57 self.u1 = UserModel().create_or_update(
58 username=u'u1', password=u'qweqwe',
58 username=u'u1', password=u'qweqwe',
59 email=u'u1@rhodecode.org', firstname=u'u1', lastname=u'u1'
59 email=u'u1@rhodecode.org', firstname=u'u1', lastname=u'u1'
60 )
60 )
61 self.u2 = UserModel().create_or_update(
61 self.u2 = UserModel().create_or_update(
62 username=u'u2', password=u'qweqwe',
62 username=u'u2', password=u'qweqwe',
63 email=u'u2@rhodecode.org', firstname=u'u2', lastname=u'u2'
63 email=u'u2@rhodecode.org', firstname=u'u2', lastname=u'u2'
64 )
64 )
65 self.u3 = UserModel().create_or_update(
65 self.u3 = UserModel().create_or_update(
66 username=u'u3', password=u'qweqwe',
66 username=u'u3', password=u'qweqwe',
67 email=u'u3@rhodecode.org', firstname=u'u3', lastname=u'u3'
67 email=u'u3@rhodecode.org', firstname=u'u3', lastname=u'u3'
68 )
68 )
69 self.anon = User.get_default_user()
69 self.anon = User.get_default_user()
70 self.a1 = UserModel().create_or_update(
70 self.a1 = UserModel().create_or_update(
71 username=u'a1', password=u'qweqwe',
71 username=u'a1', password=u'qweqwe',
72 email=u'a1@rhodecode.org', firstname=u'a1', lastname=u'a1',
72 email=u'a1@rhodecode.org', firstname=u'a1', lastname=u'a1',
73 admin=True
73 admin=True
74 )
74 )
75 Session().commit()
75 Session().commit()
76
76
77 request.addfinalizer(self.cleanup)
77 request.addfinalizer(self.cleanup)
78
78
79 def cleanup(self):
79 def cleanup(self):
80 if hasattr(self, 'test_repo'):
80 if hasattr(self, 'test_repo'):
81 RepoModel().delete(repo=self.test_repo)
81 RepoModel().delete(repo=self.test_repo)
82 Session().commit()
82
83
83 if hasattr(self, 'g1'):
84 if hasattr(self, 'g1'):
84 RepoGroupModel().delete(self.g1.group_id)
85 RepoGroupModel().delete(self.g1.group_id)
85 if hasattr(self, 'g2'):
86 if hasattr(self, 'g2'):
86 RepoGroupModel().delete(self.g2.group_id)
87 RepoGroupModel().delete(self.g2.group_id)
88 Session().commit()
87
89
88 UserModel().delete(self.u1)
90 UserModel().delete(self.u1, handle_repos='delete', handle_repo_groups='delete')
89 UserModel().delete(self.u2)
91 UserModel().delete(self.u2, handle_repos='delete', handle_repo_groups='delete')
90 UserModel().delete(self.u3)
92 UserModel().delete(self.u3, handle_repos='delete', handle_repo_groups='delete')
91 UserModel().delete(self.a1)
93 UserModel().delete(self.a1, handle_repos='delete', handle_repo_groups='delete')
94 Session().commit()
92
95
93 if hasattr(self, 'ug1'):
96 if hasattr(self, 'ug1'):
94 UserGroupModel().delete(self.ug1, force=True)
97 UserGroupModel().delete(self.ug1, force=True)
95
96 Session().commit()
98 Session().commit()
97
99
98 def test_default_perms_set(self, repo_name):
100 def test_default_perms_set(self, repo_name):
99 assert repo_perms(self.u1)[repo_name] == 'repository.read'
101 assert repo_perms(self.u1)[repo_name] == 'repository.read'
100 new_perm = 'repository.write'
102 new_perm = 'repository.write'
101 RepoModel().grant_user_permission(repo=repo_name, user=self.u1,
103 RepoModel().grant_user_permission(repo=repo_name, user=self.u1,
102 perm=new_perm)
104 perm=new_perm)
103 Session().commit()
105 Session().commit()
104 assert repo_perms(self.u1)[repo_name] == new_perm
106 assert repo_perms(self.u1)[repo_name] == new_perm
105
107
106 def test_default_admin_perms_set(self, repo_name):
108 def test_default_admin_perms_set(self, repo_name):
107 assert repo_perms(self.a1)[repo_name] == 'repository.admin'
109 assert repo_perms(self.a1)[repo_name] == 'repository.admin'
108 RepoModel().grant_user_permission(repo=repo_name, user=self.a1,
110 RepoModel().grant_user_permission(repo=repo_name, user=self.a1,
109 perm='repository.write')
111 perm='repository.write')
110 Session().commit()
112 Session().commit()
111 # cannot really downgrade admins permissions !? they still gets set as
113 # cannot really downgrade admins permissions !? they still gets set as
112 # admin !
114 # admin !
113 assert repo_perms(self.a1)[repo_name] == 'repository.admin'
115 assert repo_perms(self.a1)[repo_name] == 'repository.admin'
114
116
115 def test_default_group_perms(self, repo_name):
117 def test_default_group_perms(self, repo_name):
116 self.g1 = fixture.create_repo_group('test1', skip_if_exists=True)
118 self.g1 = fixture.create_repo_group('test1', skip_if_exists=True)
117 self.g2 = fixture.create_repo_group('test2', skip_if_exists=True)
119 self.g2 = fixture.create_repo_group('test2', skip_if_exists=True)
118
120
119 assert repo_perms(self.u1)[repo_name] == 'repository.read'
121 assert repo_perms(self.u1)[repo_name] == 'repository.read'
120 assert group_perms(self.u1) == {
122 assert group_perms(self.u1) == {
121 'test1': 'group.read', 'test2': 'group.read'}
123 'test1': 'group.read', 'test2': 'group.read'}
122 assert global_perms(self.u1) == set(
124 assert global_perms(self.u1) == set(
123 Permission.DEFAULT_USER_PERMISSIONS)
125 Permission.DEFAULT_USER_PERMISSIONS)
124
126
125 def test_default_admin_group_perms(self, repo_name):
127 def test_default_admin_group_perms(self, repo_name):
126 self.g1 = fixture.create_repo_group('test1', skip_if_exists=True)
128 self.g1 = fixture.create_repo_group('test1', skip_if_exists=True)
127 self.g2 = fixture.create_repo_group('test2', skip_if_exists=True)
129 self.g2 = fixture.create_repo_group('test2', skip_if_exists=True)
128
130
129 assert repo_perms(self.a1)[repo_name] == 'repository.admin'
131 assert repo_perms(self.a1)[repo_name] == 'repository.admin'
130 assert group_perms(self.a1) == {
132 assert group_perms(self.a1) == {
131 'test1': 'group.admin', 'test2': 'group.admin'}
133 'test1': 'group.admin', 'test2': 'group.admin'}
132
134
133 def test_default_owner_repo_perms(self, backend, user_util, test_repo):
135 def test_default_owner_repo_perms(self, backend, user_util, test_repo):
134 user = user_util.create_user()
136 user = user_util.create_user()
135 repo = test_repo('minimal', backend.alias)
137 repo = test_repo('minimal', backend.alias)
136 org_owner = repo.user
138 org_owner = repo.user
137 assert repo_perms(user)[repo.repo_name] == 'repository.read'
139 assert repo_perms(user)[repo.repo_name] == 'repository.read'
138
140
139 repo.user = user
141 repo.user = user
140 assert repo_perms(user)[repo.repo_name] == 'repository.admin'
142 assert repo_perms(user)[repo.repo_name] == 'repository.admin'
141 repo.user = org_owner
143 repo.user = org_owner
142
144
143 def test_default_owner_branch_perms(self, user_util, test_user_group):
145 def test_default_owner_branch_perms(self, user_util, test_user_group):
144 user = user_util.create_user()
146 user = user_util.create_user()
145 assert branch_perms(user) == {}
147 assert branch_perms(user) == {}
146
148
147 def test_default_owner_repo_group_perms(self, user_util, test_repo_group):
149 def test_default_owner_repo_group_perms(self, user_util, test_repo_group):
148 user = user_util.create_user()
150 user = user_util.create_user()
149 org_owner = test_repo_group.user
151 org_owner = test_repo_group.user
150
152
151 assert group_perms(user)[test_repo_group.group_name] == 'group.read'
153 assert group_perms(user)[test_repo_group.group_name] == 'group.read'
152
154
153 test_repo_group.user = user
155 test_repo_group.user = user
154 assert group_perms(user)[test_repo_group.group_name] == 'group.admin'
156 assert group_perms(user)[test_repo_group.group_name] == 'group.admin'
155 test_repo_group.user = org_owner
157 test_repo_group.user = org_owner
156
158
157 def test_default_owner_user_group_perms(self, user_util, test_user_group):
159 def test_default_owner_user_group_perms(self, user_util, test_user_group):
158 user = user_util.create_user()
160 user = user_util.create_user()
159 org_owner = test_user_group.user
161 org_owner = test_user_group.user
160
162
161 assert user_group_perms(user)[test_user_group.users_group_name] == 'usergroup.read'
163 assert user_group_perms(user)[test_user_group.users_group_name] == 'usergroup.read'
162
164
163 test_user_group.user = user
165 test_user_group.user = user
164 assert user_group_perms(user)[test_user_group.users_group_name] == 'usergroup.admin'
166 assert user_group_perms(user)[test_user_group.users_group_name] == 'usergroup.admin'
165
167
166 test_user_group.user = org_owner
168 test_user_group.user = org_owner
167
169
168 def test_propagated_permission_from_users_group_by_explicit_perms_exist(
170 def test_propagated_permission_from_users_group_by_explicit_perms_exist(
169 self, repo_name):
171 self, repo_name):
170 # make group
172 # make group
171 self.ug1 = fixture.create_user_group('G1')
173 self.ug1 = fixture.create_user_group('G1')
172 UserGroupModel().add_user_to_group(self.ug1, self.u1)
174 UserGroupModel().add_user_to_group(self.ug1, self.u1)
173
175
174 # set permission to lower
176 # set permission to lower
175 new_perm = 'repository.none'
177 new_perm = 'repository.none'
176 RepoModel().grant_user_permission(
178 RepoModel().grant_user_permission(
177 repo=repo_name, user=self.u1, perm=new_perm)
179 repo=repo_name, user=self.u1, perm=new_perm)
178 Session().commit()
180 Session().commit()
179 assert repo_perms(self.u1)[repo_name] == new_perm
181 assert repo_perms(self.u1)[repo_name] == new_perm
180
182
181 # grant perm for group this should not override permission from user
183 # grant perm for group this should not override permission from user
182 # since it has explicitly set
184 # since it has explicitly set
183 new_perm_gr = 'repository.write'
185 new_perm_gr = 'repository.write'
184 RepoModel().grant_user_group_permission(
186 RepoModel().grant_user_group_permission(
185 repo=repo_name, group_name=self.ug1, perm=new_perm_gr)
187 repo=repo_name, group_name=self.ug1, perm=new_perm_gr)
188 Session().commit()
186
189
187 assert repo_perms(self.u1)[repo_name] == new_perm
190 assert repo_perms(self.u1)[repo_name] == new_perm
188 assert group_perms(self.u1) == {}
191 assert group_perms(self.u1) == {}
189
192
190 def test_propagated_permission_from_users_group(self, repo_name):
193 def test_propagated_permission_from_users_group(self, repo_name):
191 # make group
194 # make group
192 self.ug1 = fixture.create_user_group('G1')
195 self.ug1 = fixture.create_user_group('G1')
193 UserGroupModel().add_user_to_group(self.ug1, self.u3)
196 UserGroupModel().add_user_to_group(self.ug1, self.u3)
194
197
195 # grant perm for group
198 # grant perm for group
196 # this should override default permission from user
199 # this should override default permission from user
197 new_perm_gr = 'repository.write'
200 new_perm_gr = 'repository.write'
198 RepoModel().grant_user_group_permission(
201 RepoModel().grant_user_group_permission(
199 repo=repo_name, group_name=self.ug1, perm=new_perm_gr)
202 repo=repo_name, group_name=self.ug1, perm=new_perm_gr)
203 Session().commit()
200
204
201 assert repo_perms(self.u3)[repo_name] == new_perm_gr
205 assert repo_perms(self.u3)[repo_name] == new_perm_gr
202 assert group_perms(self.u3) == {}
206 assert group_perms(self.u3) == {}
203
207
204 def test_propagated_permission_from_users_group_lower_weight(
208 def test_propagated_permission_from_users_group_lower_weight(
205 self, repo_name):
209 self, repo_name):
206 # make group with user
210 # make group with user
207 self.ug1 = fixture.create_user_group('G1')
211 self.ug1 = fixture.create_user_group('G1')
208 UserGroupModel().add_user_to_group(self.ug1, self.u1)
212 UserGroupModel().add_user_to_group(self.ug1, self.u1)
209
213
210 # set permission to lower
214 # set permission to lower
211 new_perm_h = 'repository.write'
215 new_perm_h = 'repository.write'
212 RepoModel().grant_user_permission(
216 RepoModel().grant_user_permission(
213 repo=repo_name, user=self.u1, perm=new_perm_h)
217 repo=repo_name, user=self.u1, perm=new_perm_h)
214 Session().commit()
218 Session().commit()
215
219
216 assert repo_perms(self.u1)[repo_name] == new_perm_h
220 assert repo_perms(self.u1)[repo_name] == new_perm_h
217
221
218 # grant perm for group this should NOT override permission from user
222 # grant perm for group this should NOT override permission from user
219 # since it's lower than granted
223 # since it's lower than granted
220 new_perm_l = 'repository.read'
224 new_perm_l = 'repository.read'
221 RepoModel().grant_user_group_permission(
225 RepoModel().grant_user_group_permission(
222 repo=repo_name, group_name=self.ug1, perm=new_perm_l)
226 repo=repo_name, group_name=self.ug1, perm=new_perm_l)
227 Session().commit()
223
228
224 assert repo_perms(self.u1)[repo_name] == new_perm_h
229 assert repo_perms(self.u1)[repo_name] == new_perm_h
225 assert group_perms(self.u1) == {}
230 assert group_perms(self.u1) == {}
226
231
227 def test_repo_in_group_permissions(self):
232 def test_repo_in_group_permissions(self):
228 self.g1 = fixture.create_repo_group('group1', skip_if_exists=True)
233 self.g1 = fixture.create_repo_group('group1', skip_if_exists=True)
229 self.g2 = fixture.create_repo_group('group2', skip_if_exists=True)
234 self.g2 = fixture.create_repo_group('group2', skip_if_exists=True)
230 # both perms should be read !
235 # both perms should be read !
231 assert group_perms(self.u1) == \
236 assert group_perms(self.u1) == \
232 {u'group1': u'group.read', u'group2': u'group.read'}
237 {u'group1': u'group.read', u'group2': u'group.read'}
233
238
234 assert group_perms(self.anon) == \
239 assert group_perms(self.anon) == \
235 {u'group1': u'group.read', u'group2': u'group.read'}
240 {u'group1': u'group.read', u'group2': u'group.read'}
236
241
237 # Change perms to none for both groups
242 # Change perms to none for both groups
238 RepoGroupModel().grant_user_permission(
243 RepoGroupModel().grant_user_permission(
239 repo_group=self.g1, user=self.anon, perm='group.none')
244 repo_group=self.g1, user=self.anon, perm='group.none')
240 RepoGroupModel().grant_user_permission(
245 RepoGroupModel().grant_user_permission(
241 repo_group=self.g2, user=self.anon, perm='group.none')
246 repo_group=self.g2, user=self.anon, perm='group.none')
242
247
243 assert group_perms(self.u1) == \
248 assert group_perms(self.u1) == \
244 {u'group1': u'group.none', u'group2': u'group.none'}
249 {u'group1': u'group.none', u'group2': u'group.none'}
245 assert group_perms(self.anon) == \
250 assert group_perms(self.anon) == \
246 {u'group1': u'group.none', u'group2': u'group.none'}
251 {u'group1': u'group.none', u'group2': u'group.none'}
247
252
248 # add repo to group
253 # add repo to group
249 name = RepoGroup.url_sep().join([self.g1.group_name, 'test_perm'])
254 name = RepoGroup.url_sep().join([self.g1.group_name, 'test_perm'])
250 self.test_repo = fixture.create_repo(name=name,
255 self.test_repo = fixture.create_repo(name=name,
251 repo_type='hg',
256 repo_type='hg',
252 repo_group=self.g1,
257 repo_group=self.g1,
253 cur_user=self.u1,)
258 cur_user=self.u1,)
254
259
255 assert group_perms(self.u1) == \
260 assert group_perms(self.u1) == \
256 {u'group1': u'group.none', u'group2': u'group.none'}
261 {u'group1': u'group.none', u'group2': u'group.none'}
257 assert group_perms(self.anon) == \
262 assert group_perms(self.anon) == \
258 {u'group1': u'group.none', u'group2': u'group.none'}
263 {u'group1': u'group.none', u'group2': u'group.none'}
259
264
260 # grant permission for u2 !
265 # grant permission for u2 !
261 RepoGroupModel().grant_user_permission(
266 RepoGroupModel().grant_user_permission(
262 repo_group=self.g1, user=self.u2, perm='group.read')
267 repo_group=self.g1, user=self.u2, perm='group.read')
263 RepoGroupModel().grant_user_permission(
268 RepoGroupModel().grant_user_permission(
264 repo_group=self.g2, user=self.u2, perm='group.read')
269 repo_group=self.g2, user=self.u2, perm='group.read')
265 Session().commit()
270 Session().commit()
266 assert self.u1 != self.u2
271 assert self.u1 != self.u2
267
272
268 # u1 and anon should have not change perms while u2 should !
273 # u1 and anon should have not change perms while u2 should !
269 assert group_perms(self.u1) == \
274 assert group_perms(self.u1) == \
270 {u'group1': u'group.none', u'group2': u'group.none'}
275 {u'group1': u'group.none', u'group2': u'group.none'}
271 assert group_perms(self.u2) == \
276 assert group_perms(self.u2) == \
272 {u'group1': u'group.read', u'group2': u'group.read'}
277 {u'group1': u'group.read', u'group2': u'group.read'}
273 assert group_perms(self.anon) == \
278 assert group_perms(self.anon) == \
274 {u'group1': u'group.none', u'group2': u'group.none'}
279 {u'group1': u'group.none', u'group2': u'group.none'}
275
280
276 def test_repo_group_user_as_user_group_member(self):
281 def test_repo_group_user_as_user_group_member(self):
277 # create Group1
282 # create Group1
278 self.g1 = fixture.create_repo_group('group1', skip_if_exists=True)
283 self.g1 = fixture.create_repo_group('group1', skip_if_exists=True)
279 assert group_perms(self.anon) == {u'group1': u'group.read'}
284 assert group_perms(self.anon) == {u'group1': u'group.read'}
280
285
281 # set default permission to none
286 # set default permission to none
282 RepoGroupModel().grant_user_permission(
287 RepoGroupModel().grant_user_permission(
283 repo_group=self.g1, user=self.anon, perm='group.none')
288 repo_group=self.g1, user=self.anon, perm='group.none')
289 Session().commit()
290
284 # make group
291 # make group
285 self.ug1 = fixture.create_user_group('G1')
292 self.ug1 = fixture.create_user_group('G1')
286 # add user to group
293 # add user to group
287 UserGroupModel().add_user_to_group(self.ug1, self.u1)
294 UserGroupModel().add_user_to_group(self.ug1, self.u1)
288 Session().commit()
295 Session().commit()
289
296
290 # check if user is in the group
297 # check if user is in the group
291 ug1 = UserGroupModel().get(self.ug1.users_group_id)
298 ug1 = UserGroupModel().get(self.ug1.users_group_id)
292 members = [x.user_id for x in ug1.members]
299 members = [x.user_id for x in ug1.members]
293 assert members == [self.u1.user_id]
300 assert members == [self.u1.user_id]
294 # add some user to that group
301 # add some user to that group
295
302
296 # check his permissions
303 # check his permissions
297 assert group_perms(self.anon) == {u'group1': u'group.none'}
304 assert group_perms(self.anon) == {u'group1': u'group.none'}
298 assert group_perms(self.u1) == {u'group1': u'group.none'}
305 assert group_perms(self.u1) == {u'group1': u'group.none'}
299
306
300 # grant ug1 read permissions for
307 # grant ug1 read permissions for
301 RepoGroupModel().grant_user_group_permission(
308 RepoGroupModel().grant_user_group_permission(
302 repo_group=self.g1, group_name=self.ug1, perm='group.read')
309 repo_group=self.g1, group_name=self.ug1, perm='group.read')
303 Session().commit()
310 Session().commit()
304
311
305 # check if the
312 # check if the
306 obj = Session().query(UserGroupRepoGroupToPerm)\
313 obj = Session().query(UserGroupRepoGroupToPerm)\
307 .filter(UserGroupRepoGroupToPerm.group == self.g1)\
314 .filter(UserGroupRepoGroupToPerm.group == self.g1)\
308 .filter(UserGroupRepoGroupToPerm.users_group == self.ug1)\
315 .filter(UserGroupRepoGroupToPerm.users_group == self.ug1)\
309 .scalar()
316 .scalar()
310 assert obj.permission.permission_name == 'group.read'
317 assert obj.permission.permission_name == 'group.read'
311
318
312 assert group_perms(self.anon) == {u'group1': u'group.none'}
319 assert group_perms(self.anon) == {u'group1': u'group.none'}
313 assert group_perms(self.u1) == {u'group1': u'group.read'}
320 assert group_perms(self.u1) == {u'group1': u'group.read'}
314
321
315 def test_inherited_permissions_from_default_on_user_enabled(self):
322 def test_inherited_permissions_from_default_on_user_enabled(self):
316 # enable fork and create on default user
323 # enable fork and create on default user
317 _form_result = {
324 _form_result = {
318 'default_repo_create': 'hg.create.repository',
325 'default_repo_create': 'hg.create.repository',
319 'default_fork_create': 'hg.fork.repository'
326 'default_fork_create': 'hg.fork.repository'
320 }
327 }
321 PermissionModel().set_new_user_perms(
328 PermissionModel().set_new_user_perms(
322 User.get_default_user(), _form_result)
329 User.get_default_user(), _form_result)
323 Session().commit()
330 Session().commit()
324
331
325 # make sure inherit flag is turned on
332 # make sure inherit flag is turned on
326 self.u1.inherit_default_permissions = True
333 self.u1.inherit_default_permissions = True
327 Session().commit()
334 Session().commit()
328
335
329 # this user will have inherited permissions from default user
336 # this user will have inherited permissions from default user
330 assert global_perms(self.u1) == default_perms()
337 assert global_perms(self.u1) == default_perms()
331
338
332 def test_inherited_permissions_from_default_on_user_disabled(self):
339 def test_inherited_permissions_from_default_on_user_disabled(self):
333 # disable fork and create on default user
340 # disable fork and create on default user
334 _form_result = {
341 _form_result = {
335 'default_repo_create': 'hg.create.none',
342 'default_repo_create': 'hg.create.none',
336 'default_fork_create': 'hg.fork.none'
343 'default_fork_create': 'hg.fork.none'
337 }
344 }
338 PermissionModel().set_new_user_perms(
345 PermissionModel().set_new_user_perms(
339 User.get_default_user(), _form_result)
346 User.get_default_user(), _form_result)
340 Session().commit()
347 Session().commit()
341
348
342 # make sure inherit flag is turned on
349 # make sure inherit flag is turned on
343 self.u1.inherit_default_permissions = True
350 self.u1.inherit_default_permissions = True
344 Session().commit()
351 Session().commit()
345
352
346 # this user will have inherited permissions from default user
353 # this user will have inherited permissions from default user
347 expected_perms = default_perms(
354 expected_perms = default_perms(
348 added=['hg.create.none', 'hg.fork.none'],
355 added=['hg.create.none', 'hg.fork.none'],
349 removed=['hg.create.repository', 'hg.fork.repository'])
356 removed=['hg.create.repository', 'hg.fork.repository'])
350 assert global_perms(self.u1) == expected_perms
357 assert global_perms(self.u1) == expected_perms
351
358
352 def test_non_inherited_permissions_from_default_on_user_enabled(self):
359 def test_non_inherited_permissions_from_default_on_user_enabled(self):
353 user_model = UserModel()
360 user_model = UserModel()
354 # enable fork and create on default user
361 # enable fork and create on default user
355 usr = User.DEFAULT_USER
362 usr = User.DEFAULT_USER
356 user_model.revoke_perm(usr, 'hg.create.none')
363 user_model.revoke_perm(usr, 'hg.create.none')
357 user_model.grant_perm(usr, 'hg.create.repository')
364 user_model.grant_perm(usr, 'hg.create.repository')
358 user_model.revoke_perm(usr, 'hg.fork.none')
365 user_model.revoke_perm(usr, 'hg.fork.none')
359 user_model.grant_perm(usr, 'hg.fork.repository')
366 user_model.grant_perm(usr, 'hg.fork.repository')
360
367
361 # disable global perms on specific user
368 # disable global perms on specific user
362 user_model.revoke_perm(self.u1, 'hg.create.repository')
369 user_model.revoke_perm(self.u1, 'hg.create.repository')
363 user_model.grant_perm(self.u1, 'hg.create.none')
370 user_model.grant_perm(self.u1, 'hg.create.none')
364 user_model.revoke_perm(self.u1, 'hg.fork.repository')
371 user_model.revoke_perm(self.u1, 'hg.fork.repository')
365 user_model.grant_perm(self.u1, 'hg.fork.none')
372 user_model.grant_perm(self.u1, 'hg.fork.none')
366
373
367 # TODO(marcink): check branch permissions now ?
374 # TODO(marcink): check branch permissions now ?
368
375
369 # make sure inherit flag is turned off
376 # make sure inherit flag is turned off
370 self.u1.inherit_default_permissions = False
377 self.u1.inherit_default_permissions = False
371 Session().commit()
378 Session().commit()
372
379
373 # this user will have non inherited permissions from he's
380 # this user will have non inherited permissions from he's
374 # explicitly set permissions
381 # explicitly set permissions
375 assert global_perms(self.u1) == {
382 assert global_perms(self.u1) == {
376 'hg.create.none',
383 'hg.create.none',
377 'hg.fork.none',
384 'hg.fork.none',
378 'hg.register.manual_activate',
385 'hg.register.manual_activate',
379 'hg.password_reset.enabled',
386 'hg.password_reset.enabled',
380 'hg.extern_activate.auto',
387 'hg.extern_activate.auto',
381 'repository.read',
388 'repository.read',
382 'group.read',
389 'group.read',
383 'usergroup.read',
390 'usergroup.read',
384 'branch.push_force',
391 'branch.push_force',
385 }
392 }
386
393
387 def test_non_inherited_permissions_from_default_on_user_disabled(self):
394 def test_non_inherited_permissions_from_default_on_user_disabled(self):
388 user_model = UserModel()
395 user_model = UserModel()
389 # disable fork and create on default user
396 # disable fork and create on default user
390 usr = User.DEFAULT_USER
397 usr = User.DEFAULT_USER
391 user_model.revoke_perm(usr, 'hg.create.repository')
398 user_model.revoke_perm(usr, 'hg.create.repository')
392 user_model.grant_perm(usr, 'hg.create.none')
399 user_model.grant_perm(usr, 'hg.create.none')
393 user_model.revoke_perm(usr, 'hg.fork.repository')
400 user_model.revoke_perm(usr, 'hg.fork.repository')
394 user_model.grant_perm(usr, 'hg.fork.none')
401 user_model.grant_perm(usr, 'hg.fork.none')
395
402
396 # enable global perms on specific user
403 # enable global perms on specific user
397 user_model.revoke_perm(self.u1, 'hg.create.none')
404 user_model.revoke_perm(self.u1, 'hg.create.none')
398 user_model.grant_perm(self.u1, 'hg.create.repository')
405 user_model.grant_perm(self.u1, 'hg.create.repository')
399 user_model.revoke_perm(self.u1, 'hg.fork.none')
406 user_model.revoke_perm(self.u1, 'hg.fork.none')
400 user_model.grant_perm(self.u1, 'hg.fork.repository')
407 user_model.grant_perm(self.u1, 'hg.fork.repository')
401
408
402 # make sure inherit flag is turned off
409 # make sure inherit flag is turned off
403 self.u1.inherit_default_permissions = False
410 self.u1.inherit_default_permissions = False
404 Session().commit()
411 Session().commit()
405
412
406 # TODO(marcink): check branch perms
413 # TODO(marcink): check branch perms
407
414
408 # this user will have non inherited permissions from he's
415 # this user will have non inherited permissions from he's
409 # explicitly set permissions
416 # explicitly set permissions
410 assert global_perms(self.u1) == {
417 assert global_perms(self.u1) == {
411 'hg.create.repository',
418 'hg.create.repository',
412 'hg.fork.repository',
419 'hg.fork.repository',
413 'hg.register.manual_activate',
420 'hg.register.manual_activate',
414 'hg.password_reset.enabled',
421 'hg.password_reset.enabled',
415 'hg.extern_activate.auto',
422 'hg.extern_activate.auto',
416 'repository.read',
423 'repository.read',
417 'group.read',
424 'group.read',
418 'usergroup.read',
425 'usergroup.read',
419 'branch.push_force',
426 'branch.push_force',
420 }
427 }
421
428
422 @pytest.mark.parametrize('perm, expected_perm', [
429 @pytest.mark.parametrize('perm, expected_perm', [
423 ('hg.inherit_default_perms.false', 'repository.none', ),
430 ('hg.inherit_default_perms.false', 'repository.none', ),
424 ('hg.inherit_default_perms.true', 'repository.read', ),
431 ('hg.inherit_default_perms.true', 'repository.read', ),
425 ])
432 ])
426 def test_inherited_permissions_on_objects(self, perm, expected_perm):
433 def test_inherited_permissions_on_objects(self, perm, expected_perm):
427 _form_result = {
434 _form_result = {
428 'default_inherit_default_permissions': perm,
435 'default_inherit_default_permissions': perm,
429 }
436 }
430 PermissionModel().set_new_user_perms(
437 PermissionModel().set_new_user_perms(
431 User.get_default_user(), _form_result)
438 User.get_default_user(), _form_result)
432 Session().commit()
439 Session().commit()
433
440
434 # make sure inherit flag is turned on
441 # make sure inherit flag is turned on
435 self.u1.inherit_default_permissions = True
442 self.u1.inherit_default_permissions = True
436 Session().commit()
443 Session().commit()
437
444
438 # TODO(marcink): check branch perms
445 # TODO(marcink): check branch perms
439
446
440 # this user will have inherited permissions from default user
447 # this user will have inherited permissions from default user
441 assert global_perms(self.u1) == {
448 assert global_perms(self.u1) == {
442 'hg.create.none',
449 'hg.create.none',
443 'hg.fork.none',
450 'hg.fork.none',
444 'hg.register.manual_activate',
451 'hg.register.manual_activate',
445 'hg.password_reset.enabled',
452 'hg.password_reset.enabled',
446 'hg.extern_activate.auto',
453 'hg.extern_activate.auto',
447 'repository.read',
454 'repository.read',
448 'group.read',
455 'group.read',
449 'usergroup.read',
456 'usergroup.read',
450 'branch.push_force',
457 'branch.push_force',
451 'hg.create.write_on_repogroup.true',
458 'hg.create.write_on_repogroup.true',
452 'hg.usergroup.create.false',
459 'hg.usergroup.create.false',
453 'hg.repogroup.create.false',
460 'hg.repogroup.create.false',
454 perm
461 perm
455 }
462 }
456
463
457 assert set(repo_perms(self.u1).values()) == set([expected_perm])
464 assert set(repo_perms(self.u1).values()) == set([expected_perm])
458
465
459 def test_repo_owner_permissions_not_overwritten_by_group(self):
466 def test_repo_owner_permissions_not_overwritten_by_group(self):
460 # create repo as USER,
467 # create repo as USER,
461 self.test_repo = fixture.create_repo(name='myownrepo',
468 self.test_repo = fixture.create_repo(name='myownrepo',
462 repo_type='hg',
469 repo_type='hg',
463 cur_user=self.u1)
470 cur_user=self.u1)
464
471
465 # he has permissions of admin as owner
472 # he has permissions of admin as owner
466 assert repo_perms(self.u1)['myownrepo'] == 'repository.admin'
473 assert repo_perms(self.u1)['myownrepo'] == 'repository.admin'
467
474
468 # set his permission as user group, he should still be admin
475 # set his permission as user group, he should still be admin
469 self.ug1 = fixture.create_user_group('G1')
476 self.ug1 = fixture.create_user_group('G1')
470 UserGroupModel().add_user_to_group(self.ug1, self.u1)
477 UserGroupModel().add_user_to_group(self.ug1, self.u1)
471 RepoModel().grant_user_group_permission(
478 RepoModel().grant_user_group_permission(
472 self.test_repo,
479 self.test_repo,
473 group_name=self.ug1,
480 group_name=self.ug1,
474 perm='repository.none')
481 perm='repository.none')
475 Session().commit()
482 Session().commit()
476
483
477 assert repo_perms(self.u1)['myownrepo'] == 'repository.admin'
484 assert repo_perms(self.u1)['myownrepo'] == 'repository.admin'
478
485
479 def test_repo_owner_permissions_not_overwritten_by_others(self):
486 def test_repo_owner_permissions_not_overwritten_by_others(self):
480 # create repo as USER,
487 # create repo as USER,
481 self.test_repo = fixture.create_repo(name='myownrepo',
488 self.test_repo = fixture.create_repo(name='myownrepo',
482 repo_type='hg',
489 repo_type='hg',
483 cur_user=self.u1)
490 cur_user=self.u1)
484
491
485 # he has permissions of admin as owner
492 # he has permissions of admin as owner
486 assert repo_perms(self.u1)['myownrepo'] == 'repository.admin'
493 assert repo_perms(self.u1)['myownrepo'] == 'repository.admin'
487
494
488 # set his permission as user, he should still be admin
495 # set his permission as user, he should still be admin
489 RepoModel().grant_user_permission(
496 RepoModel().grant_user_permission(
490 self.test_repo, user=self.u1, perm='repository.none')
497 self.test_repo, user=self.u1, perm='repository.none')
491 Session().commit()
498 Session().commit()
492
499
493 assert repo_perms(self.u1)['myownrepo'] == 'repository.admin'
500 assert repo_perms(self.u1)['myownrepo'] == 'repository.admin'
494
501
495 def test_repo_group_owner_permissions_not_overwritten_by_group(self):
502 def test_repo_group_owner_permissions_not_overwritten_by_group(self):
496 # "u1" shall be owner without any special permission assigned
503 # "u1" shall be owner without any special permission assigned
497 self.g1 = fixture.create_repo_group('test1')
504 self.g1 = fixture.create_repo_group('test1')
498
505
499 # Make user group and grant a permission to user group
506 # Make user group and grant a permission to user group
500 self.ug1 = fixture.create_user_group('G1')
507 self.ug1 = fixture.create_user_group('G1')
501 UserGroupModel().add_user_to_group(self.ug1, self.u1)
508 UserGroupModel().add_user_to_group(self.ug1, self.u1)
502 RepoGroupModel().grant_user_group_permission(
509 RepoGroupModel().grant_user_group_permission(
503 repo_group=self.g1, group_name=self.ug1, perm='group.write')
510 repo_group=self.g1, group_name=self.ug1, perm='group.write')
511 Session().commit()
504
512
505 # Verify that user does not get any special permission if he is not
513 # Verify that user does not get any special permission if he is not
506 # owner
514 # owner
507 assert group_perms(self.u1) == {'test1': 'group.write'}
515 assert group_perms(self.u1) == {'test1': 'group.write'}
508
516
509 # Make him owner of the repo group
517 # Make him owner of the repo group
510 self.g1.user = self.u1
518 self.g1.user = self.u1
511 assert group_perms(self.u1) == {'test1': 'group.admin'}
519 assert group_perms(self.u1) == {'test1': 'group.admin'}
512
520
513 def test_repo_group_owner_permissions_not_overwritten_by_others(self):
521 def test_repo_group_owner_permissions_not_overwritten_by_others(self):
514 # "u1" shall be owner without any special permission assigned
522 # "u1" shall be owner without any special permission assigned
515 self.g1 = fixture.create_repo_group('test1')
523 self.g1 = fixture.create_repo_group('test1')
516 RepoGroupModel().grant_user_permission(
524 RepoGroupModel().grant_user_permission(
517 repo_group=self.g1, user=self.u1, perm='group.write')
525 repo_group=self.g1, user=self.u1, perm='group.write')
526 Session().commit()
518
527
519 # Verify that user does not get any special permission if he is not
528 # Verify that user does not get any special permission if he is not
520 # owner
529 # owner
521 assert group_perms(self.u1) == {'test1': 'group.write'}
530 assert group_perms(self.u1) == {'test1': 'group.write'}
522
531
523 # Make him owner of the repo group
532 # Make him owner of the repo group
524 self.g1.user = self.u1
533 self.g1.user = self.u1
525 assert group_perms(self.u1) == {u'test1': 'group.admin'}
534 assert group_perms(self.u1) == {u'test1': 'group.admin'}
526
535
527 def _test_def_user_perm_equal(
536 def assert_user_perm_equal(
528 self, user, change_factor=0, compare_keys=None):
537 self, user, change_factor=0, compare_keys=None):
529 perms = UserToPerm.query().filter(UserToPerm.user == user).all()
538 perms = UserToPerm.query().filter(UserToPerm.user == user).all()
530 assert len(perms) == \
539 assert len(perms) == \
531 len(Permission.DEFAULT_USER_PERMISSIONS) + change_factor
540 len(Permission.DEFAULT_USER_PERMISSIONS) + change_factor
532 if compare_keys:
541 if compare_keys:
533 assert set(
542 assert set(
534 x.permissions.permission_name for x in perms) == compare_keys
543 x.permissions.permission_name for x in perms) == compare_keys
535
544
536 def _test_def_user_group_perm_equal(
545 def assert_def_user_group_perm_equal(
537 self, user_group, change_factor=0, compare_keys=None):
546 self, user_group, change_factor=0, compare_keys=None):
538 perms = UserGroupToPerm.query().filter(
547 perms = UserGroupToPerm.query().filter(
539 UserGroupToPerm.users_group == user_group).all()
548 UserGroupToPerm.users_group == user_group).all()
540 assert len(perms) == \
549 assert len(perms) == \
541 len(Permission.DEFAULT_USER_PERMISSIONS) + change_factor
550 len(Permission.DEFAULT_USER_PERMISSIONS) + change_factor
542 if compare_keys:
551 if compare_keys:
543 assert set(
552 assert set(
544 x.permissions.permission_name for x in perms) == compare_keys
553 x.permissions.permission_name for x in perms) == compare_keys
545
554
546 def test_set_default_permissions(self):
555 def test_set_default_permissions(self):
547 PermissionModel().create_default_user_permissions(user=self.u1)
556 PermissionModel().create_default_user_permissions(user=self.u1)
548 self._test_def_user_perm_equal(user=self.u1)
557 self.assert_user_perm_equal(user=self.u1)
549
558
550 def test_set_default_permissions_after_one_is_missing(self):
559 def test_set_default_permissions_after_one_is_missing(self):
551 PermissionModel().create_default_user_permissions(user=self.u1)
560 PermissionModel().create_default_user_permissions(user=self.u1)
552 self._test_def_user_perm_equal(user=self.u1)
561 self.assert_user_perm_equal(user=self.u1)
553 # now we delete one, it should be re-created after another call
562 # now we delete one, it should be re-created after another call
554 perms = UserToPerm.query().filter(UserToPerm.user == self.u1).all()
563 perms = UserToPerm.query().filter(UserToPerm.user == self.u1).all()
555 Session().delete(perms[0])
564 Session().delete(perms[0])
556 Session().commit()
565 Session().commit()
557
566
558 self._test_def_user_perm_equal(user=self.u1, change_factor=-1)
567 self.assert_user_perm_equal(user=self.u1, change_factor=-1)
559
568
560 # create missing one !
569 # create missing one !
561 PermissionModel().create_default_user_permissions(user=self.u1)
570 PermissionModel().create_default_user_permissions(user=self.u1)
562 self._test_def_user_perm_equal(user=self.u1)
571 self.assert_user_perm_equal(user=self.u1)
563
572
564 @pytest.mark.parametrize("perm, modify_to", [
573 @pytest.mark.parametrize("perm, modify_to", [
565 ('repository.read', 'repository.none'),
574 ('repository.read', 'repository.none'),
566 ('group.read', 'group.none'),
575 ('group.read', 'group.none'),
567 ('usergroup.read', 'usergroup.none'),
576 ('usergroup.read', 'usergroup.none'),
568 ('hg.create.repository', 'hg.create.none'),
577 ('hg.create.repository', 'hg.create.none'),
569 ('hg.fork.repository', 'hg.fork.none'),
578 ('hg.fork.repository', 'hg.fork.none'),
570 ('hg.register.manual_activate', 'hg.register.auto_activate',)
579 ('hg.register.manual_activate', 'hg.register.auto_activate',)
571 ])
580 ])
572 def test_set_default_permissions_after_modification(self, perm, modify_to):
581 def test_set_default_permissions_after_modification(self, perm, modify_to):
573 PermissionModel().create_default_user_permissions(user=self.u1)
582 PermissionModel().create_default_user_permissions(user=self.u1)
574 self._test_def_user_perm_equal(user=self.u1)
583 self.assert_user_perm_equal(user=self.u1)
575
584
576 old = Permission.get_by_key(perm)
585 old = Permission.get_by_key(perm)
577 new = Permission.get_by_key(modify_to)
586 new = Permission.get_by_key(modify_to)
578 assert old is not None
587 assert old is not None
579 assert new is not None
588 assert new is not None
580
589
581 # now modify permissions
590 # now modify permissions
582 p = UserToPerm.query().filter(
591 p = UserToPerm.query().filter(
583 UserToPerm.user == self.u1).filter(
592 UserToPerm.user == self.u1).filter(
584 UserToPerm.permission == old).one()
593 UserToPerm.permission == old).one()
585 p.permission = new
594 p.permission = new
586 Session().add(p)
595 Session().add(p)
587 Session().commit()
596 Session().commit()
588
597
589 PermissionModel().create_default_user_permissions(user=self.u1)
598 PermissionModel().create_default_user_permissions(user=self.u1)
590 self._test_def_user_perm_equal(user=self.u1)
599 self.assert_user_perm_equal(user=self.u1)
591
600
592 def test_clear_user_perms(self):
601 def test_clear_user_perms(self):
593 PermissionModel().create_default_user_permissions(user=self.u1)
602 PermissionModel().create_default_user_permissions(user=self.u1)
594 self._test_def_user_perm_equal(user=self.u1)
603 self.assert_user_perm_equal(user=self.u1)
595
604
596 # now clear permissions
605 # now clear permissions
597 cleared = PermissionModel()._clear_user_perms(self.u1.user_id)
606 cleared = PermissionModel()._clear_user_perms(self.u1.user_id)
598 self._test_def_user_perm_equal(user=self.u1,
607 self.assert_user_perm_equal(user=self.u1,
599 change_factor=len(cleared)*-1)
608 change_factor=len(cleared)*-1)
600
609
601 def test_clear_user_group_perms(self):
610 def test_clear_user_group_perms(self):
602 self.ug1 = fixture.create_user_group('G1')
611 self.ug1 = fixture.create_user_group('G1')
603 PermissionModel().create_default_user_group_permissions(
612 PermissionModel().create_default_user_group_permissions(
604 user_group=self.ug1)
613 user_group=self.ug1)
605 self._test_def_user_group_perm_equal(user_group=self.ug1)
614 self.assert_def_user_group_perm_equal(user_group=self.ug1)
606
615
607 # now clear permissions
616 # now clear permissions
608 cleared = PermissionModel()._clear_user_group_perms(
617 cleared = PermissionModel()._clear_user_group_perms(
609 self.ug1.users_group_id)
618 self.ug1.users_group_id)
610 self._test_def_user_group_perm_equal(user_group=self.ug1,
619 self.assert_def_user_group_perm_equal(user_group=self.ug1,
611 change_factor=len(cleared)*-1)
620 change_factor=len(cleared)*-1)
612
621
613 @pytest.mark.parametrize("form_result", [
622 @pytest.mark.parametrize("form_result", [
614 {},
623 {},
615 {'default_repo_create': 'hg.create.repository'},
624 {'default_repo_create': 'hg.create.repository'},
616 {'default_repo_create': 'hg.create.repository',
625 {'default_repo_create': 'hg.create.repository',
617 'default_repo_perm': 'repository.read'},
626 'default_repo_perm': 'repository.read'},
618 {'default_repo_create': 'hg.create.none',
627 {'default_repo_create': 'hg.create.none',
619 'default_repo_perm': 'repository.write',
628 'default_repo_perm': 'repository.write',
620 'default_fork_create': 'hg.fork.none'},
629 'default_fork_create': 'hg.fork.none'},
621 ])
630 ])
622 def test_set_new_user_permissions(self, form_result):
631 def test_set_new_user_permissions(self, form_result):
623 _form_result = {}
632 _form_result = {}
624 _form_result.update(form_result)
633 _form_result.update(form_result)
625 PermissionModel().set_new_user_perms(self.u1, _form_result)
634 PermissionModel().set_new_user_perms(self.u1, _form_result)
626 Session().commit()
635 Session().commit()
627 change_factor = -1 * (len(Permission.DEFAULT_USER_PERMISSIONS)
636 change_factor = -1 * (len(Permission.DEFAULT_USER_PERMISSIONS)
628 - len(form_result.keys()))
637 - len(form_result.keys()))
629 self._test_def_user_perm_equal(
638 self.assert_user_perm_equal(
630 self.u1, change_factor=change_factor)
639 self.u1, change_factor=change_factor)
631
640
632 @pytest.mark.parametrize("form_result", [
641 @pytest.mark.parametrize("form_result", [
633 {},
642 {},
634 {'default_repo_create': 'hg.create.repository'},
643 {'default_repo_create': 'hg.create.repository'},
635 {'default_repo_create': 'hg.create.repository',
644 {'default_repo_create': 'hg.create.repository',
636 'default_repo_perm': 'repository.read'},
645 'default_repo_perm': 'repository.read'},
637 {'default_repo_create': 'hg.create.none',
646 {'default_repo_create': 'hg.create.none',
638 'default_repo_perm': 'repository.write',
647 'default_repo_perm': 'repository.write',
639 'default_fork_create': 'hg.fork.none'},
648 'default_fork_create': 'hg.fork.none'},
640 ])
649 ])
641 def test_set_new_user_group_permissions(self, form_result):
650 def test_set_new_user_group_permissions(self, form_result):
642 _form_result = {}
651 _form_result = {}
643 _form_result.update(form_result)
652 _form_result.update(form_result)
644 self.ug1 = fixture.create_user_group('G1')
653 self.ug1 = fixture.create_user_group('G1')
645 PermissionModel().set_new_user_group_perms(self.ug1, _form_result)
654 PermissionModel().set_new_user_group_perms(self.ug1, _form_result)
646 Session().commit()
655 Session().commit()
647 change_factor = -1 * (len(Permission.DEFAULT_USER_PERMISSIONS)
656 change_factor = -1 * (len(Permission.DEFAULT_USER_PERMISSIONS)
648 - len(form_result.keys()))
657 - len(form_result.keys()))
649 self._test_def_user_group_perm_equal(
658 self.assert_def_user_group_perm_equal(
650 self.ug1, change_factor=change_factor)
659 self.ug1, change_factor=change_factor)
651
660
652 @pytest.mark.parametrize("group_active, expected_perm", [
661 @pytest.mark.parametrize("group_active, expected_perm", [
653 (True, 'repository.admin'),
662 (True, 'repository.admin'),
654 (False, 'repository.read'),
663 (False, 'repository.read'),
655 ])
664 ])
656 def test_get_default_repo_perms_from_user_group_with_active_group(
665 def test_get_default_repo_perms_from_user_group_with_active_group(
657 self, backend, user_util, group_active, expected_perm):
666 self, backend, user_util, group_active, expected_perm):
658 repo = backend.create_repo()
667 repo = backend.create_repo()
659 user = user_util.create_user()
668 user = user_util.create_user()
660 user_group = user_util.create_user_group(
669 user_group = user_util.create_user_group(
661 members=[user], users_group_active=group_active)
670 members=[user], users_group_active=group_active)
662
671
663 user_util.grant_user_group_permission_to_repo(
672 user_util.grant_user_group_permission_to_repo(
664 repo, user_group, 'repository.admin')
673 repo, user_group, 'repository.admin')
665 permissions = repo_perms(user)
674 permissions = repo_perms(user)
666 repo_permission = permissions.get(repo.repo_name)
675 repo_permission = permissions.get(repo.repo_name)
667 assert repo_permission == expected_perm
676 assert repo_permission == expected_perm
668
677
669 @pytest.mark.parametrize("group_active, expected_perm", [
678 @pytest.mark.parametrize("group_active, expected_perm", [
670 (True, 'group.admin'),
679 (True, 'group.admin'),
671 (False, 'group.read')
680 (False, 'group.read')
672 ])
681 ])
673 def test_get_default_group_perms_from_user_group_with_active_group(
682 def test_get_default_group_perms_from_user_group_with_active_group(
674 self, user_util, group_active, expected_perm):
683 self, user_util, group_active, expected_perm):
675 user = user_util.create_user()
684 user = user_util.create_user()
676 repo_group = user_util.create_repo_group()
685 repo_group = user_util.create_repo_group()
677 user_group = user_util.create_user_group(
686 user_group = user_util.create_user_group(
678 members=[user], users_group_active=group_active)
687 members=[user], users_group_active=group_active)
679
688
680 user_util.grant_user_group_permission_to_repo_group(
689 user_util.grant_user_group_permission_to_repo_group(
681 repo_group, user_group, 'group.admin')
690 repo_group, user_group, 'group.admin')
682 permissions = group_perms(user)
691 permissions = group_perms(user)
683 group_permission = permissions.get(repo_group.name)
692 group_permission = permissions.get(repo_group.name)
684 assert group_permission == expected_perm
693 assert group_permission == expected_perm
685
694
686 @pytest.mark.parametrize("group_active, expected_perm", [
695 @pytest.mark.parametrize("group_active, expected_perm", [
687 (True, 'usergroup.admin'),
696 (True, 'usergroup.admin'),
688 (False, 'usergroup.read')
697 (False, 'usergroup.read')
689 ])
698 ])
690 def test_get_default_user_group_perms_from_user_group_with_active_group(
699 def test_get_default_user_group_perms_from_user_group_with_active_group(
691 self, user_util, group_active, expected_perm):
700 self, user_util, group_active, expected_perm):
692 user = user_util.create_user()
701 user = user_util.create_user()
693 user_group = user_util.create_user_group(
702 user_group = user_util.create_user_group(
694 members=[user], users_group_active=group_active)
703 members=[user], users_group_active=group_active)
695 target_user_group = user_util.create_user_group()
704 target_user_group = user_util.create_user_group()
696
705
697 user_util.grant_user_group_permission_to_user_group(
706 user_util.grant_user_group_permission_to_user_group(
698 target_user_group, user_group, 'usergroup.admin')
707 target_user_group, user_group, 'usergroup.admin')
699 permissions = user_group_perms(user)
708 permissions = user_group_perms(user)
700 group_permission = permissions.get(target_user_group.users_group_name)
709 group_permission = permissions.get(target_user_group.users_group_name)
701 assert group_permission == expected_perm
710 assert group_permission == expected_perm
702
711
703
712
704 def repo_perms(user):
713 def repo_perms(user):
705 auth_user = AuthUser(user_id=user.user_id)
714 auth_user = AuthUser(user_id=user.user_id)
706 return auth_user.permissions['repositories']
715 return auth_user.permissions['repositories']
707
716
708
717
709 def branch_perms(user):
718 def branch_perms(user):
710 auth_user = AuthUser(user_id=user.user_id)
719 auth_user = AuthUser(user_id=user.user_id)
711 return auth_user.permissions['repository_branches']
720 return auth_user.permissions['repository_branches']
712
721
713
722
714 def group_perms(user):
723 def group_perms(user):
715 auth_user = AuthUser(user_id=user.user_id)
724 auth_user = AuthUser(user_id=user.user_id)
716 return auth_user.permissions['repositories_groups']
725 return auth_user.permissions['repositories_groups']
717
726
718
727
719 def user_group_perms(user):
728 def user_group_perms(user):
720 auth_user = AuthUser(user_id=user.user_id)
729 auth_user = AuthUser(user_id=user.user_id)
721 return auth_user.permissions['user_groups']
730 return auth_user.permissions['user_groups']
722
731
723
732
724 def global_perms(user):
733 def global_perms(user):
725 auth_user = AuthUser(user_id=user.user_id)
734 auth_user = AuthUser(user_id=user.user_id)
726 return auth_user.permissions['global']
735 return auth_user.permissions['global']
727
736
728
737
729 def default_perms(added=None, removed=None):
738 def default_perms(added=None, removed=None):
730 expected_perms = set(Permission.DEFAULT_USER_PERMISSIONS)
739 expected_perms = set(Permission.DEFAULT_USER_PERMISSIONS)
731 if removed:
740 if removed:
732 expected_perms.difference_update(removed)
741 expected_perms.difference_update(removed)
733 if added:
742 if added:
734 expected_perms.update(added)
743 expected_perms.update(added)
735 return expected_perms
744 return expected_perms
@@ -1,949 +1,964 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 mock
21 import mock
22 import pytest
22 import pytest
23 import textwrap
23 import textwrap
24
24
25 import rhodecode
25 import rhodecode
26 from rhodecode.lib.utils2 import safe_unicode
26 from rhodecode.lib.utils2 import safe_unicode
27 from rhodecode.lib.vcs.backends import get_backend
27 from rhodecode.lib.vcs.backends import get_backend
28 from rhodecode.lib.vcs.backends.base import (
28 from rhodecode.lib.vcs.backends.base import (
29 MergeResponse, MergeFailureReason, Reference)
29 MergeResponse, MergeFailureReason, Reference)
30 from rhodecode.lib.vcs.exceptions import RepositoryError
30 from rhodecode.lib.vcs.exceptions import RepositoryError
31 from rhodecode.lib.vcs.nodes import FileNode
31 from rhodecode.lib.vcs.nodes import FileNode
32 from rhodecode.model.comment import CommentsModel
32 from rhodecode.model.comment import CommentsModel
33 from rhodecode.model.db import PullRequest, Session
33 from rhodecode.model.db import PullRequest, Session
34 from rhodecode.model.pull_request import PullRequestModel
34 from rhodecode.model.pull_request import PullRequestModel
35 from rhodecode.model.user import UserModel
35 from rhodecode.model.user import UserModel
36 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
36 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
37
37
38
38
39 pytestmark = [
39 pytestmark = [
40 pytest.mark.backends("git", "hg"),
40 pytest.mark.backends("git", "hg"),
41 ]
41 ]
42
42
43
43
44 @pytest.mark.usefixtures('config_stub')
44 @pytest.mark.usefixtures('config_stub')
45 class TestPullRequestModel(object):
45 class TestPullRequestModel(object):
46
46
47 @pytest.fixture()
47 @pytest.fixture()
48 def pull_request(self, request, backend, pr_util):
48 def pull_request(self, request, backend, pr_util):
49 """
49 """
50 A pull request combined with multiples patches.
50 A pull request combined with multiples patches.
51 """
51 """
52 BackendClass = get_backend(backend.alias)
52 BackendClass = get_backend(backend.alias)
53 merge_resp = MergeResponse(
53 merge_resp = MergeResponse(
54 False, False, None, MergeFailureReason.UNKNOWN,
54 False, False, None, MergeFailureReason.UNKNOWN,
55 metadata={'exception': 'MockError'})
55 metadata={'exception': 'MockError'})
56 self.merge_patcher = mock.patch.object(
56 self.merge_patcher = mock.patch.object(
57 BackendClass, 'merge', return_value=merge_resp)
57 BackendClass, 'merge', return_value=merge_resp)
58 self.workspace_remove_patcher = mock.patch.object(
58 self.workspace_remove_patcher = mock.patch.object(
59 BackendClass, 'cleanup_merge_workspace')
59 BackendClass, 'cleanup_merge_workspace')
60
60
61 self.workspace_remove_mock = self.workspace_remove_patcher.start()
61 self.workspace_remove_mock = self.workspace_remove_patcher.start()
62 self.merge_mock = self.merge_patcher.start()
62 self.merge_mock = self.merge_patcher.start()
63 self.comment_patcher = mock.patch(
63 self.comment_patcher = mock.patch(
64 'rhodecode.model.changeset_status.ChangesetStatusModel.set_status')
64 'rhodecode.model.changeset_status.ChangesetStatusModel.set_status')
65 self.comment_patcher.start()
65 self.comment_patcher.start()
66 self.notification_patcher = mock.patch(
66 self.notification_patcher = mock.patch(
67 'rhodecode.model.notification.NotificationModel.create')
67 'rhodecode.model.notification.NotificationModel.create')
68 self.notification_patcher.start()
68 self.notification_patcher.start()
69 self.helper_patcher = mock.patch(
69 self.helper_patcher = mock.patch(
70 'rhodecode.lib.helpers.route_path')
70 'rhodecode.lib.helpers.route_path')
71 self.helper_patcher.start()
71 self.helper_patcher.start()
72
72
73 self.hook_patcher = mock.patch.object(PullRequestModel,
73 self.hook_patcher = mock.patch.object(PullRequestModel,
74 'trigger_pull_request_hook')
74 'trigger_pull_request_hook')
75 self.hook_mock = self.hook_patcher.start()
75 self.hook_mock = self.hook_patcher.start()
76
76
77 self.invalidation_patcher = mock.patch(
77 self.invalidation_patcher = mock.patch(
78 'rhodecode.model.pull_request.ScmModel.mark_for_invalidation')
78 'rhodecode.model.pull_request.ScmModel.mark_for_invalidation')
79 self.invalidation_mock = self.invalidation_patcher.start()
79 self.invalidation_mock = self.invalidation_patcher.start()
80
80
81 self.pull_request = pr_util.create_pull_request(
81 self.pull_request = pr_util.create_pull_request(
82 mergeable=True, name_suffix=u'Δ…Δ‡')
82 mergeable=True, name_suffix=u'Δ…Δ‡')
83 self.source_commit = self.pull_request.source_ref_parts.commit_id
83 self.source_commit = self.pull_request.source_ref_parts.commit_id
84 self.target_commit = self.pull_request.target_ref_parts.commit_id
84 self.target_commit = self.pull_request.target_ref_parts.commit_id
85 self.workspace_id = 'pr-%s' % self.pull_request.pull_request_id
85 self.workspace_id = 'pr-%s' % self.pull_request.pull_request_id
86 self.repo_id = self.pull_request.target_repo.repo_id
86 self.repo_id = self.pull_request.target_repo.repo_id
87
87
88 @request.addfinalizer
88 @request.addfinalizer
89 def cleanup_pull_request():
89 def cleanup_pull_request():
90 calls = [mock.call(
90 calls = [mock.call(
91 self.pull_request, self.pull_request.author, 'create')]
91 self.pull_request, self.pull_request.author, 'create')]
92 self.hook_mock.assert_has_calls(calls)
92 self.hook_mock.assert_has_calls(calls)
93
93
94 self.workspace_remove_patcher.stop()
94 self.workspace_remove_patcher.stop()
95 self.merge_patcher.stop()
95 self.merge_patcher.stop()
96 self.comment_patcher.stop()
96 self.comment_patcher.stop()
97 self.notification_patcher.stop()
97 self.notification_patcher.stop()
98 self.helper_patcher.stop()
98 self.helper_patcher.stop()
99 self.hook_patcher.stop()
99 self.hook_patcher.stop()
100 self.invalidation_patcher.stop()
100 self.invalidation_patcher.stop()
101
101
102 return self.pull_request
102 return self.pull_request
103
103
104 def test_get_all(self, pull_request):
104 def test_get_all(self, pull_request):
105 prs = PullRequestModel().get_all(pull_request.target_repo)
105 prs = PullRequestModel().get_all(pull_request.target_repo)
106 assert isinstance(prs, list)
106 assert isinstance(prs, list)
107 assert len(prs) == 1
107 assert len(prs) == 1
108
108
109 def test_count_all(self, pull_request):
109 def test_count_all(self, pull_request):
110 pr_count = PullRequestModel().count_all(pull_request.target_repo)
110 pr_count = PullRequestModel().count_all(pull_request.target_repo)
111 assert pr_count == 1
111 assert pr_count == 1
112
112
113 def test_get_awaiting_review(self, pull_request):
113 def test_get_awaiting_review(self, pull_request):
114 prs = PullRequestModel().get_awaiting_review(pull_request.target_repo)
114 prs = PullRequestModel().get_awaiting_review(pull_request.target_repo)
115 assert isinstance(prs, list)
115 assert isinstance(prs, list)
116 assert len(prs) == 1
116 assert len(prs) == 1
117
117
118 def test_count_awaiting_review(self, pull_request):
118 def test_count_awaiting_review(self, pull_request):
119 pr_count = PullRequestModel().count_awaiting_review(
119 pr_count = PullRequestModel().count_awaiting_review(
120 pull_request.target_repo)
120 pull_request.target_repo)
121 assert pr_count == 1
121 assert pr_count == 1
122
122
123 def test_get_awaiting_my_review(self, pull_request):
123 def test_get_awaiting_my_review(self, pull_request):
124 PullRequestModel().update_reviewers(
124 PullRequestModel().update_reviewers(
125 pull_request, [(pull_request.author, ['author'], False, [])],
125 pull_request, [(pull_request.author, ['author'], False, [])],
126 pull_request.author)
126 pull_request.author)
127 Session().commit()
128
127 prs = PullRequestModel().get_awaiting_my_review(
129 prs = PullRequestModel().get_awaiting_my_review(
128 pull_request.target_repo, user_id=pull_request.author.user_id)
130 pull_request.target_repo, user_id=pull_request.author.user_id)
129 assert isinstance(prs, list)
131 assert isinstance(prs, list)
130 assert len(prs) == 1
132 assert len(prs) == 1
131
133
132 def test_count_awaiting_my_review(self, pull_request):
134 def test_count_awaiting_my_review(self, pull_request):
133 PullRequestModel().update_reviewers(
135 PullRequestModel().update_reviewers(
134 pull_request, [(pull_request.author, ['author'], False, [])],
136 pull_request, [(pull_request.author, ['author'], False, [])],
135 pull_request.author)
137 pull_request.author)
138 Session().commit()
139
136 pr_count = PullRequestModel().count_awaiting_my_review(
140 pr_count = PullRequestModel().count_awaiting_my_review(
137 pull_request.target_repo, user_id=pull_request.author.user_id)
141 pull_request.target_repo, user_id=pull_request.author.user_id)
138 assert pr_count == 1
142 assert pr_count == 1
139
143
140 def test_delete_calls_cleanup_merge(self, pull_request):
144 def test_delete_calls_cleanup_merge(self, pull_request):
141 repo_id = pull_request.target_repo.repo_id
145 repo_id = pull_request.target_repo.repo_id
142 PullRequestModel().delete(pull_request, pull_request.author)
146 PullRequestModel().delete(pull_request, pull_request.author)
147 Session().commit()
143
148
144 self.workspace_remove_mock.assert_called_once_with(
149 self.workspace_remove_mock.assert_called_once_with(
145 repo_id, self.workspace_id)
150 repo_id, self.workspace_id)
146
151
147 def test_close_calls_cleanup_and_hook(self, pull_request):
152 def test_close_calls_cleanup_and_hook(self, pull_request):
148 PullRequestModel().close_pull_request(
153 PullRequestModel().close_pull_request(
149 pull_request, pull_request.author)
154 pull_request, pull_request.author)
155 Session().commit()
156
150 repo_id = pull_request.target_repo.repo_id
157 repo_id = pull_request.target_repo.repo_id
151
158
152 self.workspace_remove_mock.assert_called_once_with(
159 self.workspace_remove_mock.assert_called_once_with(
153 repo_id, self.workspace_id)
160 repo_id, self.workspace_id)
154 self.hook_mock.assert_called_with(
161 self.hook_mock.assert_called_with(
155 self.pull_request, self.pull_request.author, 'close')
162 self.pull_request, self.pull_request.author, 'close')
156
163
157 def test_merge_status(self, pull_request):
164 def test_merge_status(self, pull_request):
158 self.merge_mock.return_value = MergeResponse(
165 self.merge_mock.return_value = MergeResponse(
159 True, False, None, MergeFailureReason.NONE)
166 True, False, None, MergeFailureReason.NONE)
160
167
161 assert pull_request._last_merge_source_rev is None
168 assert pull_request._last_merge_source_rev is None
162 assert pull_request._last_merge_target_rev is None
169 assert pull_request._last_merge_target_rev is None
163 assert pull_request.last_merge_status is None
170 assert pull_request.last_merge_status is None
164
171
165 status, msg = PullRequestModel().merge_status(pull_request)
172 status, msg = PullRequestModel().merge_status(pull_request)
166 assert status is True
173 assert status is True
167 assert msg == 'This pull request can be automatically merged.'
174 assert msg == 'This pull request can be automatically merged.'
168 self.merge_mock.assert_called_with(
175 self.merge_mock.assert_called_with(
169 self.repo_id, self.workspace_id,
176 self.repo_id, self.workspace_id,
170 pull_request.target_ref_parts,
177 pull_request.target_ref_parts,
171 pull_request.source_repo.scm_instance(),
178 pull_request.source_repo.scm_instance(),
172 pull_request.source_ref_parts, dry_run=True,
179 pull_request.source_ref_parts, dry_run=True,
173 use_rebase=False, close_branch=False)
180 use_rebase=False, close_branch=False)
174
181
175 assert pull_request._last_merge_source_rev == self.source_commit
182 assert pull_request._last_merge_source_rev == self.source_commit
176 assert pull_request._last_merge_target_rev == self.target_commit
183 assert pull_request._last_merge_target_rev == self.target_commit
177 assert pull_request.last_merge_status is MergeFailureReason.NONE
184 assert pull_request.last_merge_status is MergeFailureReason.NONE
178
185
179 self.merge_mock.reset_mock()
186 self.merge_mock.reset_mock()
180 status, msg = PullRequestModel().merge_status(pull_request)
187 status, msg = PullRequestModel().merge_status(pull_request)
181 assert status is True
188 assert status is True
182 assert msg == 'This pull request can be automatically merged.'
189 assert msg == 'This pull request can be automatically merged.'
183 assert self.merge_mock.called is False
190 assert self.merge_mock.called is False
184
191
185 def test_merge_status_known_failure(self, pull_request):
192 def test_merge_status_known_failure(self, pull_request):
186 self.merge_mock.return_value = MergeResponse(
193 self.merge_mock.return_value = MergeResponse(
187 False, False, None, MergeFailureReason.MERGE_FAILED)
194 False, False, None, MergeFailureReason.MERGE_FAILED)
188
195
189 assert pull_request._last_merge_source_rev is None
196 assert pull_request._last_merge_source_rev is None
190 assert pull_request._last_merge_target_rev is None
197 assert pull_request._last_merge_target_rev is None
191 assert pull_request.last_merge_status is None
198 assert pull_request.last_merge_status is None
192
199
193 status, msg = PullRequestModel().merge_status(pull_request)
200 status, msg = PullRequestModel().merge_status(pull_request)
194 assert status is False
201 assert status is False
195 assert msg == 'This pull request cannot be merged because of merge conflicts.'
202 assert msg == 'This pull request cannot be merged because of merge conflicts.'
196 self.merge_mock.assert_called_with(
203 self.merge_mock.assert_called_with(
197 self.repo_id, self.workspace_id,
204 self.repo_id, self.workspace_id,
198 pull_request.target_ref_parts,
205 pull_request.target_ref_parts,
199 pull_request.source_repo.scm_instance(),
206 pull_request.source_repo.scm_instance(),
200 pull_request.source_ref_parts, dry_run=True,
207 pull_request.source_ref_parts, dry_run=True,
201 use_rebase=False, close_branch=False)
208 use_rebase=False, close_branch=False)
202
209
203 assert pull_request._last_merge_source_rev == self.source_commit
210 assert pull_request._last_merge_source_rev == self.source_commit
204 assert pull_request._last_merge_target_rev == self.target_commit
211 assert pull_request._last_merge_target_rev == self.target_commit
205 assert (
212 assert (
206 pull_request.last_merge_status is MergeFailureReason.MERGE_FAILED)
213 pull_request.last_merge_status is MergeFailureReason.MERGE_FAILED)
207
214
208 self.merge_mock.reset_mock()
215 self.merge_mock.reset_mock()
209 status, msg = PullRequestModel().merge_status(pull_request)
216 status, msg = PullRequestModel().merge_status(pull_request)
210 assert status is False
217 assert status is False
211 assert msg == 'This pull request cannot be merged because of merge conflicts.'
218 assert msg == 'This pull request cannot be merged because of merge conflicts.'
212 assert self.merge_mock.called is False
219 assert self.merge_mock.called is False
213
220
214 def test_merge_status_unknown_failure(self, pull_request):
221 def test_merge_status_unknown_failure(self, pull_request):
215 self.merge_mock.return_value = MergeResponse(
222 self.merge_mock.return_value = MergeResponse(
216 False, False, None, MergeFailureReason.UNKNOWN,
223 False, False, None, MergeFailureReason.UNKNOWN,
217 metadata={'exception': 'MockError'})
224 metadata={'exception': 'MockError'})
218
225
219 assert pull_request._last_merge_source_rev is None
226 assert pull_request._last_merge_source_rev is None
220 assert pull_request._last_merge_target_rev is None
227 assert pull_request._last_merge_target_rev is None
221 assert pull_request.last_merge_status is None
228 assert pull_request.last_merge_status is None
222
229
223 status, msg = PullRequestModel().merge_status(pull_request)
230 status, msg = PullRequestModel().merge_status(pull_request)
224 assert status is False
231 assert status is False
225 assert msg == (
232 assert msg == (
226 'This pull request cannot be merged because of an unhandled exception. '
233 'This pull request cannot be merged because of an unhandled exception. '
227 'MockError')
234 'MockError')
228 self.merge_mock.assert_called_with(
235 self.merge_mock.assert_called_with(
229 self.repo_id, self.workspace_id,
236 self.repo_id, self.workspace_id,
230 pull_request.target_ref_parts,
237 pull_request.target_ref_parts,
231 pull_request.source_repo.scm_instance(),
238 pull_request.source_repo.scm_instance(),
232 pull_request.source_ref_parts, dry_run=True,
239 pull_request.source_ref_parts, dry_run=True,
233 use_rebase=False, close_branch=False)
240 use_rebase=False, close_branch=False)
234
241
235 assert pull_request._last_merge_source_rev is None
242 assert pull_request._last_merge_source_rev is None
236 assert pull_request._last_merge_target_rev is None
243 assert pull_request._last_merge_target_rev is None
237 assert pull_request.last_merge_status is None
244 assert pull_request.last_merge_status is None
238
245
239 self.merge_mock.reset_mock()
246 self.merge_mock.reset_mock()
240 status, msg = PullRequestModel().merge_status(pull_request)
247 status, msg = PullRequestModel().merge_status(pull_request)
241 assert status is False
248 assert status is False
242 assert msg == (
249 assert msg == (
243 'This pull request cannot be merged because of an unhandled exception. '
250 'This pull request cannot be merged because of an unhandled exception. '
244 'MockError')
251 'MockError')
245 assert self.merge_mock.called is True
252 assert self.merge_mock.called is True
246
253
247 def test_merge_status_when_target_is_locked(self, pull_request):
254 def test_merge_status_when_target_is_locked(self, pull_request):
248 pull_request.target_repo.locked = [1, u'12345.50', 'lock_web']
255 pull_request.target_repo.locked = [1, u'12345.50', 'lock_web']
249 status, msg = PullRequestModel().merge_status(pull_request)
256 status, msg = PullRequestModel().merge_status(pull_request)
250 assert status is False
257 assert status is False
251 assert msg == (
258 assert msg == (
252 'This pull request cannot be merged because the target repository '
259 'This pull request cannot be merged because the target repository '
253 'is locked by user:1.')
260 'is locked by user:1.')
254
261
255 def test_merge_status_requirements_check_target(self, pull_request):
262 def test_merge_status_requirements_check_target(self, pull_request):
256
263
257 def has_largefiles(self, repo):
264 def has_largefiles(self, repo):
258 return repo == pull_request.source_repo
265 return repo == pull_request.source_repo
259
266
260 patcher = mock.patch.object(PullRequestModel, '_has_largefiles', has_largefiles)
267 patcher = mock.patch.object(PullRequestModel, '_has_largefiles', has_largefiles)
261 with patcher:
268 with patcher:
262 status, msg = PullRequestModel().merge_status(pull_request)
269 status, msg = PullRequestModel().merge_status(pull_request)
263
270
264 assert status is False
271 assert status is False
265 assert msg == 'Target repository large files support is disabled.'
272 assert msg == 'Target repository large files support is disabled.'
266
273
267 def test_merge_status_requirements_check_source(self, pull_request):
274 def test_merge_status_requirements_check_source(self, pull_request):
268
275
269 def has_largefiles(self, repo):
276 def has_largefiles(self, repo):
270 return repo == pull_request.target_repo
277 return repo == pull_request.target_repo
271
278
272 patcher = mock.patch.object(PullRequestModel, '_has_largefiles', has_largefiles)
279 patcher = mock.patch.object(PullRequestModel, '_has_largefiles', has_largefiles)
273 with patcher:
280 with patcher:
274 status, msg = PullRequestModel().merge_status(pull_request)
281 status, msg = PullRequestModel().merge_status(pull_request)
275
282
276 assert status is False
283 assert status is False
277 assert msg == 'Source repository large files support is disabled.'
284 assert msg == 'Source repository large files support is disabled.'
278
285
279 def test_merge(self, pull_request, merge_extras):
286 def test_merge(self, pull_request, merge_extras):
280 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
287 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
281 merge_ref = Reference(
288 merge_ref = Reference(
282 'type', 'name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
289 'type', 'name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
283 self.merge_mock.return_value = MergeResponse(
290 self.merge_mock.return_value = MergeResponse(
284 True, True, merge_ref, MergeFailureReason.NONE)
291 True, True, merge_ref, MergeFailureReason.NONE)
285
292
286 merge_extras['repository'] = pull_request.target_repo.repo_name
293 merge_extras['repository'] = pull_request.target_repo.repo_name
287 PullRequestModel().merge_repo(
294 PullRequestModel().merge_repo(
288 pull_request, pull_request.author, extras=merge_extras)
295 pull_request, pull_request.author, extras=merge_extras)
296 Session().commit()
289
297
290 message = (
298 message = (
291 u'Merge pull request #{pr_id} from {source_repo} {source_ref_name}'
299 u'Merge pull request #{pr_id} from {source_repo} {source_ref_name}'
292 u'\n\n {pr_title}'.format(
300 u'\n\n {pr_title}'.format(
293 pr_id=pull_request.pull_request_id,
301 pr_id=pull_request.pull_request_id,
294 source_repo=safe_unicode(
302 source_repo=safe_unicode(
295 pull_request.source_repo.scm_instance().name),
303 pull_request.source_repo.scm_instance().name),
296 source_ref_name=pull_request.source_ref_parts.name,
304 source_ref_name=pull_request.source_ref_parts.name,
297 pr_title=safe_unicode(pull_request.title)
305 pr_title=safe_unicode(pull_request.title)
298 )
306 )
299 )
307 )
300 self.merge_mock.assert_called_with(
308 self.merge_mock.assert_called_with(
301 self.repo_id, self.workspace_id,
309 self.repo_id, self.workspace_id,
302 pull_request.target_ref_parts,
310 pull_request.target_ref_parts,
303 pull_request.source_repo.scm_instance(),
311 pull_request.source_repo.scm_instance(),
304 pull_request.source_ref_parts,
312 pull_request.source_ref_parts,
305 user_name=user.short_contact, user_email=user.email, message=message,
313 user_name=user.short_contact, user_email=user.email, message=message,
306 use_rebase=False, close_branch=False
314 use_rebase=False, close_branch=False
307 )
315 )
308 self.invalidation_mock.assert_called_once_with(
316 self.invalidation_mock.assert_called_once_with(
309 pull_request.target_repo.repo_name)
317 pull_request.target_repo.repo_name)
310
318
311 self.hook_mock.assert_called_with(
319 self.hook_mock.assert_called_with(
312 self.pull_request, self.pull_request.author, 'merge')
320 self.pull_request, self.pull_request.author, 'merge')
313
321
314 pull_request = PullRequest.get(pull_request.pull_request_id)
322 pull_request = PullRequest.get(pull_request.pull_request_id)
315 assert pull_request.merge_rev == '6126b7bfcc82ad2d3deaee22af926b082ce54cc6'
323 assert pull_request.merge_rev == '6126b7bfcc82ad2d3deaee22af926b082ce54cc6'
316
324
317 def test_merge_with_status_lock(self, pull_request, merge_extras):
325 def test_merge_with_status_lock(self, pull_request, merge_extras):
318 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
326 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
319 merge_ref = Reference(
327 merge_ref = Reference(
320 'type', 'name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
328 'type', 'name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
321 self.merge_mock.return_value = MergeResponse(
329 self.merge_mock.return_value = MergeResponse(
322 True, True, merge_ref, MergeFailureReason.NONE)
330 True, True, merge_ref, MergeFailureReason.NONE)
323
331
324 merge_extras['repository'] = pull_request.target_repo.repo_name
332 merge_extras['repository'] = pull_request.target_repo.repo_name
325
333
326 with pull_request.set_state(PullRequest.STATE_UPDATING):
334 with pull_request.set_state(PullRequest.STATE_UPDATING):
327 assert pull_request.pull_request_state == PullRequest.STATE_UPDATING
335 assert pull_request.pull_request_state == PullRequest.STATE_UPDATING
328 PullRequestModel().merge_repo(
336 PullRequestModel().merge_repo(
329 pull_request, pull_request.author, extras=merge_extras)
337 pull_request, pull_request.author, extras=merge_extras)
338 Session().commit()
330
339
331 assert pull_request.pull_request_state == PullRequest.STATE_CREATED
340 assert pull_request.pull_request_state == PullRequest.STATE_CREATED
332
341
333 message = (
342 message = (
334 u'Merge pull request #{pr_id} from {source_repo} {source_ref_name}'
343 u'Merge pull request #{pr_id} from {source_repo} {source_ref_name}'
335 u'\n\n {pr_title}'.format(
344 u'\n\n {pr_title}'.format(
336 pr_id=pull_request.pull_request_id,
345 pr_id=pull_request.pull_request_id,
337 source_repo=safe_unicode(
346 source_repo=safe_unicode(
338 pull_request.source_repo.scm_instance().name),
347 pull_request.source_repo.scm_instance().name),
339 source_ref_name=pull_request.source_ref_parts.name,
348 source_ref_name=pull_request.source_ref_parts.name,
340 pr_title=safe_unicode(pull_request.title)
349 pr_title=safe_unicode(pull_request.title)
341 )
350 )
342 )
351 )
343 self.merge_mock.assert_called_with(
352 self.merge_mock.assert_called_with(
344 self.repo_id, self.workspace_id,
353 self.repo_id, self.workspace_id,
345 pull_request.target_ref_parts,
354 pull_request.target_ref_parts,
346 pull_request.source_repo.scm_instance(),
355 pull_request.source_repo.scm_instance(),
347 pull_request.source_ref_parts,
356 pull_request.source_ref_parts,
348 user_name=user.short_contact, user_email=user.email, message=message,
357 user_name=user.short_contact, user_email=user.email, message=message,
349 use_rebase=False, close_branch=False
358 use_rebase=False, close_branch=False
350 )
359 )
351 self.invalidation_mock.assert_called_once_with(
360 self.invalidation_mock.assert_called_once_with(
352 pull_request.target_repo.repo_name)
361 pull_request.target_repo.repo_name)
353
362
354 self.hook_mock.assert_called_with(
363 self.hook_mock.assert_called_with(
355 self.pull_request, self.pull_request.author, 'merge')
364 self.pull_request, self.pull_request.author, 'merge')
356
365
357 pull_request = PullRequest.get(pull_request.pull_request_id)
366 pull_request = PullRequest.get(pull_request.pull_request_id)
358 assert pull_request.merge_rev == '6126b7bfcc82ad2d3deaee22af926b082ce54cc6'
367 assert pull_request.merge_rev == '6126b7bfcc82ad2d3deaee22af926b082ce54cc6'
359
368
360 def test_merge_failed(self, pull_request, merge_extras):
369 def test_merge_failed(self, pull_request, merge_extras):
361 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
370 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
362 merge_ref = Reference(
371 merge_ref = Reference(
363 'type', 'name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
372 'type', 'name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
364 self.merge_mock.return_value = MergeResponse(
373 self.merge_mock.return_value = MergeResponse(
365 False, False, merge_ref, MergeFailureReason.MERGE_FAILED)
374 False, False, merge_ref, MergeFailureReason.MERGE_FAILED)
366
375
367 merge_extras['repository'] = pull_request.target_repo.repo_name
376 merge_extras['repository'] = pull_request.target_repo.repo_name
368 PullRequestModel().merge_repo(
377 PullRequestModel().merge_repo(
369 pull_request, pull_request.author, extras=merge_extras)
378 pull_request, pull_request.author, extras=merge_extras)
379 Session().commit()
370
380
371 message = (
381 message = (
372 u'Merge pull request #{pr_id} from {source_repo} {source_ref_name}'
382 u'Merge pull request #{pr_id} from {source_repo} {source_ref_name}'
373 u'\n\n {pr_title}'.format(
383 u'\n\n {pr_title}'.format(
374 pr_id=pull_request.pull_request_id,
384 pr_id=pull_request.pull_request_id,
375 source_repo=safe_unicode(
385 source_repo=safe_unicode(
376 pull_request.source_repo.scm_instance().name),
386 pull_request.source_repo.scm_instance().name),
377 source_ref_name=pull_request.source_ref_parts.name,
387 source_ref_name=pull_request.source_ref_parts.name,
378 pr_title=safe_unicode(pull_request.title)
388 pr_title=safe_unicode(pull_request.title)
379 )
389 )
380 )
390 )
381 self.merge_mock.assert_called_with(
391 self.merge_mock.assert_called_with(
382 self.repo_id, self.workspace_id,
392 self.repo_id, self.workspace_id,
383 pull_request.target_ref_parts,
393 pull_request.target_ref_parts,
384 pull_request.source_repo.scm_instance(),
394 pull_request.source_repo.scm_instance(),
385 pull_request.source_ref_parts,
395 pull_request.source_ref_parts,
386 user_name=user.short_contact, user_email=user.email, message=message,
396 user_name=user.short_contact, user_email=user.email, message=message,
387 use_rebase=False, close_branch=False
397 use_rebase=False, close_branch=False
388 )
398 )
389
399
390 pull_request = PullRequest.get(pull_request.pull_request_id)
400 pull_request = PullRequest.get(pull_request.pull_request_id)
391 assert self.invalidation_mock.called is False
401 assert self.invalidation_mock.called is False
392 assert pull_request.merge_rev is None
402 assert pull_request.merge_rev is None
393
403
394 def test_get_commit_ids(self, pull_request):
404 def test_get_commit_ids(self, pull_request):
395 # The PR has been not merget yet, so expect an exception
405 # The PR has been not merget yet, so expect an exception
396 with pytest.raises(ValueError):
406 with pytest.raises(ValueError):
397 PullRequestModel()._get_commit_ids(pull_request)
407 PullRequestModel()._get_commit_ids(pull_request)
398
408
399 # Merge revision is in the revisions list
409 # Merge revision is in the revisions list
400 pull_request.merge_rev = pull_request.revisions[0]
410 pull_request.merge_rev = pull_request.revisions[0]
401 commit_ids = PullRequestModel()._get_commit_ids(pull_request)
411 commit_ids = PullRequestModel()._get_commit_ids(pull_request)
402 assert commit_ids == pull_request.revisions
412 assert commit_ids == pull_request.revisions
403
413
404 # Merge revision is not in the revisions list
414 # Merge revision is not in the revisions list
405 pull_request.merge_rev = 'f000' * 10
415 pull_request.merge_rev = 'f000' * 10
406 commit_ids = PullRequestModel()._get_commit_ids(pull_request)
416 commit_ids = PullRequestModel()._get_commit_ids(pull_request)
407 assert commit_ids == pull_request.revisions + [pull_request.merge_rev]
417 assert commit_ids == pull_request.revisions + [pull_request.merge_rev]
408
418
409 def test_get_diff_from_pr_version(self, pull_request):
419 def test_get_diff_from_pr_version(self, pull_request):
410 source_repo = pull_request.source_repo
420 source_repo = pull_request.source_repo
411 source_ref_id = pull_request.source_ref_parts.commit_id
421 source_ref_id = pull_request.source_ref_parts.commit_id
412 target_ref_id = pull_request.target_ref_parts.commit_id
422 target_ref_id = pull_request.target_ref_parts.commit_id
413 diff = PullRequestModel()._get_diff_from_pr_or_version(
423 diff = PullRequestModel()._get_diff_from_pr_or_version(
414 source_repo, source_ref_id, target_ref_id,
424 source_repo, source_ref_id, target_ref_id,
415 hide_whitespace_changes=False, diff_context=6)
425 hide_whitespace_changes=False, diff_context=6)
416 assert 'file_1' in diff.raw
426 assert 'file_1' in diff.raw
417
427
418 def test_generate_title_returns_unicode(self):
428 def test_generate_title_returns_unicode(self):
419 title = PullRequestModel().generate_pullrequest_title(
429 title = PullRequestModel().generate_pullrequest_title(
420 source='source-dummy',
430 source='source-dummy',
421 source_ref='source-ref-dummy',
431 source_ref='source-ref-dummy',
422 target='target-dummy',
432 target='target-dummy',
423 )
433 )
424 assert type(title) == unicode
434 assert type(title) == unicode
425
435
426
436
427 @pytest.mark.usefixtures('config_stub')
437 @pytest.mark.usefixtures('config_stub')
428 class TestIntegrationMerge(object):
438 class TestIntegrationMerge(object):
429 @pytest.mark.parametrize('extra_config', (
439 @pytest.mark.parametrize('extra_config', (
430 {'vcs.hooks.protocol': 'http', 'vcs.hooks.direct_calls': False},
440 {'vcs.hooks.protocol': 'http', 'vcs.hooks.direct_calls': False},
431 ))
441 ))
432 def test_merge_triggers_push_hooks(
442 def test_merge_triggers_push_hooks(
433 self, pr_util, user_admin, capture_rcextensions, merge_extras,
443 self, pr_util, user_admin, capture_rcextensions, merge_extras,
434 extra_config):
444 extra_config):
435
445
436 pull_request = pr_util.create_pull_request(
446 pull_request = pr_util.create_pull_request(
437 approved=True, mergeable=True)
447 approved=True, mergeable=True)
438 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
448 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
439 merge_extras['repository'] = pull_request.target_repo.repo_name
449 merge_extras['repository'] = pull_request.target_repo.repo_name
440 Session().commit()
450 Session().commit()
441
451
442 with mock.patch.dict(rhodecode.CONFIG, extra_config, clear=False):
452 with mock.patch.dict(rhodecode.CONFIG, extra_config, clear=False):
443 merge_state = PullRequestModel().merge_repo(
453 merge_state = PullRequestModel().merge_repo(
444 pull_request, user_admin, extras=merge_extras)
454 pull_request, user_admin, extras=merge_extras)
455 Session().commit()
445
456
446 assert merge_state.executed
457 assert merge_state.executed
447 assert '_pre_push_hook' in capture_rcextensions
458 assert '_pre_push_hook' in capture_rcextensions
448 assert '_push_hook' in capture_rcextensions
459 assert '_push_hook' in capture_rcextensions
449
460
450 def test_merge_can_be_rejected_by_pre_push_hook(
461 def test_merge_can_be_rejected_by_pre_push_hook(
451 self, pr_util, user_admin, capture_rcextensions, merge_extras):
462 self, pr_util, user_admin, capture_rcextensions, merge_extras):
452 pull_request = pr_util.create_pull_request(
463 pull_request = pr_util.create_pull_request(
453 approved=True, mergeable=True)
464 approved=True, mergeable=True)
454 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
465 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
455 merge_extras['repository'] = pull_request.target_repo.repo_name
466 merge_extras['repository'] = pull_request.target_repo.repo_name
456 Session().commit()
467 Session().commit()
457
468
458 with mock.patch('rhodecode.EXTENSIONS.PRE_PUSH_HOOK') as pre_pull:
469 with mock.patch('rhodecode.EXTENSIONS.PRE_PUSH_HOOK') as pre_pull:
459 pre_pull.side_effect = RepositoryError("Disallow push!")
470 pre_pull.side_effect = RepositoryError("Disallow push!")
460 merge_status = PullRequestModel().merge_repo(
471 merge_status = PullRequestModel().merge_repo(
461 pull_request, user_admin, extras=merge_extras)
472 pull_request, user_admin, extras=merge_extras)
473 Session().commit()
462
474
463 assert not merge_status.executed
475 assert not merge_status.executed
464 assert 'pre_push' not in capture_rcextensions
476 assert 'pre_push' not in capture_rcextensions
465 assert 'post_push' not in capture_rcextensions
477 assert 'post_push' not in capture_rcextensions
466
478
467 def test_merge_fails_if_target_is_locked(
479 def test_merge_fails_if_target_is_locked(
468 self, pr_util, user_regular, merge_extras):
480 self, pr_util, user_regular, merge_extras):
469 pull_request = pr_util.create_pull_request(
481 pull_request = pr_util.create_pull_request(
470 approved=True, mergeable=True)
482 approved=True, mergeable=True)
471 locked_by = [user_regular.user_id + 1, 12345.50, 'lock_web']
483 locked_by = [user_regular.user_id + 1, 12345.50, 'lock_web']
472 pull_request.target_repo.locked = locked_by
484 pull_request.target_repo.locked = locked_by
473 # TODO: johbo: Check if this can work based on the database, currently
485 # TODO: johbo: Check if this can work based on the database, currently
474 # all data is pre-computed, that's why just updating the DB is not
486 # all data is pre-computed, that's why just updating the DB is not
475 # enough.
487 # enough.
476 merge_extras['locked_by'] = locked_by
488 merge_extras['locked_by'] = locked_by
477 merge_extras['repository'] = pull_request.target_repo.repo_name
489 merge_extras['repository'] = pull_request.target_repo.repo_name
478 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
490 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
479 Session().commit()
491 Session().commit()
480 merge_status = PullRequestModel().merge_repo(
492 merge_status = PullRequestModel().merge_repo(
481 pull_request, user_regular, extras=merge_extras)
493 pull_request, user_regular, extras=merge_extras)
494 Session().commit()
495
482 assert not merge_status.executed
496 assert not merge_status.executed
483
497
484
498
485 @pytest.mark.parametrize('use_outdated, inlines_count, outdated_count', [
499 @pytest.mark.parametrize('use_outdated, inlines_count, outdated_count', [
486 (False, 1, 0),
500 (False, 1, 0),
487 (True, 0, 1),
501 (True, 0, 1),
488 ])
502 ])
489 def test_outdated_comments(
503 def test_outdated_comments(
490 pr_util, use_outdated, inlines_count, outdated_count, config_stub):
504 pr_util, use_outdated, inlines_count, outdated_count, config_stub):
491 pull_request = pr_util.create_pull_request()
505 pull_request = pr_util.create_pull_request()
492 pr_util.create_inline_comment(file_path='not_in_updated_diff')
506 pr_util.create_inline_comment(file_path='not_in_updated_diff')
493
507
494 with outdated_comments_patcher(use_outdated) as outdated_comment_mock:
508 with outdated_comments_patcher(use_outdated) as outdated_comment_mock:
495 pr_util.add_one_commit()
509 pr_util.add_one_commit()
496 assert_inline_comments(
510 assert_inline_comments(
497 pull_request, visible=inlines_count, outdated=outdated_count)
511 pull_request, visible=inlines_count, outdated=outdated_count)
498 outdated_comment_mock.assert_called_with(pull_request)
512 outdated_comment_mock.assert_called_with(pull_request)
499
513
500
514
501 @pytest.mark.parametrize('mr_type, expected_msg', [
515 @pytest.mark.parametrize('mr_type, expected_msg', [
502 (MergeFailureReason.NONE,
516 (MergeFailureReason.NONE,
503 'This pull request can be automatically merged.'),
517 'This pull request can be automatically merged.'),
504 (MergeFailureReason.UNKNOWN,
518 (MergeFailureReason.UNKNOWN,
505 'This pull request cannot be merged because of an unhandled exception. CRASH'),
519 'This pull request cannot be merged because of an unhandled exception. CRASH'),
506 (MergeFailureReason.MERGE_FAILED,
520 (MergeFailureReason.MERGE_FAILED,
507 'This pull request cannot be merged because of merge conflicts.'),
521 'This pull request cannot be merged because of merge conflicts.'),
508 (MergeFailureReason.PUSH_FAILED,
522 (MergeFailureReason.PUSH_FAILED,
509 'This pull request could not be merged because push to target:`some-repo@merge_commit` failed.'),
523 'This pull request could not be merged because push to target:`some-repo@merge_commit` failed.'),
510 (MergeFailureReason.TARGET_IS_NOT_HEAD,
524 (MergeFailureReason.TARGET_IS_NOT_HEAD,
511 'This pull request cannot be merged because the target `ref_name` is not a head.'),
525 'This pull request cannot be merged because the target `ref_name` is not a head.'),
512 (MergeFailureReason.HG_SOURCE_HAS_MORE_BRANCHES,
526 (MergeFailureReason.HG_SOURCE_HAS_MORE_BRANCHES,
513 'This pull request cannot be merged because the source contains more branches than the target.'),
527 'This pull request cannot be merged because the source contains more branches than the target.'),
514 (MergeFailureReason.HG_TARGET_HAS_MULTIPLE_HEADS,
528 (MergeFailureReason.HG_TARGET_HAS_MULTIPLE_HEADS,
515 'This pull request cannot be merged because the target `ref_name` has multiple heads: `a,b,c`.'),
529 'This pull request cannot be merged because the target `ref_name` has multiple heads: `a,b,c`.'),
516 (MergeFailureReason.TARGET_IS_LOCKED,
530 (MergeFailureReason.TARGET_IS_LOCKED,
517 'This pull request cannot be merged because the target repository is locked by user:123.'),
531 'This pull request cannot be merged because the target repository is locked by user:123.'),
518 (MergeFailureReason.MISSING_TARGET_REF,
532 (MergeFailureReason.MISSING_TARGET_REF,
519 'This pull request cannot be merged because the target reference `ref_name` is missing.'),
533 'This pull request cannot be merged because the target reference `ref_name` is missing.'),
520 (MergeFailureReason.MISSING_SOURCE_REF,
534 (MergeFailureReason.MISSING_SOURCE_REF,
521 'This pull request cannot be merged because the source reference `ref_name` is missing.'),
535 'This pull request cannot be merged because the source reference `ref_name` is missing.'),
522 (MergeFailureReason.SUBREPO_MERGE_FAILED,
536 (MergeFailureReason.SUBREPO_MERGE_FAILED,
523 'This pull request cannot be merged because of conflicts related to sub repositories.'),
537 'This pull request cannot be merged because of conflicts related to sub repositories.'),
524
538
525 ])
539 ])
526 def test_merge_response_message(mr_type, expected_msg):
540 def test_merge_response_message(mr_type, expected_msg):
527 merge_ref = Reference('type', 'ref_name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
541 merge_ref = Reference('type', 'ref_name', '6126b7bfcc82ad2d3deaee22af926b082ce54cc6')
528 metadata = {
542 metadata = {
529 'exception': "CRASH",
543 'exception': "CRASH",
530 'target': 'some-repo',
544 'target': 'some-repo',
531 'merge_commit': 'merge_commit',
545 'merge_commit': 'merge_commit',
532 'target_ref': merge_ref,
546 'target_ref': merge_ref,
533 'source_ref': merge_ref,
547 'source_ref': merge_ref,
534 'heads': ','.join(['a', 'b', 'c']),
548 'heads': ','.join(['a', 'b', 'c']),
535 'locked_by': 'user:123'}
549 'locked_by': 'user:123'}
536
550
537 merge_response = MergeResponse(True, True, merge_ref, mr_type, metadata=metadata)
551 merge_response = MergeResponse(True, True, merge_ref, mr_type, metadata=metadata)
538 assert merge_response.merge_status_message == expected_msg
552 assert merge_response.merge_status_message == expected_msg
539
553
540
554
541 @pytest.fixture()
555 @pytest.fixture()
542 def merge_extras(user_regular):
556 def merge_extras(user_regular):
543 """
557 """
544 Context for the vcs operation when running a merge.
558 Context for the vcs operation when running a merge.
545 """
559 """
546 extras = {
560 extras = {
547 'ip': '127.0.0.1',
561 'ip': '127.0.0.1',
548 'username': user_regular.username,
562 'username': user_regular.username,
549 'user_id': user_regular.user_id,
563 'user_id': user_regular.user_id,
550 'action': 'push',
564 'action': 'push',
551 'repository': 'fake_target_repo_name',
565 'repository': 'fake_target_repo_name',
552 'scm': 'git',
566 'scm': 'git',
553 'config': 'fake_config_ini_path',
567 'config': 'fake_config_ini_path',
554 'repo_store': '',
568 'repo_store': '',
555 'make_lock': None,
569 'make_lock': None,
556 'locked_by': [None, None, None],
570 'locked_by': [None, None, None],
557 'server_url': 'http://test.example.com:5000',
571 'server_url': 'http://test.example.com:5000',
558 'hooks': ['push', 'pull'],
572 'hooks': ['push', 'pull'],
559 'is_shadow_repo': False,
573 'is_shadow_repo': False,
560 }
574 }
561 return extras
575 return extras
562
576
563
577
564 @pytest.mark.usefixtures('config_stub')
578 @pytest.mark.usefixtures('config_stub')
565 class TestUpdateCommentHandling(object):
579 class TestUpdateCommentHandling(object):
566
580
567 @pytest.fixture(autouse=True, scope='class')
581 @pytest.fixture(autouse=True, scope='class')
568 def enable_outdated_comments(self, request, baseapp):
582 def enable_outdated_comments(self, request, baseapp):
569 config_patch = mock.patch.dict(
583 config_patch = mock.patch.dict(
570 'rhodecode.CONFIG', {'rhodecode_use_outdated_comments': True})
584 'rhodecode.CONFIG', {'rhodecode_use_outdated_comments': True})
571 config_patch.start()
585 config_patch.start()
572
586
573 @request.addfinalizer
587 @request.addfinalizer
574 def cleanup():
588 def cleanup():
575 config_patch.stop()
589 config_patch.stop()
576
590
577 def test_comment_stays_unflagged_on_unchanged_diff(self, pr_util):
591 def test_comment_stays_unflagged_on_unchanged_diff(self, pr_util):
578 commits = [
592 commits = [
579 {'message': 'a'},
593 {'message': 'a'},
580 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
594 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
581 {'message': 'c', 'added': [FileNode('file_c', 'test_content\n')]},
595 {'message': 'c', 'added': [FileNode('file_c', 'test_content\n')]},
582 ]
596 ]
583 pull_request = pr_util.create_pull_request(
597 pull_request = pr_util.create_pull_request(
584 commits=commits, target_head='a', source_head='b', revisions=['b'])
598 commits=commits, target_head='a', source_head='b', revisions=['b'])
585 pr_util.create_inline_comment(file_path='file_b')
599 pr_util.create_inline_comment(file_path='file_b')
586 pr_util.add_one_commit(head='c')
600 pr_util.add_one_commit(head='c')
587
601
588 assert_inline_comments(pull_request, visible=1, outdated=0)
602 assert_inline_comments(pull_request, visible=1, outdated=0)
589
603
590 def test_comment_stays_unflagged_on_change_above(self, pr_util):
604 def test_comment_stays_unflagged_on_change_above(self, pr_util):
591 original_content = ''.join(
605 original_content = ''.join(
592 ['line {}\n'.format(x) for x in range(1, 11)])
606 ['line {}\n'.format(x) for x in range(1, 11)])
593 updated_content = 'new_line_at_top\n' + original_content
607 updated_content = 'new_line_at_top\n' + original_content
594 commits = [
608 commits = [
595 {'message': 'a'},
609 {'message': 'a'},
596 {'message': 'b', 'added': [FileNode('file_b', original_content)]},
610 {'message': 'b', 'added': [FileNode('file_b', original_content)]},
597 {'message': 'c', 'changed': [FileNode('file_b', updated_content)]},
611 {'message': 'c', 'changed': [FileNode('file_b', updated_content)]},
598 ]
612 ]
599 pull_request = pr_util.create_pull_request(
613 pull_request = pr_util.create_pull_request(
600 commits=commits, target_head='a', source_head='b', revisions=['b'])
614 commits=commits, target_head='a', source_head='b', revisions=['b'])
601
615
602 with outdated_comments_patcher():
616 with outdated_comments_patcher():
603 comment = pr_util.create_inline_comment(
617 comment = pr_util.create_inline_comment(
604 line_no=u'n8', file_path='file_b')
618 line_no=u'n8', file_path='file_b')
605 pr_util.add_one_commit(head='c')
619 pr_util.add_one_commit(head='c')
606
620
607 assert_inline_comments(pull_request, visible=1, outdated=0)
621 assert_inline_comments(pull_request, visible=1, outdated=0)
608 assert comment.line_no == u'n9'
622 assert comment.line_no == u'n9'
609
623
610 def test_comment_stays_unflagged_on_change_below(self, pr_util):
624 def test_comment_stays_unflagged_on_change_below(self, pr_util):
611 original_content = ''.join(['line {}\n'.format(x) for x in range(10)])
625 original_content = ''.join(['line {}\n'.format(x) for x in range(10)])
612 updated_content = original_content + 'new_line_at_end\n'
626 updated_content = original_content + 'new_line_at_end\n'
613 commits = [
627 commits = [
614 {'message': 'a'},
628 {'message': 'a'},
615 {'message': 'b', 'added': [FileNode('file_b', original_content)]},
629 {'message': 'b', 'added': [FileNode('file_b', original_content)]},
616 {'message': 'c', 'changed': [FileNode('file_b', updated_content)]},
630 {'message': 'c', 'changed': [FileNode('file_b', updated_content)]},
617 ]
631 ]
618 pull_request = pr_util.create_pull_request(
632 pull_request = pr_util.create_pull_request(
619 commits=commits, target_head='a', source_head='b', revisions=['b'])
633 commits=commits, target_head='a', source_head='b', revisions=['b'])
620 pr_util.create_inline_comment(file_path='file_b')
634 pr_util.create_inline_comment(file_path='file_b')
621 pr_util.add_one_commit(head='c')
635 pr_util.add_one_commit(head='c')
622
636
623 assert_inline_comments(pull_request, visible=1, outdated=0)
637 assert_inline_comments(pull_request, visible=1, outdated=0)
624
638
625 @pytest.mark.parametrize('line_no', ['n4', 'o4', 'n10', 'o9'])
639 @pytest.mark.parametrize('line_no', ['n4', 'o4', 'n10', 'o9'])
626 def test_comment_flagged_on_change_around_context(self, pr_util, line_no):
640 def test_comment_flagged_on_change_around_context(self, pr_util, line_no):
627 base_lines = ['line {}\n'.format(x) for x in range(1, 13)]
641 base_lines = ['line {}\n'.format(x) for x in range(1, 13)]
628 change_lines = list(base_lines)
642 change_lines = list(base_lines)
629 change_lines.insert(6, 'line 6a added\n')
643 change_lines.insert(6, 'line 6a added\n')
630
644
631 # Changes on the last line of sight
645 # Changes on the last line of sight
632 update_lines = list(change_lines)
646 update_lines = list(change_lines)
633 update_lines[0] = 'line 1 changed\n'
647 update_lines[0] = 'line 1 changed\n'
634 update_lines[-1] = 'line 12 changed\n'
648 update_lines[-1] = 'line 12 changed\n'
635
649
636 def file_b(lines):
650 def file_b(lines):
637 return FileNode('file_b', ''.join(lines))
651 return FileNode('file_b', ''.join(lines))
638
652
639 commits = [
653 commits = [
640 {'message': 'a', 'added': [file_b(base_lines)]},
654 {'message': 'a', 'added': [file_b(base_lines)]},
641 {'message': 'b', 'changed': [file_b(change_lines)]},
655 {'message': 'b', 'changed': [file_b(change_lines)]},
642 {'message': 'c', 'changed': [file_b(update_lines)]},
656 {'message': 'c', 'changed': [file_b(update_lines)]},
643 ]
657 ]
644
658
645 pull_request = pr_util.create_pull_request(
659 pull_request = pr_util.create_pull_request(
646 commits=commits, target_head='a', source_head='b', revisions=['b'])
660 commits=commits, target_head='a', source_head='b', revisions=['b'])
647 pr_util.create_inline_comment(line_no=line_no, file_path='file_b')
661 pr_util.create_inline_comment(line_no=line_no, file_path='file_b')
648
662
649 with outdated_comments_patcher():
663 with outdated_comments_patcher():
650 pr_util.add_one_commit(head='c')
664 pr_util.add_one_commit(head='c')
651 assert_inline_comments(pull_request, visible=0, outdated=1)
665 assert_inline_comments(pull_request, visible=0, outdated=1)
652
666
653 @pytest.mark.parametrize("change, content", [
667 @pytest.mark.parametrize("change, content", [
654 ('changed', 'changed\n'),
668 ('changed', 'changed\n'),
655 ('removed', ''),
669 ('removed', ''),
656 ], ids=['changed', 'removed'])
670 ], ids=['changed', 'removed'])
657 def test_comment_flagged_on_change(self, pr_util, change, content):
671 def test_comment_flagged_on_change(self, pr_util, change, content):
658 commits = [
672 commits = [
659 {'message': 'a'},
673 {'message': 'a'},
660 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
674 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
661 {'message': 'c', change: [FileNode('file_b', content)]},
675 {'message': 'c', change: [FileNode('file_b', content)]},
662 ]
676 ]
663 pull_request = pr_util.create_pull_request(
677 pull_request = pr_util.create_pull_request(
664 commits=commits, target_head='a', source_head='b', revisions=['b'])
678 commits=commits, target_head='a', source_head='b', revisions=['b'])
665 pr_util.create_inline_comment(file_path='file_b')
679 pr_util.create_inline_comment(file_path='file_b')
666
680
667 with outdated_comments_patcher():
681 with outdated_comments_patcher():
668 pr_util.add_one_commit(head='c')
682 pr_util.add_one_commit(head='c')
669 assert_inline_comments(pull_request, visible=0, outdated=1)
683 assert_inline_comments(pull_request, visible=0, outdated=1)
670
684
671
685
672 @pytest.mark.usefixtures('config_stub')
686 @pytest.mark.usefixtures('config_stub')
673 class TestUpdateChangedFiles(object):
687 class TestUpdateChangedFiles(object):
674
688
675 def test_no_changes_on_unchanged_diff(self, pr_util):
689 def test_no_changes_on_unchanged_diff(self, pr_util):
676 commits = [
690 commits = [
677 {'message': 'a'},
691 {'message': 'a'},
678 {'message': 'b',
692 {'message': 'b',
679 'added': [FileNode('file_b', 'test_content b\n')]},
693 'added': [FileNode('file_b', 'test_content b\n')]},
680 {'message': 'c',
694 {'message': 'c',
681 'added': [FileNode('file_c', 'test_content c\n')]},
695 'added': [FileNode('file_c', 'test_content c\n')]},
682 ]
696 ]
683 # open a PR from a to b, adding file_b
697 # open a PR from a to b, adding file_b
684 pull_request = pr_util.create_pull_request(
698 pull_request = pr_util.create_pull_request(
685 commits=commits, target_head='a', source_head='b', revisions=['b'],
699 commits=commits, target_head='a', source_head='b', revisions=['b'],
686 name_suffix='per-file-review')
700 name_suffix='per-file-review')
687
701
688 # modify PR adding new file file_c
702 # modify PR adding new file file_c
689 pr_util.add_one_commit(head='c')
703 pr_util.add_one_commit(head='c')
690
704
691 assert_pr_file_changes(
705 assert_pr_file_changes(
692 pull_request,
706 pull_request,
693 added=['file_c'],
707 added=['file_c'],
694 modified=[],
708 modified=[],
695 removed=[])
709 removed=[])
696
710
697 def test_modify_and_undo_modification_diff(self, pr_util):
711 def test_modify_and_undo_modification_diff(self, pr_util):
698 commits = [
712 commits = [
699 {'message': 'a'},
713 {'message': 'a'},
700 {'message': 'b',
714 {'message': 'b',
701 'added': [FileNode('file_b', 'test_content b\n')]},
715 'added': [FileNode('file_b', 'test_content b\n')]},
702 {'message': 'c',
716 {'message': 'c',
703 'changed': [FileNode('file_b', 'test_content b modified\n')]},
717 'changed': [FileNode('file_b', 'test_content b modified\n')]},
704 {'message': 'd',
718 {'message': 'd',
705 'changed': [FileNode('file_b', 'test_content b\n')]},
719 'changed': [FileNode('file_b', 'test_content b\n')]},
706 ]
720 ]
707 # open a PR from a to b, adding file_b
721 # open a PR from a to b, adding file_b
708 pull_request = pr_util.create_pull_request(
722 pull_request = pr_util.create_pull_request(
709 commits=commits, target_head='a', source_head='b', revisions=['b'],
723 commits=commits, target_head='a', source_head='b', revisions=['b'],
710 name_suffix='per-file-review')
724 name_suffix='per-file-review')
711
725
712 # modify PR modifying file file_b
726 # modify PR modifying file file_b
713 pr_util.add_one_commit(head='c')
727 pr_util.add_one_commit(head='c')
714
728
715 assert_pr_file_changes(
729 assert_pr_file_changes(
716 pull_request,
730 pull_request,
717 added=[],
731 added=[],
718 modified=['file_b'],
732 modified=['file_b'],
719 removed=[])
733 removed=[])
720
734
721 # move the head again to d, which rollbacks change,
735 # move the head again to d, which rollbacks change,
722 # meaning we should indicate no changes
736 # meaning we should indicate no changes
723 pr_util.add_one_commit(head='d')
737 pr_util.add_one_commit(head='d')
724
738
725 assert_pr_file_changes(
739 assert_pr_file_changes(
726 pull_request,
740 pull_request,
727 added=[],
741 added=[],
728 modified=[],
742 modified=[],
729 removed=[])
743 removed=[])
730
744
731 def test_updated_all_files_in_pr(self, pr_util):
745 def test_updated_all_files_in_pr(self, pr_util):
732 commits = [
746 commits = [
733 {'message': 'a'},
747 {'message': 'a'},
734 {'message': 'b', 'added': [
748 {'message': 'b', 'added': [
735 FileNode('file_a', 'test_content a\n'),
749 FileNode('file_a', 'test_content a\n'),
736 FileNode('file_b', 'test_content b\n'),
750 FileNode('file_b', 'test_content b\n'),
737 FileNode('file_c', 'test_content c\n')]},
751 FileNode('file_c', 'test_content c\n')]},
738 {'message': 'c', 'changed': [
752 {'message': 'c', 'changed': [
739 FileNode('file_a', 'test_content a changed\n'),
753 FileNode('file_a', 'test_content a changed\n'),
740 FileNode('file_b', 'test_content b changed\n'),
754 FileNode('file_b', 'test_content b changed\n'),
741 FileNode('file_c', 'test_content c changed\n')]},
755 FileNode('file_c', 'test_content c changed\n')]},
742 ]
756 ]
743 # open a PR from a to b, changing 3 files
757 # open a PR from a to b, changing 3 files
744 pull_request = pr_util.create_pull_request(
758 pull_request = pr_util.create_pull_request(
745 commits=commits, target_head='a', source_head='b', revisions=['b'],
759 commits=commits, target_head='a', source_head='b', revisions=['b'],
746 name_suffix='per-file-review')
760 name_suffix='per-file-review')
747
761
748 pr_util.add_one_commit(head='c')
762 pr_util.add_one_commit(head='c')
749
763
750 assert_pr_file_changes(
764 assert_pr_file_changes(
751 pull_request,
765 pull_request,
752 added=[],
766 added=[],
753 modified=['file_a', 'file_b', 'file_c'],
767 modified=['file_a', 'file_b', 'file_c'],
754 removed=[])
768 removed=[])
755
769
756 def test_updated_and_removed_all_files_in_pr(self, pr_util):
770 def test_updated_and_removed_all_files_in_pr(self, pr_util):
757 commits = [
771 commits = [
758 {'message': 'a'},
772 {'message': 'a'},
759 {'message': 'b', 'added': [
773 {'message': 'b', 'added': [
760 FileNode('file_a', 'test_content a\n'),
774 FileNode('file_a', 'test_content a\n'),
761 FileNode('file_b', 'test_content b\n'),
775 FileNode('file_b', 'test_content b\n'),
762 FileNode('file_c', 'test_content c\n')]},
776 FileNode('file_c', 'test_content c\n')]},
763 {'message': 'c', 'removed': [
777 {'message': 'c', 'removed': [
764 FileNode('file_a', 'test_content a changed\n'),
778 FileNode('file_a', 'test_content a changed\n'),
765 FileNode('file_b', 'test_content b changed\n'),
779 FileNode('file_b', 'test_content b changed\n'),
766 FileNode('file_c', 'test_content c changed\n')]},
780 FileNode('file_c', 'test_content c changed\n')]},
767 ]
781 ]
768 # open a PR from a to b, removing 3 files
782 # open a PR from a to b, removing 3 files
769 pull_request = pr_util.create_pull_request(
783 pull_request = pr_util.create_pull_request(
770 commits=commits, target_head='a', source_head='b', revisions=['b'],
784 commits=commits, target_head='a', source_head='b', revisions=['b'],
771 name_suffix='per-file-review')
785 name_suffix='per-file-review')
772
786
773 pr_util.add_one_commit(head='c')
787 pr_util.add_one_commit(head='c')
774
788
775 assert_pr_file_changes(
789 assert_pr_file_changes(
776 pull_request,
790 pull_request,
777 added=[],
791 added=[],
778 modified=[],
792 modified=[],
779 removed=['file_a', 'file_b', 'file_c'])
793 removed=['file_a', 'file_b', 'file_c'])
780
794
781
795
782 def test_update_writes_snapshot_into_pull_request_version(pr_util, config_stub):
796 def test_update_writes_snapshot_into_pull_request_version(pr_util, config_stub):
783 model = PullRequestModel()
797 model = PullRequestModel()
784 pull_request = pr_util.create_pull_request()
798 pull_request = pr_util.create_pull_request()
785 pr_util.update_source_repository()
799 pr_util.update_source_repository()
786
800
787 model.update_commits(pull_request)
801 model.update_commits(pull_request)
788
802
789 # Expect that it has a version entry now
803 # Expect that it has a version entry now
790 assert len(model.get_versions(pull_request)) == 1
804 assert len(model.get_versions(pull_request)) == 1
791
805
792
806
793 def test_update_skips_new_version_if_unchanged(pr_util, config_stub):
807 def test_update_skips_new_version_if_unchanged(pr_util, config_stub):
794 pull_request = pr_util.create_pull_request()
808 pull_request = pr_util.create_pull_request()
795 model = PullRequestModel()
809 model = PullRequestModel()
796 model.update_commits(pull_request)
810 model.update_commits(pull_request)
797
811
798 # Expect that it still has no versions
812 # Expect that it still has no versions
799 assert len(model.get_versions(pull_request)) == 0
813 assert len(model.get_versions(pull_request)) == 0
800
814
801
815
802 def test_update_assigns_comments_to_the_new_version(pr_util, config_stub):
816 def test_update_assigns_comments_to_the_new_version(pr_util, config_stub):
803 model = PullRequestModel()
817 model = PullRequestModel()
804 pull_request = pr_util.create_pull_request()
818 pull_request = pr_util.create_pull_request()
805 comment = pr_util.create_comment()
819 comment = pr_util.create_comment()
806 pr_util.update_source_repository()
820 pr_util.update_source_repository()
807
821
808 model.update_commits(pull_request)
822 model.update_commits(pull_request)
809
823
810 # Expect that the comment is linked to the pr version now
824 # Expect that the comment is linked to the pr version now
811 assert comment.pull_request_version == model.get_versions(pull_request)[0]
825 assert comment.pull_request_version == model.get_versions(pull_request)[0]
812
826
813
827
814 def test_update_adds_a_comment_to_the_pull_request_about_the_change(pr_util, config_stub):
828 def test_update_adds_a_comment_to_the_pull_request_about_the_change(pr_util, config_stub):
815 model = PullRequestModel()
829 model = PullRequestModel()
816 pull_request = pr_util.create_pull_request()
830 pull_request = pr_util.create_pull_request()
817 pr_util.update_source_repository()
831 pr_util.update_source_repository()
818 pr_util.update_source_repository()
832 pr_util.update_source_repository()
819
833
820 model.update_commits(pull_request)
834 model.update_commits(pull_request)
821
835
822 # Expect to find a new comment about the change
836 # Expect to find a new comment about the change
823 expected_message = textwrap.dedent(
837 expected_message = textwrap.dedent(
824 """\
838 """\
825 Pull request updated. Auto status change to |under_review|
839 Pull request updated. Auto status change to |under_review|
826
840
827 .. role:: added
841 .. role:: added
828 .. role:: removed
842 .. role:: removed
829 .. parsed-literal::
843 .. parsed-literal::
830
844
831 Changed commits:
845 Changed commits:
832 * :added:`1 added`
846 * :added:`1 added`
833 * :removed:`0 removed`
847 * :removed:`0 removed`
834
848
835 Changed files:
849 Changed files:
836 * `A file_2 <#a_c--92ed3b5f07b4>`_
850 * `A file_2 <#a_c--92ed3b5f07b4>`_
837
851
838 .. |under_review| replace:: *"Under Review"*"""
852 .. |under_review| replace:: *"Under Review"*"""
839 )
853 )
840 pull_request_comments = sorted(
854 pull_request_comments = sorted(
841 pull_request.comments, key=lambda c: c.modified_at)
855 pull_request.comments, key=lambda c: c.modified_at)
842 update_comment = pull_request_comments[-1]
856 update_comment = pull_request_comments[-1]
843 assert update_comment.text == expected_message
857 assert update_comment.text == expected_message
844
858
845
859
846 def test_create_version_from_snapshot_updates_attributes(pr_util, config_stub):
860 def test_create_version_from_snapshot_updates_attributes(pr_util, config_stub):
847 pull_request = pr_util.create_pull_request()
861 pull_request = pr_util.create_pull_request()
848
862
849 # Avoiding default values
863 # Avoiding default values
850 pull_request.status = PullRequest.STATUS_CLOSED
864 pull_request.status = PullRequest.STATUS_CLOSED
851 pull_request._last_merge_source_rev = "0" * 40
865 pull_request._last_merge_source_rev = "0" * 40
852 pull_request._last_merge_target_rev = "1" * 40
866 pull_request._last_merge_target_rev = "1" * 40
853 pull_request.last_merge_status = 1
867 pull_request.last_merge_status = 1
854 pull_request.merge_rev = "2" * 40
868 pull_request.merge_rev = "2" * 40
855
869
856 # Remember automatic values
870 # Remember automatic values
857 created_on = pull_request.created_on
871 created_on = pull_request.created_on
858 updated_on = pull_request.updated_on
872 updated_on = pull_request.updated_on
859
873
860 # Create a new version of the pull request
874 # Create a new version of the pull request
861 version = PullRequestModel()._create_version_from_snapshot(pull_request)
875 version = PullRequestModel()._create_version_from_snapshot(pull_request)
862
876
863 # Check attributes
877 # Check attributes
864 assert version.title == pr_util.create_parameters['title']
878 assert version.title == pr_util.create_parameters['title']
865 assert version.description == pr_util.create_parameters['description']
879 assert version.description == pr_util.create_parameters['description']
866 assert version.status == PullRequest.STATUS_CLOSED
880 assert version.status == PullRequest.STATUS_CLOSED
867
881
868 # versions get updated created_on
882 # versions get updated created_on
869 assert version.created_on != created_on
883 assert version.created_on != created_on
870
884
871 assert version.updated_on == updated_on
885 assert version.updated_on == updated_on
872 assert version.user_id == pull_request.user_id
886 assert version.user_id == pull_request.user_id
873 assert version.revisions == pr_util.create_parameters['revisions']
887 assert version.revisions == pr_util.create_parameters['revisions']
874 assert version.source_repo == pr_util.source_repository
888 assert version.source_repo == pr_util.source_repository
875 assert version.source_ref == pr_util.create_parameters['source_ref']
889 assert version.source_ref == pr_util.create_parameters['source_ref']
876 assert version.target_repo == pr_util.target_repository
890 assert version.target_repo == pr_util.target_repository
877 assert version.target_ref == pr_util.create_parameters['target_ref']
891 assert version.target_ref == pr_util.create_parameters['target_ref']
878 assert version._last_merge_source_rev == pull_request._last_merge_source_rev
892 assert version._last_merge_source_rev == pull_request._last_merge_source_rev
879 assert version._last_merge_target_rev == pull_request._last_merge_target_rev
893 assert version._last_merge_target_rev == pull_request._last_merge_target_rev
880 assert version.last_merge_status == pull_request.last_merge_status
894 assert version.last_merge_status == pull_request.last_merge_status
881 assert version.merge_rev == pull_request.merge_rev
895 assert version.merge_rev == pull_request.merge_rev
882 assert version.pull_request == pull_request
896 assert version.pull_request == pull_request
883
897
884
898
885 def test_link_comments_to_version_only_updates_unlinked_comments(pr_util, config_stub):
899 def test_link_comments_to_version_only_updates_unlinked_comments(pr_util, config_stub):
886 version1 = pr_util.create_version_of_pull_request()
900 version1 = pr_util.create_version_of_pull_request()
887 comment_linked = pr_util.create_comment(linked_to=version1)
901 comment_linked = pr_util.create_comment(linked_to=version1)
888 comment_unlinked = pr_util.create_comment()
902 comment_unlinked = pr_util.create_comment()
889 version2 = pr_util.create_version_of_pull_request()
903 version2 = pr_util.create_version_of_pull_request()
890
904
891 PullRequestModel()._link_comments_to_version(version2)
905 PullRequestModel()._link_comments_to_version(version2)
906 Session().commit()
892
907
893 # Expect that only the new comment is linked to version2
908 # Expect that only the new comment is linked to version2
894 assert (
909 assert (
895 comment_unlinked.pull_request_version_id ==
910 comment_unlinked.pull_request_version_id ==
896 version2.pull_request_version_id)
911 version2.pull_request_version_id)
897 assert (
912 assert (
898 comment_linked.pull_request_version_id ==
913 comment_linked.pull_request_version_id ==
899 version1.pull_request_version_id)
914 version1.pull_request_version_id)
900 assert (
915 assert (
901 comment_unlinked.pull_request_version_id !=
916 comment_unlinked.pull_request_version_id !=
902 comment_linked.pull_request_version_id)
917 comment_linked.pull_request_version_id)
903
918
904
919
905 def test_calculate_commits():
920 def test_calculate_commits():
906 old_ids = [1, 2, 3]
921 old_ids = [1, 2, 3]
907 new_ids = [1, 3, 4, 5]
922 new_ids = [1, 3, 4, 5]
908 change = PullRequestModel()._calculate_commit_id_changes(old_ids, new_ids)
923 change = PullRequestModel()._calculate_commit_id_changes(old_ids, new_ids)
909 assert change.added == [4, 5]
924 assert change.added == [4, 5]
910 assert change.common == [1, 3]
925 assert change.common == [1, 3]
911 assert change.removed == [2]
926 assert change.removed == [2]
912 assert change.total == [1, 3, 4, 5]
927 assert change.total == [1, 3, 4, 5]
913
928
914
929
915 def assert_inline_comments(pull_request, visible=None, outdated=None):
930 def assert_inline_comments(pull_request, visible=None, outdated=None):
916 if visible is not None:
931 if visible is not None:
917 inline_comments = CommentsModel().get_inline_comments(
932 inline_comments = CommentsModel().get_inline_comments(
918 pull_request.target_repo.repo_id, pull_request=pull_request)
933 pull_request.target_repo.repo_id, pull_request=pull_request)
919 inline_cnt = CommentsModel().get_inline_comments_count(
934 inline_cnt = CommentsModel().get_inline_comments_count(
920 inline_comments)
935 inline_comments)
921 assert inline_cnt == visible
936 assert inline_cnt == visible
922 if outdated is not None:
937 if outdated is not None:
923 outdated_comments = CommentsModel().get_outdated_comments(
938 outdated_comments = CommentsModel().get_outdated_comments(
924 pull_request.target_repo.repo_id, pull_request)
939 pull_request.target_repo.repo_id, pull_request)
925 assert len(outdated_comments) == outdated
940 assert len(outdated_comments) == outdated
926
941
927
942
928 def assert_pr_file_changes(
943 def assert_pr_file_changes(
929 pull_request, added=None, modified=None, removed=None):
944 pull_request, added=None, modified=None, removed=None):
930 pr_versions = PullRequestModel().get_versions(pull_request)
945 pr_versions = PullRequestModel().get_versions(pull_request)
931 # always use first version, ie original PR to calculate changes
946 # always use first version, ie original PR to calculate changes
932 pull_request_version = pr_versions[0]
947 pull_request_version = pr_versions[0]
933 old_diff_data, new_diff_data = PullRequestModel()._generate_update_diffs(
948 old_diff_data, new_diff_data = PullRequestModel()._generate_update_diffs(
934 pull_request, pull_request_version)
949 pull_request, pull_request_version)
935 file_changes = PullRequestModel()._calculate_file_changes(
950 file_changes = PullRequestModel()._calculate_file_changes(
936 old_diff_data, new_diff_data)
951 old_diff_data, new_diff_data)
937
952
938 assert added == file_changes.added, \
953 assert added == file_changes.added, \
939 'expected added:%s vs value:%s' % (added, file_changes.added)
954 'expected added:%s vs value:%s' % (added, file_changes.added)
940 assert modified == file_changes.modified, \
955 assert modified == file_changes.modified, \
941 'expected modified:%s vs value:%s' % (modified, file_changes.modified)
956 'expected modified:%s vs value:%s' % (modified, file_changes.modified)
942 assert removed == file_changes.removed, \
957 assert removed == file_changes.removed, \
943 'expected removed:%s vs value:%s' % (removed, file_changes.removed)
958 'expected removed:%s vs value:%s' % (removed, file_changes.removed)
944
959
945
960
946 def outdated_comments_patcher(use_outdated=True):
961 def outdated_comments_patcher(use_outdated=True):
947 return mock.patch.object(
962 return mock.patch.object(
948 CommentsModel, 'use_outdated_comments',
963 CommentsModel, 'use_outdated_comments',
949 return_value=use_outdated)
964 return_value=use_outdated)
@@ -1,483 +1,483 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 Test suite for making push/pull operations, on specially modified INI files
22 Test suite for making push/pull operations, on specially modified INI files
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
30
31 import time
31 import time
32
32
33 import pytest
33 import pytest
34
34
35 from rhodecode.lib import rc_cache
35 from rhodecode.lib import rc_cache
36 from rhodecode.model.auth_token import AuthTokenModel
36 from rhodecode.model.auth_token import AuthTokenModel
37 from rhodecode.model.db import Repository, UserIpMap, CacheKey
37 from rhodecode.model.db import Repository, UserIpMap, CacheKey
38 from rhodecode.model.meta import Session
38 from rhodecode.model.meta import Session
39 from rhodecode.model.repo import RepoModel
39 from rhodecode.model.repo import RepoModel
40 from rhodecode.model.user import UserModel
40 from rhodecode.model.user import UserModel
41 from rhodecode.tests import (GIT_REPO, HG_REPO, TEST_USER_ADMIN_LOGIN)
41 from rhodecode.tests import (GIT_REPO, HG_REPO, TEST_USER_ADMIN_LOGIN)
42
42
43 from rhodecode.tests.vcs_operations import (
43 from rhodecode.tests.vcs_operations import (
44 Command, _check_proper_clone, _check_proper_git_push,
44 Command, _check_proper_clone, _check_proper_git_push,
45 _add_files_and_push, HG_REPO_WITH_GROUP, GIT_REPO_WITH_GROUP)
45 _add_files_and_push, HG_REPO_WITH_GROUP, GIT_REPO_WITH_GROUP)
46
46
47
47
48 @pytest.mark.usefixtures("disable_locking", "disable_anonymous_user")
48 @pytest.mark.usefixtures("disable_locking", "disable_anonymous_user")
49 class TestVCSOperations(object):
49 class TestVCSOperations(object):
50
50
51 def test_clone_hg_repo_by_admin(self, rc_web_server, tmpdir):
51 def test_clone_hg_repo_by_admin(self, rc_web_server, tmpdir):
52 clone_url = rc_web_server.repo_clone_url(HG_REPO)
52 clone_url = rc_web_server.repo_clone_url(HG_REPO)
53 stdout, stderr = Command('/tmp').execute(
53 stdout, stderr = Command('/tmp').execute(
54 'hg clone', clone_url, tmpdir.strpath)
54 'hg clone', clone_url, tmpdir.strpath)
55 _check_proper_clone(stdout, stderr, 'hg')
55 _check_proper_clone(stdout, stderr, 'hg')
56
56
57 def test_clone_hg_repo_by_admin_pull_protocol(self, rc_web_server, tmpdir):
57 def test_clone_hg_repo_by_admin_pull_protocol(self, rc_web_server, tmpdir):
58 clone_url = rc_web_server.repo_clone_url(HG_REPO)
58 clone_url = rc_web_server.repo_clone_url(HG_REPO)
59 stdout, stderr = Command('/tmp').execute(
59 stdout, stderr = Command('/tmp').execute(
60 'hg clone --pull', clone_url, tmpdir.strpath)
60 'hg clone --pull', clone_url, tmpdir.strpath)
61 _check_proper_clone(stdout, stderr, 'hg')
61 _check_proper_clone(stdout, stderr, 'hg')
62
62
63 def test_clone_hg_repo_by_admin_pull_stream_protocol(self, rc_web_server, tmpdir):
63 def test_clone_hg_repo_by_admin_pull_stream_protocol(self, rc_web_server, tmpdir):
64 clone_url = rc_web_server.repo_clone_url(HG_REPO)
64 clone_url = rc_web_server.repo_clone_url(HG_REPO)
65 stdout, stderr = Command('/tmp').execute(
65 stdout, stderr = Command('/tmp').execute(
66 'hg clone --pull --stream', clone_url, tmpdir.strpath)
66 'hg clone --pull --stream', clone_url, tmpdir.strpath)
67 assert 'files to transfer,' in stdout
67 assert 'files to transfer,' in stdout
68 assert 'transferred 1.' in stdout
68 assert 'transferred 1.' in stdout
69 assert '114 files updated,' in stdout
69 assert '114 files updated,' in stdout
70
70
71 def test_clone_git_repo_by_admin(self, rc_web_server, tmpdir):
71 def test_clone_git_repo_by_admin(self, rc_web_server, tmpdir):
72 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
72 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
73 cmd = Command('/tmp')
73 cmd = Command('/tmp')
74 stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath)
74 stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath)
75 _check_proper_clone(stdout, stderr, 'git')
75 _check_proper_clone(stdout, stderr, 'git')
76 cmd.assert_returncode_success()
76 cmd.assert_returncode_success()
77
77
78 def test_clone_git_repo_by_admin_with_git_suffix(self, rc_web_server, tmpdir):
78 def test_clone_git_repo_by_admin_with_git_suffix(self, rc_web_server, tmpdir):
79 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
79 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
80 cmd = Command('/tmp')
80 cmd = Command('/tmp')
81 stdout, stderr = cmd.execute('git clone', clone_url+".git", tmpdir.strpath)
81 stdout, stderr = cmd.execute('git clone', clone_url+".git", tmpdir.strpath)
82 _check_proper_clone(stdout, stderr, 'git')
82 _check_proper_clone(stdout, stderr, 'git')
83 cmd.assert_returncode_success()
83 cmd.assert_returncode_success()
84
84
85 def test_clone_hg_repo_by_id_by_admin(self, rc_web_server, tmpdir):
85 def test_clone_hg_repo_by_id_by_admin(self, rc_web_server, tmpdir):
86 repo_id = Repository.get_by_repo_name(HG_REPO).repo_id
86 repo_id = Repository.get_by_repo_name(HG_REPO).repo_id
87 clone_url = rc_web_server.repo_clone_url('_%s' % repo_id)
87 clone_url = rc_web_server.repo_clone_url('_%s' % repo_id)
88 stdout, stderr = Command('/tmp').execute(
88 stdout, stderr = Command('/tmp').execute(
89 'hg clone', clone_url, tmpdir.strpath)
89 'hg clone', clone_url, tmpdir.strpath)
90 _check_proper_clone(stdout, stderr, 'hg')
90 _check_proper_clone(stdout, stderr, 'hg')
91
91
92 def test_clone_git_repo_by_id_by_admin(self, rc_web_server, tmpdir):
92 def test_clone_git_repo_by_id_by_admin(self, rc_web_server, tmpdir):
93 repo_id = Repository.get_by_repo_name(GIT_REPO).repo_id
93 repo_id = Repository.get_by_repo_name(GIT_REPO).repo_id
94 clone_url = rc_web_server.repo_clone_url('_%s' % repo_id)
94 clone_url = rc_web_server.repo_clone_url('_%s' % repo_id)
95 cmd = Command('/tmp')
95 cmd = Command('/tmp')
96 stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath)
96 stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath)
97 _check_proper_clone(stdout, stderr, 'git')
97 _check_proper_clone(stdout, stderr, 'git')
98 cmd.assert_returncode_success()
98 cmd.assert_returncode_success()
99
99
100 def test_clone_hg_repo_with_group_by_admin(self, rc_web_server, tmpdir):
100 def test_clone_hg_repo_with_group_by_admin(self, rc_web_server, tmpdir):
101 clone_url = rc_web_server.repo_clone_url(HG_REPO_WITH_GROUP)
101 clone_url = rc_web_server.repo_clone_url(HG_REPO_WITH_GROUP)
102 stdout, stderr = Command('/tmp').execute(
102 stdout, stderr = Command('/tmp').execute(
103 'hg clone', clone_url, tmpdir.strpath)
103 'hg clone', clone_url, tmpdir.strpath)
104 _check_proper_clone(stdout, stderr, 'hg')
104 _check_proper_clone(stdout, stderr, 'hg')
105
105
106 def test_clone_git_repo_with_group_by_admin(self, rc_web_server, tmpdir):
106 def test_clone_git_repo_with_group_by_admin(self, rc_web_server, tmpdir):
107 clone_url = rc_web_server.repo_clone_url(GIT_REPO_WITH_GROUP)
107 clone_url = rc_web_server.repo_clone_url(GIT_REPO_WITH_GROUP)
108 cmd = Command('/tmp')
108 cmd = Command('/tmp')
109 stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath)
109 stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath)
110 _check_proper_clone(stdout, stderr, 'git')
110 _check_proper_clone(stdout, stderr, 'git')
111 cmd.assert_returncode_success()
111 cmd.assert_returncode_success()
112
112
113 def test_clone_git_repo_shallow_by_admin(self, rc_web_server, tmpdir):
113 def test_clone_git_repo_shallow_by_admin(self, rc_web_server, tmpdir):
114 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
114 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
115 cmd = Command('/tmp')
115 cmd = Command('/tmp')
116 stdout, stderr = cmd.execute(
116 stdout, stderr = cmd.execute(
117 'git clone --depth=1', clone_url, tmpdir.strpath)
117 'git clone --depth=1', clone_url, tmpdir.strpath)
118
118
119 assert '' == stdout
119 assert '' == stdout
120 assert 'Cloning into' in stderr
120 assert 'Cloning into' in stderr
121 cmd.assert_returncode_success()
121 cmd.assert_returncode_success()
122
122
123 def test_clone_wrong_credentials_hg(self, rc_web_server, tmpdir):
123 def test_clone_wrong_credentials_hg(self, rc_web_server, tmpdir):
124 clone_url = rc_web_server.repo_clone_url(HG_REPO, passwd='bad!')
124 clone_url = rc_web_server.repo_clone_url(HG_REPO, passwd='bad!')
125 stdout, stderr = Command('/tmp').execute(
125 stdout, stderr = Command('/tmp').execute(
126 'hg clone', clone_url, tmpdir.strpath)
126 'hg clone', clone_url, tmpdir.strpath)
127 assert 'abort: authorization failed' in stderr
127 assert 'abort: authorization failed' in stderr
128
128
129 def test_clone_wrong_credentials_git(self, rc_web_server, tmpdir):
129 def test_clone_wrong_credentials_git(self, rc_web_server, tmpdir):
130 clone_url = rc_web_server.repo_clone_url(GIT_REPO, passwd='bad!')
130 clone_url = rc_web_server.repo_clone_url(GIT_REPO, passwd='bad!')
131 stdout, stderr = Command('/tmp').execute(
131 stdout, stderr = Command('/tmp').execute(
132 'git clone', clone_url, tmpdir.strpath)
132 'git clone', clone_url, tmpdir.strpath)
133 assert 'fatal: Authentication failed' in stderr
133 assert 'fatal: Authentication failed' in stderr
134
134
135 def test_clone_git_dir_as_hg(self, rc_web_server, tmpdir):
135 def test_clone_git_dir_as_hg(self, rc_web_server, tmpdir):
136 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
136 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
137 stdout, stderr = Command('/tmp').execute(
137 stdout, stderr = Command('/tmp').execute(
138 'hg clone', clone_url, tmpdir.strpath)
138 'hg clone', clone_url, tmpdir.strpath)
139 assert 'HTTP Error 404: Not Found' in stderr
139 assert 'HTTP Error 404: Not Found' in stderr
140
140
141 def test_clone_hg_repo_as_git(self, rc_web_server, tmpdir):
141 def test_clone_hg_repo_as_git(self, rc_web_server, tmpdir):
142 clone_url = rc_web_server.repo_clone_url(HG_REPO)
142 clone_url = rc_web_server.repo_clone_url(HG_REPO)
143 stdout, stderr = Command('/tmp').execute(
143 stdout, stderr = Command('/tmp').execute(
144 'git clone', clone_url, tmpdir.strpath)
144 'git clone', clone_url, tmpdir.strpath)
145 assert 'not found' in stderr
145 assert 'not found' in stderr
146
146
147 def test_clone_non_existing_path_hg(self, rc_web_server, tmpdir):
147 def test_clone_non_existing_path_hg(self, rc_web_server, tmpdir):
148 clone_url = rc_web_server.repo_clone_url('trololo')
148 clone_url = rc_web_server.repo_clone_url('trololo')
149 stdout, stderr = Command('/tmp').execute(
149 stdout, stderr = Command('/tmp').execute(
150 'hg clone', clone_url, tmpdir.strpath)
150 'hg clone', clone_url, tmpdir.strpath)
151 assert 'HTTP Error 404: Not Found' in stderr
151 assert 'HTTP Error 404: Not Found' in stderr
152
152
153 def test_clone_non_existing_path_git(self, rc_web_server, tmpdir):
153 def test_clone_non_existing_path_git(self, rc_web_server, tmpdir):
154 clone_url = rc_web_server.repo_clone_url('trololo')
154 clone_url = rc_web_server.repo_clone_url('trololo')
155 stdout, stderr = Command('/tmp').execute('git clone', clone_url)
155 stdout, stderr = Command('/tmp').execute('git clone', clone_url)
156 assert 'not found' in stderr
156 assert 'not found' in stderr
157
157
158 def test_clone_hg_with_slashes(self, rc_web_server, tmpdir):
158 def test_clone_hg_with_slashes(self, rc_web_server, tmpdir):
159 clone_url = rc_web_server.repo_clone_url('//' + HG_REPO)
159 clone_url = rc_web_server.repo_clone_url('//' + HG_REPO)
160 stdout, stderr = Command('/tmp').execute('hg clone', clone_url, tmpdir.strpath)
160 stdout, stderr = Command('/tmp').execute('hg clone', clone_url, tmpdir.strpath)
161 assert 'HTTP Error 404: Not Found' in stderr
161 assert 'HTTP Error 404: Not Found' in stderr
162
162
163 def test_clone_git_with_slashes(self, rc_web_server, tmpdir):
163 def test_clone_git_with_slashes(self, rc_web_server, tmpdir):
164 clone_url = rc_web_server.repo_clone_url('//' + GIT_REPO)
164 clone_url = rc_web_server.repo_clone_url('//' + GIT_REPO)
165 stdout, stderr = Command('/tmp').execute('git clone', clone_url)
165 stdout, stderr = Command('/tmp').execute('git clone', clone_url)
166 assert 'not found' in stderr
166 assert 'not found' in stderr
167
167
168 def test_clone_existing_path_hg_not_in_database(
168 def test_clone_existing_path_hg_not_in_database(
169 self, rc_web_server, tmpdir, fs_repo_only):
169 self, rc_web_server, tmpdir, fs_repo_only):
170
170
171 db_name = fs_repo_only('not-in-db-hg', repo_type='hg')
171 db_name = fs_repo_only('not-in-db-hg', repo_type='hg')
172 clone_url = rc_web_server.repo_clone_url(db_name)
172 clone_url = rc_web_server.repo_clone_url(db_name)
173 stdout, stderr = Command('/tmp').execute(
173 stdout, stderr = Command('/tmp').execute(
174 'hg clone', clone_url, tmpdir.strpath)
174 'hg clone', clone_url, tmpdir.strpath)
175 assert 'HTTP Error 404: Not Found' in stderr
175 assert 'HTTP Error 404: Not Found' in stderr
176
176
177 def test_clone_existing_path_git_not_in_database(
177 def test_clone_existing_path_git_not_in_database(
178 self, rc_web_server, tmpdir, fs_repo_only):
178 self, rc_web_server, tmpdir, fs_repo_only):
179 db_name = fs_repo_only('not-in-db-git', repo_type='git')
179 db_name = fs_repo_only('not-in-db-git', repo_type='git')
180 clone_url = rc_web_server.repo_clone_url(db_name)
180 clone_url = rc_web_server.repo_clone_url(db_name)
181 stdout, stderr = Command('/tmp').execute(
181 stdout, stderr = Command('/tmp').execute(
182 'git clone', clone_url, tmpdir.strpath)
182 'git clone', clone_url, tmpdir.strpath)
183 assert 'not found' in stderr
183 assert 'not found' in stderr
184
184
185 def test_clone_existing_path_hg_not_in_database_different_scm(
185 def test_clone_existing_path_hg_not_in_database_different_scm(
186 self, rc_web_server, tmpdir, fs_repo_only):
186 self, rc_web_server, tmpdir, fs_repo_only):
187 db_name = fs_repo_only('not-in-db-git', repo_type='git')
187 db_name = fs_repo_only('not-in-db-git', repo_type='git')
188 clone_url = rc_web_server.repo_clone_url(db_name)
188 clone_url = rc_web_server.repo_clone_url(db_name)
189 stdout, stderr = Command('/tmp').execute(
189 stdout, stderr = Command('/tmp').execute(
190 'hg clone', clone_url, tmpdir.strpath)
190 'hg clone', clone_url, tmpdir.strpath)
191 assert 'HTTP Error 404: Not Found' in stderr
191 assert 'HTTP Error 404: Not Found' in stderr
192
192
193 def test_clone_existing_path_git_not_in_database_different_scm(
193 def test_clone_existing_path_git_not_in_database_different_scm(
194 self, rc_web_server, tmpdir, fs_repo_only):
194 self, rc_web_server, tmpdir, fs_repo_only):
195 db_name = fs_repo_only('not-in-db-hg', repo_type='hg')
195 db_name = fs_repo_only('not-in-db-hg', repo_type='hg')
196 clone_url = rc_web_server.repo_clone_url(db_name)
196 clone_url = rc_web_server.repo_clone_url(db_name)
197 stdout, stderr = Command('/tmp').execute(
197 stdout, stderr = Command('/tmp').execute(
198 'git clone', clone_url, tmpdir.strpath)
198 'git clone', clone_url, tmpdir.strpath)
199 assert 'not found' in stderr
199 assert 'not found' in stderr
200
200
201 def test_clone_non_existing_store_path_hg(self, rc_web_server, tmpdir, user_util):
201 def test_clone_non_existing_store_path_hg(self, rc_web_server, tmpdir, user_util):
202 repo = user_util.create_repo()
202 repo = user_util.create_repo()
203 clone_url = rc_web_server.repo_clone_url(repo.repo_name)
203 clone_url = rc_web_server.repo_clone_url(repo.repo_name)
204
204
205 # Damage repo by removing it's folder
205 # Damage repo by removing it's folder
206 RepoModel()._delete_filesystem_repo(repo)
206 RepoModel()._delete_filesystem_repo(repo)
207
207
208 stdout, stderr = Command('/tmp').execute(
208 stdout, stderr = Command('/tmp').execute(
209 'hg clone', clone_url, tmpdir.strpath)
209 'hg clone', clone_url, tmpdir.strpath)
210 assert 'HTTP Error 404: Not Found' in stderr
210 assert 'HTTP Error 404: Not Found' in stderr
211
211
212 def test_clone_non_existing_store_path_git(self, rc_web_server, tmpdir, user_util):
212 def test_clone_non_existing_store_path_git(self, rc_web_server, tmpdir, user_util):
213 repo = user_util.create_repo(repo_type='git')
213 repo = user_util.create_repo(repo_type='git')
214 clone_url = rc_web_server.repo_clone_url(repo.repo_name)
214 clone_url = rc_web_server.repo_clone_url(repo.repo_name)
215
215
216 # Damage repo by removing it's folder
216 # Damage repo by removing it's folder
217 RepoModel()._delete_filesystem_repo(repo)
217 RepoModel()._delete_filesystem_repo(repo)
218
218
219 stdout, stderr = Command('/tmp').execute(
219 stdout, stderr = Command('/tmp').execute(
220 'git clone', clone_url, tmpdir.strpath)
220 'git clone', clone_url, tmpdir.strpath)
221 assert 'not found' in stderr
221 assert 'not found' in stderr
222
222
223 def test_push_new_file_hg(self, rc_web_server, tmpdir):
223 def test_push_new_file_hg(self, rc_web_server, tmpdir):
224 clone_url = rc_web_server.repo_clone_url(HG_REPO)
224 clone_url = rc_web_server.repo_clone_url(HG_REPO)
225 stdout, stderr = Command('/tmp').execute(
225 stdout, stderr = Command('/tmp').execute(
226 'hg clone', clone_url, tmpdir.strpath)
226 'hg clone', clone_url, tmpdir.strpath)
227
227
228 stdout, stderr = _add_files_and_push(
228 stdout, stderr = _add_files_and_push(
229 'hg', tmpdir.strpath, clone_url=clone_url)
229 'hg', tmpdir.strpath, clone_url=clone_url)
230
230
231 assert 'pushing to' in stdout
231 assert 'pushing to' in stdout
232 assert 'size summary' in stdout
232 assert 'size summary' in stdout
233
233
234 def test_push_new_file_git(self, rc_web_server, tmpdir):
234 def test_push_new_file_git(self, rc_web_server, tmpdir):
235 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
235 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
236 stdout, stderr = Command('/tmp').execute(
236 stdout, stderr = Command('/tmp').execute(
237 'git clone', clone_url, tmpdir.strpath)
237 'git clone', clone_url, tmpdir.strpath)
238
238
239 # commit some stuff into this repo
239 # commit some stuff into this repo
240 stdout, stderr = _add_files_and_push(
240 stdout, stderr = _add_files_and_push(
241 'git', tmpdir.strpath, clone_url=clone_url)
241 'git', tmpdir.strpath, clone_url=clone_url)
242
242
243 _check_proper_git_push(stdout, stderr)
243 _check_proper_git_push(stdout, stderr)
244
244
245 def test_push_invalidates_cache(self, rc_web_server, tmpdir):
245 def test_push_invalidates_cache(self, rc_web_server, tmpdir):
246 hg_repo = Repository.get_by_repo_name(HG_REPO)
246 hg_repo = Repository.get_by_repo_name(HG_REPO)
247
247
248 # init cache objects
248 # init cache objects
249 CacheKey.delete_all_cache()
249 CacheKey.delete_all_cache()
250 cache_namespace_uid = 'cache_push_test.{}'.format(hg_repo.repo_id)
250 cache_namespace_uid = 'cache_push_test.{}'.format(hg_repo.repo_id)
251 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
251 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
252 repo_id=hg_repo.repo_id)
252 repo_id=hg_repo.repo_id)
253
253
254 inv_context_manager = rc_cache.InvalidationContext(
254 inv_context_manager = rc_cache.InvalidationContext(
255 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace)
255 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace)
256
256
257 with inv_context_manager as invalidation_context:
257 with inv_context_manager as invalidation_context:
258 # __enter__ will create and register cache objects
258 # __enter__ will create and register cache objects
259 pass
259 pass
260
260
261 # clone to init cache
261 # clone to init cache
262 clone_url = rc_web_server.repo_clone_url(hg_repo.repo_name)
262 clone_url = rc_web_server.repo_clone_url(hg_repo.repo_name)
263 stdout, stderr = Command('/tmp').execute(
263 stdout, stderr = Command('/tmp').execute(
264 'hg clone', clone_url, tmpdir.strpath)
264 'hg clone', clone_url, tmpdir.strpath)
265
265
266 cache_keys = hg_repo.cache_keys
266 cache_keys = hg_repo.cache_keys
267 assert cache_keys != []
267 assert cache_keys != []
268 for key in cache_keys:
268 for key in cache_keys:
269 assert key.cache_active is True
269 assert key.cache_active is True
270
270
271 # PUSH that should trigger invalidation cache
271 # PUSH that should trigger invalidation cache
272 stdout, stderr = _add_files_and_push(
272 stdout, stderr = _add_files_and_push(
273 'hg', tmpdir.strpath, clone_url=clone_url, files_no=1)
273 'hg', tmpdir.strpath, clone_url=clone_url, files_no=1)
274
274
275 # flush...
275 # flush...
276 Session().commit()
276 Session().commit()
277 hg_repo = Repository.get_by_repo_name(HG_REPO)
277 hg_repo = Repository.get_by_repo_name(HG_REPO)
278 cache_keys = hg_repo.cache_keys
278 cache_keys = hg_repo.cache_keys
279 assert cache_keys != []
279 assert cache_keys != []
280 for key in cache_keys:
280 for key in cache_keys:
281 # keys should be marked as not active
281 # keys should be marked as not active
282 assert key.cache_active is False
282 assert key.cache_active is False
283
283
284 def test_push_wrong_credentials_hg(self, rc_web_server, tmpdir):
284 def test_push_wrong_credentials_hg(self, rc_web_server, tmpdir):
285 clone_url = rc_web_server.repo_clone_url(HG_REPO)
285 clone_url = rc_web_server.repo_clone_url(HG_REPO)
286 stdout, stderr = Command('/tmp').execute(
286 stdout, stderr = Command('/tmp').execute(
287 'hg clone', clone_url, tmpdir.strpath)
287 'hg clone', clone_url, tmpdir.strpath)
288
288
289 push_url = rc_web_server.repo_clone_url(
289 push_url = rc_web_server.repo_clone_url(
290 HG_REPO, user='bad', passwd='name')
290 HG_REPO, user='bad', passwd='name')
291 stdout, stderr = _add_files_and_push(
291 stdout, stderr = _add_files_and_push(
292 'hg', tmpdir.strpath, clone_url=push_url)
292 'hg', tmpdir.strpath, clone_url=push_url)
293
293
294 assert 'abort: authorization failed' in stderr
294 assert 'abort: authorization failed' in stderr
295
295
296 def test_push_wrong_credentials_git(self, rc_web_server, tmpdir):
296 def test_push_wrong_credentials_git(self, rc_web_server, tmpdir):
297 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
297 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
298 stdout, stderr = Command('/tmp').execute(
298 stdout, stderr = Command('/tmp').execute(
299 'git clone', clone_url, tmpdir.strpath)
299 'git clone', clone_url, tmpdir.strpath)
300
300
301 push_url = rc_web_server.repo_clone_url(
301 push_url = rc_web_server.repo_clone_url(
302 GIT_REPO, user='bad', passwd='name')
302 GIT_REPO, user='bad', passwd='name')
303 stdout, stderr = _add_files_and_push(
303 stdout, stderr = _add_files_and_push(
304 'git', tmpdir.strpath, clone_url=push_url)
304 'git', tmpdir.strpath, clone_url=push_url)
305
305
306 assert 'fatal: Authentication failed' in stderr
306 assert 'fatal: Authentication failed' in stderr
307
307
308 def test_push_back_to_wrong_url_hg(self, rc_web_server, tmpdir):
308 def test_push_back_to_wrong_url_hg(self, rc_web_server, tmpdir):
309 clone_url = rc_web_server.repo_clone_url(HG_REPO)
309 clone_url = rc_web_server.repo_clone_url(HG_REPO)
310 stdout, stderr = Command('/tmp').execute(
310 stdout, stderr = Command('/tmp').execute(
311 'hg clone', clone_url, tmpdir.strpath)
311 'hg clone', clone_url, tmpdir.strpath)
312
312
313 stdout, stderr = _add_files_and_push(
313 stdout, stderr = _add_files_and_push(
314 'hg', tmpdir.strpath,
314 'hg', tmpdir.strpath,
315 clone_url=rc_web_server.repo_clone_url('not-existing'))
315 clone_url=rc_web_server.repo_clone_url('not-existing'))
316
316
317 assert 'HTTP Error 404: Not Found' in stderr
317 assert 'HTTP Error 404: Not Found' in stderr
318
318
319 def test_push_back_to_wrong_url_git(self, rc_web_server, tmpdir):
319 def test_push_back_to_wrong_url_git(self, rc_web_server, tmpdir):
320 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
320 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
321 stdout, stderr = Command('/tmp').execute(
321 stdout, stderr = Command('/tmp').execute(
322 'git clone', clone_url, tmpdir.strpath)
322 'git clone', clone_url, tmpdir.strpath)
323
323
324 stdout, stderr = _add_files_and_push(
324 stdout, stderr = _add_files_and_push(
325 'git', tmpdir.strpath,
325 'git', tmpdir.strpath,
326 clone_url=rc_web_server.repo_clone_url('not-existing'))
326 clone_url=rc_web_server.repo_clone_url('not-existing'))
327
327
328 assert 'not found' in stderr
328 assert 'not found' in stderr
329
329
330 def test_ip_restriction_hg(self, rc_web_server, tmpdir):
330 def test_ip_restriction_hg(self, rc_web_server, tmpdir):
331 user_model = UserModel()
331 user_model = UserModel()
332 try:
332 try:
333 user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
333 user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
334 Session().commit()
334 Session().commit()
335 time.sleep(2)
335 time.sleep(2)
336 clone_url = rc_web_server.repo_clone_url(HG_REPO)
336 clone_url = rc_web_server.repo_clone_url(HG_REPO)
337 stdout, stderr = Command('/tmp').execute(
337 stdout, stderr = Command('/tmp').execute(
338 'hg clone', clone_url, tmpdir.strpath)
338 'hg clone', clone_url, tmpdir.strpath)
339 assert 'abort: HTTP Error 403: Forbidden' in stderr
339 assert 'abort: HTTP Error 403: Forbidden' in stderr
340 finally:
340 finally:
341 # release IP restrictions
341 # release IP restrictions
342 for ip in UserIpMap.getAll():
342 for ip in UserIpMap.getAll():
343 UserIpMap.delete(ip.ip_id)
343 UserIpMap.delete(ip.ip_id)
344 Session().commit()
344 Session().commit()
345
345
346 time.sleep(2)
346 time.sleep(2)
347
347
348 stdout, stderr = Command('/tmp').execute(
348 stdout, stderr = Command('/tmp').execute(
349 'hg clone', clone_url, tmpdir.strpath)
349 'hg clone', clone_url, tmpdir.strpath)
350 _check_proper_clone(stdout, stderr, 'hg')
350 _check_proper_clone(stdout, stderr, 'hg')
351
351
352 def test_ip_restriction_git(self, rc_web_server, tmpdir):
352 def test_ip_restriction_git(self, rc_web_server, tmpdir):
353 user_model = UserModel()
353 user_model = UserModel()
354 try:
354 try:
355 user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
355 user_model.add_extra_ip(TEST_USER_ADMIN_LOGIN, '10.10.10.10/32')
356 Session().commit()
356 Session().commit()
357 time.sleep(2)
357 time.sleep(2)
358 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
358 clone_url = rc_web_server.repo_clone_url(GIT_REPO)
359 stdout, stderr = Command('/tmp').execute(
359 stdout, stderr = Command('/tmp').execute(
360 'git clone', clone_url, tmpdir.strpath)
360 'git clone', clone_url, tmpdir.strpath)
361 msg = "The requested URL returned error: 403"
361 msg = "The requested URL returned error: 403"
362 assert msg in stderr
362 assert msg in stderr
363 finally:
363 finally:
364 # release IP restrictions
364 # release IP restrictions
365 for ip in UserIpMap.getAll():
365 for ip in UserIpMap.getAll():
366 UserIpMap.delete(ip.ip_id)
366 UserIpMap.delete(ip.ip_id)
367 Session().commit()
367 Session().commit()
368
368
369 time.sleep(2)
369 time.sleep(2)
370
370
371 cmd = Command('/tmp')
371 cmd = Command('/tmp')
372 stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath)
372 stdout, stderr = cmd.execute('git clone', clone_url, tmpdir.strpath)
373 cmd.assert_returncode_success()
373 cmd.assert_returncode_success()
374 _check_proper_clone(stdout, stderr, 'git')
374 _check_proper_clone(stdout, stderr, 'git')
375
375
376 def test_clone_by_auth_token(
376 def test_clone_by_auth_token(
377 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
377 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
378 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
378 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
379 'egg:rhodecode-enterprise-ce#rhodecode'])
379 'egg:rhodecode-enterprise-ce#rhodecode'])
380
380
381 user = user_util.create_user()
381 user = user_util.create_user()
382 token = user.auth_tokens[1]
382 token = user.auth_tokens[1]
383
383
384 clone_url = rc_web_server.repo_clone_url(
384 clone_url = rc_web_server.repo_clone_url(
385 HG_REPO, user=user.username, passwd=token)
385 HG_REPO, user=user.username, passwd=token)
386
386
387 stdout, stderr = Command('/tmp').execute(
387 stdout, stderr = Command('/tmp').execute(
388 'hg clone', clone_url, tmpdir.strpath)
388 'hg clone', clone_url, tmpdir.strpath)
389 _check_proper_clone(stdout, stderr, 'hg')
389 _check_proper_clone(stdout, stderr, 'hg')
390
390
391 def test_clone_by_auth_token_expired(
391 def test_clone_by_auth_token_expired(
392 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
392 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
393 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
393 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
394 'egg:rhodecode-enterprise-ce#rhodecode'])
394 'egg:rhodecode-enterprise-ce#rhodecode'])
395
395
396 user = user_util.create_user()
396 user = user_util.create_user()
397 auth_token = AuthTokenModel().create(
397 auth_token = AuthTokenModel().create(
398 user.user_id, 'test-token', -10, AuthTokenModel.cls.ROLE_VCS)
398 user.user_id, u'test-token', -10, AuthTokenModel.cls.ROLE_VCS)
399 token = auth_token.api_key
399 token = auth_token.api_key
400
400
401 clone_url = rc_web_server.repo_clone_url(
401 clone_url = rc_web_server.repo_clone_url(
402 HG_REPO, user=user.username, passwd=token)
402 HG_REPO, user=user.username, passwd=token)
403
403
404 stdout, stderr = Command('/tmp').execute(
404 stdout, stderr = Command('/tmp').execute(
405 'hg clone', clone_url, tmpdir.strpath)
405 'hg clone', clone_url, tmpdir.strpath)
406 assert 'abort: authorization failed' in stderr
406 assert 'abort: authorization failed' in stderr
407
407
408 def test_clone_by_auth_token_bad_role(
408 def test_clone_by_auth_token_bad_role(
409 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
409 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
410 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
410 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
411 'egg:rhodecode-enterprise-ce#rhodecode'])
411 'egg:rhodecode-enterprise-ce#rhodecode'])
412
412
413 user = user_util.create_user()
413 user = user_util.create_user()
414 auth_token = AuthTokenModel().create(
414 auth_token = AuthTokenModel().create(
415 user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_API)
415 user.user_id, u'test-token', -1, AuthTokenModel.cls.ROLE_API)
416 token = auth_token.api_key
416 token = auth_token.api_key
417
417
418 clone_url = rc_web_server.repo_clone_url(
418 clone_url = rc_web_server.repo_clone_url(
419 HG_REPO, user=user.username, passwd=token)
419 HG_REPO, user=user.username, passwd=token)
420
420
421 stdout, stderr = Command('/tmp').execute(
421 stdout, stderr = Command('/tmp').execute(
422 'hg clone', clone_url, tmpdir.strpath)
422 'hg clone', clone_url, tmpdir.strpath)
423 assert 'abort: authorization failed' in stderr
423 assert 'abort: authorization failed' in stderr
424
424
425 def test_clone_by_auth_token_user_disabled(
425 def test_clone_by_auth_token_user_disabled(
426 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
426 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
427 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
427 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
428 'egg:rhodecode-enterprise-ce#rhodecode'])
428 'egg:rhodecode-enterprise-ce#rhodecode'])
429 user = user_util.create_user()
429 user = user_util.create_user()
430 user.active = False
430 user.active = False
431 Session().add(user)
431 Session().add(user)
432 Session().commit()
432 Session().commit()
433 token = user.auth_tokens[1]
433 token = user.auth_tokens[1]
434
434
435 clone_url = rc_web_server.repo_clone_url(
435 clone_url = rc_web_server.repo_clone_url(
436 HG_REPO, user=user.username, passwd=token)
436 HG_REPO, user=user.username, passwd=token)
437
437
438 stdout, stderr = Command('/tmp').execute(
438 stdout, stderr = Command('/tmp').execute(
439 'hg clone', clone_url, tmpdir.strpath)
439 'hg clone', clone_url, tmpdir.strpath)
440 assert 'abort: authorization failed' in stderr
440 assert 'abort: authorization failed' in stderr
441
441
442 def test_clone_by_auth_token_with_scope(
442 def test_clone_by_auth_token_with_scope(
443 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
443 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
444 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
444 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
445 'egg:rhodecode-enterprise-ce#rhodecode'])
445 'egg:rhodecode-enterprise-ce#rhodecode'])
446 user = user_util.create_user()
446 user = user_util.create_user()
447 auth_token = AuthTokenModel().create(
447 auth_token = AuthTokenModel().create(
448 user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_VCS)
448 user.user_id, u'test-token', -1, AuthTokenModel.cls.ROLE_VCS)
449 token = auth_token.api_key
449 token = auth_token.api_key
450
450
451 # manually set scope
451 # manually set scope
452 auth_token.repo = Repository.get_by_repo_name(HG_REPO)
452 auth_token.repo = Repository.get_by_repo_name(HG_REPO)
453 Session().add(auth_token)
453 Session().add(auth_token)
454 Session().commit()
454 Session().commit()
455
455
456 clone_url = rc_web_server.repo_clone_url(
456 clone_url = rc_web_server.repo_clone_url(
457 HG_REPO, user=user.username, passwd=token)
457 HG_REPO, user=user.username, passwd=token)
458
458
459 stdout, stderr = Command('/tmp').execute(
459 stdout, stderr = Command('/tmp').execute(
460 'hg clone', clone_url, tmpdir.strpath)
460 'hg clone', clone_url, tmpdir.strpath)
461 _check_proper_clone(stdout, stderr, 'hg')
461 _check_proper_clone(stdout, stderr, 'hg')
462
462
463 def test_clone_by_auth_token_with_wrong_scope(
463 def test_clone_by_auth_token_with_wrong_scope(
464 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
464 self, rc_web_server, tmpdir, user_util, enable_auth_plugins):
465 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
465 enable_auth_plugins(['egg:rhodecode-enterprise-ce#token',
466 'egg:rhodecode-enterprise-ce#rhodecode'])
466 'egg:rhodecode-enterprise-ce#rhodecode'])
467 user = user_util.create_user()
467 user = user_util.create_user()
468 auth_token = AuthTokenModel().create(
468 auth_token = AuthTokenModel().create(
469 user.user_id, 'test-token', -1, AuthTokenModel.cls.ROLE_VCS)
469 user.user_id, u'test-token', -1, AuthTokenModel.cls.ROLE_VCS)
470 token = auth_token.api_key
470 token = auth_token.api_key
471
471
472 # manually set scope
472 # manually set scope
473 auth_token.repo = Repository.get_by_repo_name(GIT_REPO)
473 auth_token.repo = Repository.get_by_repo_name(GIT_REPO)
474 Session().add(auth_token)
474 Session().add(auth_token)
475 Session().commit()
475 Session().commit()
476
476
477 clone_url = rc_web_server.repo_clone_url(
477 clone_url = rc_web_server.repo_clone_url(
478 HG_REPO, user=user.username, passwd=token)
478 HG_REPO, user=user.username, passwd=token)
479
479
480 stdout, stderr = Command('/tmp').execute(
480 stdout, stderr = Command('/tmp').execute(
481 'hg clone', clone_url, tmpdir.strpath)
481 'hg clone', clone_url, tmpdir.strpath)
482 assert 'abort: authorization failed' in stderr
482 assert 'abort: authorization failed' in stderr
483
483
General Comments 0
You need to be logged in to leave comments. Login now