##// END OF EJS Templates
webhelpers: port most of the items from webhelpers to webhelpers2...
dan -
r4090:5358a9a7 default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,127 +1,127 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import pytest
23 23
24 24 import rhodecode
25 25 from rhodecode.model.db import Repository
26 26 from rhodecode.model.meta import Session
27 27 from rhodecode.model.repo import RepoModel
28 28 from rhodecode.model.repo_group import RepoGroupModel
29 29 from rhodecode.model.settings import SettingsModel
30 30 from rhodecode.tests import TestController
31 31 from rhodecode.tests.fixture import Fixture
32 32 from rhodecode.lib import helpers as h
33 33
34 34 fixture = Fixture()
35 35
36 36
37 37 def route_path(name, **kwargs):
38 38 return {
39 39 'home': '/',
40 40 'repo_group_home': '/{repo_group_name}'
41 41 }[name].format(**kwargs)
42 42
43 43
44 44 class TestHomeController(TestController):
45 45
46 46 def test_index(self):
47 47 self.log_user()
48 48 response = self.app.get(route_path('home'))
49 49 # if global permission is set
50 50 response.mustcontain('New Repository')
51 51
52 52 # search for objects inside the JavaScript JSON
53 53 for repo in Repository.getAll():
54 54 response.mustcontain('"name_raw": "%s"' % repo.repo_name)
55 55
56 56 def test_index_contains_statics_with_ver(self):
57 57 from rhodecode.lib.base import calculate_version_hash
58 58
59 59 self.log_user()
60 60 response = self.app.get(route_path('home'))
61 61
62 62 rhodecode_version_hash = calculate_version_hash(
63 63 {'beaker.session.secret': 'test-rc-uytcxaz'})
64 64 response.mustcontain('style.css?ver={0}'.format(rhodecode_version_hash))
65 65 response.mustcontain('scripts.min.js?ver={0}'.format(rhodecode_version_hash))
66 66
67 67 def test_index_contains_backend_specific_details(self, backend):
68 68 self.log_user()
69 69 response = self.app.get(route_path('home'))
70 70 tip = backend.repo.get_commit().raw_id
71 71
72 72 # html in javascript variable:
73 73 response.mustcontain(r'<i class=\"icon-%s\"' % (backend.alias, ))
74 74 response.mustcontain(r'href=\"/%s\"' % (backend.repo_name, ))
75 75
76 76 response.mustcontain("""/%s/changeset/%s""" % (backend.repo_name, tip))
77 77 response.mustcontain("""Added a symlink""")
78 78
79 79 def test_index_with_anonymous_access_disabled(self):
80 80 with fixture.anon_access(False):
81 81 response = self.app.get(route_path('home'), status=302)
82 82 assert 'login' in response.location
83 83
84 84 def test_index_page_on_groups(self, autologin_user, repo_group):
85 85 response = self.app.get(route_path('repo_group_home', repo_group_name='gr1'))
86 86 response.mustcontain("gr1/repo_in_group")
87 87
88 88 def test_index_page_on_group_with_trailing_slash(
89 89 self, autologin_user, repo_group):
90 90 response = self.app.get(route_path('repo_group_home', repo_group_name='gr1') + '/')
91 91 response.mustcontain("gr1/repo_in_group")
92 92
93 93 @pytest.fixture(scope='class')
94 94 def repo_group(self, request):
95 95 gr = fixture.create_repo_group('gr1')
96 96 fixture.create_repo(name='gr1/repo_in_group', repo_group=gr)
97 97
98 98 @request.addfinalizer
99 99 def cleanup():
100 100 RepoModel().delete('gr1/repo_in_group')
101 101 RepoGroupModel().delete(repo_group='gr1', force_delete=True)
102 102 Session().commit()
103 103
104 104 @pytest.mark.parametrize("name, state", [
105 105 ('Disabled', False),
106 106 ('Enabled', True),
107 107 ])
108 108 def test_index_show_version(self, autologin_user, name, state):
109 109 version_string = 'RhodeCode Enterprise %s' % rhodecode.__version__
110 110
111 111 sett = SettingsModel().create_or_update_setting(
112 112 'show_version', state, 'bool')
113 113 Session().add(sett)
114 114 Session().commit()
115 115 SettingsModel().invalidate_settings_cache()
116 116
117 117 response = self.app.get(route_path('home'))
118 118 if state is True:
119 119 response.mustcontain(version_string)
120 120 if state is False:
121 121 response.mustcontain(no=[version_string])
122 122
123 123 def test_logout_form_contains_csrf(self, autologin_user, csrf_token):
124 124 response = self.app.get(route_path('home'))
125 125 assert_response = response.assert_response()
126 element = assert_response.get_element('.logout #csrf_token')
126 element = assert_response.get_element('.logout [name=csrf_token]')
127 127 assert element.value == csrf_token
@@ -1,173 +1,173 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import urllib
23 23 from pyramid.view import view_config
24 from webhelpers.util import update_params
24 from webhelpers2.html.tools import update_params
25 25
26 26 from rhodecode.apps._base import BaseAppView, RepoAppView, RepoGroupAppView
27 27 from rhodecode.lib.auth import (
28 28 LoginRequired, HasRepoPermissionAnyDecorator, HasRepoGroupPermissionAnyDecorator)
29 29 from rhodecode.lib.helpers import Page
30 30 from rhodecode.lib.utils2 import safe_str
31 31 from rhodecode.lib.index import searcher_from_config
32 32 from rhodecode.model import validation_schema
33 33 from rhodecode.model.validation_schema.schemas import search_schema
34 34
35 35 log = logging.getLogger(__name__)
36 36
37 37
38 38 def perform_search(request, tmpl_context, repo_name=None, repo_group_name=None):
39 39 searcher = searcher_from_config(request.registry.settings)
40 40 formatted_results = []
41 41 execution_time = ''
42 42
43 43 schema = search_schema.SearchParamsSchema()
44 44 search_tags = []
45 45 search_params = {}
46 46 errors = []
47 47
48 48 try:
49 49 search_params = schema.deserialize(
50 50 dict(
51 51 search_query=request.GET.get('q'),
52 52 search_type=request.GET.get('type'),
53 53 search_sort=request.GET.get('sort'),
54 54 search_max_lines=request.GET.get('max_lines'),
55 55 page_limit=request.GET.get('page_limit'),
56 56 requested_page=request.GET.get('page'),
57 57 )
58 58 )
59 59 except validation_schema.Invalid as e:
60 60 errors = e.children
61 61
62 62 def url_generator(**kw):
63 63 q = urllib.quote(safe_str(search_query))
64 64 return update_params(
65 65 "?q=%s&type=%s&max_lines=%s&sort=%s" % (
66 66 q, safe_str(search_type), search_max_lines, search_sort), **kw)
67 67
68 68 c = tmpl_context
69 69 search_query = search_params.get('search_query')
70 70 search_type = search_params.get('search_type')
71 71 search_sort = search_params.get('search_sort')
72 72 search_max_lines = search_params.get('search_max_lines')
73 73 if search_params.get('search_query'):
74 74 page_limit = search_params['page_limit']
75 75 requested_page = search_params['requested_page']
76 76
77 77 try:
78 78 search_result = searcher.search(
79 79 search_query, search_type, c.auth_user, repo_name, repo_group_name,
80 80 requested_page=requested_page, page_limit=page_limit, sort=search_sort)
81 81
82 82 formatted_results = Page(
83 83 search_result['results'], page=requested_page,
84 84 item_count=search_result['count'],
85 85 items_per_page=page_limit, url=url_generator)
86 86 finally:
87 87 searcher.cleanup()
88 88
89 89 search_tags = searcher.extract_search_tags(search_query)
90 90
91 91 if not search_result['error']:
92 92 execution_time = '%s results (%.4f seconds)' % (
93 93 search_result['count'],
94 94 search_result['runtime'])
95 95 elif not errors:
96 96 node = schema['search_query']
97 97 errors = [
98 98 validation_schema.Invalid(node, search_result['error'])]
99 99
100 100 c.perm_user = c.auth_user
101 101 c.repo_name = repo_name
102 102 c.repo_group_name = repo_group_name
103 103 c.url_generator = url_generator
104 104 c.errors = errors
105 105 c.formatted_results = formatted_results
106 106 c.runtime = execution_time
107 107 c.cur_query = search_query
108 108 c.search_type = search_type
109 109 c.searcher = searcher
110 110 c.search_tags = search_tags
111 111
112 112 direction, sort_field = searcher.get_sort(search_type, search_sort)
113 113 sort_definition = searcher.sort_def(search_type, direction, sort_field)
114 114 c.sort = ''
115 115 c.sort_tag = None
116 116 c.sort_tag_dir = direction
117 117 if sort_definition:
118 118 c.sort = '{}:{}'.format(direction, sort_field)
119 119 c.sort_tag = sort_field
120 120
121 121
122 122 class SearchView(BaseAppView):
123 123 def load_default_context(self):
124 124 c = self._get_local_tmpl_context()
125 125 return c
126 126
127 127 @LoginRequired()
128 128 @view_config(
129 129 route_name='search', request_method='GET',
130 130 renderer='rhodecode:templates/search/search.mako')
131 131 def search(self):
132 132 c = self.load_default_context()
133 133 perform_search(self.request, c)
134 134 return self._get_template_context(c)
135 135
136 136
137 137 class SearchRepoView(RepoAppView):
138 138 def load_default_context(self):
139 139 c = self._get_local_tmpl_context()
140 140 c.active = 'search'
141 141 return c
142 142
143 143 @LoginRequired()
144 144 @HasRepoPermissionAnyDecorator(
145 145 'repository.read', 'repository.write', 'repository.admin')
146 146 @view_config(
147 147 route_name='search_repo', request_method='GET',
148 148 renderer='rhodecode:templates/search/search.mako')
149 149 @view_config(
150 150 route_name='search_repo_alt', request_method='GET',
151 151 renderer='rhodecode:templates/search/search.mako')
152 152 def search_repo(self):
153 153 c = self.load_default_context()
154 154 perform_search(self.request, c, repo_name=self.db_repo_name)
155 155 return self._get_template_context(c)
156 156
157 157
158 158 class SearchRepoGroupView(RepoGroupAppView):
159 159 def load_default_context(self):
160 160 c = self._get_local_tmpl_context()
161 161 c.active = 'search'
162 162 return c
163 163
164 164 @LoginRequired()
165 165 @HasRepoGroupPermissionAnyDecorator(
166 166 'group.read', 'group.write', 'group.admin')
167 167 @view_config(
168 168 route_name='search_repo_group', request_method='GET',
169 169 renderer='rhodecode:templates/search/search.mako')
170 170 def search_repo_group(self):
171 171 c = self.load_default_context()
172 172 perform_search(self.request, c, repo_group_name=self.db_repo_group_name)
173 173 return self._get_template_context(c)
@@ -1,465 +1,464 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import deform
22 22 import logging
23 23 import peppercorn
24 import webhelpers.paginate
25 24
26 25 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPNotFound
27 26
28 27 from rhodecode.integrations import integration_type_registry
29 28 from rhodecode.apps._base import BaseAppView
30 29 from rhodecode.apps._base.navigation import navigation_list
30 from rhodecode.lib.paginate import PageURL
31 31 from rhodecode.lib.auth import (
32 32 LoginRequired, CSRFRequired, HasPermissionAnyDecorator,
33 33 HasRepoPermissionAnyDecorator, HasRepoGroupPermissionAnyDecorator)
34 34 from rhodecode.lib.utils2 import safe_int
35 35 from rhodecode.lib import helpers as h
36 36 from rhodecode.model.db import Repository, RepoGroup, Session, Integration
37 37 from rhodecode.model.scm import ScmModel
38 38 from rhodecode.model.integration import IntegrationModel
39 39 from rhodecode.model.validation_schema.schemas.integration_schema import (
40 40 make_integration_schema, IntegrationScopeType)
41 41
42 42 log = logging.getLogger(__name__)
43 43
44 44
45 45 class IntegrationSettingsViewBase(BaseAppView):
46 46 """
47 47 Base Integration settings view used by both repo / global settings
48 48 """
49 49
50 50 def __init__(self, context, request):
51 51 super(IntegrationSettingsViewBase, self).__init__(context, request)
52 52 self._load_view_context()
53 53
54 54 def _load_view_context(self):
55 55 """
56 56 This avoids boilerplate for repo/global+list/edit+views/templates
57 57 by doing all possible contexts at the same time however it should
58 58 be split up into separate functions once more "contexts" exist
59 59 """
60 60
61 61 self.IntegrationType = None
62 62 self.repo = None
63 63 self.repo_group = None
64 64 self.integration = None
65 65 self.integrations = {}
66 66
67 67 request = self.request
68 68
69 69 if 'repo_name' in request.matchdict: # in repo settings context
70 70 repo_name = request.matchdict['repo_name']
71 71 self.repo = Repository.get_by_repo_name(repo_name)
72 72
73 73 if 'repo_group_name' in request.matchdict: # in group settings context
74 74 repo_group_name = request.matchdict['repo_group_name']
75 75 self.repo_group = RepoGroup.get_by_group_name(repo_group_name)
76 76
77 77 if 'integration' in request.matchdict: # integration type context
78 78 integration_type = request.matchdict['integration']
79 79 if integration_type not in integration_type_registry:
80 80 raise HTTPNotFound()
81 81
82 82 self.IntegrationType = integration_type_registry[integration_type]
83 83 if self.IntegrationType.is_dummy:
84 84 raise HTTPNotFound()
85 85
86 86 if 'integration_id' in request.matchdict: # single integration context
87 87 integration_id = request.matchdict['integration_id']
88 88 self.integration = Integration.get(integration_id)
89 89
90 90 # extra perms check just in case
91 91 if not self._has_perms_for_integration(self.integration):
92 92 raise HTTPForbidden()
93 93
94 94 self.settings = self.integration and self.integration.settings or {}
95 95 self.admin_view = not (self.repo or self.repo_group)
96 96
97 97 def _has_perms_for_integration(self, integration):
98 98 perms = self.request.user.permissions
99 99
100 100 if 'hg.admin' in perms['global']:
101 101 return True
102 102
103 103 if integration.repo:
104 104 return perms['repositories'].get(
105 105 integration.repo.repo_name) == 'repository.admin'
106 106
107 107 if integration.repo_group:
108 108 return perms['repositories_groups'].get(
109 109 integration.repo_group.group_name) == 'group.admin'
110 110
111 111 return False
112 112
113 113 def _get_local_tmpl_context(self, include_app_defaults=True):
114 114 _ = self.request.translate
115 115 c = super(IntegrationSettingsViewBase, self)._get_local_tmpl_context(
116 116 include_app_defaults=include_app_defaults)
117 117 c.active = 'integrations'
118 118
119 119 return c
120 120
121 121 def _form_schema(self):
122 122 schema = make_integration_schema(IntegrationType=self.IntegrationType,
123 123 settings=self.settings)
124 124
125 125 # returns a clone, important if mutating the schema later
126 126 return schema.bind(
127 127 permissions=self.request.user.permissions,
128 128 no_scope=not self.admin_view)
129 129
130 130 def _form_defaults(self):
131 131 _ = self.request.translate
132 132 defaults = {}
133 133
134 134 if self.integration:
135 135 defaults['settings'] = self.integration.settings or {}
136 136 defaults['options'] = {
137 137 'name': self.integration.name,
138 138 'enabled': self.integration.enabled,
139 139 'scope': {
140 140 'repo': self.integration.repo,
141 141 'repo_group': self.integration.repo_group,
142 142 'child_repos_only': self.integration.child_repos_only,
143 143 },
144 144 }
145 145 else:
146 146 if self.repo:
147 147 scope = _('{repo_name} repository').format(
148 148 repo_name=self.repo.repo_name)
149 149 elif self.repo_group:
150 150 scope = _('{repo_group_name} repo group').format(
151 151 repo_group_name=self.repo_group.group_name)
152 152 else:
153 153 scope = _('Global')
154 154
155 155 defaults['options'] = {
156 156 'enabled': True,
157 157 'name': _('{name} integration').format(
158 158 name=self.IntegrationType.display_name),
159 159 }
160 160 defaults['options']['scope'] = {
161 161 'repo': self.repo,
162 162 'repo_group': self.repo_group,
163 163 }
164 164
165 165 return defaults
166 166
167 167 def _delete_integration(self, integration):
168 168 _ = self.request.translate
169 169 Session().delete(integration)
170 170 Session().commit()
171 171 h.flash(
172 172 _('Integration {integration_name} deleted successfully.').format(
173 173 integration_name=integration.name),
174 174 category='success')
175 175
176 176 if self.repo:
177 177 redirect_to = self.request.route_path(
178 178 'repo_integrations_home', repo_name=self.repo.repo_name)
179 179 elif self.repo_group:
180 180 redirect_to = self.request.route_path(
181 181 'repo_group_integrations_home',
182 182 repo_group_name=self.repo_group.group_name)
183 183 else:
184 184 redirect_to = self.request.route_path('global_integrations_home')
185 185 raise HTTPFound(redirect_to)
186 186
187 187 def _integration_list(self):
188 188 """ List integrations """
189 189
190 190 c = self.load_default_context()
191 191 if self.repo:
192 192 scope = self.repo
193 193 elif self.repo_group:
194 194 scope = self.repo_group
195 195 else:
196 196 scope = 'all'
197 197
198 198 integrations = []
199 199
200 200 for IntType, integration in IntegrationModel().get_integrations(
201 201 scope=scope, IntegrationType=self.IntegrationType):
202 202
203 203 # extra permissions check *just in case*
204 204 if not self._has_perms_for_integration(integration):
205 205 continue
206 206
207 207 integrations.append((IntType, integration))
208 208
209 209 sort_arg = self.request.GET.get('sort', 'name:asc')
210 210 sort_dir = 'asc'
211 211 if ':' in sort_arg:
212 212 sort_field, sort_dir = sort_arg.split(':')
213 213 else:
214 214 sort_field = sort_arg, 'asc'
215 215
216 216 assert sort_field in ('name', 'integration_type', 'enabled', 'scope')
217 217
218 218 integrations.sort(
219 219 key=lambda x: getattr(x[1], sort_field),
220 220 reverse=(sort_dir == 'desc'))
221 221
222 page_url = webhelpers.paginate.PageURL(
223 self.request.path, self.request.GET)
222 page_url = PageURL(self.request.path, self.request.GET)
224 223 page = safe_int(self.request.GET.get('page', 1), 1)
225 224
226 225 integrations = h.Page(
227 226 integrations, page=page, items_per_page=10, url=page_url)
228 227
229 228 c.rev_sort_dir = sort_dir != 'desc' and 'desc' or 'asc'
230 229
231 230 c.current_IntegrationType = self.IntegrationType
232 231 c.integrations_list = integrations
233 232 c.available_integrations = integration_type_registry
234 233
235 234 return self._get_template_context(c)
236 235
237 236 def _settings_get(self, defaults=None, form=None):
238 237 """
239 238 View that displays the integration settings as a form.
240 239 """
241 240 c = self.load_default_context()
242 241
243 242 defaults = defaults or self._form_defaults()
244 243 schema = self._form_schema()
245 244
246 245 if self.integration:
247 246 buttons = ('submit', 'delete')
248 247 else:
249 248 buttons = ('submit',)
250 249
251 250 form = form or deform.Form(schema, appstruct=defaults, buttons=buttons)
252 251
253 252 c.form = form
254 253 c.current_IntegrationType = self.IntegrationType
255 254 c.integration = self.integration
256 255
257 256 return self._get_template_context(c)
258 257
259 258 def _settings_post(self):
260 259 """
261 260 View that validates and stores the integration settings.
262 261 """
263 262 _ = self.request.translate
264 263
265 264 controls = self.request.POST.items()
266 265 pstruct = peppercorn.parse(controls)
267 266
268 267 if self.integration and pstruct.get('delete'):
269 268 return self._delete_integration(self.integration)
270 269
271 270 schema = self._form_schema()
272 271
273 272 skip_settings_validation = False
274 273 if self.integration and 'enabled' not in pstruct.get('options', {}):
275 274 skip_settings_validation = True
276 275 schema['settings'].validator = None
277 276 for field in schema['settings'].children:
278 277 field.validator = None
279 278 field.missing = ''
280 279
281 280 if self.integration:
282 281 buttons = ('submit', 'delete')
283 282 else:
284 283 buttons = ('submit',)
285 284
286 285 form = deform.Form(schema, buttons=buttons)
287 286
288 287 if not self.admin_view:
289 288 # scope is read only field in these cases, and has to be added
290 289 options = pstruct.setdefault('options', {})
291 290 if 'scope' not in options:
292 291 options['scope'] = IntegrationScopeType().serialize(None, {
293 292 'repo': self.repo,
294 293 'repo_group': self.repo_group,
295 294 })
296 295
297 296 try:
298 297 valid_data = form.validate_pstruct(pstruct)
299 298 except deform.ValidationFailure as e:
300 299 h.flash(
301 300 _('Errors exist when saving integration settings. '
302 301 'Please check the form inputs.'),
303 302 category='error')
304 303 return self._settings_get(form=e)
305 304
306 305 if not self.integration:
307 306 self.integration = Integration()
308 307 self.integration.integration_type = self.IntegrationType.key
309 308 Session().add(self.integration)
310 309
311 310 scope = valid_data['options']['scope']
312 311
313 312 IntegrationModel().update_integration(self.integration,
314 313 name=valid_data['options']['name'],
315 314 enabled=valid_data['options']['enabled'],
316 315 settings=valid_data['settings'],
317 316 repo=scope['repo'],
318 317 repo_group=scope['repo_group'],
319 318 child_repos_only=scope['child_repos_only'],
320 319 )
321 320
322 321 self.integration.settings = valid_data['settings']
323 322 Session().commit()
324 323 # Display success message and redirect.
325 324 h.flash(
326 325 _('Integration {integration_name} updated successfully.').format(
327 326 integration_name=self.IntegrationType.display_name),
328 327 category='success')
329 328
330 329 # if integration scope changes, we must redirect to the right place
331 330 # keeping in mind if the original view was for /repo/ or /_admin/
332 331 admin_view = not (self.repo or self.repo_group)
333 332
334 333 if self.integration.repo and not admin_view:
335 334 redirect_to = self.request.route_path(
336 335 'repo_integrations_edit',
337 336 repo_name=self.integration.repo.repo_name,
338 337 integration=self.integration.integration_type,
339 338 integration_id=self.integration.integration_id)
340 339 elif self.integration.repo_group and not admin_view:
341 340 redirect_to = self.request.route_path(
342 341 'repo_group_integrations_edit',
343 342 repo_group_name=self.integration.repo_group.group_name,
344 343 integration=self.integration.integration_type,
345 344 integration_id=self.integration.integration_id)
346 345 else:
347 346 redirect_to = self.request.route_path(
348 347 'global_integrations_edit',
349 348 integration=self.integration.integration_type,
350 349 integration_id=self.integration.integration_id)
351 350
352 351 return HTTPFound(redirect_to)
353 352
354 353 def _new_integration(self):
355 354 c = self.load_default_context()
356 355 c.available_integrations = integration_type_registry
357 356 return self._get_template_context(c)
358 357
359 358 def load_default_context(self):
360 359 raise NotImplementedError()
361 360
362 361
363 362 class GlobalIntegrationsView(IntegrationSettingsViewBase):
364 363 def load_default_context(self):
365 364 c = self._get_local_tmpl_context()
366 365 c.repo = self.repo
367 366 c.repo_group = self.repo_group
368 367 c.navlist = navigation_list(self.request)
369 368
370 369 return c
371 370
372 371 @LoginRequired()
373 372 @HasPermissionAnyDecorator('hg.admin')
374 373 def integration_list(self):
375 374 return self._integration_list()
376 375
377 376 @LoginRequired()
378 377 @HasPermissionAnyDecorator('hg.admin')
379 378 def settings_get(self):
380 379 return self._settings_get()
381 380
382 381 @LoginRequired()
383 382 @HasPermissionAnyDecorator('hg.admin')
384 383 @CSRFRequired()
385 384 def settings_post(self):
386 385 return self._settings_post()
387 386
388 387 @LoginRequired()
389 388 @HasPermissionAnyDecorator('hg.admin')
390 389 def new_integration(self):
391 390 return self._new_integration()
392 391
393 392
394 393 class RepoIntegrationsView(IntegrationSettingsViewBase):
395 394 def load_default_context(self):
396 395 c = self._get_local_tmpl_context()
397 396
398 397 c.repo = self.repo
399 398 c.repo_group = self.repo_group
400 399
401 400 self.db_repo = self.repo
402 401 c.rhodecode_db_repo = self.repo
403 402 c.repo_name = self.db_repo.repo_name
404 403 c.repository_pull_requests = ScmModel().get_pull_requests(self.repo)
405 404 c.repository_artifacts = ScmModel().get_artifacts(self.repo)
406 405 c.repository_is_user_following = ScmModel().is_following_repo(
407 406 c.repo_name, self._rhodecode_user.user_id)
408 407 c.has_origin_repo_read_perm = False
409 408 if self.db_repo.fork:
410 409 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
411 410 'repository.write', 'repository.read', 'repository.admin')(
412 411 self.db_repo.fork.repo_name, 'summary fork link')
413 412 return c
414 413
415 414 @LoginRequired()
416 415 @HasRepoPermissionAnyDecorator('repository.admin')
417 416 def integration_list(self):
418 417 return self._integration_list()
419 418
420 419 @LoginRequired()
421 420 @HasRepoPermissionAnyDecorator('repository.admin')
422 421 def settings_get(self):
423 422 return self._settings_get()
424 423
425 424 @LoginRequired()
426 425 @HasRepoPermissionAnyDecorator('repository.admin')
427 426 @CSRFRequired()
428 427 def settings_post(self):
429 428 return self._settings_post()
430 429
431 430 @LoginRequired()
432 431 @HasRepoPermissionAnyDecorator('repository.admin')
433 432 def new_integration(self):
434 433 return self._new_integration()
435 434
436 435
437 436 class RepoGroupIntegrationsView(IntegrationSettingsViewBase):
438 437 def load_default_context(self):
439 438 c = self._get_local_tmpl_context()
440 439 c.repo = self.repo
441 440 c.repo_group = self.repo_group
442 441 c.navlist = navigation_list(self.request)
443 442
444 443 return c
445 444
446 445 @LoginRequired()
447 446 @HasRepoGroupPermissionAnyDecorator('group.admin')
448 447 def integration_list(self):
449 448 return self._integration_list()
450 449
451 450 @LoginRequired()
452 451 @HasRepoGroupPermissionAnyDecorator('group.admin')
453 452 def settings_get(self):
454 453 return self._settings_get()
455 454
456 455 @LoginRequired()
457 456 @HasRepoGroupPermissionAnyDecorator('group.admin')
458 457 @CSRFRequired()
459 458 def settings_post(self):
460 459 return self._settings_post()
461 460
462 461 @LoginRequired()
463 462 @HasRepoGroupPermissionAnyDecorator('group.admin')
464 463 def new_integration(self):
465 464 return self._new_integration()
@@ -1,357 +1,357 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22
23 from webhelpers.html.builder import literal
24 from webhelpers.html.tags import link_to
23 from webhelpers2.html.builder import literal
24 from webhelpers2.html.tags import link_to
25 25
26 26 from rhodecode.lib.utils2 import AttributeDict
27 27 from rhodecode.lib.vcs.backends.base import BaseCommit
28 28 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
29 29
30 30
31 31 log = logging.getLogger(__name__)
32 32
33 33
34 34 def action_parser(request, user_log, feed=False, parse_cs=False):
35 35 """
36 36 This helper will action_map the specified string action into translated
37 37 fancy names with icons and links
38 38
39 39 :param user_log: user log instance
40 40 :param feed: use output for feeds (no html and fancy icons)
41 41 :param parse_cs: parse Changesets into VCS instances
42 42 """
43 43 if user_log.version == 'v2':
44 44 ap = AuditLogParser(request, user_log)
45 45 return ap.callbacks()
46 46 else:
47 47 # old style
48 48 ap = ActionParser(request, user_log, feed=False, parse_commits=False)
49 49 return ap.callbacks()
50 50
51 51
52 52 class ActionParser(object):
53 53
54 54 commits_limit = 3 # display this amount always
55 55 commits_top_limit = 50 # show up to this amount of commits hidden
56 56
57 57 def __init__(self, request, user_log, feed=False, parse_commits=False):
58 58 self.user_log = user_log
59 59 self.feed = feed
60 60 self.parse_commits = parse_commits
61 61 self.request = request
62 62
63 63 self.action = user_log.action
64 64 self.action_params = ' '
65 65 x = self.action.split(':', 1)
66 66 if len(x) > 1:
67 67 self.action, self.action_params = x
68 68
69 69 def callbacks(self):
70 70 action_str = self.action_map.get(self.action, self.action)
71 71 if self.feed:
72 72 action = action_str[0].replace('[', '').replace(']', '')
73 73 else:
74 74 action = action_str[0]\
75 75 .replace('[', '<span class="journal_highlight">')\
76 76 .replace(']', '</span>')
77 77
78 78 action_params_func = _no_params_func
79 79 if callable(action_str[1]):
80 80 action_params_func = action_str[1]
81 81
82 82 # returned callbacks we need to call to get
83 83 return [
84 84 lambda: literal(action), action_params_func,
85 85 self.action_parser_icon]
86 86
87 87 @property
88 88 def action_map(self):
89 89 _ = self.request.translate
90 90 # action : translated str, callback(extractor), icon
91 91 action_map = {
92 92 'user_deleted_repo': (
93 93 _('[deleted] repository'),
94 94 None, 'icon-trash'),
95 95 'user_created_repo': (
96 96 _('[created] repository'),
97 97 None, 'icon-plus icon-plus-colored'),
98 98 'user_created_fork': (
99 99 _('[created] repository as fork'),
100 100 None, 'icon-code-fork'),
101 101 'user_forked_repo': (
102 102 _('[forked] repository'),
103 103 self.get_fork_name, 'icon-code-fork'),
104 104 'user_updated_repo': (
105 105 _('[updated] repository'),
106 106 None, 'icon-pencil icon-pencil-colored'),
107 107 'user_downloaded_archive': (
108 108 _('[downloaded] archive from repository'),
109 109 self.get_archive_name, 'icon-download-alt'),
110 110 'admin_deleted_repo': (
111 111 _('[delete] repository'),
112 112 None, 'icon-trash'),
113 113 'admin_created_repo': (
114 114 _('[created] repository'),
115 115 None, 'icon-plus icon-plus-colored'),
116 116 'admin_forked_repo': (
117 117 _('[forked] repository'),
118 118 None, 'icon-code-fork icon-fork-colored'),
119 119 'admin_updated_repo': (
120 120 _('[updated] repository'),
121 121 None, 'icon-pencil icon-pencil-colored'),
122 122 'admin_created_user': (
123 123 _('[created] user'),
124 124 self.get_user_name, 'icon-user icon-user-colored'),
125 125 'admin_updated_user': (
126 126 _('[updated] user'),
127 127 self.get_user_name, 'icon-user icon-user-colored'),
128 128 'admin_created_users_group': (
129 129 _('[created] user group'),
130 130 self.get_users_group, 'icon-pencil icon-pencil-colored'),
131 131 'admin_updated_users_group': (
132 132 _('[updated] user group'),
133 133 self.get_users_group, 'icon-pencil icon-pencil-colored'),
134 134 'user_commented_revision': (
135 135 _('[commented] on commit in repository'),
136 136 self.get_cs_links, 'icon-comment icon-comment-colored'),
137 137 'user_commented_pull_request': (
138 138 _('[commented] on pull request for'),
139 139 self.get_pull_request, 'icon-comment icon-comment-colored'),
140 140 'user_closed_pull_request': (
141 141 _('[closed] pull request for'),
142 142 self.get_pull_request, 'icon-check'),
143 143 'user_merged_pull_request': (
144 144 _('[merged] pull request for'),
145 145 self.get_pull_request, 'icon-check'),
146 146 'push': (
147 147 _('[pushed] into'),
148 148 self.get_cs_links, 'icon-arrow-up'),
149 149 'push_local': (
150 150 _('[committed via RhodeCode] into repository'),
151 151 self.get_cs_links, 'icon-pencil icon-pencil-colored'),
152 152 'push_remote': (
153 153 _('[pulled from remote] into repository'),
154 154 self.get_cs_links, 'icon-arrow-up'),
155 155 'pull': (
156 156 _('[pulled] from'),
157 157 None, 'icon-arrow-down'),
158 158 'started_following_repo': (
159 159 _('[started following] repository'),
160 160 None, 'icon-heart icon-heart-colored'),
161 161 'stopped_following_repo': (
162 162 _('[stopped following] repository'),
163 163 None, 'icon-heart-empty icon-heart-colored'),
164 164 }
165 165 return action_map
166 166
167 167 def get_fork_name(self):
168 168 from rhodecode.lib import helpers as h
169 169 _ = self.request.translate
170 170 repo_name = self.action_params
171 171 _url = h.route_path('repo_summary', repo_name=repo_name)
172 172 return _('fork name %s') % link_to(self.action_params, _url)
173 173
174 174 def get_user_name(self):
175 175 user_name = self.action_params
176 176 return user_name
177 177
178 178 def get_users_group(self):
179 179 group_name = self.action_params
180 180 return group_name
181 181
182 182 def get_pull_request(self):
183 183 from rhodecode.lib import helpers as h
184 184 _ = self.request.translate
185 185 pull_request_id = self.action_params
186 186 if self.is_deleted():
187 187 repo_name = self.user_log.repository_name
188 188 else:
189 189 repo_name = self.user_log.repository.repo_name
190 190 return link_to(
191 191 _('Pull request #%s') % pull_request_id,
192 192 h.route_path('pullrequest_show', repo_name=repo_name,
193 193 pull_request_id=pull_request_id))
194 194
195 195 def get_archive_name(self):
196 196 archive_name = self.action_params
197 197 return archive_name
198 198
199 199 def action_parser_icon(self):
200 200 tmpl = """<i class="%s" alt="%s"></i>"""
201 201 ico = self.action_map.get(self.action, ['', '', ''])[2]
202 202 return literal(tmpl % (ico, self.action))
203 203
204 204 def get_cs_links(self):
205 205 from rhodecode.lib import helpers as h
206 206 _ = self.request.translate
207 207 if self.is_deleted():
208 208 return self.action_params
209 209
210 210 repo_name = self.user_log.repository.repo_name
211 211 commit_ids = self.action_params.split(',')
212 212 commits = self.get_commits(commit_ids)
213 213
214 214 link_generator = (
215 215 self.lnk(commit, repo_name)
216 216 for commit in commits[:self.commits_limit])
217 217 commit_links = [" " + ', '.join(link_generator)]
218 218 _op1, _name1 = _get_op(commit_ids[0])
219 219 _op2, _name2 = _get_op(commit_ids[-1])
220 220
221 221 commit_id_range = '%s...%s' % (_name1, _name2)
222 222
223 223 compare_view = (
224 224 ' <div class="compare_view tooltip" title="%s">'
225 225 '<a href="%s">%s</a> </div>' % (
226 226 _('Show all combined commits %s->%s') % (
227 227 commit_ids[0][:12], commit_ids[-1][:12]
228 228 ),
229 229 h.route_path(
230 230 'repo_commit', repo_name=repo_name,
231 231 commit_id=commit_id_range), _('compare view')
232 232 )
233 233 )
234 234
235 235 if len(commit_ids) > self.commits_limit:
236 236 more_count = len(commit_ids) - self.commits_limit
237 237 commit_links.append(
238 238 _(' and %(num)s more commits') % {'num': more_count}
239 239 )
240 240
241 241 if len(commits) > 1:
242 242 commit_links.append(compare_view)
243 243 return ''.join(commit_links)
244 244
245 245 def get_commits(self, commit_ids):
246 246 commits = []
247 247 if not filter(lambda v: v != '', commit_ids):
248 248 return commits
249 249
250 250 repo = None
251 251 if self.parse_commits:
252 252 repo = self.user_log.repository.scm_instance()
253 253
254 254 for commit_id in commit_ids[:self.commits_top_limit]:
255 255 _op, _name = _get_op(commit_id)
256 256
257 257 # we want parsed commits, or new log store format is bad
258 258 if self.parse_commits:
259 259 try:
260 260 commit = repo.get_commit(commit_id=commit_id)
261 261 commits.append(commit)
262 262 except CommitDoesNotExistError:
263 263 log.error(
264 264 'cannot find commit id %s in this repository',
265 265 commit_id)
266 266 commits.append(commit_id)
267 267 continue
268 268 else:
269 269 fake_commit = AttributeDict({
270 270 'short_id': commit_id[:12],
271 271 'raw_id': commit_id,
272 272 'message': '',
273 273 'op': _op,
274 274 'ref_name': _name
275 275 })
276 276 commits.append(fake_commit)
277 277
278 278 return commits
279 279
280 280 def lnk(self, commit_or_id, repo_name):
281 281 from rhodecode.lib.helpers import tooltip
282 282 from rhodecode.lib import helpers as h
283 283 _ = self.request.translate
284 284 title = ''
285 285 lazy_cs = True
286 286 if isinstance(commit_or_id, (BaseCommit, AttributeDict)):
287 287 lazy_cs = True
288 288 if (getattr(commit_or_id, 'op', None) and
289 289 getattr(commit_or_id, 'ref_name', None)):
290 290 lazy_cs = False
291 291 lbl = '?'
292 292 if commit_or_id.op == 'delete_branch':
293 293 lbl = '%s' % _('Deleted branch: %s') % commit_or_id.ref_name
294 294 title = ''
295 295 elif commit_or_id.op == 'tag':
296 296 lbl = '%s' % _('Created tag: %s') % commit_or_id.ref_name
297 297 title = ''
298 298 _url = '#'
299 299
300 300 else:
301 301 lbl = '%s' % (commit_or_id.short_id[:8])
302 302 _url = h.route_path('repo_commit', repo_name=repo_name,
303 303 commit_id=commit_or_id.raw_id)
304 304 title = tooltip(commit_or_id.message)
305 305 else:
306 306 # commit cannot be found/striped/removed etc.
307 307 lbl = ('%s' % commit_or_id)[:12]
308 308 _url = '#'
309 309 title = _('Commit not found')
310 310 if self.parse_commits:
311 311 return link_to(lbl, _url, title=title, class_='tooltip')
312 312 return link_to(lbl, _url, raw_id=commit_or_id.raw_id, repo_name=repo_name,
313 313 class_='lazy-cs' if lazy_cs else '')
314 314
315 315 def is_deleted(self):
316 316 return self.user_log.repository is None
317 317
318 318
319 319 class AuditLogParser(object):
320 320 def __init__(self, request, audit_log_entry):
321 321 self.audit_log_entry = audit_log_entry
322 322 self.request = request
323 323
324 324 def get_icon(self, action):
325 325 return 'icon-rhodecode'
326 326
327 327 def callbacks(self):
328 328 action_str = self.audit_log_entry.action
329 329
330 330 def callback():
331 331 # returned callbacks we need to call to get
332 332 action = action_str \
333 333 .replace('[', '<span class="journal_highlight">')\
334 334 .replace(']', '</span>')
335 335 return literal(action)
336 336
337 337 def icon():
338 338 tmpl = """<i class="%s" alt="%s"></i>"""
339 339 ico = self.get_icon(action_str)
340 340 return literal(tmpl % (ico, action_str))
341 341
342 342 action_params_func = _no_params_func
343 343
344 344 return [
345 345 callback, action_params_func, icon]
346 346
347 347
348 348 def _no_params_func():
349 349 return ""
350 350
351 351
352 352 def _get_op(commit_id):
353 353 _op = None
354 354 _name = commit_id
355 355 if len(commit_id.split('=>')) == 2:
356 356 _op, _name = commit_id.split('=>')
357 357 return _op, _name
@@ -1,2369 +1,2368 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 authentication and permission libraries
23 23 """
24 24
25 25 import os
26 26 import time
27 27 import inspect
28 28 import collections
29 29 import fnmatch
30 30 import hashlib
31 31 import itertools
32 32 import logging
33 33 import random
34 34 import traceback
35 35 from functools import wraps
36 36
37 37 import ipaddress
38 38
39 39 from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPNotFound
40 40 from sqlalchemy.orm.exc import ObjectDeletedError
41 41 from sqlalchemy.orm import joinedload
42 42 from zope.cachedescriptors.property import Lazy as LazyProperty
43 43
44 44 import rhodecode
45 45 from rhodecode.model import meta
46 46 from rhodecode.model.meta import Session
47 47 from rhodecode.model.user import UserModel
48 48 from rhodecode.model.db import (
49 49 User, Repository, Permission, UserToPerm, UserGroupToPerm, UserGroupMember,
50 50 UserIpMap, UserApiKeys, RepoGroup, UserGroup)
51 51 from rhodecode.lib import rc_cache
52 52 from rhodecode.lib.utils2 import safe_unicode, aslist, safe_str, md5, safe_int, sha1
53 53 from rhodecode.lib.utils import (
54 54 get_repo_slug, get_repo_group_slug, get_user_group_slug)
55 55 from rhodecode.lib.caching_query import FromCache
56 56
57 57
58 58 if rhodecode.is_unix:
59 59 import bcrypt
60 60
61 61 log = logging.getLogger(__name__)
62 62
63 63 csrf_token_key = "csrf_token"
64 64
65 65
66 66 class PasswordGenerator(object):
67 67 """
68 68 This is a simple class for generating password from different sets of
69 69 characters
70 70 usage::
71 71 passwd_gen = PasswordGenerator()
72 72 #print 8-letter password containing only big and small letters
73 73 of alphabet
74 74 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
75 75 """
76 76 ALPHABETS_NUM = r'''1234567890'''
77 77 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
78 78 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
79 79 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
80 80 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
81 81 + ALPHABETS_NUM + ALPHABETS_SPECIAL
82 82 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
83 83 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
84 84 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
85 85 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
86 86
87 87 def __init__(self, passwd=''):
88 88 self.passwd = passwd
89 89
90 90 def gen_password(self, length, type_=None):
91 91 if type_ is None:
92 92 type_ = self.ALPHABETS_FULL
93 93 self.passwd = ''.join([random.choice(type_) for _ in range(length)])
94 94 return self.passwd
95 95
96 96
97 97 class _RhodeCodeCryptoBase(object):
98 98 ENC_PREF = None
99 99
100 100 def hash_create(self, str_):
101 101 """
102 102 hash the string using
103 103
104 104 :param str_: password to hash
105 105 """
106 106 raise NotImplementedError
107 107
108 108 def hash_check_with_upgrade(self, password, hashed):
109 109 """
110 110 Returns tuple in which first element is boolean that states that
111 111 given password matches it's hashed version, and the second is new hash
112 112 of the password, in case this password should be migrated to new
113 113 cipher.
114 114 """
115 115 checked_hash = self.hash_check(password, hashed)
116 116 return checked_hash, None
117 117
118 118 def hash_check(self, password, hashed):
119 119 """
120 120 Checks matching password with it's hashed value.
121 121
122 122 :param password: password
123 123 :param hashed: password in hashed form
124 124 """
125 125 raise NotImplementedError
126 126
127 127 def _assert_bytes(self, value):
128 128 """
129 129 Passing in an `unicode` object can lead to hard to detect issues
130 130 if passwords contain non-ascii characters. Doing a type check
131 131 during runtime, so that such mistakes are detected early on.
132 132 """
133 133 if not isinstance(value, str):
134 134 raise TypeError(
135 135 "Bytestring required as input, got %r." % (value, ))
136 136
137 137
138 138 class _RhodeCodeCryptoBCrypt(_RhodeCodeCryptoBase):
139 139 ENC_PREF = ('$2a$10', '$2b$10')
140 140
141 141 def hash_create(self, str_):
142 142 self._assert_bytes(str_)
143 143 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
144 144
145 145 def hash_check_with_upgrade(self, password, hashed):
146 146 """
147 147 Returns tuple in which first element is boolean that states that
148 148 given password matches it's hashed version, and the second is new hash
149 149 of the password, in case this password should be migrated to new
150 150 cipher.
151 151
152 152 This implements special upgrade logic which works like that:
153 153 - check if the given password == bcrypted hash, if yes then we
154 154 properly used password and it was already in bcrypt. Proceed
155 155 without any changes
156 156 - if bcrypt hash check is not working try with sha256. If hash compare
157 157 is ok, it means we using correct but old hashed password. indicate
158 158 hash change and proceed
159 159 """
160 160
161 161 new_hash = None
162 162
163 163 # regular pw check
164 164 password_match_bcrypt = self.hash_check(password, hashed)
165 165
166 166 # now we want to know if the password was maybe from sha256
167 167 # basically calling _RhodeCodeCryptoSha256().hash_check()
168 168 if not password_match_bcrypt:
169 169 if _RhodeCodeCryptoSha256().hash_check(password, hashed):
170 170 new_hash = self.hash_create(password) # make new bcrypt hash
171 171 password_match_bcrypt = True
172 172
173 173 return password_match_bcrypt, new_hash
174 174
175 175 def hash_check(self, password, hashed):
176 176 """
177 177 Checks matching password with it's hashed value.
178 178
179 179 :param password: password
180 180 :param hashed: password in hashed form
181 181 """
182 182 self._assert_bytes(password)
183 183 try:
184 184 return bcrypt.hashpw(password, hashed) == hashed
185 185 except ValueError as e:
186 186 # we're having a invalid salt here probably, we should not crash
187 187 # just return with False as it would be a wrong password.
188 188 log.debug('Failed to check password hash using bcrypt %s',
189 189 safe_str(e))
190 190
191 191 return False
192 192
193 193
194 194 class _RhodeCodeCryptoSha256(_RhodeCodeCryptoBase):
195 195 ENC_PREF = '_'
196 196
197 197 def hash_create(self, str_):
198 198 self._assert_bytes(str_)
199 199 return hashlib.sha256(str_).hexdigest()
200 200
201 201 def hash_check(self, password, hashed):
202 202 """
203 203 Checks matching password with it's hashed value.
204 204
205 205 :param password: password
206 206 :param hashed: password in hashed form
207 207 """
208 208 self._assert_bytes(password)
209 209 return hashlib.sha256(password).hexdigest() == hashed
210 210
211 211
212 212 class _RhodeCodeCryptoTest(_RhodeCodeCryptoBase):
213 213 ENC_PREF = '_'
214 214
215 215 def hash_create(self, str_):
216 216 self._assert_bytes(str_)
217 217 return sha1(str_)
218 218
219 219 def hash_check(self, password, hashed):
220 220 """
221 221 Checks matching password with it's hashed value.
222 222
223 223 :param password: password
224 224 :param hashed: password in hashed form
225 225 """
226 226 self._assert_bytes(password)
227 227 return sha1(password) == hashed
228 228
229 229
230 230 def crypto_backend():
231 231 """
232 232 Return the matching crypto backend.
233 233
234 234 Selection is based on if we run tests or not, we pick sha1-test backend to run
235 235 tests faster since BCRYPT is expensive to calculate
236 236 """
237 237 if rhodecode.is_test:
238 238 RhodeCodeCrypto = _RhodeCodeCryptoTest()
239 239 else:
240 240 RhodeCodeCrypto = _RhodeCodeCryptoBCrypt()
241 241
242 242 return RhodeCodeCrypto
243 243
244 244
245 245 def get_crypt_password(password):
246 246 """
247 247 Create the hash of `password` with the active crypto backend.
248 248
249 249 :param password: The cleartext password.
250 250 :type password: unicode
251 251 """
252 252 password = safe_str(password)
253 253 return crypto_backend().hash_create(password)
254 254
255 255
256 256 def check_password(password, hashed):
257 257 """
258 258 Check if the value in `password` matches the hash in `hashed`.
259 259
260 260 :param password: The cleartext password.
261 261 :type password: unicode
262 262
263 263 :param hashed: The expected hashed version of the password.
264 264 :type hashed: The hash has to be passed in in text representation.
265 265 """
266 266 password = safe_str(password)
267 267 return crypto_backend().hash_check(password, hashed)
268 268
269 269
270 270 def generate_auth_token(data, salt=None):
271 271 """
272 272 Generates API KEY from given string
273 273 """
274 274
275 275 if salt is None:
276 276 salt = os.urandom(16)
277 277 return hashlib.sha1(safe_str(data) + salt).hexdigest()
278 278
279 279
280 280 def get_came_from(request):
281 281 """
282 282 get query_string+path from request sanitized after removing auth_token
283 283 """
284 284 _req = request
285 285
286 286 path = _req.path
287 287 if 'auth_token' in _req.GET:
288 288 # sanitize the request and remove auth_token for redirection
289 289 _req.GET.pop('auth_token')
290 290 qs = _req.query_string
291 291 if qs:
292 292 path += '?' + qs
293 293
294 294 return path
295 295
296 296
297 297 class CookieStoreWrapper(object):
298 298
299 299 def __init__(self, cookie_store):
300 300 self.cookie_store = cookie_store
301 301
302 302 def __repr__(self):
303 303 return 'CookieStore<%s>' % (self.cookie_store)
304 304
305 305 def get(self, key, other=None):
306 306 if isinstance(self.cookie_store, dict):
307 307 return self.cookie_store.get(key, other)
308 308 elif isinstance(self.cookie_store, AuthUser):
309 309 return self.cookie_store.__dict__.get(key, other)
310 310
311 311
312 312 def _cached_perms_data(user_id, scope, user_is_admin,
313 313 user_inherit_default_permissions, explicit, algo,
314 314 calculate_super_admin):
315 315
316 316 permissions = PermissionCalculator(
317 317 user_id, scope, user_is_admin, user_inherit_default_permissions,
318 318 explicit, algo, calculate_super_admin)
319 319 return permissions.calculate()
320 320
321 321
322 322 class PermOrigin(object):
323 323 SUPER_ADMIN = 'superadmin'
324 324 ARCHIVED = 'archived'
325 325
326 326 REPO_USER = 'user:%s'
327 327 REPO_USERGROUP = 'usergroup:%s'
328 328 REPO_OWNER = 'repo.owner'
329 329 REPO_DEFAULT = 'repo.default'
330 330 REPO_DEFAULT_NO_INHERIT = 'repo.default.no.inherit'
331 331 REPO_PRIVATE = 'repo.private'
332 332
333 333 REPOGROUP_USER = 'user:%s'
334 334 REPOGROUP_USERGROUP = 'usergroup:%s'
335 335 REPOGROUP_OWNER = 'group.owner'
336 336 REPOGROUP_DEFAULT = 'group.default'
337 337 REPOGROUP_DEFAULT_NO_INHERIT = 'group.default.no.inherit'
338 338
339 339 USERGROUP_USER = 'user:%s'
340 340 USERGROUP_USERGROUP = 'usergroup:%s'
341 341 USERGROUP_OWNER = 'usergroup.owner'
342 342 USERGROUP_DEFAULT = 'usergroup.default'
343 343 USERGROUP_DEFAULT_NO_INHERIT = 'usergroup.default.no.inherit'
344 344
345 345
346 346 class PermOriginDict(dict):
347 347 """
348 348 A special dict used for tracking permissions along with their origins.
349 349
350 350 `__setitem__` has been overridden to expect a tuple(perm, origin)
351 351 `__getitem__` will return only the perm
352 352 `.perm_origin_stack` will return the stack of (perm, origin) set per key
353 353
354 354 >>> perms = PermOriginDict()
355 355 >>> perms['resource'] = 'read', 'default'
356 356 >>> perms['resource']
357 357 'read'
358 358 >>> perms['resource'] = 'write', 'admin'
359 359 >>> perms['resource']
360 360 'write'
361 361 >>> perms.perm_origin_stack
362 362 {'resource': [('read', 'default'), ('write', 'admin')]}
363 363 """
364 364
365 365 def __init__(self, *args, **kw):
366 366 dict.__init__(self, *args, **kw)
367 367 self.perm_origin_stack = collections.OrderedDict()
368 368
369 369 def __setitem__(self, key, (perm, origin)):
370 370 self.perm_origin_stack.setdefault(key, []).append(
371 371 (perm, origin))
372 372 dict.__setitem__(self, key, perm)
373 373
374 374
375 375 class BranchPermOriginDict(PermOriginDict):
376 376 """
377 377 Dedicated branch permissions dict, with tracking of patterns and origins.
378 378
379 379 >>> perms = BranchPermOriginDict()
380 380 >>> perms['resource'] = '*pattern', 'read', 'default'
381 381 >>> perms['resource']
382 382 {'*pattern': 'read'}
383 383 >>> perms['resource'] = '*pattern', 'write', 'admin'
384 384 >>> perms['resource']
385 385 {'*pattern': 'write'}
386 386 >>> perms.perm_origin_stack
387 387 {'resource': {'*pattern': [('read', 'default'), ('write', 'admin')]}}
388 388 """
389 389 def __setitem__(self, key, (pattern, perm, origin)):
390 390
391 391 self.perm_origin_stack.setdefault(key, {}) \
392 392 .setdefault(pattern, []).append((perm, origin))
393 393
394 394 if key in self:
395 395 self[key].__setitem__(pattern, perm)
396 396 else:
397 397 patterns = collections.OrderedDict()
398 398 patterns[pattern] = perm
399 399 dict.__setitem__(self, key, patterns)
400 400
401 401
402 402 class PermissionCalculator(object):
403 403
404 404 def __init__(
405 405 self, user_id, scope, user_is_admin,
406 406 user_inherit_default_permissions, explicit, algo,
407 407 calculate_super_admin_as_user=False):
408 408
409 409 self.user_id = user_id
410 410 self.user_is_admin = user_is_admin
411 411 self.inherit_default_permissions = user_inherit_default_permissions
412 412 self.explicit = explicit
413 413 self.algo = algo
414 414 self.calculate_super_admin_as_user = calculate_super_admin_as_user
415 415
416 416 scope = scope or {}
417 417 self.scope_repo_id = scope.get('repo_id')
418 418 self.scope_repo_group_id = scope.get('repo_group_id')
419 419 self.scope_user_group_id = scope.get('user_group_id')
420 420
421 421 self.default_user_id = User.get_default_user(cache=True).user_id
422 422
423 423 self.permissions_repositories = PermOriginDict()
424 424 self.permissions_repository_groups = PermOriginDict()
425 425 self.permissions_user_groups = PermOriginDict()
426 426 self.permissions_repository_branches = BranchPermOriginDict()
427 427 self.permissions_global = set()
428 428
429 429 self.default_repo_perms = Permission.get_default_repo_perms(
430 430 self.default_user_id, self.scope_repo_id)
431 431 self.default_repo_groups_perms = Permission.get_default_group_perms(
432 432 self.default_user_id, self.scope_repo_group_id)
433 433 self.default_user_group_perms = \
434 434 Permission.get_default_user_group_perms(
435 435 self.default_user_id, self.scope_user_group_id)
436 436
437 437 # default branch perms
438 438 self.default_branch_repo_perms = \
439 439 Permission.get_default_repo_branch_perms(
440 440 self.default_user_id, self.scope_repo_id)
441 441
442 442 def calculate(self):
443 443 if self.user_is_admin and not self.calculate_super_admin_as_user:
444 444 return self._calculate_admin_permissions()
445 445
446 446 self._calculate_global_default_permissions()
447 447 self._calculate_global_permissions()
448 448 self._calculate_default_permissions()
449 449 self._calculate_repository_permissions()
450 450 self._calculate_repository_branch_permissions()
451 451 self._calculate_repository_group_permissions()
452 452 self._calculate_user_group_permissions()
453 453 return self._permission_structure()
454 454
455 455 def _calculate_admin_permissions(self):
456 456 """
457 457 admin user have all default rights for repositories
458 458 and groups set to admin
459 459 """
460 460 self.permissions_global.add('hg.admin')
461 461 self.permissions_global.add('hg.create.write_on_repogroup.true')
462 462
463 463 # repositories
464 464 for perm in self.default_repo_perms:
465 465 r_k = perm.UserRepoToPerm.repository.repo_name
466 466 archived = perm.UserRepoToPerm.repository.archived
467 467 p = 'repository.admin'
468 468 self.permissions_repositories[r_k] = p, PermOrigin.SUPER_ADMIN
469 469 # special case for archived repositories, which we block still even for
470 470 # super admins
471 471 if archived:
472 472 p = 'repository.read'
473 473 self.permissions_repositories[r_k] = p, PermOrigin.ARCHIVED
474 474
475 475 # repository groups
476 476 for perm in self.default_repo_groups_perms:
477 477 rg_k = perm.UserRepoGroupToPerm.group.group_name
478 478 p = 'group.admin'
479 479 self.permissions_repository_groups[rg_k] = p, PermOrigin.SUPER_ADMIN
480 480
481 481 # user groups
482 482 for perm in self.default_user_group_perms:
483 483 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
484 484 p = 'usergroup.admin'
485 485 self.permissions_user_groups[u_k] = p, PermOrigin.SUPER_ADMIN
486 486
487 487 # branch permissions
488 488 # since super-admin also can have custom rule permissions
489 489 # we *always* need to calculate those inherited from default, and also explicit
490 490 self._calculate_default_permissions_repository_branches(
491 491 user_inherit_object_permissions=False)
492 492 self._calculate_repository_branch_permissions()
493 493
494 494 return self._permission_structure()
495 495
496 496 def _calculate_global_default_permissions(self):
497 497 """
498 498 global permissions taken from the default user
499 499 """
500 500 default_global_perms = UserToPerm.query()\
501 501 .filter(UserToPerm.user_id == self.default_user_id)\
502 502 .options(joinedload(UserToPerm.permission))
503 503
504 504 for perm in default_global_perms:
505 505 self.permissions_global.add(perm.permission.permission_name)
506 506
507 507 if self.user_is_admin:
508 508 self.permissions_global.add('hg.admin')
509 509 self.permissions_global.add('hg.create.write_on_repogroup.true')
510 510
511 511 def _calculate_global_permissions(self):
512 512 """
513 513 Set global system permissions with user permissions or permissions
514 514 taken from the user groups of the current user.
515 515
516 516 The permissions include repo creating, repo group creating, forking
517 517 etc.
518 518 """
519 519
520 520 # now we read the defined permissions and overwrite what we have set
521 521 # before those can be configured from groups or users explicitly.
522 522
523 523 # In case we want to extend this list we should make sure
524 524 # this is in sync with User.DEFAULT_USER_PERMISSIONS definitions
525 525 _configurable = frozenset([
526 526 'hg.fork.none', 'hg.fork.repository',
527 527 'hg.create.none', 'hg.create.repository',
528 528 'hg.usergroup.create.false', 'hg.usergroup.create.true',
529 529 'hg.repogroup.create.false', 'hg.repogroup.create.true',
530 530 'hg.create.write_on_repogroup.false', 'hg.create.write_on_repogroup.true',
531 531 'hg.inherit_default_perms.false', 'hg.inherit_default_perms.true'
532 532 ])
533 533
534 534 # USER GROUPS comes first user group global permissions
535 535 user_perms_from_users_groups = Session().query(UserGroupToPerm)\
536 536 .options(joinedload(UserGroupToPerm.permission))\
537 537 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
538 538 UserGroupMember.users_group_id))\
539 539 .filter(UserGroupMember.user_id == self.user_id)\
540 540 .order_by(UserGroupToPerm.users_group_id)\
541 541 .all()
542 542
543 543 # need to group here by groups since user can be in more than
544 544 # one group, so we get all groups
545 545 _explicit_grouped_perms = [
546 546 [x, list(y)] for x, y in
547 547 itertools.groupby(user_perms_from_users_groups,
548 548 lambda _x: _x.users_group)]
549 549
550 550 for gr, perms in _explicit_grouped_perms:
551 551 # since user can be in multiple groups iterate over them and
552 552 # select the lowest permissions first (more explicit)
553 553 # TODO(marcink): do this^^
554 554
555 555 # group doesn't inherit default permissions so we actually set them
556 556 if not gr.inherit_default_permissions:
557 557 # NEED TO IGNORE all previously set configurable permissions
558 558 # and replace them with explicitly set from this user
559 559 # group permissions
560 560 self.permissions_global = self.permissions_global.difference(
561 561 _configurable)
562 562 for perm in perms:
563 563 self.permissions_global.add(perm.permission.permission_name)
564 564
565 565 # user explicit global permissions
566 566 user_perms = Session().query(UserToPerm)\
567 567 .options(joinedload(UserToPerm.permission))\
568 568 .filter(UserToPerm.user_id == self.user_id).all()
569 569
570 570 if not self.inherit_default_permissions:
571 571 # NEED TO IGNORE all configurable permissions and
572 572 # replace them with explicitly set from this user permissions
573 573 self.permissions_global = self.permissions_global.difference(
574 574 _configurable)
575 575 for perm in user_perms:
576 576 self.permissions_global.add(perm.permission.permission_name)
577 577
578 578 def _calculate_default_permissions_repositories(self, user_inherit_object_permissions):
579 579 for perm in self.default_repo_perms:
580 580 r_k = perm.UserRepoToPerm.repository.repo_name
581 581 archived = perm.UserRepoToPerm.repository.archived
582 582 p = perm.Permission.permission_name
583 583 o = PermOrigin.REPO_DEFAULT
584 584 self.permissions_repositories[r_k] = p, o
585 585
586 586 # if we decide this user isn't inheriting permissions from
587 587 # default user we set him to .none so only explicit
588 588 # permissions work
589 589 if not user_inherit_object_permissions:
590 590 p = 'repository.none'
591 591 o = PermOrigin.REPO_DEFAULT_NO_INHERIT
592 592 self.permissions_repositories[r_k] = p, o
593 593
594 594 if perm.Repository.private and not (
595 595 perm.Repository.user_id == self.user_id):
596 596 # disable defaults for private repos,
597 597 p = 'repository.none'
598 598 o = PermOrigin.REPO_PRIVATE
599 599 self.permissions_repositories[r_k] = p, o
600 600
601 601 elif perm.Repository.user_id == self.user_id:
602 602 # set admin if owner
603 603 p = 'repository.admin'
604 604 o = PermOrigin.REPO_OWNER
605 605 self.permissions_repositories[r_k] = p, o
606 606
607 607 if self.user_is_admin:
608 608 p = 'repository.admin'
609 609 o = PermOrigin.SUPER_ADMIN
610 610 self.permissions_repositories[r_k] = p, o
611 611
612 612 # finally in case of archived repositories, we downgrade higher
613 613 # permissions to read
614 614 if archived:
615 615 current_perm = self.permissions_repositories[r_k]
616 616 if current_perm in ['repository.write', 'repository.admin']:
617 617 p = 'repository.read'
618 618 o = PermOrigin.ARCHIVED
619 619 self.permissions_repositories[r_k] = p, o
620 620
621 621 def _calculate_default_permissions_repository_branches(self, user_inherit_object_permissions):
622 622 for perm in self.default_branch_repo_perms:
623 623
624 624 r_k = perm.UserRepoToPerm.repository.repo_name
625 625 p = perm.Permission.permission_name
626 626 pattern = perm.UserToRepoBranchPermission.branch_pattern
627 627 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
628 628
629 629 if not self.explicit:
630 630 cur_perm = self.permissions_repository_branches.get(r_k)
631 631 if cur_perm:
632 632 cur_perm = cur_perm[pattern]
633 633 cur_perm = cur_perm or 'branch.none'
634 634
635 635 p = self._choose_permission(p, cur_perm)
636 636
637 637 # NOTE(marcink): register all pattern/perm instances in this
638 638 # special dict that aggregates entries
639 639 self.permissions_repository_branches[r_k] = pattern, p, o
640 640
641 641 def _calculate_default_permissions_repository_groups(self, user_inherit_object_permissions):
642 642 for perm in self.default_repo_groups_perms:
643 643 rg_k = perm.UserRepoGroupToPerm.group.group_name
644 644 p = perm.Permission.permission_name
645 645 o = PermOrigin.REPOGROUP_DEFAULT
646 646 self.permissions_repository_groups[rg_k] = p, o
647 647
648 648 # if we decide this user isn't inheriting permissions from default
649 649 # user we set him to .none so only explicit permissions work
650 650 if not user_inherit_object_permissions:
651 651 p = 'group.none'
652 652 o = PermOrigin.REPOGROUP_DEFAULT_NO_INHERIT
653 653 self.permissions_repository_groups[rg_k] = p, o
654 654
655 655 if perm.RepoGroup.user_id == self.user_id:
656 656 # set admin if owner
657 657 p = 'group.admin'
658 658 o = PermOrigin.REPOGROUP_OWNER
659 659 self.permissions_repository_groups[rg_k] = p, o
660 660
661 661 if self.user_is_admin:
662 662 p = 'group.admin'
663 663 o = PermOrigin.SUPER_ADMIN
664 664 self.permissions_repository_groups[rg_k] = p, o
665 665
666 666 def _calculate_default_permissions_user_groups(self, user_inherit_object_permissions):
667 667 for perm in self.default_user_group_perms:
668 668 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
669 669 p = perm.Permission.permission_name
670 670 o = PermOrigin.USERGROUP_DEFAULT
671 671 self.permissions_user_groups[u_k] = p, o
672 672
673 673 # if we decide this user isn't inheriting permissions from default
674 674 # user we set him to .none so only explicit permissions work
675 675 if not user_inherit_object_permissions:
676 676 p = 'usergroup.none'
677 677 o = PermOrigin.USERGROUP_DEFAULT_NO_INHERIT
678 678 self.permissions_user_groups[u_k] = p, o
679 679
680 680 if perm.UserGroup.user_id == self.user_id:
681 681 # set admin if owner
682 682 p = 'usergroup.admin'
683 683 o = PermOrigin.USERGROUP_OWNER
684 684 self.permissions_user_groups[u_k] = p, o
685 685
686 686 if self.user_is_admin:
687 687 p = 'usergroup.admin'
688 688 o = PermOrigin.SUPER_ADMIN
689 689 self.permissions_user_groups[u_k] = p, o
690 690
691 691 def _calculate_default_permissions(self):
692 692 """
693 693 Set default user permissions for repositories, repository branches,
694 694 repository groups, user groups taken from the default user.
695 695
696 696 Calculate inheritance of object permissions based on what we have now
697 697 in GLOBAL permissions. We check if .false is in GLOBAL since this is
698 698 explicitly set. Inherit is the opposite of .false being there.
699 699
700 700 .. note::
701 701
702 702 the syntax is little bit odd but what we need to check here is
703 703 the opposite of .false permission being in the list so even for
704 704 inconsistent state when both .true/.false is there
705 705 .false is more important
706 706
707 707 """
708 708 user_inherit_object_permissions = not ('hg.inherit_default_perms.false'
709 709 in self.permissions_global)
710 710
711 711 # default permissions inherited from `default` user permissions
712 712 self._calculate_default_permissions_repositories(
713 713 user_inherit_object_permissions)
714 714
715 715 self._calculate_default_permissions_repository_branches(
716 716 user_inherit_object_permissions)
717 717
718 718 self._calculate_default_permissions_repository_groups(
719 719 user_inherit_object_permissions)
720 720
721 721 self._calculate_default_permissions_user_groups(
722 722 user_inherit_object_permissions)
723 723
724 724 def _calculate_repository_permissions(self):
725 725 """
726 726 Repository access permissions for the current user.
727 727
728 728 Check if the user is part of user groups for this repository and
729 729 fill in the permission from it. `_choose_permission` decides of which
730 730 permission should be selected based on selected method.
731 731 """
732 732
733 733 # user group for repositories permissions
734 734 user_repo_perms_from_user_group = Permission\
735 735 .get_default_repo_perms_from_user_group(
736 736 self.user_id, self.scope_repo_id)
737 737
738 738 multiple_counter = collections.defaultdict(int)
739 739 for perm in user_repo_perms_from_user_group:
740 740 r_k = perm.UserGroupRepoToPerm.repository.repo_name
741 741 multiple_counter[r_k] += 1
742 742 p = perm.Permission.permission_name
743 743 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
744 744 .users_group.users_group_name
745 745
746 746 if multiple_counter[r_k] > 1:
747 747 cur_perm = self.permissions_repositories[r_k]
748 748 p = self._choose_permission(p, cur_perm)
749 749
750 750 self.permissions_repositories[r_k] = p, o
751 751
752 752 if perm.Repository.user_id == self.user_id:
753 753 # set admin if owner
754 754 p = 'repository.admin'
755 755 o = PermOrigin.REPO_OWNER
756 756 self.permissions_repositories[r_k] = p, o
757 757
758 758 if self.user_is_admin:
759 759 p = 'repository.admin'
760 760 o = PermOrigin.SUPER_ADMIN
761 761 self.permissions_repositories[r_k] = p, o
762 762
763 763 # user explicit permissions for repositories, overrides any specified
764 764 # by the group permission
765 765 user_repo_perms = Permission.get_default_repo_perms(
766 766 self.user_id, self.scope_repo_id)
767 767 for perm in user_repo_perms:
768 768 r_k = perm.UserRepoToPerm.repository.repo_name
769 769 p = perm.Permission.permission_name
770 770 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
771 771
772 772 if not self.explicit:
773 773 cur_perm = self.permissions_repositories.get(
774 774 r_k, 'repository.none')
775 775 p = self._choose_permission(p, cur_perm)
776 776
777 777 self.permissions_repositories[r_k] = p, o
778 778
779 779 if perm.Repository.user_id == self.user_id:
780 780 # set admin if owner
781 781 p = 'repository.admin'
782 782 o = PermOrigin.REPO_OWNER
783 783 self.permissions_repositories[r_k] = p, o
784 784
785 785 if self.user_is_admin:
786 786 p = 'repository.admin'
787 787 o = PermOrigin.SUPER_ADMIN
788 788 self.permissions_repositories[r_k] = p, o
789 789
790 790 def _calculate_repository_branch_permissions(self):
791 791 # user group for repositories permissions
792 792 user_repo_branch_perms_from_user_group = Permission\
793 793 .get_default_repo_branch_perms_from_user_group(
794 794 self.user_id, self.scope_repo_id)
795 795
796 796 multiple_counter = collections.defaultdict(int)
797 797 for perm in user_repo_branch_perms_from_user_group:
798 798 r_k = perm.UserGroupRepoToPerm.repository.repo_name
799 799 p = perm.Permission.permission_name
800 800 pattern = perm.UserGroupToRepoBranchPermission.branch_pattern
801 801 o = PermOrigin.REPO_USERGROUP % perm.UserGroupRepoToPerm\
802 802 .users_group.users_group_name
803 803
804 804 multiple_counter[r_k] += 1
805 805 if multiple_counter[r_k] > 1:
806 806 cur_perm = self.permissions_repository_branches[r_k][pattern]
807 807 p = self._choose_permission(p, cur_perm)
808 808
809 809 self.permissions_repository_branches[r_k] = pattern, p, o
810 810
811 811 # user explicit branch permissions for repositories, overrides
812 812 # any specified by the group permission
813 813 user_repo_branch_perms = Permission.get_default_repo_branch_perms(
814 814 self.user_id, self.scope_repo_id)
815 815
816 816 for perm in user_repo_branch_perms:
817 817
818 818 r_k = perm.UserRepoToPerm.repository.repo_name
819 819 p = perm.Permission.permission_name
820 820 pattern = perm.UserToRepoBranchPermission.branch_pattern
821 821 o = PermOrigin.REPO_USER % perm.UserRepoToPerm.user.username
822 822
823 823 if not self.explicit:
824 824 cur_perm = self.permissions_repository_branches.get(r_k)
825 825 if cur_perm:
826 826 cur_perm = cur_perm[pattern]
827 827 cur_perm = cur_perm or 'branch.none'
828 828 p = self._choose_permission(p, cur_perm)
829 829
830 830 # NOTE(marcink): register all pattern/perm instances in this
831 831 # special dict that aggregates entries
832 832 self.permissions_repository_branches[r_k] = pattern, p, o
833 833
834 834 def _calculate_repository_group_permissions(self):
835 835 """
836 836 Repository group permissions for the current user.
837 837
838 838 Check if the user is part of user groups for repository groups and
839 839 fill in the permissions from it. `_choose_permission` decides of which
840 840 permission should be selected based on selected method.
841 841 """
842 842 # user group for repo groups permissions
843 843 user_repo_group_perms_from_user_group = Permission\
844 844 .get_default_group_perms_from_user_group(
845 845 self.user_id, self.scope_repo_group_id)
846 846
847 847 multiple_counter = collections.defaultdict(int)
848 848 for perm in user_repo_group_perms_from_user_group:
849 849 rg_k = perm.UserGroupRepoGroupToPerm.group.group_name
850 850 multiple_counter[rg_k] += 1
851 851 o = PermOrigin.REPOGROUP_USERGROUP % perm.UserGroupRepoGroupToPerm\
852 852 .users_group.users_group_name
853 853 p = perm.Permission.permission_name
854 854
855 855 if multiple_counter[rg_k] > 1:
856 856 cur_perm = self.permissions_repository_groups[rg_k]
857 857 p = self._choose_permission(p, cur_perm)
858 858 self.permissions_repository_groups[rg_k] = p, o
859 859
860 860 if perm.RepoGroup.user_id == self.user_id:
861 861 # set admin if owner, even for member of other user group
862 862 p = 'group.admin'
863 863 o = PermOrigin.REPOGROUP_OWNER
864 864 self.permissions_repository_groups[rg_k] = p, o
865 865
866 866 if self.user_is_admin:
867 867 p = 'group.admin'
868 868 o = PermOrigin.SUPER_ADMIN
869 869 self.permissions_repository_groups[rg_k] = p, o
870 870
871 871 # user explicit permissions for repository groups
872 872 user_repo_groups_perms = Permission.get_default_group_perms(
873 873 self.user_id, self.scope_repo_group_id)
874 874 for perm in user_repo_groups_perms:
875 875 rg_k = perm.UserRepoGroupToPerm.group.group_name
876 876 o = PermOrigin.REPOGROUP_USER % perm.UserRepoGroupToPerm\
877 877 .user.username
878 878 p = perm.Permission.permission_name
879 879
880 880 if not self.explicit:
881 881 cur_perm = self.permissions_repository_groups.get(rg_k, 'group.none')
882 882 p = self._choose_permission(p, cur_perm)
883 883
884 884 self.permissions_repository_groups[rg_k] = p, o
885 885
886 886 if perm.RepoGroup.user_id == self.user_id:
887 887 # set admin if owner
888 888 p = 'group.admin'
889 889 o = PermOrigin.REPOGROUP_OWNER
890 890 self.permissions_repository_groups[rg_k] = p, o
891 891
892 892 if self.user_is_admin:
893 893 p = 'group.admin'
894 894 o = PermOrigin.SUPER_ADMIN
895 895 self.permissions_repository_groups[rg_k] = p, o
896 896
897 897 def _calculate_user_group_permissions(self):
898 898 """
899 899 User group permissions for the current user.
900 900 """
901 901 # user group for user group permissions
902 902 user_group_from_user_group = Permission\
903 903 .get_default_user_group_perms_from_user_group(
904 904 self.user_id, self.scope_user_group_id)
905 905
906 906 multiple_counter = collections.defaultdict(int)
907 907 for perm in user_group_from_user_group:
908 908 ug_k = perm.UserGroupUserGroupToPerm\
909 909 .target_user_group.users_group_name
910 910 multiple_counter[ug_k] += 1
911 911 o = PermOrigin.USERGROUP_USERGROUP % perm.UserGroupUserGroupToPerm\
912 912 .user_group.users_group_name
913 913 p = perm.Permission.permission_name
914 914
915 915 if multiple_counter[ug_k] > 1:
916 916 cur_perm = self.permissions_user_groups[ug_k]
917 917 p = self._choose_permission(p, cur_perm)
918 918
919 919 self.permissions_user_groups[ug_k] = p, o
920 920
921 921 if perm.UserGroup.user_id == self.user_id:
922 922 # set admin if owner, even for member of other user group
923 923 p = 'usergroup.admin'
924 924 o = PermOrigin.USERGROUP_OWNER
925 925 self.permissions_user_groups[ug_k] = p, o
926 926
927 927 if self.user_is_admin:
928 928 p = 'usergroup.admin'
929 929 o = PermOrigin.SUPER_ADMIN
930 930 self.permissions_user_groups[ug_k] = p, o
931 931
932 932 # user explicit permission for user groups
933 933 user_user_groups_perms = Permission.get_default_user_group_perms(
934 934 self.user_id, self.scope_user_group_id)
935 935 for perm in user_user_groups_perms:
936 936 ug_k = perm.UserUserGroupToPerm.user_group.users_group_name
937 937 o = PermOrigin.USERGROUP_USER % perm.UserUserGroupToPerm\
938 938 .user.username
939 939 p = perm.Permission.permission_name
940 940
941 941 if not self.explicit:
942 942 cur_perm = self.permissions_user_groups.get(ug_k, 'usergroup.none')
943 943 p = self._choose_permission(p, cur_perm)
944 944
945 945 self.permissions_user_groups[ug_k] = p, o
946 946
947 947 if perm.UserGroup.user_id == self.user_id:
948 948 # set admin if owner
949 949 p = 'usergroup.admin'
950 950 o = PermOrigin.USERGROUP_OWNER
951 951 self.permissions_user_groups[ug_k] = p, o
952 952
953 953 if self.user_is_admin:
954 954 p = 'usergroup.admin'
955 955 o = PermOrigin.SUPER_ADMIN
956 956 self.permissions_user_groups[ug_k] = p, o
957 957
958 958 def _choose_permission(self, new_perm, cur_perm):
959 959 new_perm_val = Permission.PERM_WEIGHTS[new_perm]
960 960 cur_perm_val = Permission.PERM_WEIGHTS[cur_perm]
961 961 if self.algo == 'higherwin':
962 962 if new_perm_val > cur_perm_val:
963 963 return new_perm
964 964 return cur_perm
965 965 elif self.algo == 'lowerwin':
966 966 if new_perm_val < cur_perm_val:
967 967 return new_perm
968 968 return cur_perm
969 969
970 970 def _permission_structure(self):
971 971 return {
972 972 'global': self.permissions_global,
973 973 'repositories': self.permissions_repositories,
974 974 'repository_branches': self.permissions_repository_branches,
975 975 'repositories_groups': self.permissions_repository_groups,
976 976 'user_groups': self.permissions_user_groups,
977 977 }
978 978
979 979
980 980 def allowed_auth_token_access(view_name, auth_token, whitelist=None):
981 981 """
982 982 Check if given controller_name is in whitelist of auth token access
983 983 """
984 984 if not whitelist:
985 985 from rhodecode import CONFIG
986 986 whitelist = aslist(
987 987 CONFIG.get('api_access_controllers_whitelist'), sep=',')
988 988 # backward compat translation
989 989 compat = {
990 990 # old controller, new VIEW
991 991 'ChangesetController:*': 'RepoCommitsView:*',
992 992 'ChangesetController:changeset_patch': 'RepoCommitsView:repo_commit_patch',
993 993 'ChangesetController:changeset_raw': 'RepoCommitsView:repo_commit_raw',
994 994 'FilesController:raw': 'RepoCommitsView:repo_commit_raw',
995 995 'FilesController:archivefile': 'RepoFilesView:repo_archivefile',
996 996 'GistsController:*': 'GistView:*',
997 997 }
998 998
999 999 log.debug(
1000 1000 'Allowed views for AUTH TOKEN access: %s', whitelist)
1001 1001 auth_token_access_valid = False
1002 1002
1003 1003 for entry in whitelist:
1004 1004 token_match = True
1005 1005 if entry in compat:
1006 1006 # translate from old Controllers to Pyramid Views
1007 1007 entry = compat[entry]
1008 1008
1009 1009 if '@' in entry:
1010 1010 # specific AuthToken
1011 1011 entry, allowed_token = entry.split('@', 1)
1012 1012 token_match = auth_token == allowed_token
1013 1013
1014 1014 if fnmatch.fnmatch(view_name, entry) and token_match:
1015 1015 auth_token_access_valid = True
1016 1016 break
1017 1017
1018 1018 if auth_token_access_valid:
1019 1019 log.debug('view: `%s` matches entry in whitelist: %s',
1020 1020 view_name, whitelist)
1021 1021
1022 1022 else:
1023 1023 msg = ('view: `%s` does *NOT* match any entry in whitelist: %s'
1024 1024 % (view_name, whitelist))
1025 1025 if auth_token:
1026 1026 # if we use auth token key and don't have access it's a warning
1027 1027 log.warning(msg)
1028 1028 else:
1029 1029 log.debug(msg)
1030 1030
1031 1031 return auth_token_access_valid
1032 1032
1033 1033
1034 1034 class AuthUser(object):
1035 1035 """
1036 1036 A simple object that handles all attributes of user in RhodeCode
1037 1037
1038 1038 It does lookup based on API key,given user, or user present in session
1039 1039 Then it fills all required information for such user. It also checks if
1040 1040 anonymous access is enabled and if so, it returns default user as logged in
1041 1041 """
1042 1042 GLOBAL_PERMS = [x[0] for x in Permission.PERMS]
1043 1043
1044 1044 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
1045 1045
1046 1046 self.user_id = user_id
1047 1047 self._api_key = api_key
1048 1048
1049 1049 self.api_key = None
1050 1050 self.username = username
1051 1051 self.ip_addr = ip_addr
1052 1052 self.name = ''
1053 1053 self.lastname = ''
1054 1054 self.first_name = ''
1055 1055 self.last_name = ''
1056 1056 self.email = ''
1057 1057 self.is_authenticated = False
1058 1058 self.admin = False
1059 1059 self.inherit_default_permissions = False
1060 1060 self.password = ''
1061 1061
1062 1062 self.anonymous_user = None # propagated on propagate_data
1063 1063 self.propagate_data()
1064 1064 self._instance = None
1065 1065 self._permissions_scoped_cache = {} # used to bind scoped calculation
1066 1066
1067 1067 @LazyProperty
1068 1068 def permissions(self):
1069 1069 return self.get_perms(user=self, cache=None)
1070 1070
1071 1071 @LazyProperty
1072 1072 def permissions_safe(self):
1073 1073 """
1074 1074 Filtered permissions excluding not allowed repositories
1075 1075 """
1076 1076 perms = self.get_perms(user=self, cache=None)
1077 1077
1078 1078 perms['repositories'] = {
1079 1079 k: v for k, v in perms['repositories'].items()
1080 1080 if v != 'repository.none'}
1081 1081 perms['repositories_groups'] = {
1082 1082 k: v for k, v in perms['repositories_groups'].items()
1083 1083 if v != 'group.none'}
1084 1084 perms['user_groups'] = {
1085 1085 k: v for k, v in perms['user_groups'].items()
1086 1086 if v != 'usergroup.none'}
1087 1087 perms['repository_branches'] = {
1088 1088 k: v for k, v in perms['repository_branches'].iteritems()
1089 1089 if v != 'branch.none'}
1090 1090 return perms
1091 1091
1092 1092 @LazyProperty
1093 1093 def permissions_full_details(self):
1094 1094 return self.get_perms(
1095 1095 user=self, cache=None, calculate_super_admin=True)
1096 1096
1097 1097 def permissions_with_scope(self, scope):
1098 1098 """
1099 1099 Call the get_perms function with scoped data. The scope in that function
1100 1100 narrows the SQL calls to the given ID of objects resulting in fetching
1101 1101 Just particular permission we want to obtain. If scope is an empty dict
1102 1102 then it basically narrows the scope to GLOBAL permissions only.
1103 1103
1104 1104 :param scope: dict
1105 1105 """
1106 1106 if 'repo_name' in scope:
1107 1107 obj = Repository.get_by_repo_name(scope['repo_name'])
1108 1108 if obj:
1109 1109 scope['repo_id'] = obj.repo_id
1110 1110 _scope = collections.OrderedDict()
1111 1111 _scope['repo_id'] = -1
1112 1112 _scope['user_group_id'] = -1
1113 1113 _scope['repo_group_id'] = -1
1114 1114
1115 1115 for k in sorted(scope.keys()):
1116 1116 _scope[k] = scope[k]
1117 1117
1118 1118 # store in cache to mimic how the @LazyProperty works,
1119 1119 # the difference here is that we use the unique key calculated
1120 1120 # from params and values
1121 1121 return self.get_perms(user=self, cache=None, scope=_scope)
1122 1122
1123 1123 def get_instance(self):
1124 1124 return User.get(self.user_id)
1125 1125
1126 1126 def propagate_data(self):
1127 1127 """
1128 1128 Fills in user data and propagates values to this instance. Maps fetched
1129 1129 user attributes to this class instance attributes
1130 1130 """
1131 1131 log.debug('AuthUser: starting data propagation for new potential user')
1132 1132 user_model = UserModel()
1133 1133 anon_user = self.anonymous_user = User.get_default_user(cache=True)
1134 1134 is_user_loaded = False
1135 1135
1136 1136 # lookup by userid
1137 1137 if self.user_id is not None and self.user_id != anon_user.user_id:
1138 1138 log.debug('Trying Auth User lookup by USER ID: `%s`', self.user_id)
1139 1139 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
1140 1140
1141 1141 # try go get user by api key
1142 1142 elif self._api_key and self._api_key != anon_user.api_key:
1143 1143 log.debug('Trying Auth User lookup by API KEY: `...%s`', self._api_key[-4:])
1144 1144 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
1145 1145
1146 1146 # lookup by username
1147 1147 elif self.username:
1148 1148 log.debug('Trying Auth User lookup by USER NAME: `%s`', self.username)
1149 1149 is_user_loaded = user_model.fill_data(self, username=self.username)
1150 1150 else:
1151 1151 log.debug('No data in %s that could been used to log in', self)
1152 1152
1153 1153 if not is_user_loaded:
1154 1154 log.debug(
1155 1155 'Failed to load user. Fallback to default user %s', anon_user)
1156 1156 # if we cannot authenticate user try anonymous
1157 1157 if anon_user.active:
1158 1158 log.debug('default user is active, using it as a session user')
1159 1159 user_model.fill_data(self, user_id=anon_user.user_id)
1160 1160 # then we set this user is logged in
1161 1161 self.is_authenticated = True
1162 1162 else:
1163 1163 log.debug('default user is NOT active')
1164 1164 # in case of disabled anonymous user we reset some of the
1165 1165 # parameters so such user is "corrupted", skipping the fill_data
1166 1166 for attr in ['user_id', 'username', 'admin', 'active']:
1167 1167 setattr(self, attr, None)
1168 1168 self.is_authenticated = False
1169 1169
1170 1170 if not self.username:
1171 1171 self.username = 'None'
1172 1172
1173 1173 log.debug('AuthUser: propagated user is now %s', self)
1174 1174
1175 1175 def get_perms(self, user, scope=None, explicit=True, algo='higherwin',
1176 1176 calculate_super_admin=False, cache=None):
1177 1177 """
1178 1178 Fills user permission attribute with permissions taken from database
1179 1179 works for permissions given for repositories, and for permissions that
1180 1180 are granted to groups
1181 1181
1182 1182 :param user: instance of User object from database
1183 1183 :param explicit: In case there are permissions both for user and a group
1184 1184 that user is part of, explicit flag will defiine if user will
1185 1185 explicitly override permissions from group, if it's False it will
1186 1186 make decision based on the algo
1187 1187 :param algo: algorithm to decide what permission should be choose if
1188 1188 it's multiple defined, eg user in two different groups. It also
1189 1189 decides if explicit flag is turned off how to specify the permission
1190 1190 for case when user is in a group + have defined separate permission
1191 1191 :param calculate_super_admin: calculate permissions for super-admin in the
1192 1192 same way as for regular user without speedups
1193 1193 :param cache: Use caching for calculation, None = let the cache backend decide
1194 1194 """
1195 1195 user_id = user.user_id
1196 1196 user_is_admin = user.is_admin
1197 1197
1198 1198 # inheritance of global permissions like create repo/fork repo etc
1199 1199 user_inherit_default_permissions = user.inherit_default_permissions
1200 1200
1201 1201 cache_seconds = safe_int(
1202 1202 rhodecode.CONFIG.get('rc_cache.cache_perms.expiration_time'))
1203 1203
1204 1204 if cache is None:
1205 1205 # let the backend cache decide
1206 1206 cache_on = cache_seconds > 0
1207 1207 else:
1208 1208 cache_on = cache
1209 1209
1210 1210 log.debug(
1211 1211 'Computing PERMISSION tree for user %s scope `%s` '
1212 1212 'with caching: %s[TTL: %ss]', user, scope, cache_on, cache_seconds or 0)
1213 1213
1214 1214 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
1215 1215 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1216 1216
1217 1217 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
1218 1218 condition=cache_on)
1219 1219 def compute_perm_tree(cache_name,
1220 1220 user_id, scope, user_is_admin,user_inherit_default_permissions,
1221 1221 explicit, algo, calculate_super_admin):
1222 1222 return _cached_perms_data(
1223 1223 user_id, scope, user_is_admin, user_inherit_default_permissions,
1224 1224 explicit, algo, calculate_super_admin)
1225 1225
1226 1226 start = time.time()
1227 1227 result = compute_perm_tree(
1228 1228 'permissions', user_id, scope, user_is_admin,
1229 1229 user_inherit_default_permissions, explicit, algo,
1230 1230 calculate_super_admin)
1231 1231
1232 1232 result_repr = []
1233 1233 for k in result:
1234 1234 result_repr.append((k, len(result[k])))
1235 1235 total = time.time() - start
1236 1236 log.debug('PERMISSION tree for user %s computed in %.4fs: %s',
1237 1237 user, total, result_repr)
1238 1238
1239 1239 return result
1240 1240
1241 1241 @property
1242 1242 def is_default(self):
1243 1243 return self.username == User.DEFAULT_USER
1244 1244
1245 1245 @property
1246 1246 def is_admin(self):
1247 1247 return self.admin
1248 1248
1249 1249 @property
1250 1250 def is_user_object(self):
1251 1251 return self.user_id is not None
1252 1252
1253 1253 @property
1254 1254 def repositories_admin(self):
1255 1255 """
1256 1256 Returns list of repositories you're an admin of
1257 1257 """
1258 1258 return [
1259 1259 x[0] for x in self.permissions['repositories'].items()
1260 1260 if x[1] == 'repository.admin']
1261 1261
1262 1262 @property
1263 1263 def repository_groups_admin(self):
1264 1264 """
1265 1265 Returns list of repository groups you're an admin of
1266 1266 """
1267 1267 return [
1268 1268 x[0] for x in self.permissions['repositories_groups'].items()
1269 1269 if x[1] == 'group.admin']
1270 1270
1271 1271 @property
1272 1272 def user_groups_admin(self):
1273 1273 """
1274 1274 Returns list of user groups you're an admin of
1275 1275 """
1276 1276 return [
1277 1277 x[0] for x in self.permissions['user_groups'].items()
1278 1278 if x[1] == 'usergroup.admin']
1279 1279
1280 1280 def repo_acl_ids(self, perms=None, name_filter=None, cache=False):
1281 1281 """
1282 1282 Returns list of repository ids that user have access to based on given
1283 1283 perms. The cache flag should be only used in cases that are used for
1284 1284 display purposes, NOT IN ANY CASE for permission checks.
1285 1285 """
1286 1286 from rhodecode.model.scm import RepoList
1287 1287 if not perms:
1288 1288 perms = [
1289 1289 'repository.read', 'repository.write', 'repository.admin']
1290 1290
1291 1291 def _cached_repo_acl(user_id, perm_def, _name_filter):
1292 1292 qry = Repository.query()
1293 1293 if _name_filter:
1294 1294 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1295 1295 qry = qry.filter(
1296 1296 Repository.repo_name.ilike(ilike_expression))
1297 1297
1298 1298 return [x.repo_id for x in
1299 1299 RepoList(qry, perm_set=perm_def)]
1300 1300
1301 1301 return _cached_repo_acl(self.user_id, perms, name_filter)
1302 1302
1303 1303 def repo_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1304 1304 """
1305 1305 Returns list of repository group ids that user have access to based on given
1306 1306 perms. The cache flag should be only used in cases that are used for
1307 1307 display purposes, NOT IN ANY CASE for permission checks.
1308 1308 """
1309 1309 from rhodecode.model.scm import RepoGroupList
1310 1310 if not perms:
1311 1311 perms = [
1312 1312 'group.read', 'group.write', 'group.admin']
1313 1313
1314 1314 def _cached_repo_group_acl(user_id, perm_def, _name_filter):
1315 1315 qry = RepoGroup.query()
1316 1316 if _name_filter:
1317 1317 ilike_expression = u'%{}%'.format(safe_unicode(_name_filter))
1318 1318 qry = qry.filter(
1319 1319 RepoGroup.group_name.ilike(ilike_expression))
1320 1320
1321 1321 return [x.group_id for x in
1322 1322 RepoGroupList(qry, perm_set=perm_def)]
1323 1323
1324 1324 return _cached_repo_group_acl(self.user_id, perms, name_filter)
1325 1325
1326 1326 def user_group_acl_ids(self, perms=None, name_filter=None, cache=False):
1327 1327 """
1328 1328 Returns list of user group ids that user have access to based on given
1329 1329 perms. The cache flag should be only used in cases that are used for
1330 1330 display purposes, NOT IN ANY CASE for permission checks.
1331 1331 """
1332 1332 from rhodecode.model.scm import UserGroupList
1333 1333 if not perms:
1334 1334 perms = [
1335 1335 'usergroup.read', 'usergroup.write', 'usergroup.admin']
1336 1336
1337 1337 def _cached_user_group_acl(user_id, perm_def, name_filter):
1338 1338 qry = UserGroup.query()
1339 1339 if name_filter:
1340 1340 ilike_expression = u'%{}%'.format(safe_unicode(name_filter))
1341 1341 qry = qry.filter(
1342 1342 UserGroup.users_group_name.ilike(ilike_expression))
1343 1343
1344 1344 return [x.users_group_id for x in
1345 1345 UserGroupList(qry, perm_set=perm_def)]
1346 1346
1347 1347 return _cached_user_group_acl(self.user_id, perms, name_filter)
1348 1348
1349 1349 @property
1350 1350 def ip_allowed(self):
1351 1351 """
1352 1352 Checks if ip_addr used in constructor is allowed from defined list of
1353 1353 allowed ip_addresses for user
1354 1354
1355 1355 :returns: boolean, True if ip is in allowed ip range
1356 1356 """
1357 1357 # check IP
1358 1358 inherit = self.inherit_default_permissions
1359 1359 return AuthUser.check_ip_allowed(self.user_id, self.ip_addr,
1360 1360 inherit_from_default=inherit)
1361 1361 @property
1362 1362 def personal_repo_group(self):
1363 1363 return RepoGroup.get_user_personal_repo_group(self.user_id)
1364 1364
1365 1365 @LazyProperty
1366 1366 def feed_token(self):
1367 1367 return self.get_instance().feed_token
1368 1368
1369 1369 @LazyProperty
1370 1370 def artifact_token(self):
1371 1371 return self.get_instance().artifact_token
1372 1372
1373 1373 @classmethod
1374 1374 def check_ip_allowed(cls, user_id, ip_addr, inherit_from_default):
1375 1375 allowed_ips = AuthUser.get_allowed_ips(
1376 1376 user_id, cache=True, inherit_from_default=inherit_from_default)
1377 1377 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips):
1378 1378 log.debug('IP:%s for user %s is in range of %s',
1379 1379 ip_addr, user_id, allowed_ips)
1380 1380 return True
1381 1381 else:
1382 1382 log.info('Access for IP:%s forbidden for user %s, '
1383 1383 'not in %s', ip_addr, user_id, allowed_ips)
1384 1384 return False
1385 1385
1386 1386 def get_branch_permissions(self, repo_name, perms=None):
1387 1387 perms = perms or self.permissions_with_scope({'repo_name': repo_name})
1388 1388 branch_perms = perms.get('repository_branches', {})
1389 1389 if not branch_perms:
1390 1390 return {}
1391 1391 repo_branch_perms = branch_perms.get(repo_name)
1392 1392 return repo_branch_perms or {}
1393 1393
1394 1394 def get_rule_and_branch_permission(self, repo_name, branch_name):
1395 1395 """
1396 1396 Check if this AuthUser has defined any permissions for branches. If any of
1397 1397 the rules match in order, we return the matching permissions
1398 1398 """
1399 1399
1400 1400 rule = default_perm = ''
1401 1401
1402 1402 repo_branch_perms = self.get_branch_permissions(repo_name=repo_name)
1403 1403 if not repo_branch_perms:
1404 1404 return rule, default_perm
1405 1405
1406 1406 # now calculate the permissions
1407 1407 for pattern, branch_perm in repo_branch_perms.items():
1408 1408 if fnmatch.fnmatch(branch_name, pattern):
1409 1409 rule = '`{}`=>{}'.format(pattern, branch_perm)
1410 1410 return rule, branch_perm
1411 1411
1412 1412 return rule, default_perm
1413 1413
1414 1414 def __repr__(self):
1415 1415 return "<AuthUser('id:%s[%s] ip:%s auth:%s')>"\
1416 1416 % (self.user_id, self.username, self.ip_addr, self.is_authenticated)
1417 1417
1418 1418 def set_authenticated(self, authenticated=True):
1419 1419 if self.user_id != self.anonymous_user.user_id:
1420 1420 self.is_authenticated = authenticated
1421 1421
1422 1422 def get_cookie_store(self):
1423 1423 return {
1424 1424 'username': self.username,
1425 1425 'password': md5(self.password or ''),
1426 1426 'user_id': self.user_id,
1427 1427 'is_authenticated': self.is_authenticated
1428 1428 }
1429 1429
1430 1430 @classmethod
1431 1431 def from_cookie_store(cls, cookie_store):
1432 1432 """
1433 1433 Creates AuthUser from a cookie store
1434 1434
1435 1435 :param cls:
1436 1436 :param cookie_store:
1437 1437 """
1438 1438 user_id = cookie_store.get('user_id')
1439 1439 username = cookie_store.get('username')
1440 1440 api_key = cookie_store.get('api_key')
1441 1441 return AuthUser(user_id, api_key, username)
1442 1442
1443 1443 @classmethod
1444 1444 def get_allowed_ips(cls, user_id, cache=False, inherit_from_default=False):
1445 1445 _set = set()
1446 1446
1447 1447 if inherit_from_default:
1448 1448 def_user_id = User.get_default_user(cache=True).user_id
1449 1449 default_ips = UserIpMap.query().filter(UserIpMap.user_id == def_user_id)
1450 1450 if cache:
1451 1451 default_ips = default_ips.options(
1452 1452 FromCache("sql_cache_short", "get_user_ips_default"))
1453 1453
1454 1454 # populate from default user
1455 1455 for ip in default_ips:
1456 1456 try:
1457 1457 _set.add(ip.ip_addr)
1458 1458 except ObjectDeletedError:
1459 1459 # since we use heavy caching sometimes it happens that
1460 1460 # we get deleted objects here, we just skip them
1461 1461 pass
1462 1462
1463 1463 # NOTE:(marcink) we don't want to load any rules for empty
1464 1464 # user_id which is the case of access of non logged users when anonymous
1465 1465 # access is disabled
1466 1466 user_ips = []
1467 1467 if user_id:
1468 1468 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
1469 1469 if cache:
1470 1470 user_ips = user_ips.options(
1471 1471 FromCache("sql_cache_short", "get_user_ips_%s" % user_id))
1472 1472
1473 1473 for ip in user_ips:
1474 1474 try:
1475 1475 _set.add(ip.ip_addr)
1476 1476 except ObjectDeletedError:
1477 1477 # since we use heavy caching sometimes it happens that we get
1478 1478 # deleted objects here, we just skip them
1479 1479 pass
1480 1480 return _set or {ip for ip in ['0.0.0.0/0', '::/0']}
1481 1481
1482 1482
1483 1483 def set_available_permissions(settings):
1484 1484 """
1485 1485 This function will propagate pyramid settings with all available defined
1486 1486 permission given in db. We don't want to check each time from db for new
1487 1487 permissions since adding a new permission also requires application restart
1488 1488 ie. to decorate new views with the newly created permission
1489 1489
1490 1490 :param settings: current pyramid registry.settings
1491 1491
1492 1492 """
1493 1493 log.debug('auth: getting information about all available permissions')
1494 1494 try:
1495 1495 sa = meta.Session
1496 1496 all_perms = sa.query(Permission).all()
1497 1497 settings.setdefault('available_permissions',
1498 1498 [x.permission_name for x in all_perms])
1499 1499 log.debug('auth: set available permissions')
1500 1500 except Exception:
1501 1501 log.exception('Failed to fetch permissions from the database.')
1502 1502 raise
1503 1503
1504 1504
1505 1505 def get_csrf_token(session, force_new=False, save_if_missing=True):
1506 1506 """
1507 1507 Return the current authentication token, creating one if one doesn't
1508 1508 already exist and the save_if_missing flag is present.
1509 1509
1510 1510 :param session: pass in the pyramid session, else we use the global ones
1511 1511 :param force_new: force to re-generate the token and store it in session
1512 1512 :param save_if_missing: save the newly generated token if it's missing in
1513 1513 session
1514 1514 """
1515 1515 # NOTE(marcink): probably should be replaced with below one from pyramid 1.9
1516 1516 # from pyramid.csrf import get_csrf_token
1517 1517
1518 1518 if (csrf_token_key not in session and save_if_missing) or force_new:
1519 1519 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
1520 1520 session[csrf_token_key] = token
1521 1521 if hasattr(session, 'save'):
1522 1522 session.save()
1523 1523 return session.get(csrf_token_key)
1524 1524
1525 1525
1526 1526 def get_request(perm_class_instance):
1527 1527 from pyramid.threadlocal import get_current_request
1528 1528 pyramid_request = get_current_request()
1529 1529 return pyramid_request
1530 1530
1531 1531
1532 1532 # CHECK DECORATORS
1533 1533 class CSRFRequired(object):
1534 1534 """
1535 1535 Decorator for authenticating a form
1536 1536
1537 1537 This decorator uses an authorization token stored in the client's
1538 1538 session for prevention of certain Cross-site request forgery (CSRF)
1539 1539 attacks (See
1540 1540 http://en.wikipedia.org/wiki/Cross-site_request_forgery for more
1541 1541 information).
1542 1542
1543 For use with the ``webhelpers.secure_form`` helper functions.
1543 For use with the ``secure_form`` helper functions.
1544 1544
1545 1545 """
1546 def __init__(self, token=csrf_token_key, header='X-CSRF-Token',
1547 except_methods=None):
1546 def __init__(self, token=csrf_token_key, header='X-CSRF-Token', except_methods=None):
1548 1547 self.token = token
1549 1548 self.header = header
1550 1549 self.except_methods = except_methods or []
1551 1550
1552 1551 def __call__(self, func):
1553 1552 return get_cython_compat_decorator(self.__wrapper, func)
1554 1553
1555 1554 def _get_csrf(self, _request):
1556 1555 return _request.POST.get(self.token, _request.headers.get(self.header))
1557 1556
1558 1557 def check_csrf(self, _request, cur_token):
1559 1558 supplied_token = self._get_csrf(_request)
1560 1559 return supplied_token and supplied_token == cur_token
1561 1560
1562 1561 def _get_request(self):
1563 1562 return get_request(self)
1564 1563
1565 1564 def __wrapper(self, func, *fargs, **fkwargs):
1566 1565 request = self._get_request()
1567 1566
1568 1567 if request.method in self.except_methods:
1569 1568 return func(*fargs, **fkwargs)
1570 1569
1571 1570 cur_token = get_csrf_token(request.session, save_if_missing=False)
1572 1571 if self.check_csrf(request, cur_token):
1573 1572 if request.POST.get(self.token):
1574 1573 del request.POST[self.token]
1575 1574 return func(*fargs, **fkwargs)
1576 1575 else:
1577 1576 reason = 'token-missing'
1578 1577 supplied_token = self._get_csrf(request)
1579 1578 if supplied_token and cur_token != supplied_token:
1580 1579 reason = 'token-mismatch [%s:%s]' % (
1581 1580 cur_token or ''[:6], supplied_token or ''[:6])
1582 1581
1583 1582 csrf_message = \
1584 1583 ("Cross-site request forgery detected, request denied. See "
1585 1584 "http://en.wikipedia.org/wiki/Cross-site_request_forgery for "
1586 1585 "more information.")
1587 1586 log.warn('Cross-site request forgery detected, request %r DENIED: %s '
1588 1587 'REMOTE_ADDR:%s, HEADERS:%s' % (
1589 1588 request, reason, request.remote_addr, request.headers))
1590 1589
1591 1590 raise HTTPForbidden(explanation=csrf_message)
1592 1591
1593 1592
1594 1593 class LoginRequired(object):
1595 1594 """
1596 1595 Must be logged in to execute this function else
1597 1596 redirect to login page
1598 1597
1599 1598 :param api_access: if enabled this checks only for valid auth token
1600 1599 and grants access based on valid token
1601 1600 """
1602 1601 def __init__(self, auth_token_access=None):
1603 1602 self.auth_token_access = auth_token_access
1604 1603 if self.auth_token_access:
1605 1604 valid_type = set(auth_token_access).intersection(set(UserApiKeys.ROLES))
1606 1605 if not valid_type:
1607 1606 raise ValueError('auth_token_access must be on of {}, got {}'.format(
1608 1607 UserApiKeys.ROLES, auth_token_access))
1609 1608
1610 1609 def __call__(self, func):
1611 1610 return get_cython_compat_decorator(self.__wrapper, func)
1612 1611
1613 1612 def _get_request(self):
1614 1613 return get_request(self)
1615 1614
1616 1615 def __wrapper(self, func, *fargs, **fkwargs):
1617 1616 from rhodecode.lib import helpers as h
1618 1617 cls = fargs[0]
1619 1618 user = cls._rhodecode_user
1620 1619 request = self._get_request()
1621 1620 _ = request.translate
1622 1621
1623 1622 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
1624 1623 log.debug('Starting login restriction checks for user: %s', user)
1625 1624 # check if our IP is allowed
1626 1625 ip_access_valid = True
1627 1626 if not user.ip_allowed:
1628 1627 h.flash(h.literal(_('IP {} not allowed'.format(user.ip_addr))),
1629 1628 category='warning')
1630 1629 ip_access_valid = False
1631 1630
1632 1631 # we used stored token that is extract from GET or URL param (if any)
1633 1632 _auth_token = request.user_auth_token
1634 1633
1635 1634 # check if we used an AUTH_TOKEN and it's a valid one
1636 1635 # defined white-list of controllers which API access will be enabled
1637 1636 whitelist = None
1638 1637 if self.auth_token_access:
1639 1638 # since this location is allowed by @LoginRequired decorator it's our
1640 1639 # only whitelist
1641 1640 whitelist = [loc]
1642 1641 auth_token_access_valid = allowed_auth_token_access(
1643 1642 loc, whitelist=whitelist, auth_token=_auth_token)
1644 1643
1645 1644 # explicit controller is enabled or API is in our whitelist
1646 1645 if auth_token_access_valid:
1647 1646 log.debug('Checking AUTH TOKEN access for %s', cls)
1648 1647 db_user = user.get_instance()
1649 1648
1650 1649 if db_user:
1651 1650 if self.auth_token_access:
1652 1651 roles = self.auth_token_access
1653 1652 else:
1654 1653 roles = [UserApiKeys.ROLE_HTTP]
1655 1654 log.debug('AUTH TOKEN: checking auth for user %s and roles %s',
1656 1655 db_user, roles)
1657 1656 token_match = db_user.authenticate_by_token(
1658 1657 _auth_token, roles=roles)
1659 1658 else:
1660 1659 log.debug('Unable to fetch db instance for auth user: %s', user)
1661 1660 token_match = False
1662 1661
1663 1662 if _auth_token and token_match:
1664 1663 auth_token_access_valid = True
1665 1664 log.debug('AUTH TOKEN ****%s is VALID', _auth_token[-4:])
1666 1665 else:
1667 1666 auth_token_access_valid = False
1668 1667 if not _auth_token:
1669 1668 log.debug("AUTH TOKEN *NOT* present in request")
1670 1669 else:
1671 1670 log.warning("AUTH TOKEN ****%s *NOT* valid", _auth_token[-4:])
1672 1671
1673 1672 log.debug('Checking if %s is authenticated @ %s', user.username, loc)
1674 1673 reason = 'RHODECODE_AUTH' if user.is_authenticated \
1675 1674 else 'AUTH_TOKEN_AUTH'
1676 1675
1677 1676 if ip_access_valid and (
1678 1677 user.is_authenticated or auth_token_access_valid):
1679 1678 log.info('user %s authenticating with:%s IS authenticated on func %s',
1680 1679 user, reason, loc)
1681 1680
1682 1681 return func(*fargs, **fkwargs)
1683 1682 else:
1684 1683 log.warning(
1685 1684 'user %s authenticating with:%s NOT authenticated on '
1686 1685 'func: %s: IP_ACCESS:%s AUTH_TOKEN_ACCESS:%s',
1687 1686 user, reason, loc, ip_access_valid, auth_token_access_valid)
1688 1687 # we preserve the get PARAM
1689 1688 came_from = get_came_from(request)
1690 1689
1691 1690 log.debug('redirecting to login page with %s', came_from)
1692 1691 raise HTTPFound(
1693 1692 h.route_path('login', _query={'came_from': came_from}))
1694 1693
1695 1694
1696 1695 class NotAnonymous(object):
1697 1696 """
1698 1697 Must be logged in to execute this function else
1699 1698 redirect to login page
1700 1699 """
1701 1700
1702 1701 def __call__(self, func):
1703 1702 return get_cython_compat_decorator(self.__wrapper, func)
1704 1703
1705 1704 def _get_request(self):
1706 1705 return get_request(self)
1707 1706
1708 1707 def __wrapper(self, func, *fargs, **fkwargs):
1709 1708 import rhodecode.lib.helpers as h
1710 1709 cls = fargs[0]
1711 1710 self.user = cls._rhodecode_user
1712 1711 request = self._get_request()
1713 1712 _ = request.translate
1714 1713 log.debug('Checking if user is not anonymous @%s', cls)
1715 1714
1716 1715 anonymous = self.user.username == User.DEFAULT_USER
1717 1716
1718 1717 if anonymous:
1719 1718 came_from = get_came_from(request)
1720 1719 h.flash(_('You need to be a registered user to '
1721 1720 'perform this action'),
1722 1721 category='warning')
1723 1722 raise HTTPFound(
1724 1723 h.route_path('login', _query={'came_from': came_from}))
1725 1724 else:
1726 1725 return func(*fargs, **fkwargs)
1727 1726
1728 1727
1729 1728 class PermsDecorator(object):
1730 1729 """
1731 1730 Base class for controller decorators, we extract the current user from
1732 1731 the class itself, which has it stored in base controllers
1733 1732 """
1734 1733
1735 1734 def __init__(self, *required_perms):
1736 1735 self.required_perms = set(required_perms)
1737 1736
1738 1737 def __call__(self, func):
1739 1738 return get_cython_compat_decorator(self.__wrapper, func)
1740 1739
1741 1740 def _get_request(self):
1742 1741 return get_request(self)
1743 1742
1744 1743 def __wrapper(self, func, *fargs, **fkwargs):
1745 1744 import rhodecode.lib.helpers as h
1746 1745 cls = fargs[0]
1747 1746 _user = cls._rhodecode_user
1748 1747 request = self._get_request()
1749 1748 _ = request.translate
1750 1749
1751 1750 log.debug('checking %s permissions %s for %s %s',
1752 1751 self.__class__.__name__, self.required_perms, cls, _user)
1753 1752
1754 1753 if self.check_permissions(_user):
1755 1754 log.debug('Permission granted for %s %s', cls, _user)
1756 1755 return func(*fargs, **fkwargs)
1757 1756
1758 1757 else:
1759 1758 log.debug('Permission denied for %s %s', cls, _user)
1760 1759 anonymous = _user.username == User.DEFAULT_USER
1761 1760
1762 1761 if anonymous:
1763 1762 came_from = get_came_from(self._get_request())
1764 1763 h.flash(_('You need to be signed in to view this page'),
1765 1764 category='warning')
1766 1765 raise HTTPFound(
1767 1766 h.route_path('login', _query={'came_from': came_from}))
1768 1767
1769 1768 else:
1770 1769 # redirect with 404 to prevent resource discovery
1771 1770 raise HTTPNotFound()
1772 1771
1773 1772 def check_permissions(self, user):
1774 1773 """Dummy function for overriding"""
1775 1774 raise NotImplementedError(
1776 1775 'You have to write this function in child class')
1777 1776
1778 1777
1779 1778 class HasPermissionAllDecorator(PermsDecorator):
1780 1779 """
1781 1780 Checks for access permission for all given predicates. All of them
1782 1781 have to be meet in order to fulfill the request
1783 1782 """
1784 1783
1785 1784 def check_permissions(self, user):
1786 1785 perms = user.permissions_with_scope({})
1787 1786 if self.required_perms.issubset(perms['global']):
1788 1787 return True
1789 1788 return False
1790 1789
1791 1790
1792 1791 class HasPermissionAnyDecorator(PermsDecorator):
1793 1792 """
1794 1793 Checks for access permission for any of given predicates. In order to
1795 1794 fulfill the request any of predicates must be meet
1796 1795 """
1797 1796
1798 1797 def check_permissions(self, user):
1799 1798 perms = user.permissions_with_scope({})
1800 1799 if self.required_perms.intersection(perms['global']):
1801 1800 return True
1802 1801 return False
1803 1802
1804 1803
1805 1804 class HasRepoPermissionAllDecorator(PermsDecorator):
1806 1805 """
1807 1806 Checks for access permission for all given predicates for specific
1808 1807 repository. All of them have to be meet in order to fulfill the request
1809 1808 """
1810 1809 def _get_repo_name(self):
1811 1810 _request = self._get_request()
1812 1811 return get_repo_slug(_request)
1813 1812
1814 1813 def check_permissions(self, user):
1815 1814 perms = user.permissions
1816 1815 repo_name = self._get_repo_name()
1817 1816
1818 1817 try:
1819 1818 user_perms = {perms['repositories'][repo_name]}
1820 1819 except KeyError:
1821 1820 log.debug('cannot locate repo with name: `%s` in permissions defs',
1822 1821 repo_name)
1823 1822 return False
1824 1823
1825 1824 log.debug('checking `%s` permissions for repo `%s`',
1826 1825 user_perms, repo_name)
1827 1826 if self.required_perms.issubset(user_perms):
1828 1827 return True
1829 1828 return False
1830 1829
1831 1830
1832 1831 class HasRepoPermissionAnyDecorator(PermsDecorator):
1833 1832 """
1834 1833 Checks for access permission for any of given predicates for specific
1835 1834 repository. In order to fulfill the request any of predicates must be meet
1836 1835 """
1837 1836 def _get_repo_name(self):
1838 1837 _request = self._get_request()
1839 1838 return get_repo_slug(_request)
1840 1839
1841 1840 def check_permissions(self, user):
1842 1841 perms = user.permissions
1843 1842 repo_name = self._get_repo_name()
1844 1843
1845 1844 try:
1846 1845 user_perms = {perms['repositories'][repo_name]}
1847 1846 except KeyError:
1848 1847 log.debug(
1849 1848 'cannot locate repo with name: `%s` in permissions defs',
1850 1849 repo_name)
1851 1850 return False
1852 1851
1853 1852 log.debug('checking `%s` permissions for repo `%s`',
1854 1853 user_perms, repo_name)
1855 1854 if self.required_perms.intersection(user_perms):
1856 1855 return True
1857 1856 return False
1858 1857
1859 1858
1860 1859 class HasRepoGroupPermissionAllDecorator(PermsDecorator):
1861 1860 """
1862 1861 Checks for access permission for all given predicates for specific
1863 1862 repository group. All of them have to be meet in order to
1864 1863 fulfill the request
1865 1864 """
1866 1865 def _get_repo_group_name(self):
1867 1866 _request = self._get_request()
1868 1867 return get_repo_group_slug(_request)
1869 1868
1870 1869 def check_permissions(self, user):
1871 1870 perms = user.permissions
1872 1871 group_name = self._get_repo_group_name()
1873 1872 try:
1874 1873 user_perms = {perms['repositories_groups'][group_name]}
1875 1874 except KeyError:
1876 1875 log.debug(
1877 1876 'cannot locate repo group with name: `%s` in permissions defs',
1878 1877 group_name)
1879 1878 return False
1880 1879
1881 1880 log.debug('checking `%s` permissions for repo group `%s`',
1882 1881 user_perms, group_name)
1883 1882 if self.required_perms.issubset(user_perms):
1884 1883 return True
1885 1884 return False
1886 1885
1887 1886
1888 1887 class HasRepoGroupPermissionAnyDecorator(PermsDecorator):
1889 1888 """
1890 1889 Checks for access permission for any of given predicates for specific
1891 1890 repository group. In order to fulfill the request any
1892 1891 of predicates must be met
1893 1892 """
1894 1893 def _get_repo_group_name(self):
1895 1894 _request = self._get_request()
1896 1895 return get_repo_group_slug(_request)
1897 1896
1898 1897 def check_permissions(self, user):
1899 1898 perms = user.permissions
1900 1899 group_name = self._get_repo_group_name()
1901 1900
1902 1901 try:
1903 1902 user_perms = {perms['repositories_groups'][group_name]}
1904 1903 except KeyError:
1905 1904 log.debug(
1906 1905 'cannot locate repo group with name: `%s` in permissions defs',
1907 1906 group_name)
1908 1907 return False
1909 1908
1910 1909 log.debug('checking `%s` permissions for repo group `%s`',
1911 1910 user_perms, group_name)
1912 1911 if self.required_perms.intersection(user_perms):
1913 1912 return True
1914 1913 return False
1915 1914
1916 1915
1917 1916 class HasUserGroupPermissionAllDecorator(PermsDecorator):
1918 1917 """
1919 1918 Checks for access permission for all given predicates for specific
1920 1919 user group. All of them have to be meet in order to fulfill the request
1921 1920 """
1922 1921 def _get_user_group_name(self):
1923 1922 _request = self._get_request()
1924 1923 return get_user_group_slug(_request)
1925 1924
1926 1925 def check_permissions(self, user):
1927 1926 perms = user.permissions
1928 1927 group_name = self._get_user_group_name()
1929 1928 try:
1930 1929 user_perms = {perms['user_groups'][group_name]}
1931 1930 except KeyError:
1932 1931 return False
1933 1932
1934 1933 if self.required_perms.issubset(user_perms):
1935 1934 return True
1936 1935 return False
1937 1936
1938 1937
1939 1938 class HasUserGroupPermissionAnyDecorator(PermsDecorator):
1940 1939 """
1941 1940 Checks for access permission for any of given predicates for specific
1942 1941 user group. In order to fulfill the request any of predicates must be meet
1943 1942 """
1944 1943 def _get_user_group_name(self):
1945 1944 _request = self._get_request()
1946 1945 return get_user_group_slug(_request)
1947 1946
1948 1947 def check_permissions(self, user):
1949 1948 perms = user.permissions
1950 1949 group_name = self._get_user_group_name()
1951 1950 try:
1952 1951 user_perms = {perms['user_groups'][group_name]}
1953 1952 except KeyError:
1954 1953 return False
1955 1954
1956 1955 if self.required_perms.intersection(user_perms):
1957 1956 return True
1958 1957 return False
1959 1958
1960 1959
1961 1960 # CHECK FUNCTIONS
1962 1961 class PermsFunction(object):
1963 1962 """Base function for other check functions"""
1964 1963
1965 1964 def __init__(self, *perms):
1966 1965 self.required_perms = set(perms)
1967 1966 self.repo_name = None
1968 1967 self.repo_group_name = None
1969 1968 self.user_group_name = None
1970 1969
1971 1970 def __bool__(self):
1972 1971 frame = inspect.currentframe()
1973 1972 stack_trace = traceback.format_stack(frame)
1974 1973 log.error('Checking bool value on a class instance of perm '
1975 1974 'function is not allowed: %s', ''.join(stack_trace))
1976 1975 # rather than throwing errors, here we always return False so if by
1977 1976 # accident someone checks truth for just an instance it will always end
1978 1977 # up in returning False
1979 1978 return False
1980 1979 __nonzero__ = __bool__
1981 1980
1982 1981 def __call__(self, check_location='', user=None):
1983 1982 if not user:
1984 1983 log.debug('Using user attribute from global request')
1985 1984 request = self._get_request()
1986 1985 user = request.user
1987 1986
1988 1987 # init auth user if not already given
1989 1988 if not isinstance(user, AuthUser):
1990 1989 log.debug('Wrapping user %s into AuthUser', user)
1991 1990 user = AuthUser(user.user_id)
1992 1991
1993 1992 cls_name = self.__class__.__name__
1994 1993 check_scope = self._get_check_scope(cls_name)
1995 1994 check_location = check_location or 'unspecified location'
1996 1995
1997 1996 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
1998 1997 self.required_perms, user, check_scope, check_location)
1999 1998 if not user:
2000 1999 log.warning('Empty user given for permission check')
2001 2000 return False
2002 2001
2003 2002 if self.check_permissions(user):
2004 2003 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2005 2004 check_scope, user, check_location)
2006 2005 return True
2007 2006
2008 2007 else:
2009 2008 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2010 2009 check_scope, user, check_location)
2011 2010 return False
2012 2011
2013 2012 def _get_request(self):
2014 2013 return get_request(self)
2015 2014
2016 2015 def _get_check_scope(self, cls_name):
2017 2016 return {
2018 2017 'HasPermissionAll': 'GLOBAL',
2019 2018 'HasPermissionAny': 'GLOBAL',
2020 2019 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
2021 2020 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
2022 2021 'HasRepoGroupPermissionAll': 'repo_group:%s' % self.repo_group_name,
2023 2022 'HasRepoGroupPermissionAny': 'repo_group:%s' % self.repo_group_name,
2024 2023 'HasUserGroupPermissionAll': 'user_group:%s' % self.user_group_name,
2025 2024 'HasUserGroupPermissionAny': 'user_group:%s' % self.user_group_name,
2026 2025 }.get(cls_name, '?:%s' % cls_name)
2027 2026
2028 2027 def check_permissions(self, user):
2029 2028 """Dummy function for overriding"""
2030 2029 raise Exception('You have to write this function in child class')
2031 2030
2032 2031
2033 2032 class HasPermissionAll(PermsFunction):
2034 2033 def check_permissions(self, user):
2035 2034 perms = user.permissions_with_scope({})
2036 2035 if self.required_perms.issubset(perms.get('global')):
2037 2036 return True
2038 2037 return False
2039 2038
2040 2039
2041 2040 class HasPermissionAny(PermsFunction):
2042 2041 def check_permissions(self, user):
2043 2042 perms = user.permissions_with_scope({})
2044 2043 if self.required_perms.intersection(perms.get('global')):
2045 2044 return True
2046 2045 return False
2047 2046
2048 2047
2049 2048 class HasRepoPermissionAll(PermsFunction):
2050 2049 def __call__(self, repo_name=None, check_location='', user=None):
2051 2050 self.repo_name = repo_name
2052 2051 return super(HasRepoPermissionAll, self).__call__(check_location, user)
2053 2052
2054 2053 def _get_repo_name(self):
2055 2054 if not self.repo_name:
2056 2055 _request = self._get_request()
2057 2056 self.repo_name = get_repo_slug(_request)
2058 2057 return self.repo_name
2059 2058
2060 2059 def check_permissions(self, user):
2061 2060 self.repo_name = self._get_repo_name()
2062 2061 perms = user.permissions
2063 2062 try:
2064 2063 user_perms = {perms['repositories'][self.repo_name]}
2065 2064 except KeyError:
2066 2065 return False
2067 2066 if self.required_perms.issubset(user_perms):
2068 2067 return True
2069 2068 return False
2070 2069
2071 2070
2072 2071 class HasRepoPermissionAny(PermsFunction):
2073 2072 def __call__(self, repo_name=None, check_location='', user=None):
2074 2073 self.repo_name = repo_name
2075 2074 return super(HasRepoPermissionAny, self).__call__(check_location, user)
2076 2075
2077 2076 def _get_repo_name(self):
2078 2077 if not self.repo_name:
2079 2078 _request = self._get_request()
2080 2079 self.repo_name = get_repo_slug(_request)
2081 2080 return self.repo_name
2082 2081
2083 2082 def check_permissions(self, user):
2084 2083 self.repo_name = self._get_repo_name()
2085 2084 perms = user.permissions
2086 2085 try:
2087 2086 user_perms = {perms['repositories'][self.repo_name]}
2088 2087 except KeyError:
2089 2088 return False
2090 2089 if self.required_perms.intersection(user_perms):
2091 2090 return True
2092 2091 return False
2093 2092
2094 2093
2095 2094 class HasRepoGroupPermissionAny(PermsFunction):
2096 2095 def __call__(self, group_name=None, check_location='', user=None):
2097 2096 self.repo_group_name = group_name
2098 2097 return super(HasRepoGroupPermissionAny, self).__call__(check_location, user)
2099 2098
2100 2099 def check_permissions(self, user):
2101 2100 perms = user.permissions
2102 2101 try:
2103 2102 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2104 2103 except KeyError:
2105 2104 return False
2106 2105 if self.required_perms.intersection(user_perms):
2107 2106 return True
2108 2107 return False
2109 2108
2110 2109
2111 2110 class HasRepoGroupPermissionAll(PermsFunction):
2112 2111 def __call__(self, group_name=None, check_location='', user=None):
2113 2112 self.repo_group_name = group_name
2114 2113 return super(HasRepoGroupPermissionAll, self).__call__(check_location, user)
2115 2114
2116 2115 def check_permissions(self, user):
2117 2116 perms = user.permissions
2118 2117 try:
2119 2118 user_perms = {perms['repositories_groups'][self.repo_group_name]}
2120 2119 except KeyError:
2121 2120 return False
2122 2121 if self.required_perms.issubset(user_perms):
2123 2122 return True
2124 2123 return False
2125 2124
2126 2125
2127 2126 class HasUserGroupPermissionAny(PermsFunction):
2128 2127 def __call__(self, user_group_name=None, check_location='', user=None):
2129 2128 self.user_group_name = user_group_name
2130 2129 return super(HasUserGroupPermissionAny, self).__call__(check_location, user)
2131 2130
2132 2131 def check_permissions(self, user):
2133 2132 perms = user.permissions
2134 2133 try:
2135 2134 user_perms = {perms['user_groups'][self.user_group_name]}
2136 2135 except KeyError:
2137 2136 return False
2138 2137 if self.required_perms.intersection(user_perms):
2139 2138 return True
2140 2139 return False
2141 2140
2142 2141
2143 2142 class HasUserGroupPermissionAll(PermsFunction):
2144 2143 def __call__(self, user_group_name=None, check_location='', user=None):
2145 2144 self.user_group_name = user_group_name
2146 2145 return super(HasUserGroupPermissionAll, self).__call__(check_location, user)
2147 2146
2148 2147 def check_permissions(self, user):
2149 2148 perms = user.permissions
2150 2149 try:
2151 2150 user_perms = {perms['user_groups'][self.user_group_name]}
2152 2151 except KeyError:
2153 2152 return False
2154 2153 if self.required_perms.issubset(user_perms):
2155 2154 return True
2156 2155 return False
2157 2156
2158 2157
2159 2158 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
2160 2159 class HasPermissionAnyMiddleware(object):
2161 2160 def __init__(self, *perms):
2162 2161 self.required_perms = set(perms)
2163 2162
2164 2163 def __call__(self, auth_user, repo_name):
2165 2164 # repo_name MUST be unicode, since we handle keys in permission
2166 2165 # dict by unicode
2167 2166 repo_name = safe_unicode(repo_name)
2168 2167 log.debug(
2169 2168 'Checking VCS protocol permissions %s for user:%s repo:`%s`',
2170 2169 self.required_perms, auth_user, repo_name)
2171 2170
2172 2171 if self.check_permissions(auth_user, repo_name):
2173 2172 log.debug('Permission to repo:`%s` GRANTED for user:%s @ %s',
2174 2173 repo_name, auth_user, 'PermissionMiddleware')
2175 2174 return True
2176 2175
2177 2176 else:
2178 2177 log.debug('Permission to repo:`%s` DENIED for user:%s @ %s',
2179 2178 repo_name, auth_user, 'PermissionMiddleware')
2180 2179 return False
2181 2180
2182 2181 def check_permissions(self, user, repo_name):
2183 2182 perms = user.permissions_with_scope({'repo_name': repo_name})
2184 2183
2185 2184 try:
2186 2185 user_perms = {perms['repositories'][repo_name]}
2187 2186 except Exception:
2188 2187 log.exception('Error while accessing user permissions')
2189 2188 return False
2190 2189
2191 2190 if self.required_perms.intersection(user_perms):
2192 2191 return True
2193 2192 return False
2194 2193
2195 2194
2196 2195 # SPECIAL VERSION TO HANDLE API AUTH
2197 2196 class _BaseApiPerm(object):
2198 2197 def __init__(self, *perms):
2199 2198 self.required_perms = set(perms)
2200 2199
2201 2200 def __call__(self, check_location=None, user=None, repo_name=None,
2202 2201 group_name=None, user_group_name=None):
2203 2202 cls_name = self.__class__.__name__
2204 2203 check_scope = 'global:%s' % (self.required_perms,)
2205 2204 if repo_name:
2206 2205 check_scope += ', repo_name:%s' % (repo_name,)
2207 2206
2208 2207 if group_name:
2209 2208 check_scope += ', repo_group_name:%s' % (group_name,)
2210 2209
2211 2210 if user_group_name:
2212 2211 check_scope += ', user_group_name:%s' % (user_group_name,)
2213 2212
2214 2213 log.debug('checking cls:%s %s %s @ %s',
2215 2214 cls_name, self.required_perms, check_scope, check_location)
2216 2215 if not user:
2217 2216 log.debug('Empty User passed into arguments')
2218 2217 return False
2219 2218
2220 2219 # process user
2221 2220 if not isinstance(user, AuthUser):
2222 2221 user = AuthUser(user.user_id)
2223 2222 if not check_location:
2224 2223 check_location = 'unspecified'
2225 2224 if self.check_permissions(user.permissions, repo_name, group_name,
2226 2225 user_group_name):
2227 2226 log.debug('Permission to repo:`%s` GRANTED for user:`%s` @ %s',
2228 2227 check_scope, user, check_location)
2229 2228 return True
2230 2229
2231 2230 else:
2232 2231 log.debug('Permission to repo:`%s` DENIED for user:`%s` @ %s',
2233 2232 check_scope, user, check_location)
2234 2233 return False
2235 2234
2236 2235 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2237 2236 user_group_name=None):
2238 2237 """
2239 2238 implement in child class should return True if permissions are ok,
2240 2239 False otherwise
2241 2240
2242 2241 :param perm_defs: dict with permission definitions
2243 2242 :param repo_name: repo name
2244 2243 """
2245 2244 raise NotImplementedError()
2246 2245
2247 2246
2248 2247 class HasPermissionAllApi(_BaseApiPerm):
2249 2248 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2250 2249 user_group_name=None):
2251 2250 if self.required_perms.issubset(perm_defs.get('global')):
2252 2251 return True
2253 2252 return False
2254 2253
2255 2254
2256 2255 class HasPermissionAnyApi(_BaseApiPerm):
2257 2256 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2258 2257 user_group_name=None):
2259 2258 if self.required_perms.intersection(perm_defs.get('global')):
2260 2259 return True
2261 2260 return False
2262 2261
2263 2262
2264 2263 class HasRepoPermissionAllApi(_BaseApiPerm):
2265 2264 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2266 2265 user_group_name=None):
2267 2266 try:
2268 2267 _user_perms = {perm_defs['repositories'][repo_name]}
2269 2268 except KeyError:
2270 2269 log.warning(traceback.format_exc())
2271 2270 return False
2272 2271 if self.required_perms.issubset(_user_perms):
2273 2272 return True
2274 2273 return False
2275 2274
2276 2275
2277 2276 class HasRepoPermissionAnyApi(_BaseApiPerm):
2278 2277 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2279 2278 user_group_name=None):
2280 2279 try:
2281 2280 _user_perms = {perm_defs['repositories'][repo_name]}
2282 2281 except KeyError:
2283 2282 log.warning(traceback.format_exc())
2284 2283 return False
2285 2284 if self.required_perms.intersection(_user_perms):
2286 2285 return True
2287 2286 return False
2288 2287
2289 2288
2290 2289 class HasRepoGroupPermissionAnyApi(_BaseApiPerm):
2291 2290 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2292 2291 user_group_name=None):
2293 2292 try:
2294 2293 _user_perms = {perm_defs['repositories_groups'][group_name]}
2295 2294 except KeyError:
2296 2295 log.warning(traceback.format_exc())
2297 2296 return False
2298 2297 if self.required_perms.intersection(_user_perms):
2299 2298 return True
2300 2299 return False
2301 2300
2302 2301
2303 2302 class HasRepoGroupPermissionAllApi(_BaseApiPerm):
2304 2303 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2305 2304 user_group_name=None):
2306 2305 try:
2307 2306 _user_perms = {perm_defs['repositories_groups'][group_name]}
2308 2307 except KeyError:
2309 2308 log.warning(traceback.format_exc())
2310 2309 return False
2311 2310 if self.required_perms.issubset(_user_perms):
2312 2311 return True
2313 2312 return False
2314 2313
2315 2314
2316 2315 class HasUserGroupPermissionAnyApi(_BaseApiPerm):
2317 2316 def check_permissions(self, perm_defs, repo_name=None, group_name=None,
2318 2317 user_group_name=None):
2319 2318 try:
2320 2319 _user_perms = {perm_defs['user_groups'][user_group_name]}
2321 2320 except KeyError:
2322 2321 log.warning(traceback.format_exc())
2323 2322 return False
2324 2323 if self.required_perms.intersection(_user_perms):
2325 2324 return True
2326 2325 return False
2327 2326
2328 2327
2329 2328 def check_ip_access(source_ip, allowed_ips=None):
2330 2329 """
2331 2330 Checks if source_ip is a subnet of any of allowed_ips.
2332 2331
2333 2332 :param source_ip:
2334 2333 :param allowed_ips: list of allowed ips together with mask
2335 2334 """
2336 2335 log.debug('checking if ip:%s is subnet of %s', source_ip, allowed_ips)
2337 2336 source_ip_address = ipaddress.ip_address(safe_unicode(source_ip))
2338 2337 if isinstance(allowed_ips, (tuple, list, set)):
2339 2338 for ip in allowed_ips:
2340 2339 ip = safe_unicode(ip)
2341 2340 try:
2342 2341 network_address = ipaddress.ip_network(ip, strict=False)
2343 2342 if source_ip_address in network_address:
2344 2343 log.debug('IP %s is network %s', source_ip_address, network_address)
2345 2344 return True
2346 2345 # for any case we cannot determine the IP, don't crash just
2347 2346 # skip it and log as error, we want to say forbidden still when
2348 2347 # sending bad IP
2349 2348 except Exception:
2350 2349 log.error(traceback.format_exc())
2351 2350 continue
2352 2351 return False
2353 2352
2354 2353
2355 2354 def get_cython_compat_decorator(wrapper, func):
2356 2355 """
2357 2356 Creates a cython compatible decorator. The previously used
2358 2357 decorator.decorator() function seems to be incompatible with cython.
2359 2358
2360 2359 :param wrapper: __wrapper method of the decorator class
2361 2360 :param func: decorated function
2362 2361 """
2363 2362 @wraps(func)
2364 2363 def local_wrapper(*args, **kwds):
2365 2364 return wrapper(func, *args, **kwds)
2366 2365 local_wrapper.__wrapped__ = func
2367 2366 return local_wrapper
2368 2367
2369 2368
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now