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