##// END OF EJS Templates
app: improve logging, and remove DB calls on app startup.
milka -
r4548:2f66e04c default
parent child Browse files
Show More
@@ -1,1414 +1,1418 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2020 RhodeCode GmbH
3 # Copyright (C) 2016-2020 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 datetime
22 import datetime
23 import formencode
23 import formencode
24 import formencode.htmlfill
24 import formencode.htmlfill
25
25
26 from pyramid.httpexceptions import HTTPFound
26 from pyramid.httpexceptions import HTTPFound
27 from pyramid.view import view_config
27 from pyramid.view import view_config
28 from pyramid.renderers import render
28 from pyramid.renderers import render
29 from pyramid.response import Response
29 from pyramid.response import Response
30
30
31 from rhodecode import events
31 from rhodecode import events
32 from rhodecode.apps._base import BaseAppView, DataGridAppView, UserAppView
32 from rhodecode.apps._base import BaseAppView, DataGridAppView, UserAppView
33 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
33 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
34 from rhodecode.authentication.base import get_authn_registry, RhodeCodeExternalAuthPlugin
34 from rhodecode.authentication.base import get_authn_registry, RhodeCodeExternalAuthPlugin
35 from rhodecode.authentication.plugins import auth_rhodecode
35 from rhodecode.authentication.plugins import auth_rhodecode
36 from rhodecode.events import trigger
36 from rhodecode.events import trigger
37 from rhodecode.model.db import true, UserNotice
37 from rhodecode.model.db import true, UserNotice
38
38
39 from rhodecode.lib import audit_logger, rc_cache
39 from rhodecode.lib import audit_logger, rc_cache, auth
40 from rhodecode.lib.exceptions import (
40 from rhodecode.lib.exceptions import (
41 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
41 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
42 UserOwnsUserGroupsException, UserOwnsPullRequestsException,
42 UserOwnsUserGroupsException, UserOwnsPullRequestsException,
43 UserOwnsArtifactsException, DefaultUserException)
43 UserOwnsArtifactsException, DefaultUserException)
44 from rhodecode.lib.ext_json import json
44 from rhodecode.lib.ext_json import json
45 from rhodecode.lib.auth import (
45 from rhodecode.lib.auth import (
46 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
46 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
47 from rhodecode.lib import helpers as h
47 from rhodecode.lib import helpers as h
48 from rhodecode.lib.helpers import SqlPage
48 from rhodecode.lib.helpers import SqlPage
49 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
49 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
50 from rhodecode.model.auth_token import AuthTokenModel
50 from rhodecode.model.auth_token import AuthTokenModel
51 from rhodecode.model.forms import (
51 from rhodecode.model.forms import (
52 UserForm, UserIndividualPermissionsForm, UserPermissionsForm,
52 UserForm, UserIndividualPermissionsForm, UserPermissionsForm,
53 UserExtraEmailForm, UserExtraIpForm)
53 UserExtraEmailForm, UserExtraIpForm)
54 from rhodecode.model.permission import PermissionModel
54 from rhodecode.model.permission import PermissionModel
55 from rhodecode.model.repo_group import RepoGroupModel
55 from rhodecode.model.repo_group import RepoGroupModel
56 from rhodecode.model.ssh_key import SshKeyModel
56 from rhodecode.model.ssh_key import SshKeyModel
57 from rhodecode.model.user import UserModel
57 from rhodecode.model.user import UserModel
58 from rhodecode.model.user_group import UserGroupModel
58 from rhodecode.model.user_group import UserGroupModel
59 from rhodecode.model.db import (
59 from rhodecode.model.db import (
60 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
60 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
61 UserApiKeys, UserSshKeys, RepoGroup)
61 UserApiKeys, UserSshKeys, RepoGroup)
62 from rhodecode.model.meta import Session
62 from rhodecode.model.meta import Session
63
63
64 log = logging.getLogger(__name__)
64 log = logging.getLogger(__name__)
65
65
66
66
67 class AdminUsersView(BaseAppView, DataGridAppView):
67 class AdminUsersView(BaseAppView, DataGridAppView):
68
68
69 def load_default_context(self):
69 def load_default_context(self):
70 c = self._get_local_tmpl_context()
70 c = self._get_local_tmpl_context()
71 return c
71 return c
72
72
73 @LoginRequired()
73 @LoginRequired()
74 @HasPermissionAllDecorator('hg.admin')
74 @HasPermissionAllDecorator('hg.admin')
75 @view_config(
75 @view_config(
76 route_name='users', request_method='GET',
76 route_name='users', request_method='GET',
77 renderer='rhodecode:templates/admin/users/users.mako')
77 renderer='rhodecode:templates/admin/users/users.mako')
78 def users_list(self):
78 def users_list(self):
79 c = self.load_default_context()
79 c = self.load_default_context()
80 return self._get_template_context(c)
80 return self._get_template_context(c)
81
81
82 @LoginRequired()
82 @LoginRequired()
83 @HasPermissionAllDecorator('hg.admin')
83 @HasPermissionAllDecorator('hg.admin')
84 @view_config(
84 @view_config(
85 # renderer defined below
85 # renderer defined below
86 route_name='users_data', request_method='GET',
86 route_name='users_data', request_method='GET',
87 renderer='json_ext', xhr=True)
87 renderer='json_ext', xhr=True)
88 def users_list_data(self):
88 def users_list_data(self):
89 self.load_default_context()
89 self.load_default_context()
90 column_map = {
90 column_map = {
91 'first_name': 'name',
91 'first_name': 'name',
92 'last_name': 'lastname',
92 'last_name': 'lastname',
93 }
93 }
94 draw, start, limit = self._extract_chunk(self.request)
94 draw, start, limit = self._extract_chunk(self.request)
95 search_q, order_by, order_dir = self._extract_ordering(
95 search_q, order_by, order_dir = self._extract_ordering(
96 self.request, column_map=column_map)
96 self.request, column_map=column_map)
97 _render = self.request.get_partial_renderer(
97 _render = self.request.get_partial_renderer(
98 'rhodecode:templates/data_table/_dt_elements.mako')
98 'rhodecode:templates/data_table/_dt_elements.mako')
99
99
100 def user_actions(user_id, username):
100 def user_actions(user_id, username):
101 return _render("user_actions", user_id, username)
101 return _render("user_actions", user_id, username)
102
102
103 users_data_total_count = User.query()\
103 users_data_total_count = User.query()\
104 .filter(User.username != User.DEFAULT_USER) \
104 .filter(User.username != User.DEFAULT_USER) \
105 .count()
105 .count()
106
106
107 users_data_total_inactive_count = User.query()\
107 users_data_total_inactive_count = User.query()\
108 .filter(User.username != User.DEFAULT_USER) \
108 .filter(User.username != User.DEFAULT_USER) \
109 .filter(User.active != true())\
109 .filter(User.active != true())\
110 .count()
110 .count()
111
111
112 # json generate
112 # json generate
113 base_q = User.query().filter(User.username != User.DEFAULT_USER)
113 base_q = User.query().filter(User.username != User.DEFAULT_USER)
114 base_inactive_q = base_q.filter(User.active != true())
114 base_inactive_q = base_q.filter(User.active != true())
115
115
116 if search_q:
116 if search_q:
117 like_expression = u'%{}%'.format(safe_unicode(search_q))
117 like_expression = u'%{}%'.format(safe_unicode(search_q))
118 base_q = base_q.filter(or_(
118 base_q = base_q.filter(or_(
119 User.username.ilike(like_expression),
119 User.username.ilike(like_expression),
120 User._email.ilike(like_expression),
120 User._email.ilike(like_expression),
121 User.name.ilike(like_expression),
121 User.name.ilike(like_expression),
122 User.lastname.ilike(like_expression),
122 User.lastname.ilike(like_expression),
123 ))
123 ))
124 base_inactive_q = base_q.filter(User.active != true())
124 base_inactive_q = base_q.filter(User.active != true())
125
125
126 users_data_total_filtered_count = base_q.count()
126 users_data_total_filtered_count = base_q.count()
127 users_data_total_filtered_inactive_count = base_inactive_q.count()
127 users_data_total_filtered_inactive_count = base_inactive_q.count()
128
128
129 sort_col = getattr(User, order_by, None)
129 sort_col = getattr(User, order_by, None)
130 if sort_col:
130 if sort_col:
131 if order_dir == 'asc':
131 if order_dir == 'asc':
132 # handle null values properly to order by NULL last
132 # handle null values properly to order by NULL last
133 if order_by in ['last_activity']:
133 if order_by in ['last_activity']:
134 sort_col = coalesce(sort_col, datetime.date.max)
134 sort_col = coalesce(sort_col, datetime.date.max)
135 sort_col = sort_col.asc()
135 sort_col = sort_col.asc()
136 else:
136 else:
137 # handle null values properly to order by NULL last
137 # handle null values properly to order by NULL last
138 if order_by in ['last_activity']:
138 if order_by in ['last_activity']:
139 sort_col = coalesce(sort_col, datetime.date.min)
139 sort_col = coalesce(sort_col, datetime.date.min)
140 sort_col = sort_col.desc()
140 sort_col = sort_col.desc()
141
141
142 base_q = base_q.order_by(sort_col)
142 base_q = base_q.order_by(sort_col)
143 base_q = base_q.offset(start).limit(limit)
143 base_q = base_q.offset(start).limit(limit)
144
144
145 users_list = base_q.all()
145 users_list = base_q.all()
146
146
147 users_data = []
147 users_data = []
148 for user in users_list:
148 for user in users_list:
149 users_data.append({
149 users_data.append({
150 "username": h.gravatar_with_user(self.request, user.username),
150 "username": h.gravatar_with_user(self.request, user.username),
151 "email": user.email,
151 "email": user.email,
152 "first_name": user.first_name,
152 "first_name": user.first_name,
153 "last_name": user.last_name,
153 "last_name": user.last_name,
154 "last_login": h.format_date(user.last_login),
154 "last_login": h.format_date(user.last_login),
155 "last_activity": h.format_date(user.last_activity),
155 "last_activity": h.format_date(user.last_activity),
156 "active": h.bool2icon(user.active),
156 "active": h.bool2icon(user.active),
157 "active_raw": user.active,
157 "active_raw": user.active,
158 "admin": h.bool2icon(user.admin),
158 "admin": h.bool2icon(user.admin),
159 "extern_type": user.extern_type,
159 "extern_type": user.extern_type,
160 "extern_name": user.extern_name,
160 "extern_name": user.extern_name,
161 "action": user_actions(user.user_id, user.username),
161 "action": user_actions(user.user_id, user.username),
162 })
162 })
163 data = ({
163 data = ({
164 'draw': draw,
164 'draw': draw,
165 'data': users_data,
165 'data': users_data,
166 'recordsTotal': users_data_total_count,
166 'recordsTotal': users_data_total_count,
167 'recordsFiltered': users_data_total_filtered_count,
167 'recordsFiltered': users_data_total_filtered_count,
168 'recordsTotalInactive': users_data_total_inactive_count,
168 'recordsTotalInactive': users_data_total_inactive_count,
169 'recordsFilteredInactive': users_data_total_filtered_inactive_count
169 'recordsFilteredInactive': users_data_total_filtered_inactive_count
170 })
170 })
171
171
172 return data
172 return data
173
173
174 def _set_personal_repo_group_template_vars(self, c_obj):
174 def _set_personal_repo_group_template_vars(self, c_obj):
175 DummyUser = AttributeDict({
175 DummyUser = AttributeDict({
176 'username': '${username}',
176 'username': '${username}',
177 'user_id': '${user_id}',
177 'user_id': '${user_id}',
178 })
178 })
179 c_obj.default_create_repo_group = RepoGroupModel() \
179 c_obj.default_create_repo_group = RepoGroupModel() \
180 .get_default_create_personal_repo_group()
180 .get_default_create_personal_repo_group()
181 c_obj.personal_repo_group_name = RepoGroupModel() \
181 c_obj.personal_repo_group_name = RepoGroupModel() \
182 .get_personal_group_name(DummyUser)
182 .get_personal_group_name(DummyUser)
183
183
184 @LoginRequired()
184 @LoginRequired()
185 @HasPermissionAllDecorator('hg.admin')
185 @HasPermissionAllDecorator('hg.admin')
186 @view_config(
186 @view_config(
187 route_name='users_new', request_method='GET',
187 route_name='users_new', request_method='GET',
188 renderer='rhodecode:templates/admin/users/user_add.mako')
188 renderer='rhodecode:templates/admin/users/user_add.mako')
189 def users_new(self):
189 def users_new(self):
190 _ = self.request.translate
190 _ = self.request.translate
191 c = self.load_default_context()
191 c = self.load_default_context()
192 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
192 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
193 self._set_personal_repo_group_template_vars(c)
193 self._set_personal_repo_group_template_vars(c)
194 return self._get_template_context(c)
194 return self._get_template_context(c)
195
195
196 @LoginRequired()
196 @LoginRequired()
197 @HasPermissionAllDecorator('hg.admin')
197 @HasPermissionAllDecorator('hg.admin')
198 @CSRFRequired()
198 @CSRFRequired()
199 @view_config(
199 @view_config(
200 route_name='users_create', request_method='POST',
200 route_name='users_create', request_method='POST',
201 renderer='rhodecode:templates/admin/users/user_add.mako')
201 renderer='rhodecode:templates/admin/users/user_add.mako')
202 def users_create(self):
202 def users_create(self):
203 _ = self.request.translate
203 _ = self.request.translate
204 c = self.load_default_context()
204 c = self.load_default_context()
205 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
205 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
206 user_model = UserModel()
206 user_model = UserModel()
207 user_form = UserForm(self.request.translate)()
207 user_form = UserForm(self.request.translate)()
208 try:
208 try:
209 form_result = user_form.to_python(dict(self.request.POST))
209 form_result = user_form.to_python(dict(self.request.POST))
210 user = user_model.create(form_result)
210 user = user_model.create(form_result)
211 Session().flush()
211 Session().flush()
212 creation_data = user.get_api_data()
212 creation_data = user.get_api_data()
213 username = form_result['username']
213 username = form_result['username']
214
214
215 audit_logger.store_web(
215 audit_logger.store_web(
216 'user.create', action_data={'data': creation_data},
216 'user.create', action_data={'data': creation_data},
217 user=c.rhodecode_user)
217 user=c.rhodecode_user)
218
218
219 user_link = h.link_to(
219 user_link = h.link_to(
220 h.escape(username),
220 h.escape(username),
221 h.route_path('user_edit', user_id=user.user_id))
221 h.route_path('user_edit', user_id=user.user_id))
222 h.flash(h.literal(_('Created user %(user_link)s')
222 h.flash(h.literal(_('Created user %(user_link)s')
223 % {'user_link': user_link}), category='success')
223 % {'user_link': user_link}), category='success')
224 Session().commit()
224 Session().commit()
225 except formencode.Invalid as errors:
225 except formencode.Invalid as errors:
226 self._set_personal_repo_group_template_vars(c)
226 self._set_personal_repo_group_template_vars(c)
227 data = render(
227 data = render(
228 'rhodecode:templates/admin/users/user_add.mako',
228 'rhodecode:templates/admin/users/user_add.mako',
229 self._get_template_context(c), self.request)
229 self._get_template_context(c), self.request)
230 html = formencode.htmlfill.render(
230 html = formencode.htmlfill.render(
231 data,
231 data,
232 defaults=errors.value,
232 defaults=errors.value,
233 errors=errors.error_dict or {},
233 errors=errors.error_dict or {},
234 prefix_error=False,
234 prefix_error=False,
235 encoding="UTF-8",
235 encoding="UTF-8",
236 force_defaults=False
236 force_defaults=False
237 )
237 )
238 return Response(html)
238 return Response(html)
239 except UserCreationError as e:
239 except UserCreationError as e:
240 h.flash(e, 'error')
240 h.flash(e, 'error')
241 except Exception:
241 except Exception:
242 log.exception("Exception creation of user")
242 log.exception("Exception creation of user")
243 h.flash(_('Error occurred during creation of user %s')
243 h.flash(_('Error occurred during creation of user %s')
244 % self.request.POST.get('username'), category='error')
244 % self.request.POST.get('username'), category='error')
245 raise HTTPFound(h.route_path('users'))
245 raise HTTPFound(h.route_path('users'))
246
246
247
247
248 class UsersView(UserAppView):
248 class UsersView(UserAppView):
249 ALLOW_SCOPED_TOKENS = False
249 ALLOW_SCOPED_TOKENS = False
250 """
250 """
251 This view has alternative version inside EE, if modified please take a look
251 This view has alternative version inside EE, if modified please take a look
252 in there as well.
252 in there as well.
253 """
253 """
254
254
255 def get_auth_plugins(self):
255 def get_auth_plugins(self):
256 valid_plugins = []
256 valid_plugins = []
257 authn_registry = get_authn_registry(self.request.registry)
257 authn_registry = get_authn_registry(self.request.registry)
258 for plugin in authn_registry.get_plugins_for_authentication():
258 for plugin in authn_registry.get_plugins_for_authentication():
259 if isinstance(plugin, RhodeCodeExternalAuthPlugin):
259 if isinstance(plugin, RhodeCodeExternalAuthPlugin):
260 valid_plugins.append(plugin)
260 valid_plugins.append(plugin)
261 elif plugin.name == 'rhodecode':
261 elif plugin.name == 'rhodecode':
262 valid_plugins.append(plugin)
262 valid_plugins.append(plugin)
263
263
264 # extend our choices if user has set a bound plugin which isn't enabled at the
264 # extend our choices if user has set a bound plugin which isn't enabled at the
265 # moment
265 # moment
266 extern_type = self.db_user.extern_type
266 extern_type = self.db_user.extern_type
267 if extern_type not in [x.uid for x in valid_plugins]:
267 if extern_type not in [x.uid for x in valid_plugins]:
268 try:
268 try:
269 plugin = authn_registry.get_plugin_by_uid(extern_type)
269 plugin = authn_registry.get_plugin_by_uid(extern_type)
270 if plugin:
270 if plugin:
271 valid_plugins.append(plugin)
271 valid_plugins.append(plugin)
272
272
273 except Exception:
273 except Exception:
274 log.exception(
274 log.exception(
275 'Could not extend user plugins with `{}`'.format(extern_type))
275 'Could not extend user plugins with `{}`'.format(extern_type))
276 return valid_plugins
276 return valid_plugins
277
277
278 def load_default_context(self):
278 def load_default_context(self):
279 req = self.request
279 req = self.request
280
280
281 c = self._get_local_tmpl_context()
281 c = self._get_local_tmpl_context()
282 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
282 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
283 c.allowed_languages = [
283 c.allowed_languages = [
284 ('en', 'English (en)'),
284 ('en', 'English (en)'),
285 ('de', 'German (de)'),
285 ('de', 'German (de)'),
286 ('fr', 'French (fr)'),
286 ('fr', 'French (fr)'),
287 ('it', 'Italian (it)'),
287 ('it', 'Italian (it)'),
288 ('ja', 'Japanese (ja)'),
288 ('ja', 'Japanese (ja)'),
289 ('pl', 'Polish (pl)'),
289 ('pl', 'Polish (pl)'),
290 ('pt', 'Portuguese (pt)'),
290 ('pt', 'Portuguese (pt)'),
291 ('ru', 'Russian (ru)'),
291 ('ru', 'Russian (ru)'),
292 ('zh', 'Chinese (zh)'),
292 ('zh', 'Chinese (zh)'),
293 ]
293 ]
294
294
295 c.allowed_extern_types = [
295 c.allowed_extern_types = [
296 (x.uid, x.get_display_name()) for x in self.get_auth_plugins()
296 (x.uid, x.get_display_name()) for x in self.get_auth_plugins()
297 ]
297 ]
298 perms = req.registry.settings.get('available_permissions')
299 if not perms:
300 # inject info about available permissions
301 auth.set_available_permissions(req.registry.settings)
298
302
299 c.available_permissions = req.registry.settings['available_permissions']
303 c.available_permissions = req.registry.settings['available_permissions']
300 PermissionModel().set_global_permission_choices(
304 PermissionModel().set_global_permission_choices(
301 c, gettext_translator=req.translate)
305 c, gettext_translator=req.translate)
302
306
303 return c
307 return c
304
308
305 @LoginRequired()
309 @LoginRequired()
306 @HasPermissionAllDecorator('hg.admin')
310 @HasPermissionAllDecorator('hg.admin')
307 @CSRFRequired()
311 @CSRFRequired()
308 @view_config(
312 @view_config(
309 route_name='user_update', request_method='POST',
313 route_name='user_update', request_method='POST',
310 renderer='rhodecode:templates/admin/users/user_edit.mako')
314 renderer='rhodecode:templates/admin/users/user_edit.mako')
311 def user_update(self):
315 def user_update(self):
312 _ = self.request.translate
316 _ = self.request.translate
313 c = self.load_default_context()
317 c = self.load_default_context()
314
318
315 user_id = self.db_user_id
319 user_id = self.db_user_id
316 c.user = self.db_user
320 c.user = self.db_user
317
321
318 c.active = 'profile'
322 c.active = 'profile'
319 c.extern_type = c.user.extern_type
323 c.extern_type = c.user.extern_type
320 c.extern_name = c.user.extern_name
324 c.extern_name = c.user.extern_name
321 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
325 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
322 available_languages = [x[0] for x in c.allowed_languages]
326 available_languages = [x[0] for x in c.allowed_languages]
323 _form = UserForm(self.request.translate, edit=True,
327 _form = UserForm(self.request.translate, edit=True,
324 available_languages=available_languages,
328 available_languages=available_languages,
325 old_data={'user_id': user_id,
329 old_data={'user_id': user_id,
326 'email': c.user.email})()
330 'email': c.user.email})()
327 form_result = {}
331 form_result = {}
328 old_values = c.user.get_api_data()
332 old_values = c.user.get_api_data()
329 try:
333 try:
330 form_result = _form.to_python(dict(self.request.POST))
334 form_result = _form.to_python(dict(self.request.POST))
331 skip_attrs = ['extern_name']
335 skip_attrs = ['extern_name']
332 # TODO: plugin should define if username can be updated
336 # TODO: plugin should define if username can be updated
333 if c.extern_type != "rhodecode":
337 if c.extern_type != "rhodecode":
334 # forbid updating username for external accounts
338 # forbid updating username for external accounts
335 skip_attrs.append('username')
339 skip_attrs.append('username')
336
340
337 UserModel().update_user(
341 UserModel().update_user(
338 user_id, skip_attrs=skip_attrs, **form_result)
342 user_id, skip_attrs=skip_attrs, **form_result)
339
343
340 audit_logger.store_web(
344 audit_logger.store_web(
341 'user.edit', action_data={'old_data': old_values},
345 'user.edit', action_data={'old_data': old_values},
342 user=c.rhodecode_user)
346 user=c.rhodecode_user)
343
347
344 Session().commit()
348 Session().commit()
345 h.flash(_('User updated successfully'), category='success')
349 h.flash(_('User updated successfully'), category='success')
346 except formencode.Invalid as errors:
350 except formencode.Invalid as errors:
347 data = render(
351 data = render(
348 'rhodecode:templates/admin/users/user_edit.mako',
352 'rhodecode:templates/admin/users/user_edit.mako',
349 self._get_template_context(c), self.request)
353 self._get_template_context(c), self.request)
350 html = formencode.htmlfill.render(
354 html = formencode.htmlfill.render(
351 data,
355 data,
352 defaults=errors.value,
356 defaults=errors.value,
353 errors=errors.error_dict or {},
357 errors=errors.error_dict or {},
354 prefix_error=False,
358 prefix_error=False,
355 encoding="UTF-8",
359 encoding="UTF-8",
356 force_defaults=False
360 force_defaults=False
357 )
361 )
358 return Response(html)
362 return Response(html)
359 except UserCreationError as e:
363 except UserCreationError as e:
360 h.flash(e, 'error')
364 h.flash(e, 'error')
361 except Exception:
365 except Exception:
362 log.exception("Exception updating user")
366 log.exception("Exception updating user")
363 h.flash(_('Error occurred during update of user %s')
367 h.flash(_('Error occurred during update of user %s')
364 % form_result.get('username'), category='error')
368 % form_result.get('username'), category='error')
365 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
369 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
366
370
367 @LoginRequired()
371 @LoginRequired()
368 @HasPermissionAllDecorator('hg.admin')
372 @HasPermissionAllDecorator('hg.admin')
369 @CSRFRequired()
373 @CSRFRequired()
370 @view_config(
374 @view_config(
371 route_name='user_delete', request_method='POST',
375 route_name='user_delete', request_method='POST',
372 renderer='rhodecode:templates/admin/users/user_edit.mako')
376 renderer='rhodecode:templates/admin/users/user_edit.mako')
373 def user_delete(self):
377 def user_delete(self):
374 _ = self.request.translate
378 _ = self.request.translate
375 c = self.load_default_context()
379 c = self.load_default_context()
376 c.user = self.db_user
380 c.user = self.db_user
377
381
378 _repos = c.user.repositories
382 _repos = c.user.repositories
379 _repo_groups = c.user.repository_groups
383 _repo_groups = c.user.repository_groups
380 _user_groups = c.user.user_groups
384 _user_groups = c.user.user_groups
381 _pull_requests = c.user.user_pull_requests
385 _pull_requests = c.user.user_pull_requests
382 _artifacts = c.user.artifacts
386 _artifacts = c.user.artifacts
383
387
384 handle_repos = None
388 handle_repos = None
385 handle_repo_groups = None
389 handle_repo_groups = None
386 handle_user_groups = None
390 handle_user_groups = None
387 handle_pull_requests = None
391 handle_pull_requests = None
388 handle_artifacts = None
392 handle_artifacts = None
389
393
390 # calls for flash of handle based on handle case detach or delete
394 # calls for flash of handle based on handle case detach or delete
391 def set_handle_flash_repos():
395 def set_handle_flash_repos():
392 handle = handle_repos
396 handle = handle_repos
393 if handle == 'detach':
397 if handle == 'detach':
394 h.flash(_('Detached %s repositories') % len(_repos),
398 h.flash(_('Detached %s repositories') % len(_repos),
395 category='success')
399 category='success')
396 elif handle == 'delete':
400 elif handle == 'delete':
397 h.flash(_('Deleted %s repositories') % len(_repos),
401 h.flash(_('Deleted %s repositories') % len(_repos),
398 category='success')
402 category='success')
399
403
400 def set_handle_flash_repo_groups():
404 def set_handle_flash_repo_groups():
401 handle = handle_repo_groups
405 handle = handle_repo_groups
402 if handle == 'detach':
406 if handle == 'detach':
403 h.flash(_('Detached %s repository groups') % len(_repo_groups),
407 h.flash(_('Detached %s repository groups') % len(_repo_groups),
404 category='success')
408 category='success')
405 elif handle == 'delete':
409 elif handle == 'delete':
406 h.flash(_('Deleted %s repository groups') % len(_repo_groups),
410 h.flash(_('Deleted %s repository groups') % len(_repo_groups),
407 category='success')
411 category='success')
408
412
409 def set_handle_flash_user_groups():
413 def set_handle_flash_user_groups():
410 handle = handle_user_groups
414 handle = handle_user_groups
411 if handle == 'detach':
415 if handle == 'detach':
412 h.flash(_('Detached %s user groups') % len(_user_groups),
416 h.flash(_('Detached %s user groups') % len(_user_groups),
413 category='success')
417 category='success')
414 elif handle == 'delete':
418 elif handle == 'delete':
415 h.flash(_('Deleted %s user groups') % len(_user_groups),
419 h.flash(_('Deleted %s user groups') % len(_user_groups),
416 category='success')
420 category='success')
417
421
418 def set_handle_flash_pull_requests():
422 def set_handle_flash_pull_requests():
419 handle = handle_pull_requests
423 handle = handle_pull_requests
420 if handle == 'detach':
424 if handle == 'detach':
421 h.flash(_('Detached %s pull requests') % len(_pull_requests),
425 h.flash(_('Detached %s pull requests') % len(_pull_requests),
422 category='success')
426 category='success')
423 elif handle == 'delete':
427 elif handle == 'delete':
424 h.flash(_('Deleted %s pull requests') % len(_pull_requests),
428 h.flash(_('Deleted %s pull requests') % len(_pull_requests),
425 category='success')
429 category='success')
426
430
427 def set_handle_flash_artifacts():
431 def set_handle_flash_artifacts():
428 handle = handle_artifacts
432 handle = handle_artifacts
429 if handle == 'detach':
433 if handle == 'detach':
430 h.flash(_('Detached %s artifacts') % len(_artifacts),
434 h.flash(_('Detached %s artifacts') % len(_artifacts),
431 category='success')
435 category='success')
432 elif handle == 'delete':
436 elif handle == 'delete':
433 h.flash(_('Deleted %s artifacts') % len(_artifacts),
437 h.flash(_('Deleted %s artifacts') % len(_artifacts),
434 category='success')
438 category='success')
435
439
436 handle_user = User.get_first_super_admin()
440 handle_user = User.get_first_super_admin()
437 handle_user_id = safe_int(self.request.POST.get('detach_user_id'))
441 handle_user_id = safe_int(self.request.POST.get('detach_user_id'))
438 if handle_user_id:
442 if handle_user_id:
439 # NOTE(marcink): we get new owner for objects...
443 # NOTE(marcink): we get new owner for objects...
440 handle_user = User.get_or_404(handle_user_id)
444 handle_user = User.get_or_404(handle_user_id)
441
445
442 if _repos and self.request.POST.get('user_repos'):
446 if _repos and self.request.POST.get('user_repos'):
443 handle_repos = self.request.POST['user_repos']
447 handle_repos = self.request.POST['user_repos']
444
448
445 if _repo_groups and self.request.POST.get('user_repo_groups'):
449 if _repo_groups and self.request.POST.get('user_repo_groups'):
446 handle_repo_groups = self.request.POST['user_repo_groups']
450 handle_repo_groups = self.request.POST['user_repo_groups']
447
451
448 if _user_groups and self.request.POST.get('user_user_groups'):
452 if _user_groups and self.request.POST.get('user_user_groups'):
449 handle_user_groups = self.request.POST['user_user_groups']
453 handle_user_groups = self.request.POST['user_user_groups']
450
454
451 if _pull_requests and self.request.POST.get('user_pull_requests'):
455 if _pull_requests and self.request.POST.get('user_pull_requests'):
452 handle_pull_requests = self.request.POST['user_pull_requests']
456 handle_pull_requests = self.request.POST['user_pull_requests']
453
457
454 if _artifacts and self.request.POST.get('user_artifacts'):
458 if _artifacts and self.request.POST.get('user_artifacts'):
455 handle_artifacts = self.request.POST['user_artifacts']
459 handle_artifacts = self.request.POST['user_artifacts']
456
460
457 old_values = c.user.get_api_data()
461 old_values = c.user.get_api_data()
458
462
459 try:
463 try:
460
464
461 UserModel().delete(
465 UserModel().delete(
462 c.user,
466 c.user,
463 handle_repos=handle_repos,
467 handle_repos=handle_repos,
464 handle_repo_groups=handle_repo_groups,
468 handle_repo_groups=handle_repo_groups,
465 handle_user_groups=handle_user_groups,
469 handle_user_groups=handle_user_groups,
466 handle_pull_requests=handle_pull_requests,
470 handle_pull_requests=handle_pull_requests,
467 handle_artifacts=handle_artifacts,
471 handle_artifacts=handle_artifacts,
468 handle_new_owner=handle_user
472 handle_new_owner=handle_user
469 )
473 )
470
474
471 audit_logger.store_web(
475 audit_logger.store_web(
472 'user.delete', action_data={'old_data': old_values},
476 'user.delete', action_data={'old_data': old_values},
473 user=c.rhodecode_user)
477 user=c.rhodecode_user)
474
478
475 Session().commit()
479 Session().commit()
476 set_handle_flash_repos()
480 set_handle_flash_repos()
477 set_handle_flash_repo_groups()
481 set_handle_flash_repo_groups()
478 set_handle_flash_user_groups()
482 set_handle_flash_user_groups()
479 set_handle_flash_pull_requests()
483 set_handle_flash_pull_requests()
480 set_handle_flash_artifacts()
484 set_handle_flash_artifacts()
481 username = h.escape(old_values['username'])
485 username = h.escape(old_values['username'])
482 h.flash(_('Successfully deleted user `{}`').format(username), category='success')
486 h.flash(_('Successfully deleted user `{}`').format(username), category='success')
483 except (UserOwnsReposException, UserOwnsRepoGroupsException,
487 except (UserOwnsReposException, UserOwnsRepoGroupsException,
484 UserOwnsUserGroupsException, UserOwnsPullRequestsException,
488 UserOwnsUserGroupsException, UserOwnsPullRequestsException,
485 UserOwnsArtifactsException, DefaultUserException) as e:
489 UserOwnsArtifactsException, DefaultUserException) as e:
486 h.flash(e, category='warning')
490 h.flash(e, category='warning')
487 except Exception:
491 except Exception:
488 log.exception("Exception during deletion of user")
492 log.exception("Exception during deletion of user")
489 h.flash(_('An error occurred during deletion of user'),
493 h.flash(_('An error occurred during deletion of user'),
490 category='error')
494 category='error')
491 raise HTTPFound(h.route_path('users'))
495 raise HTTPFound(h.route_path('users'))
492
496
493 @LoginRequired()
497 @LoginRequired()
494 @HasPermissionAllDecorator('hg.admin')
498 @HasPermissionAllDecorator('hg.admin')
495 @view_config(
499 @view_config(
496 route_name='user_edit', request_method='GET',
500 route_name='user_edit', request_method='GET',
497 renderer='rhodecode:templates/admin/users/user_edit.mako')
501 renderer='rhodecode:templates/admin/users/user_edit.mako')
498 def user_edit(self):
502 def user_edit(self):
499 _ = self.request.translate
503 _ = self.request.translate
500 c = self.load_default_context()
504 c = self.load_default_context()
501 c.user = self.db_user
505 c.user = self.db_user
502
506
503 c.active = 'profile'
507 c.active = 'profile'
504 c.extern_type = c.user.extern_type
508 c.extern_type = c.user.extern_type
505 c.extern_name = c.user.extern_name
509 c.extern_name = c.user.extern_name
506 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
510 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
507
511
508 defaults = c.user.get_dict()
512 defaults = c.user.get_dict()
509 defaults.update({'language': c.user.user_data.get('language')})
513 defaults.update({'language': c.user.user_data.get('language')})
510
514
511 data = render(
515 data = render(
512 'rhodecode:templates/admin/users/user_edit.mako',
516 'rhodecode:templates/admin/users/user_edit.mako',
513 self._get_template_context(c), self.request)
517 self._get_template_context(c), self.request)
514 html = formencode.htmlfill.render(
518 html = formencode.htmlfill.render(
515 data,
519 data,
516 defaults=defaults,
520 defaults=defaults,
517 encoding="UTF-8",
521 encoding="UTF-8",
518 force_defaults=False
522 force_defaults=False
519 )
523 )
520 return Response(html)
524 return Response(html)
521
525
522 @LoginRequired()
526 @LoginRequired()
523 @HasPermissionAllDecorator('hg.admin')
527 @HasPermissionAllDecorator('hg.admin')
524 @view_config(
528 @view_config(
525 route_name='user_edit_advanced', request_method='GET',
529 route_name='user_edit_advanced', request_method='GET',
526 renderer='rhodecode:templates/admin/users/user_edit.mako')
530 renderer='rhodecode:templates/admin/users/user_edit.mako')
527 def user_edit_advanced(self):
531 def user_edit_advanced(self):
528 _ = self.request.translate
532 _ = self.request.translate
529 c = self.load_default_context()
533 c = self.load_default_context()
530
534
531 user_id = self.db_user_id
535 user_id = self.db_user_id
532 c.user = self.db_user
536 c.user = self.db_user
533
537
534 c.detach_user = User.get_first_super_admin()
538 c.detach_user = User.get_first_super_admin()
535 detach_user_id = safe_int(self.request.GET.get('detach_user_id'))
539 detach_user_id = safe_int(self.request.GET.get('detach_user_id'))
536 if detach_user_id:
540 if detach_user_id:
537 c.detach_user = User.get_or_404(detach_user_id)
541 c.detach_user = User.get_or_404(detach_user_id)
538
542
539 c.active = 'advanced'
543 c.active = 'advanced'
540 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
544 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
541 c.personal_repo_group_name = RepoGroupModel()\
545 c.personal_repo_group_name = RepoGroupModel()\
542 .get_personal_group_name(c.user)
546 .get_personal_group_name(c.user)
543
547
544 c.user_to_review_rules = sorted(
548 c.user_to_review_rules = sorted(
545 (x.user for x in c.user.user_review_rules),
549 (x.user for x in c.user.user_review_rules),
546 key=lambda u: u.username.lower())
550 key=lambda u: u.username.lower())
547
551
548 defaults = c.user.get_dict()
552 defaults = c.user.get_dict()
549
553
550 # Interim workaround if the user participated on any pull requests as a
554 # Interim workaround if the user participated on any pull requests as a
551 # reviewer.
555 # reviewer.
552 has_review = len(c.user.reviewer_pull_requests)
556 has_review = len(c.user.reviewer_pull_requests)
553 c.can_delete_user = not has_review
557 c.can_delete_user = not has_review
554 c.can_delete_user_message = ''
558 c.can_delete_user_message = ''
555 inactive_link = h.link_to(
559 inactive_link = h.link_to(
556 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
560 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
557 if has_review == 1:
561 if has_review == 1:
558 c.can_delete_user_message = h.literal(_(
562 c.can_delete_user_message = h.literal(_(
559 'The user participates as reviewer in {} pull request and '
563 'The user participates as reviewer in {} pull request and '
560 'cannot be deleted. \nYou can set the user to '
564 'cannot be deleted. \nYou can set the user to '
561 '"{}" instead of deleting it.').format(
565 '"{}" instead of deleting it.').format(
562 has_review, inactive_link))
566 has_review, inactive_link))
563 elif has_review:
567 elif has_review:
564 c.can_delete_user_message = h.literal(_(
568 c.can_delete_user_message = h.literal(_(
565 'The user participates as reviewer in {} pull requests and '
569 'The user participates as reviewer in {} pull requests and '
566 'cannot be deleted. \nYou can set the user to '
570 'cannot be deleted. \nYou can set the user to '
567 '"{}" instead of deleting it.').format(
571 '"{}" instead of deleting it.').format(
568 has_review, inactive_link))
572 has_review, inactive_link))
569
573
570 data = render(
574 data = render(
571 'rhodecode:templates/admin/users/user_edit.mako',
575 'rhodecode:templates/admin/users/user_edit.mako',
572 self._get_template_context(c), self.request)
576 self._get_template_context(c), self.request)
573 html = formencode.htmlfill.render(
577 html = formencode.htmlfill.render(
574 data,
578 data,
575 defaults=defaults,
579 defaults=defaults,
576 encoding="UTF-8",
580 encoding="UTF-8",
577 force_defaults=False
581 force_defaults=False
578 )
582 )
579 return Response(html)
583 return Response(html)
580
584
581 @LoginRequired()
585 @LoginRequired()
582 @HasPermissionAllDecorator('hg.admin')
586 @HasPermissionAllDecorator('hg.admin')
583 @view_config(
587 @view_config(
584 route_name='user_edit_global_perms', request_method='GET',
588 route_name='user_edit_global_perms', request_method='GET',
585 renderer='rhodecode:templates/admin/users/user_edit.mako')
589 renderer='rhodecode:templates/admin/users/user_edit.mako')
586 def user_edit_global_perms(self):
590 def user_edit_global_perms(self):
587 _ = self.request.translate
591 _ = self.request.translate
588 c = self.load_default_context()
592 c = self.load_default_context()
589 c.user = self.db_user
593 c.user = self.db_user
590
594
591 c.active = 'global_perms'
595 c.active = 'global_perms'
592
596
593 c.default_user = User.get_default_user()
597 c.default_user = User.get_default_user()
594 defaults = c.user.get_dict()
598 defaults = c.user.get_dict()
595 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
599 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
596 defaults.update(c.default_user.get_default_perms())
600 defaults.update(c.default_user.get_default_perms())
597 defaults.update(c.user.get_default_perms())
601 defaults.update(c.user.get_default_perms())
598
602
599 data = render(
603 data = render(
600 'rhodecode:templates/admin/users/user_edit.mako',
604 'rhodecode:templates/admin/users/user_edit.mako',
601 self._get_template_context(c), self.request)
605 self._get_template_context(c), self.request)
602 html = formencode.htmlfill.render(
606 html = formencode.htmlfill.render(
603 data,
607 data,
604 defaults=defaults,
608 defaults=defaults,
605 encoding="UTF-8",
609 encoding="UTF-8",
606 force_defaults=False
610 force_defaults=False
607 )
611 )
608 return Response(html)
612 return Response(html)
609
613
610 @LoginRequired()
614 @LoginRequired()
611 @HasPermissionAllDecorator('hg.admin')
615 @HasPermissionAllDecorator('hg.admin')
612 @CSRFRequired()
616 @CSRFRequired()
613 @view_config(
617 @view_config(
614 route_name='user_edit_global_perms_update', request_method='POST',
618 route_name='user_edit_global_perms_update', request_method='POST',
615 renderer='rhodecode:templates/admin/users/user_edit.mako')
619 renderer='rhodecode:templates/admin/users/user_edit.mako')
616 def user_edit_global_perms_update(self):
620 def user_edit_global_perms_update(self):
617 _ = self.request.translate
621 _ = self.request.translate
618 c = self.load_default_context()
622 c = self.load_default_context()
619
623
620 user_id = self.db_user_id
624 user_id = self.db_user_id
621 c.user = self.db_user
625 c.user = self.db_user
622
626
623 c.active = 'global_perms'
627 c.active = 'global_perms'
624 try:
628 try:
625 # first stage that verifies the checkbox
629 # first stage that verifies the checkbox
626 _form = UserIndividualPermissionsForm(self.request.translate)
630 _form = UserIndividualPermissionsForm(self.request.translate)
627 form_result = _form.to_python(dict(self.request.POST))
631 form_result = _form.to_python(dict(self.request.POST))
628 inherit_perms = form_result['inherit_default_permissions']
632 inherit_perms = form_result['inherit_default_permissions']
629 c.user.inherit_default_permissions = inherit_perms
633 c.user.inherit_default_permissions = inherit_perms
630 Session().add(c.user)
634 Session().add(c.user)
631
635
632 if not inherit_perms:
636 if not inherit_perms:
633 # only update the individual ones if we un check the flag
637 # only update the individual ones if we un check the flag
634 _form = UserPermissionsForm(
638 _form = UserPermissionsForm(
635 self.request.translate,
639 self.request.translate,
636 [x[0] for x in c.repo_create_choices],
640 [x[0] for x in c.repo_create_choices],
637 [x[0] for x in c.repo_create_on_write_choices],
641 [x[0] for x in c.repo_create_on_write_choices],
638 [x[0] for x in c.repo_group_create_choices],
642 [x[0] for x in c.repo_group_create_choices],
639 [x[0] for x in c.user_group_create_choices],
643 [x[0] for x in c.user_group_create_choices],
640 [x[0] for x in c.fork_choices],
644 [x[0] for x in c.fork_choices],
641 [x[0] for x in c.inherit_default_permission_choices])()
645 [x[0] for x in c.inherit_default_permission_choices])()
642
646
643 form_result = _form.to_python(dict(self.request.POST))
647 form_result = _form.to_python(dict(self.request.POST))
644 form_result.update({'perm_user_id': c.user.user_id})
648 form_result.update({'perm_user_id': c.user.user_id})
645
649
646 PermissionModel().update_user_permissions(form_result)
650 PermissionModel().update_user_permissions(form_result)
647
651
648 # TODO(marcink): implement global permissions
652 # TODO(marcink): implement global permissions
649 # audit_log.store_web('user.edit.permissions')
653 # audit_log.store_web('user.edit.permissions')
650
654
651 Session().commit()
655 Session().commit()
652
656
653 h.flash(_('User global permissions updated successfully'),
657 h.flash(_('User global permissions updated successfully'),
654 category='success')
658 category='success')
655
659
656 except formencode.Invalid as errors:
660 except formencode.Invalid as errors:
657 data = render(
661 data = render(
658 'rhodecode:templates/admin/users/user_edit.mako',
662 'rhodecode:templates/admin/users/user_edit.mako',
659 self._get_template_context(c), self.request)
663 self._get_template_context(c), self.request)
660 html = formencode.htmlfill.render(
664 html = formencode.htmlfill.render(
661 data,
665 data,
662 defaults=errors.value,
666 defaults=errors.value,
663 errors=errors.error_dict or {},
667 errors=errors.error_dict or {},
664 prefix_error=False,
668 prefix_error=False,
665 encoding="UTF-8",
669 encoding="UTF-8",
666 force_defaults=False
670 force_defaults=False
667 )
671 )
668 return Response(html)
672 return Response(html)
669 except Exception:
673 except Exception:
670 log.exception("Exception during permissions saving")
674 log.exception("Exception during permissions saving")
671 h.flash(_('An error occurred during permissions saving'),
675 h.flash(_('An error occurred during permissions saving'),
672 category='error')
676 category='error')
673
677
674 affected_user_ids = [user_id]
678 affected_user_ids = [user_id]
675 PermissionModel().trigger_permission_flush(affected_user_ids)
679 PermissionModel().trigger_permission_flush(affected_user_ids)
676 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
680 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
677
681
678 @LoginRequired()
682 @LoginRequired()
679 @HasPermissionAllDecorator('hg.admin')
683 @HasPermissionAllDecorator('hg.admin')
680 @CSRFRequired()
684 @CSRFRequired()
681 @view_config(
685 @view_config(
682 route_name='user_enable_force_password_reset', request_method='POST',
686 route_name='user_enable_force_password_reset', request_method='POST',
683 renderer='rhodecode:templates/admin/users/user_edit.mako')
687 renderer='rhodecode:templates/admin/users/user_edit.mako')
684 def user_enable_force_password_reset(self):
688 def user_enable_force_password_reset(self):
685 _ = self.request.translate
689 _ = self.request.translate
686 c = self.load_default_context()
690 c = self.load_default_context()
687
691
688 user_id = self.db_user_id
692 user_id = self.db_user_id
689 c.user = self.db_user
693 c.user = self.db_user
690
694
691 try:
695 try:
692 c.user.update_userdata(force_password_change=True)
696 c.user.update_userdata(force_password_change=True)
693
697
694 msg = _('Force password change enabled for user')
698 msg = _('Force password change enabled for user')
695 audit_logger.store_web('user.edit.password_reset.enabled',
699 audit_logger.store_web('user.edit.password_reset.enabled',
696 user=c.rhodecode_user)
700 user=c.rhodecode_user)
697
701
698 Session().commit()
702 Session().commit()
699 h.flash(msg, category='success')
703 h.flash(msg, category='success')
700 except Exception:
704 except Exception:
701 log.exception("Exception during password reset for user")
705 log.exception("Exception during password reset for user")
702 h.flash(_('An error occurred during password reset for user'),
706 h.flash(_('An error occurred during password reset for user'),
703 category='error')
707 category='error')
704
708
705 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
709 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
706
710
707 @LoginRequired()
711 @LoginRequired()
708 @HasPermissionAllDecorator('hg.admin')
712 @HasPermissionAllDecorator('hg.admin')
709 @CSRFRequired()
713 @CSRFRequired()
710 @view_config(
714 @view_config(
711 route_name='user_disable_force_password_reset', request_method='POST',
715 route_name='user_disable_force_password_reset', request_method='POST',
712 renderer='rhodecode:templates/admin/users/user_edit.mako')
716 renderer='rhodecode:templates/admin/users/user_edit.mako')
713 def user_disable_force_password_reset(self):
717 def user_disable_force_password_reset(self):
714 _ = self.request.translate
718 _ = self.request.translate
715 c = self.load_default_context()
719 c = self.load_default_context()
716
720
717 user_id = self.db_user_id
721 user_id = self.db_user_id
718 c.user = self.db_user
722 c.user = self.db_user
719
723
720 try:
724 try:
721 c.user.update_userdata(force_password_change=False)
725 c.user.update_userdata(force_password_change=False)
722
726
723 msg = _('Force password change disabled for user')
727 msg = _('Force password change disabled for user')
724 audit_logger.store_web(
728 audit_logger.store_web(
725 'user.edit.password_reset.disabled',
729 'user.edit.password_reset.disabled',
726 user=c.rhodecode_user)
730 user=c.rhodecode_user)
727
731
728 Session().commit()
732 Session().commit()
729 h.flash(msg, category='success')
733 h.flash(msg, category='success')
730 except Exception:
734 except Exception:
731 log.exception("Exception during password reset for user")
735 log.exception("Exception during password reset for user")
732 h.flash(_('An error occurred during password reset for user'),
736 h.flash(_('An error occurred during password reset for user'),
733 category='error')
737 category='error')
734
738
735 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
739 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
736
740
737 @LoginRequired()
741 @LoginRequired()
738 @HasPermissionAllDecorator('hg.admin')
742 @HasPermissionAllDecorator('hg.admin')
739 @CSRFRequired()
743 @CSRFRequired()
740 @view_config(
744 @view_config(
741 route_name='user_notice_dismiss', request_method='POST',
745 route_name='user_notice_dismiss', request_method='POST',
742 renderer='json_ext', xhr=True)
746 renderer='json_ext', xhr=True)
743 def user_notice_dismiss(self):
747 def user_notice_dismiss(self):
744 _ = self.request.translate
748 _ = self.request.translate
745 c = self.load_default_context()
749 c = self.load_default_context()
746
750
747 user_id = self.db_user_id
751 user_id = self.db_user_id
748 c.user = self.db_user
752 c.user = self.db_user
749 user_notice_id = safe_int(self.request.POST.get('notice_id'))
753 user_notice_id = safe_int(self.request.POST.get('notice_id'))
750 notice = UserNotice().query()\
754 notice = UserNotice().query()\
751 .filter(UserNotice.user_id == user_id)\
755 .filter(UserNotice.user_id == user_id)\
752 .filter(UserNotice.user_notice_id == user_notice_id)\
756 .filter(UserNotice.user_notice_id == user_notice_id)\
753 .scalar()
757 .scalar()
754 read = False
758 read = False
755 if notice:
759 if notice:
756 notice.notice_read = True
760 notice.notice_read = True
757 Session().add(notice)
761 Session().add(notice)
758 Session().commit()
762 Session().commit()
759 read = True
763 read = True
760
764
761 return {'notice': user_notice_id, 'read': read}
765 return {'notice': user_notice_id, 'read': read}
762
766
763 @LoginRequired()
767 @LoginRequired()
764 @HasPermissionAllDecorator('hg.admin')
768 @HasPermissionAllDecorator('hg.admin')
765 @CSRFRequired()
769 @CSRFRequired()
766 @view_config(
770 @view_config(
767 route_name='user_create_personal_repo_group', request_method='POST',
771 route_name='user_create_personal_repo_group', request_method='POST',
768 renderer='rhodecode:templates/admin/users/user_edit.mako')
772 renderer='rhodecode:templates/admin/users/user_edit.mako')
769 def user_create_personal_repo_group(self):
773 def user_create_personal_repo_group(self):
770 """
774 """
771 Create personal repository group for this user
775 Create personal repository group for this user
772 """
776 """
773 from rhodecode.model.repo_group import RepoGroupModel
777 from rhodecode.model.repo_group import RepoGroupModel
774
778
775 _ = self.request.translate
779 _ = self.request.translate
776 c = self.load_default_context()
780 c = self.load_default_context()
777
781
778 user_id = self.db_user_id
782 user_id = self.db_user_id
779 c.user = self.db_user
783 c.user = self.db_user
780
784
781 personal_repo_group = RepoGroup.get_user_personal_repo_group(
785 personal_repo_group = RepoGroup.get_user_personal_repo_group(
782 c.user.user_id)
786 c.user.user_id)
783 if personal_repo_group:
787 if personal_repo_group:
784 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
788 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
785
789
786 personal_repo_group_name = RepoGroupModel().get_personal_group_name(c.user)
790 personal_repo_group_name = RepoGroupModel().get_personal_group_name(c.user)
787 named_personal_group = RepoGroup.get_by_group_name(
791 named_personal_group = RepoGroup.get_by_group_name(
788 personal_repo_group_name)
792 personal_repo_group_name)
789 try:
793 try:
790
794
791 if named_personal_group and named_personal_group.user_id == c.user.user_id:
795 if named_personal_group and named_personal_group.user_id == c.user.user_id:
792 # migrate the same named group, and mark it as personal
796 # migrate the same named group, and mark it as personal
793 named_personal_group.personal = True
797 named_personal_group.personal = True
794 Session().add(named_personal_group)
798 Session().add(named_personal_group)
795 Session().commit()
799 Session().commit()
796 msg = _('Linked repository group `%s` as personal' % (
800 msg = _('Linked repository group `%s` as personal' % (
797 personal_repo_group_name,))
801 personal_repo_group_name,))
798 h.flash(msg, category='success')
802 h.flash(msg, category='success')
799 elif not named_personal_group:
803 elif not named_personal_group:
800 RepoGroupModel().create_personal_repo_group(c.user)
804 RepoGroupModel().create_personal_repo_group(c.user)
801
805
802 msg = _('Created repository group `%s`' % (
806 msg = _('Created repository group `%s`' % (
803 personal_repo_group_name,))
807 personal_repo_group_name,))
804 h.flash(msg, category='success')
808 h.flash(msg, category='success')
805 else:
809 else:
806 msg = _('Repository group `%s` is already taken' % (
810 msg = _('Repository group `%s` is already taken' % (
807 personal_repo_group_name,))
811 personal_repo_group_name,))
808 h.flash(msg, category='warning')
812 h.flash(msg, category='warning')
809 except Exception:
813 except Exception:
810 log.exception("Exception during repository group creation")
814 log.exception("Exception during repository group creation")
811 msg = _(
815 msg = _(
812 'An error occurred during repository group creation for user')
816 'An error occurred during repository group creation for user')
813 h.flash(msg, category='error')
817 h.flash(msg, category='error')
814 Session().rollback()
818 Session().rollback()
815
819
816 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
820 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
817
821
818 @LoginRequired()
822 @LoginRequired()
819 @HasPermissionAllDecorator('hg.admin')
823 @HasPermissionAllDecorator('hg.admin')
820 @view_config(
824 @view_config(
821 route_name='edit_user_auth_tokens', request_method='GET',
825 route_name='edit_user_auth_tokens', request_method='GET',
822 renderer='rhodecode:templates/admin/users/user_edit.mako')
826 renderer='rhodecode:templates/admin/users/user_edit.mako')
823 def auth_tokens(self):
827 def auth_tokens(self):
824 _ = self.request.translate
828 _ = self.request.translate
825 c = self.load_default_context()
829 c = self.load_default_context()
826 c.user = self.db_user
830 c.user = self.db_user
827
831
828 c.active = 'auth_tokens'
832 c.active = 'auth_tokens'
829
833
830 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
834 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
831 c.role_values = [
835 c.role_values = [
832 (x, AuthTokenModel.cls._get_role_name(x))
836 (x, AuthTokenModel.cls._get_role_name(x))
833 for x in AuthTokenModel.cls.ROLES]
837 for x in AuthTokenModel.cls.ROLES]
834 c.role_options = [(c.role_values, _("Role"))]
838 c.role_options = [(c.role_values, _("Role"))]
835 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
839 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
836 c.user.user_id, show_expired=True)
840 c.user.user_id, show_expired=True)
837 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
841 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
838 return self._get_template_context(c)
842 return self._get_template_context(c)
839
843
840 @LoginRequired()
844 @LoginRequired()
841 @HasPermissionAllDecorator('hg.admin')
845 @HasPermissionAllDecorator('hg.admin')
842 @view_config(
846 @view_config(
843 route_name='edit_user_auth_tokens_view', request_method='POST',
847 route_name='edit_user_auth_tokens_view', request_method='POST',
844 renderer='json_ext', xhr=True)
848 renderer='json_ext', xhr=True)
845 def auth_tokens_view(self):
849 def auth_tokens_view(self):
846 _ = self.request.translate
850 _ = self.request.translate
847 c = self.load_default_context()
851 c = self.load_default_context()
848 c.user = self.db_user
852 c.user = self.db_user
849
853
850 auth_token_id = self.request.POST.get('auth_token_id')
854 auth_token_id = self.request.POST.get('auth_token_id')
851
855
852 if auth_token_id:
856 if auth_token_id:
853 token = UserApiKeys.get_or_404(auth_token_id)
857 token = UserApiKeys.get_or_404(auth_token_id)
854
858
855 return {
859 return {
856 'auth_token': token.api_key
860 'auth_token': token.api_key
857 }
861 }
858
862
859 def maybe_attach_token_scope(self, token):
863 def maybe_attach_token_scope(self, token):
860 # implemented in EE edition
864 # implemented in EE edition
861 pass
865 pass
862
866
863 @LoginRequired()
867 @LoginRequired()
864 @HasPermissionAllDecorator('hg.admin')
868 @HasPermissionAllDecorator('hg.admin')
865 @CSRFRequired()
869 @CSRFRequired()
866 @view_config(
870 @view_config(
867 route_name='edit_user_auth_tokens_add', request_method='POST')
871 route_name='edit_user_auth_tokens_add', request_method='POST')
868 def auth_tokens_add(self):
872 def auth_tokens_add(self):
869 _ = self.request.translate
873 _ = self.request.translate
870 c = self.load_default_context()
874 c = self.load_default_context()
871
875
872 user_id = self.db_user_id
876 user_id = self.db_user_id
873 c.user = self.db_user
877 c.user = self.db_user
874
878
875 user_data = c.user.get_api_data()
879 user_data = c.user.get_api_data()
876 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
880 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
877 description = self.request.POST.get('description')
881 description = self.request.POST.get('description')
878 role = self.request.POST.get('role')
882 role = self.request.POST.get('role')
879
883
880 token = UserModel().add_auth_token(
884 token = UserModel().add_auth_token(
881 user=c.user.user_id,
885 user=c.user.user_id,
882 lifetime_minutes=lifetime, role=role, description=description,
886 lifetime_minutes=lifetime, role=role, description=description,
883 scope_callback=self.maybe_attach_token_scope)
887 scope_callback=self.maybe_attach_token_scope)
884 token_data = token.get_api_data()
888 token_data = token.get_api_data()
885
889
886 audit_logger.store_web(
890 audit_logger.store_web(
887 'user.edit.token.add', action_data={
891 'user.edit.token.add', action_data={
888 'data': {'token': token_data, 'user': user_data}},
892 'data': {'token': token_data, 'user': user_data}},
889 user=self._rhodecode_user, )
893 user=self._rhodecode_user, )
890 Session().commit()
894 Session().commit()
891
895
892 h.flash(_("Auth token successfully created"), category='success')
896 h.flash(_("Auth token successfully created"), category='success')
893 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
897 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
894
898
895 @LoginRequired()
899 @LoginRequired()
896 @HasPermissionAllDecorator('hg.admin')
900 @HasPermissionAllDecorator('hg.admin')
897 @CSRFRequired()
901 @CSRFRequired()
898 @view_config(
902 @view_config(
899 route_name='edit_user_auth_tokens_delete', request_method='POST')
903 route_name='edit_user_auth_tokens_delete', request_method='POST')
900 def auth_tokens_delete(self):
904 def auth_tokens_delete(self):
901 _ = self.request.translate
905 _ = self.request.translate
902 c = self.load_default_context()
906 c = self.load_default_context()
903
907
904 user_id = self.db_user_id
908 user_id = self.db_user_id
905 c.user = self.db_user
909 c.user = self.db_user
906
910
907 user_data = c.user.get_api_data()
911 user_data = c.user.get_api_data()
908
912
909 del_auth_token = self.request.POST.get('del_auth_token')
913 del_auth_token = self.request.POST.get('del_auth_token')
910
914
911 if del_auth_token:
915 if del_auth_token:
912 token = UserApiKeys.get_or_404(del_auth_token)
916 token = UserApiKeys.get_or_404(del_auth_token)
913 token_data = token.get_api_data()
917 token_data = token.get_api_data()
914
918
915 AuthTokenModel().delete(del_auth_token, c.user.user_id)
919 AuthTokenModel().delete(del_auth_token, c.user.user_id)
916 audit_logger.store_web(
920 audit_logger.store_web(
917 'user.edit.token.delete', action_data={
921 'user.edit.token.delete', action_data={
918 'data': {'token': token_data, 'user': user_data}},
922 'data': {'token': token_data, 'user': user_data}},
919 user=self._rhodecode_user,)
923 user=self._rhodecode_user,)
920 Session().commit()
924 Session().commit()
921 h.flash(_("Auth token successfully deleted"), category='success')
925 h.flash(_("Auth token successfully deleted"), category='success')
922
926
923 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
927 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
924
928
925 @LoginRequired()
929 @LoginRequired()
926 @HasPermissionAllDecorator('hg.admin')
930 @HasPermissionAllDecorator('hg.admin')
927 @view_config(
931 @view_config(
928 route_name='edit_user_ssh_keys', request_method='GET',
932 route_name='edit_user_ssh_keys', request_method='GET',
929 renderer='rhodecode:templates/admin/users/user_edit.mako')
933 renderer='rhodecode:templates/admin/users/user_edit.mako')
930 def ssh_keys(self):
934 def ssh_keys(self):
931 _ = self.request.translate
935 _ = self.request.translate
932 c = self.load_default_context()
936 c = self.load_default_context()
933 c.user = self.db_user
937 c.user = self.db_user
934
938
935 c.active = 'ssh_keys'
939 c.active = 'ssh_keys'
936 c.default_key = self.request.GET.get('default_key')
940 c.default_key = self.request.GET.get('default_key')
937 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
941 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
938 return self._get_template_context(c)
942 return self._get_template_context(c)
939
943
940 @LoginRequired()
944 @LoginRequired()
941 @HasPermissionAllDecorator('hg.admin')
945 @HasPermissionAllDecorator('hg.admin')
942 @view_config(
946 @view_config(
943 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
947 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
944 renderer='rhodecode:templates/admin/users/user_edit.mako')
948 renderer='rhodecode:templates/admin/users/user_edit.mako')
945 def ssh_keys_generate_keypair(self):
949 def ssh_keys_generate_keypair(self):
946 _ = self.request.translate
950 _ = self.request.translate
947 c = self.load_default_context()
951 c = self.load_default_context()
948
952
949 c.user = self.db_user
953 c.user = self.db_user
950
954
951 c.active = 'ssh_keys_generate'
955 c.active = 'ssh_keys_generate'
952 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
956 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
953 private_format = self.request.GET.get('private_format') \
957 private_format = self.request.GET.get('private_format') \
954 or SshKeyModel.DEFAULT_PRIVATE_KEY_FORMAT
958 or SshKeyModel.DEFAULT_PRIVATE_KEY_FORMAT
955 c.private, c.public = SshKeyModel().generate_keypair(
959 c.private, c.public = SshKeyModel().generate_keypair(
956 comment=comment, private_format=private_format)
960 comment=comment, private_format=private_format)
957
961
958 return self._get_template_context(c)
962 return self._get_template_context(c)
959
963
960 @LoginRequired()
964 @LoginRequired()
961 @HasPermissionAllDecorator('hg.admin')
965 @HasPermissionAllDecorator('hg.admin')
962 @CSRFRequired()
966 @CSRFRequired()
963 @view_config(
967 @view_config(
964 route_name='edit_user_ssh_keys_add', request_method='POST')
968 route_name='edit_user_ssh_keys_add', request_method='POST')
965 def ssh_keys_add(self):
969 def ssh_keys_add(self):
966 _ = self.request.translate
970 _ = self.request.translate
967 c = self.load_default_context()
971 c = self.load_default_context()
968
972
969 user_id = self.db_user_id
973 user_id = self.db_user_id
970 c.user = self.db_user
974 c.user = self.db_user
971
975
972 user_data = c.user.get_api_data()
976 user_data = c.user.get_api_data()
973 key_data = self.request.POST.get('key_data')
977 key_data = self.request.POST.get('key_data')
974 description = self.request.POST.get('description')
978 description = self.request.POST.get('description')
975
979
976 fingerprint = 'unknown'
980 fingerprint = 'unknown'
977 try:
981 try:
978 if not key_data:
982 if not key_data:
979 raise ValueError('Please add a valid public key')
983 raise ValueError('Please add a valid public key')
980
984
981 key = SshKeyModel().parse_key(key_data.strip())
985 key = SshKeyModel().parse_key(key_data.strip())
982 fingerprint = key.hash_md5()
986 fingerprint = key.hash_md5()
983
987
984 ssh_key = SshKeyModel().create(
988 ssh_key = SshKeyModel().create(
985 c.user.user_id, fingerprint, key.keydata, description)
989 c.user.user_id, fingerprint, key.keydata, description)
986 ssh_key_data = ssh_key.get_api_data()
990 ssh_key_data = ssh_key.get_api_data()
987
991
988 audit_logger.store_web(
992 audit_logger.store_web(
989 'user.edit.ssh_key.add', action_data={
993 'user.edit.ssh_key.add', action_data={
990 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
994 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
991 user=self._rhodecode_user, )
995 user=self._rhodecode_user, )
992 Session().commit()
996 Session().commit()
993
997
994 # Trigger an event on change of keys.
998 # Trigger an event on change of keys.
995 trigger(SshKeyFileChangeEvent(), self.request.registry)
999 trigger(SshKeyFileChangeEvent(), self.request.registry)
996
1000
997 h.flash(_("Ssh Key successfully created"), category='success')
1001 h.flash(_("Ssh Key successfully created"), category='success')
998
1002
999 except IntegrityError:
1003 except IntegrityError:
1000 log.exception("Exception during ssh key saving")
1004 log.exception("Exception during ssh key saving")
1001 err = 'Such key with fingerprint `{}` already exists, ' \
1005 err = 'Such key with fingerprint `{}` already exists, ' \
1002 'please use a different one'.format(fingerprint)
1006 'please use a different one'.format(fingerprint)
1003 h.flash(_('An error occurred during ssh key saving: {}').format(err),
1007 h.flash(_('An error occurred during ssh key saving: {}').format(err),
1004 category='error')
1008 category='error')
1005 except Exception as e:
1009 except Exception as e:
1006 log.exception("Exception during ssh key saving")
1010 log.exception("Exception during ssh key saving")
1007 h.flash(_('An error occurred during ssh key saving: {}').format(e),
1011 h.flash(_('An error occurred during ssh key saving: {}').format(e),
1008 category='error')
1012 category='error')
1009
1013
1010 return HTTPFound(
1014 return HTTPFound(
1011 h.route_path('edit_user_ssh_keys', user_id=user_id))
1015 h.route_path('edit_user_ssh_keys', user_id=user_id))
1012
1016
1013 @LoginRequired()
1017 @LoginRequired()
1014 @HasPermissionAllDecorator('hg.admin')
1018 @HasPermissionAllDecorator('hg.admin')
1015 @CSRFRequired()
1019 @CSRFRequired()
1016 @view_config(
1020 @view_config(
1017 route_name='edit_user_ssh_keys_delete', request_method='POST')
1021 route_name='edit_user_ssh_keys_delete', request_method='POST')
1018 def ssh_keys_delete(self):
1022 def ssh_keys_delete(self):
1019 _ = self.request.translate
1023 _ = self.request.translate
1020 c = self.load_default_context()
1024 c = self.load_default_context()
1021
1025
1022 user_id = self.db_user_id
1026 user_id = self.db_user_id
1023 c.user = self.db_user
1027 c.user = self.db_user
1024
1028
1025 user_data = c.user.get_api_data()
1029 user_data = c.user.get_api_data()
1026
1030
1027 del_ssh_key = self.request.POST.get('del_ssh_key')
1031 del_ssh_key = self.request.POST.get('del_ssh_key')
1028
1032
1029 if del_ssh_key:
1033 if del_ssh_key:
1030 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
1034 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
1031 ssh_key_data = ssh_key.get_api_data()
1035 ssh_key_data = ssh_key.get_api_data()
1032
1036
1033 SshKeyModel().delete(del_ssh_key, c.user.user_id)
1037 SshKeyModel().delete(del_ssh_key, c.user.user_id)
1034 audit_logger.store_web(
1038 audit_logger.store_web(
1035 'user.edit.ssh_key.delete', action_data={
1039 'user.edit.ssh_key.delete', action_data={
1036 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
1040 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
1037 user=self._rhodecode_user,)
1041 user=self._rhodecode_user,)
1038 Session().commit()
1042 Session().commit()
1039 # Trigger an event on change of keys.
1043 # Trigger an event on change of keys.
1040 trigger(SshKeyFileChangeEvent(), self.request.registry)
1044 trigger(SshKeyFileChangeEvent(), self.request.registry)
1041 h.flash(_("Ssh key successfully deleted"), category='success')
1045 h.flash(_("Ssh key successfully deleted"), category='success')
1042
1046
1043 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
1047 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
1044
1048
1045 @LoginRequired()
1049 @LoginRequired()
1046 @HasPermissionAllDecorator('hg.admin')
1050 @HasPermissionAllDecorator('hg.admin')
1047 @view_config(
1051 @view_config(
1048 route_name='edit_user_emails', request_method='GET',
1052 route_name='edit_user_emails', request_method='GET',
1049 renderer='rhodecode:templates/admin/users/user_edit.mako')
1053 renderer='rhodecode:templates/admin/users/user_edit.mako')
1050 def emails(self):
1054 def emails(self):
1051 _ = self.request.translate
1055 _ = self.request.translate
1052 c = self.load_default_context()
1056 c = self.load_default_context()
1053 c.user = self.db_user
1057 c.user = self.db_user
1054
1058
1055 c.active = 'emails'
1059 c.active = 'emails'
1056 c.user_email_map = UserEmailMap.query() \
1060 c.user_email_map = UserEmailMap.query() \
1057 .filter(UserEmailMap.user == c.user).all()
1061 .filter(UserEmailMap.user == c.user).all()
1058
1062
1059 return self._get_template_context(c)
1063 return self._get_template_context(c)
1060
1064
1061 @LoginRequired()
1065 @LoginRequired()
1062 @HasPermissionAllDecorator('hg.admin')
1066 @HasPermissionAllDecorator('hg.admin')
1063 @CSRFRequired()
1067 @CSRFRequired()
1064 @view_config(
1068 @view_config(
1065 route_name='edit_user_emails_add', request_method='POST')
1069 route_name='edit_user_emails_add', request_method='POST')
1066 def emails_add(self):
1070 def emails_add(self):
1067 _ = self.request.translate
1071 _ = self.request.translate
1068 c = self.load_default_context()
1072 c = self.load_default_context()
1069
1073
1070 user_id = self.db_user_id
1074 user_id = self.db_user_id
1071 c.user = self.db_user
1075 c.user = self.db_user
1072
1076
1073 email = self.request.POST.get('new_email')
1077 email = self.request.POST.get('new_email')
1074 user_data = c.user.get_api_data()
1078 user_data = c.user.get_api_data()
1075 try:
1079 try:
1076
1080
1077 form = UserExtraEmailForm(self.request.translate)()
1081 form = UserExtraEmailForm(self.request.translate)()
1078 data = form.to_python({'email': email})
1082 data = form.to_python({'email': email})
1079 email = data['email']
1083 email = data['email']
1080
1084
1081 UserModel().add_extra_email(c.user.user_id, email)
1085 UserModel().add_extra_email(c.user.user_id, email)
1082 audit_logger.store_web(
1086 audit_logger.store_web(
1083 'user.edit.email.add',
1087 'user.edit.email.add',
1084 action_data={'email': email, 'user': user_data},
1088 action_data={'email': email, 'user': user_data},
1085 user=self._rhodecode_user)
1089 user=self._rhodecode_user)
1086 Session().commit()
1090 Session().commit()
1087 h.flash(_("Added new email address `%s` for user account") % email,
1091 h.flash(_("Added new email address `%s` for user account") % email,
1088 category='success')
1092 category='success')
1089 except formencode.Invalid as error:
1093 except formencode.Invalid as error:
1090 h.flash(h.escape(error.error_dict['email']), category='error')
1094 h.flash(h.escape(error.error_dict['email']), category='error')
1091 except IntegrityError:
1095 except IntegrityError:
1092 log.warning("Email %s already exists", email)
1096 log.warning("Email %s already exists", email)
1093 h.flash(_('Email `{}` is already registered for another user.').format(email),
1097 h.flash(_('Email `{}` is already registered for another user.').format(email),
1094 category='error')
1098 category='error')
1095 except Exception:
1099 except Exception:
1096 log.exception("Exception during email saving")
1100 log.exception("Exception during email saving")
1097 h.flash(_('An error occurred during email saving'),
1101 h.flash(_('An error occurred during email saving'),
1098 category='error')
1102 category='error')
1099 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1103 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1100
1104
1101 @LoginRequired()
1105 @LoginRequired()
1102 @HasPermissionAllDecorator('hg.admin')
1106 @HasPermissionAllDecorator('hg.admin')
1103 @CSRFRequired()
1107 @CSRFRequired()
1104 @view_config(
1108 @view_config(
1105 route_name='edit_user_emails_delete', request_method='POST')
1109 route_name='edit_user_emails_delete', request_method='POST')
1106 def emails_delete(self):
1110 def emails_delete(self):
1107 _ = self.request.translate
1111 _ = self.request.translate
1108 c = self.load_default_context()
1112 c = self.load_default_context()
1109
1113
1110 user_id = self.db_user_id
1114 user_id = self.db_user_id
1111 c.user = self.db_user
1115 c.user = self.db_user
1112
1116
1113 email_id = self.request.POST.get('del_email_id')
1117 email_id = self.request.POST.get('del_email_id')
1114 user_model = UserModel()
1118 user_model = UserModel()
1115
1119
1116 email = UserEmailMap.query().get(email_id).email
1120 email = UserEmailMap.query().get(email_id).email
1117 user_data = c.user.get_api_data()
1121 user_data = c.user.get_api_data()
1118 user_model.delete_extra_email(c.user.user_id, email_id)
1122 user_model.delete_extra_email(c.user.user_id, email_id)
1119 audit_logger.store_web(
1123 audit_logger.store_web(
1120 'user.edit.email.delete',
1124 'user.edit.email.delete',
1121 action_data={'email': email, 'user': user_data},
1125 action_data={'email': email, 'user': user_data},
1122 user=self._rhodecode_user)
1126 user=self._rhodecode_user)
1123 Session().commit()
1127 Session().commit()
1124 h.flash(_("Removed email address from user account"),
1128 h.flash(_("Removed email address from user account"),
1125 category='success')
1129 category='success')
1126 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1130 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1127
1131
1128 @LoginRequired()
1132 @LoginRequired()
1129 @HasPermissionAllDecorator('hg.admin')
1133 @HasPermissionAllDecorator('hg.admin')
1130 @view_config(
1134 @view_config(
1131 route_name='edit_user_ips', request_method='GET',
1135 route_name='edit_user_ips', request_method='GET',
1132 renderer='rhodecode:templates/admin/users/user_edit.mako')
1136 renderer='rhodecode:templates/admin/users/user_edit.mako')
1133 def ips(self):
1137 def ips(self):
1134 _ = self.request.translate
1138 _ = self.request.translate
1135 c = self.load_default_context()
1139 c = self.load_default_context()
1136 c.user = self.db_user
1140 c.user = self.db_user
1137
1141
1138 c.active = 'ips'
1142 c.active = 'ips'
1139 c.user_ip_map = UserIpMap.query() \
1143 c.user_ip_map = UserIpMap.query() \
1140 .filter(UserIpMap.user == c.user).all()
1144 .filter(UserIpMap.user == c.user).all()
1141
1145
1142 c.inherit_default_ips = c.user.inherit_default_permissions
1146 c.inherit_default_ips = c.user.inherit_default_permissions
1143 c.default_user_ip_map = UserIpMap.query() \
1147 c.default_user_ip_map = UserIpMap.query() \
1144 .filter(UserIpMap.user == User.get_default_user()).all()
1148 .filter(UserIpMap.user == User.get_default_user()).all()
1145
1149
1146 return self._get_template_context(c)
1150 return self._get_template_context(c)
1147
1151
1148 @LoginRequired()
1152 @LoginRequired()
1149 @HasPermissionAllDecorator('hg.admin')
1153 @HasPermissionAllDecorator('hg.admin')
1150 @CSRFRequired()
1154 @CSRFRequired()
1151 @view_config(
1155 @view_config(
1152 route_name='edit_user_ips_add', request_method='POST')
1156 route_name='edit_user_ips_add', request_method='POST')
1153 # NOTE(marcink): this view is allowed for default users, as we can
1157 # NOTE(marcink): this view is allowed for default users, as we can
1154 # edit their IP white list
1158 # edit their IP white list
1155 def ips_add(self):
1159 def ips_add(self):
1156 _ = self.request.translate
1160 _ = self.request.translate
1157 c = self.load_default_context()
1161 c = self.load_default_context()
1158
1162
1159 user_id = self.db_user_id
1163 user_id = self.db_user_id
1160 c.user = self.db_user
1164 c.user = self.db_user
1161
1165
1162 user_model = UserModel()
1166 user_model = UserModel()
1163 desc = self.request.POST.get('description')
1167 desc = self.request.POST.get('description')
1164 try:
1168 try:
1165 ip_list = user_model.parse_ip_range(
1169 ip_list = user_model.parse_ip_range(
1166 self.request.POST.get('new_ip'))
1170 self.request.POST.get('new_ip'))
1167 except Exception as e:
1171 except Exception as e:
1168 ip_list = []
1172 ip_list = []
1169 log.exception("Exception during ip saving")
1173 log.exception("Exception during ip saving")
1170 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1174 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1171 category='error')
1175 category='error')
1172 added = []
1176 added = []
1173 user_data = c.user.get_api_data()
1177 user_data = c.user.get_api_data()
1174 for ip in ip_list:
1178 for ip in ip_list:
1175 try:
1179 try:
1176 form = UserExtraIpForm(self.request.translate)()
1180 form = UserExtraIpForm(self.request.translate)()
1177 data = form.to_python({'ip': ip})
1181 data = form.to_python({'ip': ip})
1178 ip = data['ip']
1182 ip = data['ip']
1179
1183
1180 user_model.add_extra_ip(c.user.user_id, ip, desc)
1184 user_model.add_extra_ip(c.user.user_id, ip, desc)
1181 audit_logger.store_web(
1185 audit_logger.store_web(
1182 'user.edit.ip.add',
1186 'user.edit.ip.add',
1183 action_data={'ip': ip, 'user': user_data},
1187 action_data={'ip': ip, 'user': user_data},
1184 user=self._rhodecode_user)
1188 user=self._rhodecode_user)
1185 Session().commit()
1189 Session().commit()
1186 added.append(ip)
1190 added.append(ip)
1187 except formencode.Invalid as error:
1191 except formencode.Invalid as error:
1188 msg = error.error_dict['ip']
1192 msg = error.error_dict['ip']
1189 h.flash(msg, category='error')
1193 h.flash(msg, category='error')
1190 except Exception:
1194 except Exception:
1191 log.exception("Exception during ip saving")
1195 log.exception("Exception during ip saving")
1192 h.flash(_('An error occurred during ip saving'),
1196 h.flash(_('An error occurred during ip saving'),
1193 category='error')
1197 category='error')
1194 if added:
1198 if added:
1195 h.flash(
1199 h.flash(
1196 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1200 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1197 category='success')
1201 category='success')
1198 if 'default_user' in self.request.POST:
1202 if 'default_user' in self.request.POST:
1199 # case for editing global IP list we do it for 'DEFAULT' user
1203 # case for editing global IP list we do it for 'DEFAULT' user
1200 raise HTTPFound(h.route_path('admin_permissions_ips'))
1204 raise HTTPFound(h.route_path('admin_permissions_ips'))
1201 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1205 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1202
1206
1203 @LoginRequired()
1207 @LoginRequired()
1204 @HasPermissionAllDecorator('hg.admin')
1208 @HasPermissionAllDecorator('hg.admin')
1205 @CSRFRequired()
1209 @CSRFRequired()
1206 @view_config(
1210 @view_config(
1207 route_name='edit_user_ips_delete', request_method='POST')
1211 route_name='edit_user_ips_delete', request_method='POST')
1208 # NOTE(marcink): this view is allowed for default users, as we can
1212 # NOTE(marcink): this view is allowed for default users, as we can
1209 # edit their IP white list
1213 # edit their IP white list
1210 def ips_delete(self):
1214 def ips_delete(self):
1211 _ = self.request.translate
1215 _ = self.request.translate
1212 c = self.load_default_context()
1216 c = self.load_default_context()
1213
1217
1214 user_id = self.db_user_id
1218 user_id = self.db_user_id
1215 c.user = self.db_user
1219 c.user = self.db_user
1216
1220
1217 ip_id = self.request.POST.get('del_ip_id')
1221 ip_id = self.request.POST.get('del_ip_id')
1218 user_model = UserModel()
1222 user_model = UserModel()
1219 user_data = c.user.get_api_data()
1223 user_data = c.user.get_api_data()
1220 ip = UserIpMap.query().get(ip_id).ip_addr
1224 ip = UserIpMap.query().get(ip_id).ip_addr
1221 user_model.delete_extra_ip(c.user.user_id, ip_id)
1225 user_model.delete_extra_ip(c.user.user_id, ip_id)
1222 audit_logger.store_web(
1226 audit_logger.store_web(
1223 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1227 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1224 user=self._rhodecode_user)
1228 user=self._rhodecode_user)
1225 Session().commit()
1229 Session().commit()
1226 h.flash(_("Removed ip address from user whitelist"), category='success')
1230 h.flash(_("Removed ip address from user whitelist"), category='success')
1227
1231
1228 if 'default_user' in self.request.POST:
1232 if 'default_user' in self.request.POST:
1229 # case for editing global IP list we do it for 'DEFAULT' user
1233 # case for editing global IP list we do it for 'DEFAULT' user
1230 raise HTTPFound(h.route_path('admin_permissions_ips'))
1234 raise HTTPFound(h.route_path('admin_permissions_ips'))
1231 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1235 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1232
1236
1233 @LoginRequired()
1237 @LoginRequired()
1234 @HasPermissionAllDecorator('hg.admin')
1238 @HasPermissionAllDecorator('hg.admin')
1235 @view_config(
1239 @view_config(
1236 route_name='edit_user_groups_management', request_method='GET',
1240 route_name='edit_user_groups_management', request_method='GET',
1237 renderer='rhodecode:templates/admin/users/user_edit.mako')
1241 renderer='rhodecode:templates/admin/users/user_edit.mako')
1238 def groups_management(self):
1242 def groups_management(self):
1239 c = self.load_default_context()
1243 c = self.load_default_context()
1240 c.user = self.db_user
1244 c.user = self.db_user
1241 c.data = c.user.group_member
1245 c.data = c.user.group_member
1242
1246
1243 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1247 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1244 for group in c.user.group_member]
1248 for group in c.user.group_member]
1245 c.groups = json.dumps(groups)
1249 c.groups = json.dumps(groups)
1246 c.active = 'groups'
1250 c.active = 'groups'
1247
1251
1248 return self._get_template_context(c)
1252 return self._get_template_context(c)
1249
1253
1250 @LoginRequired()
1254 @LoginRequired()
1251 @HasPermissionAllDecorator('hg.admin')
1255 @HasPermissionAllDecorator('hg.admin')
1252 @CSRFRequired()
1256 @CSRFRequired()
1253 @view_config(
1257 @view_config(
1254 route_name='edit_user_groups_management_updates', request_method='POST')
1258 route_name='edit_user_groups_management_updates', request_method='POST')
1255 def groups_management_updates(self):
1259 def groups_management_updates(self):
1256 _ = self.request.translate
1260 _ = self.request.translate
1257 c = self.load_default_context()
1261 c = self.load_default_context()
1258
1262
1259 user_id = self.db_user_id
1263 user_id = self.db_user_id
1260 c.user = self.db_user
1264 c.user = self.db_user
1261
1265
1262 user_groups = set(self.request.POST.getall('users_group_id'))
1266 user_groups = set(self.request.POST.getall('users_group_id'))
1263 user_groups_objects = []
1267 user_groups_objects = []
1264
1268
1265 for ugid in user_groups:
1269 for ugid in user_groups:
1266 user_groups_objects.append(
1270 user_groups_objects.append(
1267 UserGroupModel().get_group(safe_int(ugid)))
1271 UserGroupModel().get_group(safe_int(ugid)))
1268 user_group_model = UserGroupModel()
1272 user_group_model = UserGroupModel()
1269 added_to_groups, removed_from_groups = \
1273 added_to_groups, removed_from_groups = \
1270 user_group_model.change_groups(c.user, user_groups_objects)
1274 user_group_model.change_groups(c.user, user_groups_objects)
1271
1275
1272 user_data = c.user.get_api_data()
1276 user_data = c.user.get_api_data()
1273 for user_group_id in added_to_groups:
1277 for user_group_id in added_to_groups:
1274 user_group = UserGroup.get(user_group_id)
1278 user_group = UserGroup.get(user_group_id)
1275 old_values = user_group.get_api_data()
1279 old_values = user_group.get_api_data()
1276 audit_logger.store_web(
1280 audit_logger.store_web(
1277 'user_group.edit.member.add',
1281 'user_group.edit.member.add',
1278 action_data={'user': user_data, 'old_data': old_values},
1282 action_data={'user': user_data, 'old_data': old_values},
1279 user=self._rhodecode_user)
1283 user=self._rhodecode_user)
1280
1284
1281 for user_group_id in removed_from_groups:
1285 for user_group_id in removed_from_groups:
1282 user_group = UserGroup.get(user_group_id)
1286 user_group = UserGroup.get(user_group_id)
1283 old_values = user_group.get_api_data()
1287 old_values = user_group.get_api_data()
1284 audit_logger.store_web(
1288 audit_logger.store_web(
1285 'user_group.edit.member.delete',
1289 'user_group.edit.member.delete',
1286 action_data={'user': user_data, 'old_data': old_values},
1290 action_data={'user': user_data, 'old_data': old_values},
1287 user=self._rhodecode_user)
1291 user=self._rhodecode_user)
1288
1292
1289 Session().commit()
1293 Session().commit()
1290 c.active = 'user_groups_management'
1294 c.active = 'user_groups_management'
1291 h.flash(_("Groups successfully changed"), category='success')
1295 h.flash(_("Groups successfully changed"), category='success')
1292
1296
1293 return HTTPFound(h.route_path(
1297 return HTTPFound(h.route_path(
1294 'edit_user_groups_management', user_id=user_id))
1298 'edit_user_groups_management', user_id=user_id))
1295
1299
1296 @LoginRequired()
1300 @LoginRequired()
1297 @HasPermissionAllDecorator('hg.admin')
1301 @HasPermissionAllDecorator('hg.admin')
1298 @view_config(
1302 @view_config(
1299 route_name='edit_user_audit_logs', request_method='GET',
1303 route_name='edit_user_audit_logs', request_method='GET',
1300 renderer='rhodecode:templates/admin/users/user_edit.mako')
1304 renderer='rhodecode:templates/admin/users/user_edit.mako')
1301 def user_audit_logs(self):
1305 def user_audit_logs(self):
1302 _ = self.request.translate
1306 _ = self.request.translate
1303 c = self.load_default_context()
1307 c = self.load_default_context()
1304 c.user = self.db_user
1308 c.user = self.db_user
1305
1309
1306 c.active = 'audit'
1310 c.active = 'audit'
1307
1311
1308 p = safe_int(self.request.GET.get('page', 1), 1)
1312 p = safe_int(self.request.GET.get('page', 1), 1)
1309
1313
1310 filter_term = self.request.GET.get('filter')
1314 filter_term = self.request.GET.get('filter')
1311 user_log = UserModel().get_user_log(c.user, filter_term)
1315 user_log = UserModel().get_user_log(c.user, filter_term)
1312
1316
1313 def url_generator(page_num):
1317 def url_generator(page_num):
1314 query_params = {
1318 query_params = {
1315 'page': page_num
1319 'page': page_num
1316 }
1320 }
1317 if filter_term:
1321 if filter_term:
1318 query_params['filter'] = filter_term
1322 query_params['filter'] = filter_term
1319 return self.request.current_route_path(_query=query_params)
1323 return self.request.current_route_path(_query=query_params)
1320
1324
1321 c.audit_logs = SqlPage(
1325 c.audit_logs = SqlPage(
1322 user_log, page=p, items_per_page=10, url_maker=url_generator)
1326 user_log, page=p, items_per_page=10, url_maker=url_generator)
1323 c.filter_term = filter_term
1327 c.filter_term = filter_term
1324 return self._get_template_context(c)
1328 return self._get_template_context(c)
1325
1329
1326 @LoginRequired()
1330 @LoginRequired()
1327 @HasPermissionAllDecorator('hg.admin')
1331 @HasPermissionAllDecorator('hg.admin')
1328 @view_config(
1332 @view_config(
1329 route_name='edit_user_audit_logs_download', request_method='GET',
1333 route_name='edit_user_audit_logs_download', request_method='GET',
1330 renderer='string')
1334 renderer='string')
1331 def user_audit_logs_download(self):
1335 def user_audit_logs_download(self):
1332 _ = self.request.translate
1336 _ = self.request.translate
1333 c = self.load_default_context()
1337 c = self.load_default_context()
1334 c.user = self.db_user
1338 c.user = self.db_user
1335
1339
1336 user_log = UserModel().get_user_log(c.user, filter_term=None)
1340 user_log = UserModel().get_user_log(c.user, filter_term=None)
1337
1341
1338 audit_log_data = {}
1342 audit_log_data = {}
1339 for entry in user_log:
1343 for entry in user_log:
1340 audit_log_data[entry.user_log_id] = entry.get_dict()
1344 audit_log_data[entry.user_log_id] = entry.get_dict()
1341
1345
1342 response = Response(json.dumps(audit_log_data, indent=4))
1346 response = Response(json.dumps(audit_log_data, indent=4))
1343 response.content_disposition = str(
1347 response.content_disposition = str(
1344 'attachment; filename=%s' % 'user_{}_audit_logs.json'.format(c.user.user_id))
1348 'attachment; filename=%s' % 'user_{}_audit_logs.json'.format(c.user.user_id))
1345 response.content_type = 'application/json'
1349 response.content_type = 'application/json'
1346
1350
1347 return response
1351 return response
1348
1352
1349 @LoginRequired()
1353 @LoginRequired()
1350 @HasPermissionAllDecorator('hg.admin')
1354 @HasPermissionAllDecorator('hg.admin')
1351 @view_config(
1355 @view_config(
1352 route_name='edit_user_perms_summary', request_method='GET',
1356 route_name='edit_user_perms_summary', request_method='GET',
1353 renderer='rhodecode:templates/admin/users/user_edit.mako')
1357 renderer='rhodecode:templates/admin/users/user_edit.mako')
1354 def user_perms_summary(self):
1358 def user_perms_summary(self):
1355 _ = self.request.translate
1359 _ = self.request.translate
1356 c = self.load_default_context()
1360 c = self.load_default_context()
1357 c.user = self.db_user
1361 c.user = self.db_user
1358
1362
1359 c.active = 'perms_summary'
1363 c.active = 'perms_summary'
1360 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1364 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1361
1365
1362 return self._get_template_context(c)
1366 return self._get_template_context(c)
1363
1367
1364 @LoginRequired()
1368 @LoginRequired()
1365 @HasPermissionAllDecorator('hg.admin')
1369 @HasPermissionAllDecorator('hg.admin')
1366 @view_config(
1370 @view_config(
1367 route_name='edit_user_perms_summary_json', request_method='GET',
1371 route_name='edit_user_perms_summary_json', request_method='GET',
1368 renderer='json_ext')
1372 renderer='json_ext')
1369 def user_perms_summary_json(self):
1373 def user_perms_summary_json(self):
1370 self.load_default_context()
1374 self.load_default_context()
1371 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1375 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1372
1376
1373 return perm_user.permissions
1377 return perm_user.permissions
1374
1378
1375 @LoginRequired()
1379 @LoginRequired()
1376 @HasPermissionAllDecorator('hg.admin')
1380 @HasPermissionAllDecorator('hg.admin')
1377 @view_config(
1381 @view_config(
1378 route_name='edit_user_caches', request_method='GET',
1382 route_name='edit_user_caches', request_method='GET',
1379 renderer='rhodecode:templates/admin/users/user_edit.mako')
1383 renderer='rhodecode:templates/admin/users/user_edit.mako')
1380 def user_caches(self):
1384 def user_caches(self):
1381 _ = self.request.translate
1385 _ = self.request.translate
1382 c = self.load_default_context()
1386 c = self.load_default_context()
1383 c.user = self.db_user
1387 c.user = self.db_user
1384
1388
1385 c.active = 'caches'
1389 c.active = 'caches'
1386 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1390 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1387
1391
1388 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1392 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1389 c.region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1393 c.region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1390 c.backend = c.region.backend
1394 c.backend = c.region.backend
1391 c.user_keys = sorted(c.region.backend.list_keys(prefix=cache_namespace_uid))
1395 c.user_keys = sorted(c.region.backend.list_keys(prefix=cache_namespace_uid))
1392
1396
1393 return self._get_template_context(c)
1397 return self._get_template_context(c)
1394
1398
1395 @LoginRequired()
1399 @LoginRequired()
1396 @HasPermissionAllDecorator('hg.admin')
1400 @HasPermissionAllDecorator('hg.admin')
1397 @CSRFRequired()
1401 @CSRFRequired()
1398 @view_config(
1402 @view_config(
1399 route_name='edit_user_caches_update', request_method='POST')
1403 route_name='edit_user_caches_update', request_method='POST')
1400 def user_caches_update(self):
1404 def user_caches_update(self):
1401 _ = self.request.translate
1405 _ = self.request.translate
1402 c = self.load_default_context()
1406 c = self.load_default_context()
1403 c.user = self.db_user
1407 c.user = self.db_user
1404
1408
1405 c.active = 'caches'
1409 c.active = 'caches'
1406 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1410 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1407
1411
1408 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1412 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1409 del_keys = rc_cache.clear_cache_namespace('cache_perms', cache_namespace_uid)
1413 del_keys = rc_cache.clear_cache_namespace('cache_perms', cache_namespace_uid)
1410
1414
1411 h.flash(_("Deleted {} cache keys").format(del_keys), category='success')
1415 h.flash(_("Deleted {} cache keys").format(del_keys), category='success')
1412
1416
1413 return HTTPFound(h.route_path(
1417 return HTTPFound(h.route_path(
1414 'edit_user_caches', user_id=c.user.user_id))
1418 'edit_user_caches', user_id=c.user.user_id))
@@ -1,768 +1,766 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 import sys
22 import sys
23 import logging
23 import logging
24 import collections
24 import collections
25 import tempfile
25 import tempfile
26 import time
26 import time
27
27
28 from paste.gzipper import make_gzip_middleware
28 from paste.gzipper import make_gzip_middleware
29 import pyramid.events
29 import pyramid.events
30 from pyramid.wsgi import wsgiapp
30 from pyramid.wsgi import wsgiapp
31 from pyramid.authorization import ACLAuthorizationPolicy
31 from pyramid.authorization import ACLAuthorizationPolicy
32 from pyramid.config import Configurator
32 from pyramid.config import Configurator
33 from pyramid.settings import asbool, aslist
33 from pyramid.settings import asbool, aslist
34 from pyramid.httpexceptions import (
34 from pyramid.httpexceptions import (
35 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
35 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
36 from pyramid.renderers import render_to_response
36 from pyramid.renderers import render_to_response
37
37
38 from rhodecode.model import meta
38 from rhodecode.model import meta
39 from rhodecode.config import patches
39 from rhodecode.config import patches
40 from rhodecode.config import utils as config_utils
40 from rhodecode.config import utils as config_utils
41 from rhodecode.config.environment import load_pyramid_environment
41 from rhodecode.config.environment import load_pyramid_environment
42
42
43 import rhodecode.events
43 import rhodecode.events
44 from rhodecode.lib.middleware.vcs import VCSMiddleware
44 from rhodecode.lib.middleware.vcs import VCSMiddleware
45 from rhodecode.lib.request import Request
45 from rhodecode.lib.request import Request
46 from rhodecode.lib.vcs import VCSCommunicationError
46 from rhodecode.lib.vcs import VCSCommunicationError
47 from rhodecode.lib.exceptions import VCSServerUnavailable
47 from rhodecode.lib.exceptions import VCSServerUnavailable
48 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
48 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
49 from rhodecode.lib.middleware.https_fixup import HttpsFixup
49 from rhodecode.lib.middleware.https_fixup import HttpsFixup
50 from rhodecode.lib.celerylib.loader import configure_celery
50 from rhodecode.lib.celerylib.loader import configure_celery
51 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
51 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
52 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
52 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
53 from rhodecode.lib.exc_tracking import store_exception
53 from rhodecode.lib.exc_tracking import store_exception
54 from rhodecode.subscribers import (
54 from rhodecode.subscribers import (
55 scan_repositories_if_enabled, write_js_routes_if_enabled,
55 scan_repositories_if_enabled, write_js_routes_if_enabled,
56 write_metadata_if_needed, write_usage_data, inject_app_settings)
56 write_metadata_if_needed, write_usage_data)
57
57
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61
61
62 def is_http_error(response):
62 def is_http_error(response):
63 # error which should have traceback
63 # error which should have traceback
64 return response.status_code > 499
64 return response.status_code > 499
65
65
66
66
67 def should_load_all():
67 def should_load_all():
68 """
68 """
69 Returns if all application components should be loaded. In some cases it's
69 Returns if all application components should be loaded. In some cases it's
70 desired to skip apps loading for faster shell script execution
70 desired to skip apps loading for faster shell script execution
71 """
71 """
72 ssh_cmd = os.environ.get('RC_CMD_SSH_WRAPPER')
72 ssh_cmd = os.environ.get('RC_CMD_SSH_WRAPPER')
73 if ssh_cmd:
73 if ssh_cmd:
74 return False
74 return False
75
75
76 return True
76 return True
77
77
78
78
79 def make_pyramid_app(global_config, **settings):
79 def make_pyramid_app(global_config, **settings):
80 """
80 """
81 Constructs the WSGI application based on Pyramid.
81 Constructs the WSGI application based on Pyramid.
82
82
83 Specials:
83 Specials:
84
84
85 * The application can also be integrated like a plugin via the call to
85 * The application can also be integrated like a plugin via the call to
86 `includeme`. This is accompanied with the other utility functions which
86 `includeme`. This is accompanied with the other utility functions which
87 are called. Changing this should be done with great care to not break
87 are called. Changing this should be done with great care to not break
88 cases when these fragments are assembled from another place.
88 cases when these fragments are assembled from another place.
89
89
90 """
90 """
91
91
92 # Allows to use format style "{ENV_NAME}" placeholders in the configuration. It
92 # Allows to use format style "{ENV_NAME}" placeholders in the configuration. It
93 # will be replaced by the value of the environment variable "NAME" in this case.
93 # will be replaced by the value of the environment variable "NAME" in this case.
94 start_time = time.time()
94 start_time = time.time()
95
95
96 debug = asbool(global_config.get('debug'))
96 debug = asbool(global_config.get('debug'))
97 if debug:
97 if debug:
98 enable_debug()
98 enable_debug()
99
99
100 environ = {'ENV_{}'.format(key): value for key, value in os.environ.items()}
100 environ = {'ENV_{}'.format(key): value for key, value in os.environ.items()}
101
101
102 global_config = _substitute_values(global_config, environ)
102 global_config = _substitute_values(global_config, environ)
103 settings = _substitute_values(settings, environ)
103 settings = _substitute_values(settings, environ)
104
104
105 sanitize_settings_and_apply_defaults(global_config, settings)
105 sanitize_settings_and_apply_defaults(global_config, settings)
106
106
107 config = Configurator(settings=settings)
107 config = Configurator(settings=settings)
108
108
109 # Apply compatibility patches
109 # Apply compatibility patches
110 patches.inspect_getargspec()
110 patches.inspect_getargspec()
111
111
112 load_pyramid_environment(global_config, settings)
112 load_pyramid_environment(global_config, settings)
113
113
114 # Static file view comes first
114 # Static file view comes first
115 includeme_first(config)
115 includeme_first(config)
116
116
117 includeme(config)
117 includeme(config)
118
118
119 pyramid_app = config.make_wsgi_app()
119 pyramid_app = config.make_wsgi_app()
120 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
120 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
121 pyramid_app.config = config
121 pyramid_app.config = config
122
122
123 config.configure_celery(global_config['__file__'])
123 config.configure_celery(global_config['__file__'])
124 # creating the app uses a connection - return it after we are done
124 # creating the app uses a connection - return it after we are done
125 meta.Session.remove()
125 meta.Session.remove()
126 total_time = time.time() - start_time
126 total_time = time.time() - start_time
127 log.info('Pyramid app `%s` created and configured in %.2fs',
127 log.info('Pyramid app `%s` created and configured in %.2fs',
128 pyramid_app.func_name, total_time)
128 pyramid_app.func_name, total_time)
129
129
130 return pyramid_app
130 return pyramid_app
131
131
132
132
133 def not_found_view(request):
133 def not_found_view(request):
134 """
134 """
135 This creates the view which should be registered as not-found-view to
135 This creates the view which should be registered as not-found-view to
136 pyramid.
136 pyramid.
137 """
137 """
138
138
139 if not getattr(request, 'vcs_call', None):
139 if not getattr(request, 'vcs_call', None):
140 # handle like regular case with our error_handler
140 # handle like regular case with our error_handler
141 return error_handler(HTTPNotFound(), request)
141 return error_handler(HTTPNotFound(), request)
142
142
143 # handle not found view as a vcs call
143 # handle not found view as a vcs call
144 settings = request.registry.settings
144 settings = request.registry.settings
145 ae_client = getattr(request, 'ae_client', None)
145 ae_client = getattr(request, 'ae_client', None)
146 vcs_app = VCSMiddleware(
146 vcs_app = VCSMiddleware(
147 HTTPNotFound(), request.registry, settings,
147 HTTPNotFound(), request.registry, settings,
148 appenlight_client=ae_client)
148 appenlight_client=ae_client)
149
149
150 return wsgiapp(vcs_app)(None, request)
150 return wsgiapp(vcs_app)(None, request)
151
151
152
152
153 def error_handler(exception, request):
153 def error_handler(exception, request):
154 import rhodecode
154 import rhodecode
155 from rhodecode.lib import helpers
155 from rhodecode.lib import helpers
156
156
157 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
157 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
158
158
159 base_response = HTTPInternalServerError()
159 base_response = HTTPInternalServerError()
160 # prefer original exception for the response since it may have headers set
160 # prefer original exception for the response since it may have headers set
161 if isinstance(exception, HTTPException):
161 if isinstance(exception, HTTPException):
162 base_response = exception
162 base_response = exception
163 elif isinstance(exception, VCSCommunicationError):
163 elif isinstance(exception, VCSCommunicationError):
164 base_response = VCSServerUnavailable()
164 base_response = VCSServerUnavailable()
165
165
166 if is_http_error(base_response):
166 if is_http_error(base_response):
167 log.exception(
167 log.exception(
168 'error occurred handling this request for path: %s', request.path)
168 'error occurred handling this request for path: %s', request.path)
169
169
170 error_explanation = base_response.explanation or str(base_response)
170 error_explanation = base_response.explanation or str(base_response)
171 if base_response.status_code == 404:
171 if base_response.status_code == 404:
172 error_explanation += " Optionally you don't have permission to access this page."
172 error_explanation += " Optionally you don't have permission to access this page."
173 c = AttributeDict()
173 c = AttributeDict()
174 c.error_message = base_response.status
174 c.error_message = base_response.status
175 c.error_explanation = error_explanation
175 c.error_explanation = error_explanation
176 c.visual = AttributeDict()
176 c.visual = AttributeDict()
177
177
178 c.visual.rhodecode_support_url = (
178 c.visual.rhodecode_support_url = (
179 request.registry.settings.get('rhodecode_support_url') or
179 request.registry.settings.get('rhodecode_support_url') or
180 request.route_url('rhodecode_support')
180 request.route_url('rhodecode_support')
181 )
181 )
182 c.redirect_time = 0
182 c.redirect_time = 0
183 c.rhodecode_name = rhodecode_title
183 c.rhodecode_name = rhodecode_title
184 if not c.rhodecode_name:
184 if not c.rhodecode_name:
185 c.rhodecode_name = 'Rhodecode'
185 c.rhodecode_name = 'Rhodecode'
186
186
187 c.causes = []
187 c.causes = []
188 if is_http_error(base_response):
188 if is_http_error(base_response):
189 c.causes.append('Server is overloaded.')
189 c.causes.append('Server is overloaded.')
190 c.causes.append('Server database connection is lost.')
190 c.causes.append('Server database connection is lost.')
191 c.causes.append('Server expected unhandled error.')
191 c.causes.append('Server expected unhandled error.')
192
192
193 if hasattr(base_response, 'causes'):
193 if hasattr(base_response, 'causes'):
194 c.causes = base_response.causes
194 c.causes = base_response.causes
195
195
196 c.messages = helpers.flash.pop_messages(request=request)
196 c.messages = helpers.flash.pop_messages(request=request)
197
197
198 exc_info = sys.exc_info()
198 exc_info = sys.exc_info()
199 c.exception_id = id(exc_info)
199 c.exception_id = id(exc_info)
200 c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \
200 c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \
201 or base_response.status_code > 499
201 or base_response.status_code > 499
202 c.exception_id_url = request.route_url(
202 c.exception_id_url = request.route_url(
203 'admin_settings_exception_tracker_show', exception_id=c.exception_id)
203 'admin_settings_exception_tracker_show', exception_id=c.exception_id)
204
204
205 if c.show_exception_id:
205 if c.show_exception_id:
206 store_exception(c.exception_id, exc_info)
206 store_exception(c.exception_id, exc_info)
207
207
208 response = render_to_response(
208 response = render_to_response(
209 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
209 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
210 response=base_response)
210 response=base_response)
211
211
212 return response
212 return response
213
213
214
214
215 def includeme_first(config):
215 def includeme_first(config):
216 # redirect automatic browser favicon.ico requests to correct place
216 # redirect automatic browser favicon.ico requests to correct place
217 def favicon_redirect(context, request):
217 def favicon_redirect(context, request):
218 return HTTPFound(
218 return HTTPFound(
219 request.static_path('rhodecode:public/images/favicon.ico'))
219 request.static_path('rhodecode:public/images/favicon.ico'))
220
220
221 config.add_view(favicon_redirect, route_name='favicon')
221 config.add_view(favicon_redirect, route_name='favicon')
222 config.add_route('favicon', '/favicon.ico')
222 config.add_route('favicon', '/favicon.ico')
223
223
224 def robots_redirect(context, request):
224 def robots_redirect(context, request):
225 return HTTPFound(
225 return HTTPFound(
226 request.static_path('rhodecode:public/robots.txt'))
226 request.static_path('rhodecode:public/robots.txt'))
227
227
228 config.add_view(robots_redirect, route_name='robots')
228 config.add_view(robots_redirect, route_name='robots')
229 config.add_route('robots', '/robots.txt')
229 config.add_route('robots', '/robots.txt')
230
230
231 config.add_static_view(
231 config.add_static_view(
232 '_static/deform', 'deform:static')
232 '_static/deform', 'deform:static')
233 config.add_static_view(
233 config.add_static_view(
234 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
234 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
235
235
236
236
237 def includeme(config):
237 def includeme(config):
238 log.debug('Initializing main includeme from %s', os.path.basename(__file__))
238 log.debug('Initializing main includeme from %s', os.path.basename(__file__))
239 settings = config.registry.settings
239 settings = config.registry.settings
240 config.set_request_factory(Request)
240 config.set_request_factory(Request)
241
241
242 # plugin information
242 # plugin information
243 config.registry.rhodecode_plugins = collections.OrderedDict()
243 config.registry.rhodecode_plugins = collections.OrderedDict()
244
244
245 config.add_directive(
245 config.add_directive(
246 'register_rhodecode_plugin', register_rhodecode_plugin)
246 'register_rhodecode_plugin', register_rhodecode_plugin)
247
247
248 config.add_directive('configure_celery', configure_celery)
248 config.add_directive('configure_celery', configure_celery)
249
249
250 if asbool(settings.get('appenlight', 'false')):
250 if asbool(settings.get('appenlight', 'false')):
251 config.include('appenlight_client.ext.pyramid_tween')
251 config.include('appenlight_client.ext.pyramid_tween')
252
252
253 load_all = should_load_all()
253 load_all = should_load_all()
254
254
255 # Includes which are required. The application would fail without them.
255 # Includes which are required. The application would fail without them.
256 config.include('pyramid_mako')
256 config.include('pyramid_mako')
257 config.include('rhodecode.lib.rc_beaker')
257 config.include('rhodecode.lib.rc_beaker')
258 config.include('rhodecode.lib.rc_cache')
258 config.include('rhodecode.lib.rc_cache')
259
259
260 config.include('rhodecode.apps._base.navigation')
260 config.include('rhodecode.apps._base.navigation')
261 config.include('rhodecode.apps._base.subscribers')
261 config.include('rhodecode.apps._base.subscribers')
262 config.include('rhodecode.tweens')
262 config.include('rhodecode.tweens')
263 config.include('rhodecode.authentication')
263 config.include('rhodecode.authentication')
264
264
265 if load_all:
265 if load_all:
266 config.include('rhodecode.integrations')
266 config.include('rhodecode.integrations')
267
267
268 if load_all:
268 if load_all:
269 # load CE authentication plugins
269 # load CE authentication plugins
270 config.include('rhodecode.authentication.plugins.auth_crowd')
270 config.include('rhodecode.authentication.plugins.auth_crowd')
271 config.include('rhodecode.authentication.plugins.auth_headers')
271 config.include('rhodecode.authentication.plugins.auth_headers')
272 config.include('rhodecode.authentication.plugins.auth_jasig_cas')
272 config.include('rhodecode.authentication.plugins.auth_jasig_cas')
273 config.include('rhodecode.authentication.plugins.auth_ldap')
273 config.include('rhodecode.authentication.plugins.auth_ldap')
274 config.include('rhodecode.authentication.plugins.auth_pam')
274 config.include('rhodecode.authentication.plugins.auth_pam')
275 config.include('rhodecode.authentication.plugins.auth_rhodecode')
275 config.include('rhodecode.authentication.plugins.auth_rhodecode')
276 config.include('rhodecode.authentication.plugins.auth_token')
276 config.include('rhodecode.authentication.plugins.auth_token')
277
277
278 # Auto discover authentication plugins and include their configuration.
278 # Auto discover authentication plugins and include their configuration.
279 if asbool(settings.get('auth_plugin.import_legacy_plugins', 'true')):
279 if asbool(settings.get('auth_plugin.import_legacy_plugins', 'true')):
280 from rhodecode.authentication import discover_legacy_plugins
280 from rhodecode.authentication import discover_legacy_plugins
281 discover_legacy_plugins(config)
281 discover_legacy_plugins(config)
282
282
283 # apps
283 # apps
284 if load_all:
284 if load_all:
285 config.include('rhodecode.apps._base')
285 config.include('rhodecode.apps._base')
286 config.include('rhodecode.apps.hovercards')
286 config.include('rhodecode.apps.hovercards')
287 config.include('rhodecode.apps.ops')
287 config.include('rhodecode.apps.ops')
288 config.include('rhodecode.apps.admin')
288 config.include('rhodecode.apps.admin')
289 config.include('rhodecode.apps.channelstream')
289 config.include('rhodecode.apps.channelstream')
290 config.include('rhodecode.apps.file_store')
290 config.include('rhodecode.apps.file_store')
291 config.include('rhodecode.apps.login')
291 config.include('rhodecode.apps.login')
292 config.include('rhodecode.apps.home')
292 config.include('rhodecode.apps.home')
293 config.include('rhodecode.apps.journal')
293 config.include('rhodecode.apps.journal')
294 config.include('rhodecode.apps.repository')
294 config.include('rhodecode.apps.repository')
295 config.include('rhodecode.apps.repo_group')
295 config.include('rhodecode.apps.repo_group')
296 config.include('rhodecode.apps.user_group')
296 config.include('rhodecode.apps.user_group')
297 config.include('rhodecode.apps.search')
297 config.include('rhodecode.apps.search')
298 config.include('rhodecode.apps.user_profile')
298 config.include('rhodecode.apps.user_profile')
299 config.include('rhodecode.apps.user_group_profile')
299 config.include('rhodecode.apps.user_group_profile')
300 config.include('rhodecode.apps.my_account')
300 config.include('rhodecode.apps.my_account')
301 config.include('rhodecode.apps.svn_support')
301 config.include('rhodecode.apps.svn_support')
302 config.include('rhodecode.apps.ssh_support')
302 config.include('rhodecode.apps.ssh_support')
303 config.include('rhodecode.apps.gist')
303 config.include('rhodecode.apps.gist')
304 config.include('rhodecode.apps.debug_style')
304 config.include('rhodecode.apps.debug_style')
305 config.include('rhodecode.api')
305 config.include('rhodecode.api')
306
306
307 config.add_route('rhodecode_support', 'https://rhodecode.com/help/', static=True)
307 config.add_route('rhodecode_support', 'https://rhodecode.com/help/', static=True)
308 config.add_translation_dirs('rhodecode:i18n/')
308 config.add_translation_dirs('rhodecode:i18n/')
309 settings['default_locale_name'] = settings.get('lang', 'en')
309 settings['default_locale_name'] = settings.get('lang', 'en')
310
310
311 # Add subscribers.
311 # Add subscribers.
312 if load_all:
312 if load_all:
313 config.add_subscriber(inject_app_settings,
314 pyramid.events.ApplicationCreated)
315 config.add_subscriber(scan_repositories_if_enabled,
313 config.add_subscriber(scan_repositories_if_enabled,
316 pyramid.events.ApplicationCreated)
314 pyramid.events.ApplicationCreated)
317 config.add_subscriber(write_metadata_if_needed,
315 config.add_subscriber(write_metadata_if_needed,
318 pyramid.events.ApplicationCreated)
316 pyramid.events.ApplicationCreated)
319 config.add_subscriber(write_usage_data,
317 config.add_subscriber(write_usage_data,
320 pyramid.events.ApplicationCreated)
318 pyramid.events.ApplicationCreated)
321 config.add_subscriber(write_js_routes_if_enabled,
319 config.add_subscriber(write_js_routes_if_enabled,
322 pyramid.events.ApplicationCreated)
320 pyramid.events.ApplicationCreated)
323
321
324 # request custom methods
322 # request custom methods
325 config.add_request_method(
323 config.add_request_method(
326 'rhodecode.lib.partial_renderer.get_partial_renderer',
324 'rhodecode.lib.partial_renderer.get_partial_renderer',
327 'get_partial_renderer')
325 'get_partial_renderer')
328
326
329 config.add_request_method(
327 config.add_request_method(
330 'rhodecode.lib.request_counter.get_request_counter',
328 'rhodecode.lib.request_counter.get_request_counter',
331 'request_count')
329 'request_count')
332
330
333 # Set the authorization policy.
331 # Set the authorization policy.
334 authz_policy = ACLAuthorizationPolicy()
332 authz_policy = ACLAuthorizationPolicy()
335 config.set_authorization_policy(authz_policy)
333 config.set_authorization_policy(authz_policy)
336
334
337 # Set the default renderer for HTML templates to mako.
335 # Set the default renderer for HTML templates to mako.
338 config.add_mako_renderer('.html')
336 config.add_mako_renderer('.html')
339
337
340 config.add_renderer(
338 config.add_renderer(
341 name='json_ext',
339 name='json_ext',
342 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
340 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
343
341
344 config.add_renderer(
342 config.add_renderer(
345 name='string_html',
343 name='string_html',
346 factory='rhodecode.lib.string_renderer.html')
344 factory='rhodecode.lib.string_renderer.html')
347
345
348 # include RhodeCode plugins
346 # include RhodeCode plugins
349 includes = aslist(settings.get('rhodecode.includes', []))
347 includes = aslist(settings.get('rhodecode.includes', []))
350 for inc in includes:
348 for inc in includes:
351 config.include(inc)
349 config.include(inc)
352
350
353 # custom not found view, if our pyramid app doesn't know how to handle
351 # custom not found view, if our pyramid app doesn't know how to handle
354 # the request pass it to potential VCS handling ap
352 # the request pass it to potential VCS handling ap
355 config.add_notfound_view(not_found_view)
353 config.add_notfound_view(not_found_view)
356 if not settings.get('debugtoolbar.enabled', False):
354 if not settings.get('debugtoolbar.enabled', False):
357 # disabled debugtoolbar handle all exceptions via the error_handlers
355 # disabled debugtoolbar handle all exceptions via the error_handlers
358 config.add_view(error_handler, context=Exception)
356 config.add_view(error_handler, context=Exception)
359
357
360 # all errors including 403/404/50X
358 # all errors including 403/404/50X
361 config.add_view(error_handler, context=HTTPError)
359 config.add_view(error_handler, context=HTTPError)
362
360
363
361
364 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
362 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
365 """
363 """
366 Apply outer WSGI middlewares around the application.
364 Apply outer WSGI middlewares around the application.
367 """
365 """
368 registry = config.registry
366 registry = config.registry
369 settings = registry.settings
367 settings = registry.settings
370
368
371 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
369 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
372 pyramid_app = HttpsFixup(pyramid_app, settings)
370 pyramid_app = HttpsFixup(pyramid_app, settings)
373
371
374 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
372 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
375 pyramid_app, settings)
373 pyramid_app, settings)
376 registry.ae_client = _ae_client
374 registry.ae_client = _ae_client
377
375
378 if settings['gzip_responses']:
376 if settings['gzip_responses']:
379 pyramid_app = make_gzip_middleware(
377 pyramid_app = make_gzip_middleware(
380 pyramid_app, settings, compress_level=1)
378 pyramid_app, settings, compress_level=1)
381
379
382 # this should be the outer most middleware in the wsgi stack since
380 # this should be the outer most middleware in the wsgi stack since
383 # middleware like Routes make database calls
381 # middleware like Routes make database calls
384 def pyramid_app_with_cleanup(environ, start_response):
382 def pyramid_app_with_cleanup(environ, start_response):
385 try:
383 try:
386 return pyramid_app(environ, start_response)
384 return pyramid_app(environ, start_response)
387 finally:
385 finally:
388 # Dispose current database session and rollback uncommitted
386 # Dispose current database session and rollback uncommitted
389 # transactions.
387 # transactions.
390 meta.Session.remove()
388 meta.Session.remove()
391
389
392 # In a single threaded mode server, on non sqlite db we should have
390 # In a single threaded mode server, on non sqlite db we should have
393 # '0 Current Checked out connections' at the end of a request,
391 # '0 Current Checked out connections' at the end of a request,
394 # if not, then something, somewhere is leaving a connection open
392 # if not, then something, somewhere is leaving a connection open
395 pool = meta.Base.metadata.bind.engine.pool
393 pool = meta.Base.metadata.bind.engine.pool
396 log.debug('sa pool status: %s', pool.status())
394 log.debug('sa pool status: %s', pool.status())
397 log.debug('Request processing finalized')
395 log.debug('Request processing finalized')
398
396
399 return pyramid_app_with_cleanup
397 return pyramid_app_with_cleanup
400
398
401
399
402 def sanitize_settings_and_apply_defaults(global_config, settings):
400 def sanitize_settings_and_apply_defaults(global_config, settings):
403 """
401 """
404 Applies settings defaults and does all type conversion.
402 Applies settings defaults and does all type conversion.
405
403
406 We would move all settings parsing and preparation into this place, so that
404 We would move all settings parsing and preparation into this place, so that
407 we have only one place left which deals with this part. The remaining parts
405 we have only one place left which deals with this part. The remaining parts
408 of the application would start to rely fully on well prepared settings.
406 of the application would start to rely fully on well prepared settings.
409
407
410 This piece would later be split up per topic to avoid a big fat monster
408 This piece would later be split up per topic to avoid a big fat monster
411 function.
409 function.
412 """
410 """
413
411
414 settings.setdefault('rhodecode.edition', 'Community Edition')
412 settings.setdefault('rhodecode.edition', 'Community Edition')
415 settings.setdefault('rhodecode.edition_id', 'CE')
413 settings.setdefault('rhodecode.edition_id', 'CE')
416
414
417 if 'mako.default_filters' not in settings:
415 if 'mako.default_filters' not in settings:
418 # set custom default filters if we don't have it defined
416 # set custom default filters if we don't have it defined
419 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
417 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
420 settings['mako.default_filters'] = 'h_filter'
418 settings['mako.default_filters'] = 'h_filter'
421
419
422 if 'mako.directories' not in settings:
420 if 'mako.directories' not in settings:
423 mako_directories = settings.setdefault('mako.directories', [
421 mako_directories = settings.setdefault('mako.directories', [
424 # Base templates of the original application
422 # Base templates of the original application
425 'rhodecode:templates',
423 'rhodecode:templates',
426 ])
424 ])
427 log.debug(
425 log.debug(
428 "Using the following Mako template directories: %s",
426 "Using the following Mako template directories: %s",
429 mako_directories)
427 mako_directories)
430
428
431 # NOTE(marcink): fix redis requirement for schema of connection since 3.X
429 # NOTE(marcink): fix redis requirement for schema of connection since 3.X
432 if 'beaker.session.type' in settings and settings['beaker.session.type'] == 'ext:redis':
430 if 'beaker.session.type' in settings and settings['beaker.session.type'] == 'ext:redis':
433 raw_url = settings['beaker.session.url']
431 raw_url = settings['beaker.session.url']
434 if not raw_url.startswith(('redis://', 'rediss://', 'unix://')):
432 if not raw_url.startswith(('redis://', 'rediss://', 'unix://')):
435 settings['beaker.session.url'] = 'redis://' + raw_url
433 settings['beaker.session.url'] = 'redis://' + raw_url
436
434
437 # Default includes, possible to change as a user
435 # Default includes, possible to change as a user
438 pyramid_includes = settings.setdefault('pyramid.includes', [])
436 pyramid_includes = settings.setdefault('pyramid.includes', [])
439 log.debug(
437 log.debug(
440 "Using the following pyramid.includes: %s",
438 "Using the following pyramid.includes: %s",
441 pyramid_includes)
439 pyramid_includes)
442
440
443 # TODO: johbo: Re-think this, usually the call to config.include
441 # TODO: johbo: Re-think this, usually the call to config.include
444 # should allow to pass in a prefix.
442 # should allow to pass in a prefix.
445 settings.setdefault('rhodecode.api.url', '/_admin/api')
443 settings.setdefault('rhodecode.api.url', '/_admin/api')
446 settings.setdefault('__file__', global_config.get('__file__'))
444 settings.setdefault('__file__', global_config.get('__file__'))
447
445
448 # Sanitize generic settings.
446 # Sanitize generic settings.
449 _list_setting(settings, 'default_encoding', 'UTF-8')
447 _list_setting(settings, 'default_encoding', 'UTF-8')
450 _bool_setting(settings, 'is_test', 'false')
448 _bool_setting(settings, 'is_test', 'false')
451 _bool_setting(settings, 'gzip_responses', 'false')
449 _bool_setting(settings, 'gzip_responses', 'false')
452
450
453 # Call split out functions that sanitize settings for each topic.
451 # Call split out functions that sanitize settings for each topic.
454 _sanitize_appenlight_settings(settings)
452 _sanitize_appenlight_settings(settings)
455 _sanitize_vcs_settings(settings)
453 _sanitize_vcs_settings(settings)
456 _sanitize_cache_settings(settings)
454 _sanitize_cache_settings(settings)
457
455
458 # configure instance id
456 # configure instance id
459 config_utils.set_instance_id(settings)
457 config_utils.set_instance_id(settings)
460
458
461 return settings
459 return settings
462
460
463
461
464 def enable_debug():
462 def enable_debug():
465 """
463 """
466 Helper to enable debug on running instance
464 Helper to enable debug on running instance
467 :return:
465 :return:
468 """
466 """
469 import tempfile
467 import tempfile
470 import textwrap
468 import textwrap
471 import logging.config
469 import logging.config
472
470
473 ini_template = textwrap.dedent("""
471 ini_template = textwrap.dedent("""
474 #####################################
472 #####################################
475 ### DEBUG LOGGING CONFIGURATION ####
473 ### DEBUG LOGGING CONFIGURATION ####
476 #####################################
474 #####################################
477 [loggers]
475 [loggers]
478 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
476 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
479
477
480 [handlers]
478 [handlers]
481 keys = console, console_sql
479 keys = console, console_sql
482
480
483 [formatters]
481 [formatters]
484 keys = generic, color_formatter, color_formatter_sql
482 keys = generic, color_formatter, color_formatter_sql
485
483
486 #############
484 #############
487 ## LOGGERS ##
485 ## LOGGERS ##
488 #############
486 #############
489 [logger_root]
487 [logger_root]
490 level = NOTSET
488 level = NOTSET
491 handlers = console
489 handlers = console
492
490
493 [logger_sqlalchemy]
491 [logger_sqlalchemy]
494 level = INFO
492 level = INFO
495 handlers = console_sql
493 handlers = console_sql
496 qualname = sqlalchemy.engine
494 qualname = sqlalchemy.engine
497 propagate = 0
495 propagate = 0
498
496
499 [logger_beaker]
497 [logger_beaker]
500 level = DEBUG
498 level = DEBUG
501 handlers =
499 handlers =
502 qualname = beaker.container
500 qualname = beaker.container
503 propagate = 1
501 propagate = 1
504
502
505 [logger_rhodecode]
503 [logger_rhodecode]
506 level = DEBUG
504 level = DEBUG
507 handlers =
505 handlers =
508 qualname = rhodecode
506 qualname = rhodecode
509 propagate = 1
507 propagate = 1
510
508
511 [logger_ssh_wrapper]
509 [logger_ssh_wrapper]
512 level = DEBUG
510 level = DEBUG
513 handlers =
511 handlers =
514 qualname = ssh_wrapper
512 qualname = ssh_wrapper
515 propagate = 1
513 propagate = 1
516
514
517 [logger_celery]
515 [logger_celery]
518 level = DEBUG
516 level = DEBUG
519 handlers =
517 handlers =
520 qualname = celery
518 qualname = celery
521
519
522
520
523 ##############
521 ##############
524 ## HANDLERS ##
522 ## HANDLERS ##
525 ##############
523 ##############
526
524
527 [handler_console]
525 [handler_console]
528 class = StreamHandler
526 class = StreamHandler
529 args = (sys.stderr, )
527 args = (sys.stderr, )
530 level = DEBUG
528 level = DEBUG
531 formatter = color_formatter
529 formatter = color_formatter
532
530
533 [handler_console_sql]
531 [handler_console_sql]
534 # "level = DEBUG" logs SQL queries and results.
532 # "level = DEBUG" logs SQL queries and results.
535 # "level = INFO" logs SQL queries.
533 # "level = INFO" logs SQL queries.
536 # "level = WARN" logs neither. (Recommended for production systems.)
534 # "level = WARN" logs neither. (Recommended for production systems.)
537 class = StreamHandler
535 class = StreamHandler
538 args = (sys.stderr, )
536 args = (sys.stderr, )
539 level = WARN
537 level = WARN
540 formatter = color_formatter_sql
538 formatter = color_formatter_sql
541
539
542 ################
540 ################
543 ## FORMATTERS ##
541 ## FORMATTERS ##
544 ################
542 ################
545
543
546 [formatter_generic]
544 [formatter_generic]
547 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
545 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
548 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s
546 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s
549 datefmt = %Y-%m-%d %H:%M:%S
547 datefmt = %Y-%m-%d %H:%M:%S
550
548
551 [formatter_color_formatter]
549 [formatter_color_formatter]
552 class = rhodecode.lib.logging_formatter.ColorRequestTrackingFormatter
550 class = rhodecode.lib.logging_formatter.ColorRequestTrackingFormatter
553 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s
551 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s
554 datefmt = %Y-%m-%d %H:%M:%S
552 datefmt = %Y-%m-%d %H:%M:%S
555
553
556 [formatter_color_formatter_sql]
554 [formatter_color_formatter_sql]
557 class = rhodecode.lib.logging_formatter.ColorFormatterSql
555 class = rhodecode.lib.logging_formatter.ColorFormatterSql
558 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
556 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
559 datefmt = %Y-%m-%d %H:%M:%S
557 datefmt = %Y-%m-%d %H:%M:%S
560 """)
558 """)
561
559
562 with tempfile.NamedTemporaryFile(prefix='rc_debug_logging_', suffix='.ini',
560 with tempfile.NamedTemporaryFile(prefix='rc_debug_logging_', suffix='.ini',
563 delete=False) as f:
561 delete=False) as f:
564 log.info('Saved Temporary DEBUG config at %s', f.name)
562 log.info('Saved Temporary DEBUG config at %s', f.name)
565 f.write(ini_template)
563 f.write(ini_template)
566
564
567 logging.config.fileConfig(f.name)
565 logging.config.fileConfig(f.name)
568 log.debug('DEBUG MODE ON')
566 log.debug('DEBUG MODE ON')
569 os.remove(f.name)
567 os.remove(f.name)
570
568
571
569
572 def _sanitize_appenlight_settings(settings):
570 def _sanitize_appenlight_settings(settings):
573 _bool_setting(settings, 'appenlight', 'false')
571 _bool_setting(settings, 'appenlight', 'false')
574
572
575
573
576 def _sanitize_vcs_settings(settings):
574 def _sanitize_vcs_settings(settings):
577 """
575 """
578 Applies settings defaults and does type conversion for all VCS related
576 Applies settings defaults and does type conversion for all VCS related
579 settings.
577 settings.
580 """
578 """
581 _string_setting(settings, 'vcs.svn.compatible_version', '')
579 _string_setting(settings, 'vcs.svn.compatible_version', '')
582 _string_setting(settings, 'vcs.hooks.protocol', 'http')
580 _string_setting(settings, 'vcs.hooks.protocol', 'http')
583 _string_setting(settings, 'vcs.hooks.host', '127.0.0.1')
581 _string_setting(settings, 'vcs.hooks.host', '127.0.0.1')
584 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
582 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
585 _string_setting(settings, 'vcs.server', '')
583 _string_setting(settings, 'vcs.server', '')
586 _string_setting(settings, 'vcs.server.protocol', 'http')
584 _string_setting(settings, 'vcs.server.protocol', 'http')
587 _bool_setting(settings, 'startup.import_repos', 'false')
585 _bool_setting(settings, 'startup.import_repos', 'false')
588 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
586 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
589 _bool_setting(settings, 'vcs.server.enable', 'true')
587 _bool_setting(settings, 'vcs.server.enable', 'true')
590 _bool_setting(settings, 'vcs.start_server', 'false')
588 _bool_setting(settings, 'vcs.start_server', 'false')
591 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
589 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
592 _int_setting(settings, 'vcs.connection_timeout', 3600)
590 _int_setting(settings, 'vcs.connection_timeout', 3600)
593
591
594 # Support legacy values of vcs.scm_app_implementation. Legacy
592 # Support legacy values of vcs.scm_app_implementation. Legacy
595 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http', or
593 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http', or
596 # disabled since 4.13 'vcsserver.scm_app' which is now mapped to 'http'.
594 # disabled since 4.13 'vcsserver.scm_app' which is now mapped to 'http'.
597 scm_app_impl = settings['vcs.scm_app_implementation']
595 scm_app_impl = settings['vcs.scm_app_implementation']
598 if scm_app_impl in ['rhodecode.lib.middleware.utils.scm_app_http', 'vcsserver.scm_app']:
596 if scm_app_impl in ['rhodecode.lib.middleware.utils.scm_app_http', 'vcsserver.scm_app']:
599 settings['vcs.scm_app_implementation'] = 'http'
597 settings['vcs.scm_app_implementation'] = 'http'
600
598
601
599
602 def _sanitize_cache_settings(settings):
600 def _sanitize_cache_settings(settings):
603 temp_store = tempfile.gettempdir()
601 temp_store = tempfile.gettempdir()
604 default_cache_dir = os.path.join(temp_store, 'rc_cache')
602 default_cache_dir = os.path.join(temp_store, 'rc_cache')
605
603
606 # save default, cache dir, and use it for all backends later.
604 # save default, cache dir, and use it for all backends later.
607 default_cache_dir = _string_setting(
605 default_cache_dir = _string_setting(
608 settings,
606 settings,
609 'cache_dir',
607 'cache_dir',
610 default_cache_dir, lower=False, default_when_empty=True)
608 default_cache_dir, lower=False, default_when_empty=True)
611
609
612 # ensure we have our dir created
610 # ensure we have our dir created
613 if not os.path.isdir(default_cache_dir):
611 if not os.path.isdir(default_cache_dir):
614 os.makedirs(default_cache_dir, mode=0o755)
612 os.makedirs(default_cache_dir, mode=0o755)
615
613
616 # exception store cache
614 # exception store cache
617 _string_setting(
615 _string_setting(
618 settings,
616 settings,
619 'exception_tracker.store_path',
617 'exception_tracker.store_path',
620 temp_store, lower=False, default_when_empty=True)
618 temp_store, lower=False, default_when_empty=True)
621 _bool_setting(
619 _bool_setting(
622 settings,
620 settings,
623 'exception_tracker.send_email',
621 'exception_tracker.send_email',
624 'false')
622 'false')
625 _string_setting(
623 _string_setting(
626 settings,
624 settings,
627 'exception_tracker.email_prefix',
625 'exception_tracker.email_prefix',
628 '[RHODECODE ERROR]', lower=False, default_when_empty=True)
626 '[RHODECODE ERROR]', lower=False, default_when_empty=True)
629
627
630 # cache_perms
628 # cache_perms
631 _string_setting(
629 _string_setting(
632 settings,
630 settings,
633 'rc_cache.cache_perms.backend',
631 'rc_cache.cache_perms.backend',
634 'dogpile.cache.rc.file_namespace', lower=False)
632 'dogpile.cache.rc.file_namespace', lower=False)
635 _int_setting(
633 _int_setting(
636 settings,
634 settings,
637 'rc_cache.cache_perms.expiration_time',
635 'rc_cache.cache_perms.expiration_time',
638 60)
636 60)
639 _string_setting(
637 _string_setting(
640 settings,
638 settings,
641 'rc_cache.cache_perms.arguments.filename',
639 'rc_cache.cache_perms.arguments.filename',
642 os.path.join(default_cache_dir, 'rc_cache_1'), lower=False)
640 os.path.join(default_cache_dir, 'rc_cache_1'), lower=False)
643
641
644 # cache_repo
642 # cache_repo
645 _string_setting(
643 _string_setting(
646 settings,
644 settings,
647 'rc_cache.cache_repo.backend',
645 'rc_cache.cache_repo.backend',
648 'dogpile.cache.rc.file_namespace', lower=False)
646 'dogpile.cache.rc.file_namespace', lower=False)
649 _int_setting(
647 _int_setting(
650 settings,
648 settings,
651 'rc_cache.cache_repo.expiration_time',
649 'rc_cache.cache_repo.expiration_time',
652 60)
650 60)
653 _string_setting(
651 _string_setting(
654 settings,
652 settings,
655 'rc_cache.cache_repo.arguments.filename',
653 'rc_cache.cache_repo.arguments.filename',
656 os.path.join(default_cache_dir, 'rc_cache_2'), lower=False)
654 os.path.join(default_cache_dir, 'rc_cache_2'), lower=False)
657
655
658 # cache_license
656 # cache_license
659 _string_setting(
657 _string_setting(
660 settings,
658 settings,
661 'rc_cache.cache_license.backend',
659 'rc_cache.cache_license.backend',
662 'dogpile.cache.rc.file_namespace', lower=False)
660 'dogpile.cache.rc.file_namespace', lower=False)
663 _int_setting(
661 _int_setting(
664 settings,
662 settings,
665 'rc_cache.cache_license.expiration_time',
663 'rc_cache.cache_license.expiration_time',
666 5*60)
664 5*60)
667 _string_setting(
665 _string_setting(
668 settings,
666 settings,
669 'rc_cache.cache_license.arguments.filename',
667 'rc_cache.cache_license.arguments.filename',
670 os.path.join(default_cache_dir, 'rc_cache_3'), lower=False)
668 os.path.join(default_cache_dir, 'rc_cache_3'), lower=False)
671
669
672 # cache_repo_longterm memory, 96H
670 # cache_repo_longterm memory, 96H
673 _string_setting(
671 _string_setting(
674 settings,
672 settings,
675 'rc_cache.cache_repo_longterm.backend',
673 'rc_cache.cache_repo_longterm.backend',
676 'dogpile.cache.rc.memory_lru', lower=False)
674 'dogpile.cache.rc.memory_lru', lower=False)
677 _int_setting(
675 _int_setting(
678 settings,
676 settings,
679 'rc_cache.cache_repo_longterm.expiration_time',
677 'rc_cache.cache_repo_longterm.expiration_time',
680 345600)
678 345600)
681 _int_setting(
679 _int_setting(
682 settings,
680 settings,
683 'rc_cache.cache_repo_longterm.max_size',
681 'rc_cache.cache_repo_longterm.max_size',
684 10000)
682 10000)
685
683
686 # sql_cache_short
684 # sql_cache_short
687 _string_setting(
685 _string_setting(
688 settings,
686 settings,
689 'rc_cache.sql_cache_short.backend',
687 'rc_cache.sql_cache_short.backend',
690 'dogpile.cache.rc.memory_lru', lower=False)
688 'dogpile.cache.rc.memory_lru', lower=False)
691 _int_setting(
689 _int_setting(
692 settings,
690 settings,
693 'rc_cache.sql_cache_short.expiration_time',
691 'rc_cache.sql_cache_short.expiration_time',
694 30)
692 30)
695 _int_setting(
693 _int_setting(
696 settings,
694 settings,
697 'rc_cache.sql_cache_short.max_size',
695 'rc_cache.sql_cache_short.max_size',
698 10000)
696 10000)
699
697
700
698
701 def _int_setting(settings, name, default):
699 def _int_setting(settings, name, default):
702 settings[name] = int(settings.get(name, default))
700 settings[name] = int(settings.get(name, default))
703 return settings[name]
701 return settings[name]
704
702
705
703
706 def _bool_setting(settings, name, default):
704 def _bool_setting(settings, name, default):
707 input_val = settings.get(name, default)
705 input_val = settings.get(name, default)
708 if isinstance(input_val, unicode):
706 if isinstance(input_val, unicode):
709 input_val = input_val.encode('utf8')
707 input_val = input_val.encode('utf8')
710 settings[name] = asbool(input_val)
708 settings[name] = asbool(input_val)
711 return settings[name]
709 return settings[name]
712
710
713
711
714 def _list_setting(settings, name, default):
712 def _list_setting(settings, name, default):
715 raw_value = settings.get(name, default)
713 raw_value = settings.get(name, default)
716
714
717 old_separator = ','
715 old_separator = ','
718 if old_separator in raw_value:
716 if old_separator in raw_value:
719 # If we get a comma separated list, pass it to our own function.
717 # If we get a comma separated list, pass it to our own function.
720 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
718 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
721 else:
719 else:
722 # Otherwise we assume it uses pyramids space/newline separation.
720 # Otherwise we assume it uses pyramids space/newline separation.
723 settings[name] = aslist(raw_value)
721 settings[name] = aslist(raw_value)
724 return settings[name]
722 return settings[name]
725
723
726
724
727 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
725 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
728 value = settings.get(name, default)
726 value = settings.get(name, default)
729
727
730 if default_when_empty and not value:
728 if default_when_empty and not value:
731 # use default value when value is empty
729 # use default value when value is empty
732 value = default
730 value = default
733
731
734 if lower:
732 if lower:
735 value = value.lower()
733 value = value.lower()
736 settings[name] = value
734 settings[name] = value
737 return settings[name]
735 return settings[name]
738
736
739
737
740 def _substitute_values(mapping, substitutions):
738 def _substitute_values(mapping, substitutions):
741 result = {}
739 result = {}
742
740
743 try:
741 try:
744 for key, value in mapping.items():
742 for key, value in mapping.items():
745 # initialize without substitution first
743 # initialize without substitution first
746 result[key] = value
744 result[key] = value
747
745
748 # Note: Cannot use regular replacements, since they would clash
746 # Note: Cannot use regular replacements, since they would clash
749 # with the implementation of ConfigParser. Using "format" instead.
747 # with the implementation of ConfigParser. Using "format" instead.
750 try:
748 try:
751 result[key] = value.format(**substitutions)
749 result[key] = value.format(**substitutions)
752 except KeyError as e:
750 except KeyError as e:
753 env_var = '{}'.format(e.args[0])
751 env_var = '{}'.format(e.args[0])
754
752
755 msg = 'Failed to substitute: `{key}={{{var}}}` with environment entry. ' \
753 msg = 'Failed to substitute: `{key}={{{var}}}` with environment entry. ' \
756 'Make sure your environment has {var} set, or remove this ' \
754 'Make sure your environment has {var} set, or remove this ' \
757 'variable from config file'.format(key=key, var=env_var)
755 'variable from config file'.format(key=key, var=env_var)
758
756
759 if env_var.startswith('ENV_'):
757 if env_var.startswith('ENV_'):
760 raise ValueError(msg)
758 raise ValueError(msg)
761 else:
759 else:
762 log.warning(msg)
760 log.warning(msg)
763
761
764 except ValueError as e:
762 except ValueError as e:
765 log.warning('Failed to substitute ENV variable: %s', e)
763 log.warning('Failed to substitute ENV variable: %s', e)
766 result = mapping
764 result = mapping
767
765
768 return result
766 return result
@@ -1,82 +1,86 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2015-2020 RhodeCode GmbH
3 # Copyright (C) 2015-2020 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 from dogpile.cache import register_backend
22 from dogpile.cache import register_backend
23
23
24 register_backend(
24 register_backend(
25 "dogpile.cache.rc.memory_lru", "rhodecode.lib.rc_cache.backends",
25 "dogpile.cache.rc.memory_lru", "rhodecode.lib.rc_cache.backends",
26 "LRUMemoryBackend")
26 "LRUMemoryBackend")
27
27
28 register_backend(
28 register_backend(
29 "dogpile.cache.rc.file_namespace", "rhodecode.lib.rc_cache.backends",
29 "dogpile.cache.rc.file_namespace", "rhodecode.lib.rc_cache.backends",
30 "FileNamespaceBackend")
30 "FileNamespaceBackend")
31
31
32 register_backend(
32 register_backend(
33 "dogpile.cache.rc.redis", "rhodecode.lib.rc_cache.backends",
33 "dogpile.cache.rc.redis", "rhodecode.lib.rc_cache.backends",
34 "RedisPickleBackend")
34 "RedisPickleBackend")
35
35
36 register_backend(
36 register_backend(
37 "dogpile.cache.rc.redis_msgpack", "rhodecode.lib.rc_cache.backends",
37 "dogpile.cache.rc.redis_msgpack", "rhodecode.lib.rc_cache.backends",
38 "RedisMsgPackBackend")
38 "RedisMsgPackBackend")
39
39
40
40
41 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
42
42
43 from . import region_meta
43 from . import region_meta
44 from .utils import (
44 from .utils import (
45 get_default_cache_settings, backend_key_generator, get_or_create_region,
45 get_default_cache_settings, backend_key_generator, get_or_create_region,
46 clear_cache_namespace, make_region, InvalidationContext,
46 clear_cache_namespace, make_region, InvalidationContext,
47 FreshRegionCache, ActiveRegionCache)
47 FreshRegionCache, ActiveRegionCache)
48
48
49
49
50 FILE_TREE_CACHE_VER = 'v4'
50 FILE_TREE_CACHE_VER = 'v4'
51 LICENSE_CACHE_VER = 'v2'
51 LICENSE_CACHE_VER = 'v2'
52
52
53
53
54 def configure_dogpile_cache(settings):
54 def configure_dogpile_cache(settings):
55 cache_dir = settings.get('cache_dir')
55 cache_dir = settings.get('cache_dir')
56 if cache_dir:
56 if cache_dir:
57 region_meta.dogpile_config_defaults['cache_dir'] = cache_dir
57 region_meta.dogpile_config_defaults['cache_dir'] = cache_dir
58
58
59 rc_cache_data = get_default_cache_settings(settings, prefixes=['rc_cache.'])
59 rc_cache_data = get_default_cache_settings(settings, prefixes=['rc_cache.'])
60
60
61 # inspect available namespaces
61 # inspect available namespaces
62 avail_regions = set()
62 avail_regions = set()
63 for key in rc_cache_data.keys():
63 for key in rc_cache_data.keys():
64 namespace_name = key.split('.', 1)[0]
64 namespace_name = key.split('.', 1)[0]
65 avail_regions.add(namespace_name)
65 avail_regions.add(namespace_name)
66 log.debug('dogpile: found following cache regions: %s', avail_regions)
66 log.debug('dogpile: found following cache regions: %s', avail_regions)
67
67
68 # register them into namespace
68 # register them into namespace
69 for region_name in avail_regions:
69 for region_name in avail_regions:
70 new_region = make_region(
70 new_region = make_region(
71 name=region_name,
71 name=region_name,
72 function_key_generator=None
72 function_key_generator=None
73 )
73 )
74
74
75 new_region.configure_from_config(settings, 'rc_cache.{}.'.format(region_name))
75 new_region.configure_from_config(settings, 'rc_cache.{}.'.format(region_name))
76 new_region.function_key_generator = backend_key_generator(new_region.actual_backend)
76 new_region.function_key_generator = backend_key_generator(new_region.actual_backend)
77 log.debug('dogpile: registering a new region %s[%s]', region_name, new_region.__dict__)
77 if log.isEnabledFor(logging.DEBUG):
78 region_args = dict(backend=new_region.actual_backend.__class__,
79 region_invalidator=new_region.region_invalidator.__class__)
80 log.debug('dogpile: registering a new region `%s` %s', region_name, region_args)
81
78 region_meta.dogpile_cache_regions[region_name] = new_region
82 region_meta.dogpile_cache_regions[region_name] = new_region
79
83
80
84
81 def includeme(config):
85 def includeme(config):
82 configure_dogpile_cache(config.registry.settings)
86 configure_dogpile_cache(config.registry.settings)
@@ -1,395 +1,389 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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 import io
20 import io
21 import math
21 import math
22 import re
22 import re
23 import os
23 import os
24 import datetime
24 import datetime
25 import logging
25 import logging
26 import Queue
26 import Queue
27 import subprocess32
27 import subprocess32
28
28
29
29
30 from dateutil.parser import parse
30 from dateutil.parser import parse
31 from pyramid.threadlocal import get_current_request
31 from pyramid.threadlocal import get_current_request
32 from pyramid.interfaces import IRoutesMapper
32 from pyramid.interfaces import IRoutesMapper
33 from pyramid.settings import asbool
33 from pyramid.settings import asbool
34 from pyramid.path import AssetResolver
34 from pyramid.path import AssetResolver
35 from threading import Thread
35 from threading import Thread
36
36
37 from rhodecode.translation import _ as tsf
37 from rhodecode.translation import _ as tsf
38 from rhodecode.config.jsroutes import generate_jsroutes_content
38 from rhodecode.config.jsroutes import generate_jsroutes_content
39 from rhodecode.lib import auth
39 from rhodecode.lib import auth
40 from rhodecode.lib.base import get_auth_user
40 from rhodecode.lib.base import get_auth_user
41
41
42 import rhodecode
42 import rhodecode
43
43
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47
47
48 def add_renderer_globals(event):
48 def add_renderer_globals(event):
49 from rhodecode.lib import helpers
49 from rhodecode.lib import helpers
50
50
51 # TODO: When executed in pyramid view context the request is not available
51 # TODO: When executed in pyramid view context the request is not available
52 # in the event. Find a better solution to get the request.
52 # in the event. Find a better solution to get the request.
53 request = event['request'] or get_current_request()
53 request = event['request'] or get_current_request()
54
54
55 # Add Pyramid translation as '_' to context
55 # Add Pyramid translation as '_' to context
56 event['_'] = request.translate
56 event['_'] = request.translate
57 event['_ungettext'] = request.plularize
57 event['_ungettext'] = request.plularize
58 event['h'] = helpers
58 event['h'] = helpers
59
59
60
60
61 def add_localizer(event):
61 def add_localizer(event):
62 request = event.request
62 request = event.request
63 localizer = request.localizer
63 localizer = request.localizer
64
64
65 def auto_translate(*args, **kwargs):
65 def auto_translate(*args, **kwargs):
66 return localizer.translate(tsf(*args, **kwargs))
66 return localizer.translate(tsf(*args, **kwargs))
67
67
68 request.translate = auto_translate
68 request.translate = auto_translate
69 request.plularize = localizer.pluralize
69 request.plularize = localizer.pluralize
70
70
71
71
72 def set_user_lang(event):
72 def set_user_lang(event):
73 request = event.request
73 request = event.request
74 cur_user = getattr(request, 'user', None)
74 cur_user = getattr(request, 'user', None)
75
75
76 if cur_user:
76 if cur_user:
77 user_lang = cur_user.get_instance().user_data.get('language')
77 user_lang = cur_user.get_instance().user_data.get('language')
78 if user_lang:
78 if user_lang:
79 log.debug('lang: setting current user:%s language to: %s', cur_user, user_lang)
79 log.debug('lang: setting current user:%s language to: %s', cur_user, user_lang)
80 event.request._LOCALE_ = user_lang
80 event.request._LOCALE_ = user_lang
81
81
82
82
83 def add_request_user_context(event):
83 def add_request_user_context(event):
84 """
84 """
85 Adds auth user into request context
85 Adds auth user into request context
86 """
86 """
87 request = event.request
87 request = event.request
88 # access req_id as soon as possible
88 # access req_id as soon as possible
89 req_id = request.req_id
89 req_id = request.req_id
90
90
91 if hasattr(request, 'vcs_call'):
91 if hasattr(request, 'vcs_call'):
92 # skip vcs calls
92 # skip vcs calls
93 return
93 return
94
94
95 if hasattr(request, 'rpc_method'):
95 if hasattr(request, 'rpc_method'):
96 # skip api calls
96 # skip api calls
97 return
97 return
98
98
99 auth_user, auth_token = get_auth_user(request)
99 auth_user, auth_token = get_auth_user(request)
100 request.user = auth_user
100 request.user = auth_user
101 request.user_auth_token = auth_token
101 request.user_auth_token = auth_token
102 request.environ['rc_auth_user'] = auth_user
102 request.environ['rc_auth_user'] = auth_user
103 request.environ['rc_auth_user_id'] = auth_user.user_id
103 request.environ['rc_auth_user_id'] = auth_user.user_id
104 request.environ['rc_req_id'] = req_id
104 request.environ['rc_req_id'] = req_id
105
105
106
106
107 def inject_app_settings(event):
108 settings = event.app.registry.settings
109 # inject info about available permissions
110 auth.set_available_permissions(settings)
111
112
113 def scan_repositories_if_enabled(event):
107 def scan_repositories_if_enabled(event):
114 """
108 """
115 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
109 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
116 does a repository scan if enabled in the settings.
110 does a repository scan if enabled in the settings.
117 """
111 """
118 settings = event.app.registry.settings
112 settings = event.app.registry.settings
119 vcs_server_enabled = settings['vcs.server.enable']
113 vcs_server_enabled = settings['vcs.server.enable']
120 import_on_startup = settings['startup.import_repos']
114 import_on_startup = settings['startup.import_repos']
121 if vcs_server_enabled and import_on_startup:
115 if vcs_server_enabled and import_on_startup:
122 from rhodecode.model.scm import ScmModel
116 from rhodecode.model.scm import ScmModel
123 from rhodecode.lib.utils import repo2db_mapper, get_rhodecode_base_path
117 from rhodecode.lib.utils import repo2db_mapper, get_rhodecode_base_path
124 repositories = ScmModel().repo_scan(get_rhodecode_base_path())
118 repositories = ScmModel().repo_scan(get_rhodecode_base_path())
125 repo2db_mapper(repositories, remove_obsolete=False)
119 repo2db_mapper(repositories, remove_obsolete=False)
126
120
127
121
128 def write_metadata_if_needed(event):
122 def write_metadata_if_needed(event):
129 """
123 """
130 Writes upgrade metadata
124 Writes upgrade metadata
131 """
125 """
132 import rhodecode
126 import rhodecode
133 from rhodecode.lib import system_info
127 from rhodecode.lib import system_info
134 from rhodecode.lib import ext_json
128 from rhodecode.lib import ext_json
135
129
136 fname = '.rcmetadata.json'
130 fname = '.rcmetadata.json'
137 ini_loc = os.path.dirname(rhodecode.CONFIG.get('__file__'))
131 ini_loc = os.path.dirname(rhodecode.CONFIG.get('__file__'))
138 metadata_destination = os.path.join(ini_loc, fname)
132 metadata_destination = os.path.join(ini_loc, fname)
139
133
140 def get_update_age():
134 def get_update_age():
141 now = datetime.datetime.utcnow()
135 now = datetime.datetime.utcnow()
142
136
143 with open(metadata_destination, 'rb') as f:
137 with open(metadata_destination, 'rb') as f:
144 data = ext_json.json.loads(f.read())
138 data = ext_json.json.loads(f.read())
145 if 'created_on' in data:
139 if 'created_on' in data:
146 update_date = parse(data['created_on'])
140 update_date = parse(data['created_on'])
147 diff = now - update_date
141 diff = now - update_date
148 return diff.total_seconds() / 60.0
142 return diff.total_seconds() / 60.0
149
143
150 return 0
144 return 0
151
145
152 def write():
146 def write():
153 configuration = system_info.SysInfo(
147 configuration = system_info.SysInfo(
154 system_info.rhodecode_config)()['value']
148 system_info.rhodecode_config)()['value']
155 license_token = configuration['config']['license_token']
149 license_token = configuration['config']['license_token']
156
150
157 setup = dict(
151 setup = dict(
158 workers=configuration['config']['server:main'].get(
152 workers=configuration['config']['server:main'].get(
159 'workers', '?'),
153 'workers', '?'),
160 worker_type=configuration['config']['server:main'].get(
154 worker_type=configuration['config']['server:main'].get(
161 'worker_class', 'sync'),
155 'worker_class', 'sync'),
162 )
156 )
163 dbinfo = system_info.SysInfo(system_info.database_info)()['value']
157 dbinfo = system_info.SysInfo(system_info.database_info)()['value']
164 del dbinfo['url']
158 del dbinfo['url']
165
159
166 metadata = dict(
160 metadata = dict(
167 desc='upgrade metadata info',
161 desc='upgrade metadata info',
168 license_token=license_token,
162 license_token=license_token,
169 created_on=datetime.datetime.utcnow().isoformat(),
163 created_on=datetime.datetime.utcnow().isoformat(),
170 usage=system_info.SysInfo(system_info.usage_info)()['value'],
164 usage=system_info.SysInfo(system_info.usage_info)()['value'],
171 platform=system_info.SysInfo(system_info.platform_type)()['value'],
165 platform=system_info.SysInfo(system_info.platform_type)()['value'],
172 database=dbinfo,
166 database=dbinfo,
173 cpu=system_info.SysInfo(system_info.cpu)()['value'],
167 cpu=system_info.SysInfo(system_info.cpu)()['value'],
174 memory=system_info.SysInfo(system_info.memory)()['value'],
168 memory=system_info.SysInfo(system_info.memory)()['value'],
175 setup=setup
169 setup=setup
176 )
170 )
177
171
178 with open(metadata_destination, 'wb') as f:
172 with open(metadata_destination, 'wb') as f:
179 f.write(ext_json.json.dumps(metadata))
173 f.write(ext_json.json.dumps(metadata))
180
174
181 settings = event.app.registry.settings
175 settings = event.app.registry.settings
182 if settings.get('metadata.skip'):
176 if settings.get('metadata.skip'):
183 return
177 return
184
178
185 # only write this every 24h, workers restart caused unwanted delays
179 # only write this every 24h, workers restart caused unwanted delays
186 try:
180 try:
187 age_in_min = get_update_age()
181 age_in_min = get_update_age()
188 except Exception:
182 except Exception:
189 age_in_min = 0
183 age_in_min = 0
190
184
191 if age_in_min > 60 * 60 * 24:
185 if age_in_min > 60 * 60 * 24:
192 return
186 return
193
187
194 try:
188 try:
195 write()
189 write()
196 except Exception:
190 except Exception:
197 pass
191 pass
198
192
199
193
200 def write_usage_data(event):
194 def write_usage_data(event):
201 import rhodecode
195 import rhodecode
202 from rhodecode.lib import system_info
196 from rhodecode.lib import system_info
203 from rhodecode.lib import ext_json
197 from rhodecode.lib import ext_json
204
198
205 settings = event.app.registry.settings
199 settings = event.app.registry.settings
206 instance_tag = settings.get('metadata.write_usage_tag')
200 instance_tag = settings.get('metadata.write_usage_tag')
207 if not settings.get('metadata.write_usage'):
201 if not settings.get('metadata.write_usage'):
208 return
202 return
209
203
210 def get_update_age(dest_file):
204 def get_update_age(dest_file):
211 now = datetime.datetime.utcnow()
205 now = datetime.datetime.utcnow()
212
206
213 with open(dest_file, 'rb') as f:
207 with open(dest_file, 'rb') as f:
214 data = ext_json.json.loads(f.read())
208 data = ext_json.json.loads(f.read())
215 if 'created_on' in data:
209 if 'created_on' in data:
216 update_date = parse(data['created_on'])
210 update_date = parse(data['created_on'])
217 diff = now - update_date
211 diff = now - update_date
218 return math.ceil(diff.total_seconds() / 60.0)
212 return math.ceil(diff.total_seconds() / 60.0)
219
213
220 return 0
214 return 0
221
215
222 utc_date = datetime.datetime.utcnow()
216 utc_date = datetime.datetime.utcnow()
223 hour_quarter = int(math.ceil((utc_date.hour + utc_date.minute/60.0) / 6.))
217 hour_quarter = int(math.ceil((utc_date.hour + utc_date.minute/60.0) / 6.))
224 fname = '.rc_usage_{date.year}{date.month:02d}{date.day:02d}_{hour}.json'.format(
218 fname = '.rc_usage_{date.year}{date.month:02d}{date.day:02d}_{hour}.json'.format(
225 date=utc_date, hour=hour_quarter)
219 date=utc_date, hour=hour_quarter)
226 ini_loc = os.path.dirname(rhodecode.CONFIG.get('__file__'))
220 ini_loc = os.path.dirname(rhodecode.CONFIG.get('__file__'))
227
221
228 usage_dir = os.path.join(ini_loc, '.rcusage')
222 usage_dir = os.path.join(ini_loc, '.rcusage')
229 if not os.path.isdir(usage_dir):
223 if not os.path.isdir(usage_dir):
230 os.makedirs(usage_dir)
224 os.makedirs(usage_dir)
231 usage_metadata_destination = os.path.join(usage_dir, fname)
225 usage_metadata_destination = os.path.join(usage_dir, fname)
232
226
233 try:
227 try:
234 age_in_min = get_update_age(usage_metadata_destination)
228 age_in_min = get_update_age(usage_metadata_destination)
235 except Exception:
229 except Exception:
236 age_in_min = 0
230 age_in_min = 0
237
231
238 # write every 6th hour
232 # write every 6th hour
239 if age_in_min and age_in_min < 60 * 6:
233 if age_in_min and age_in_min < 60 * 6:
240 log.debug('Usage file created %s minutes ago, skipping (threashold: %s)...',
234 log.debug('Usage file created %s minutes ago, skipping (threashold: %s)...',
241 age_in_min, 60 * 6)
235 age_in_min, 60 * 6)
242 return
236 return
243
237
244 def write(dest_file):
238 def write(dest_file):
245 configuration = system_info.SysInfo(system_info.rhodecode_config)()['value']
239 configuration = system_info.SysInfo(system_info.rhodecode_config)()['value']
246 license_token = configuration['config']['license_token']
240 license_token = configuration['config']['license_token']
247
241
248 metadata = dict(
242 metadata = dict(
249 desc='Usage data',
243 desc='Usage data',
250 instance_tag=instance_tag,
244 instance_tag=instance_tag,
251 license_token=license_token,
245 license_token=license_token,
252 created_on=datetime.datetime.utcnow().isoformat(),
246 created_on=datetime.datetime.utcnow().isoformat(),
253 usage=system_info.SysInfo(system_info.usage_info)()['value'],
247 usage=system_info.SysInfo(system_info.usage_info)()['value'],
254 )
248 )
255
249
256 with open(dest_file, 'wb') as f:
250 with open(dest_file, 'wb') as f:
257 f.write(ext_json.json.dumps(metadata, indent=2, sort_keys=True))
251 f.write(ext_json.json.dumps(metadata, indent=2, sort_keys=True))
258
252
259 try:
253 try:
260 log.debug('Writing usage file at: %s', usage_metadata_destination)
254 log.debug('Writing usage file at: %s', usage_metadata_destination)
261 write(usage_metadata_destination)
255 write(usage_metadata_destination)
262 except Exception:
256 except Exception:
263 pass
257 pass
264
258
265
259
266 def write_js_routes_if_enabled(event):
260 def write_js_routes_if_enabled(event):
267 registry = event.app.registry
261 registry = event.app.registry
268
262
269 mapper = registry.queryUtility(IRoutesMapper)
263 mapper = registry.queryUtility(IRoutesMapper)
270 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
264 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
271
265
272 def _extract_route_information(route):
266 def _extract_route_information(route):
273 """
267 """
274 Convert a route into tuple(name, path, args), eg:
268 Convert a route into tuple(name, path, args), eg:
275 ('show_user', '/profile/%(username)s', ['username'])
269 ('show_user', '/profile/%(username)s', ['username'])
276 """
270 """
277
271
278 routepath = route.pattern
272 routepath = route.pattern
279 pattern = route.pattern
273 pattern = route.pattern
280
274
281 def replace(matchobj):
275 def replace(matchobj):
282 if matchobj.group(1):
276 if matchobj.group(1):
283 return "%%(%s)s" % matchobj.group(1).split(':')[0]
277 return "%%(%s)s" % matchobj.group(1).split(':')[0]
284 else:
278 else:
285 return "%%(%s)s" % matchobj.group(2)
279 return "%%(%s)s" % matchobj.group(2)
286
280
287 routepath = _argument_prog.sub(replace, routepath)
281 routepath = _argument_prog.sub(replace, routepath)
288
282
289 if not routepath.startswith('/'):
283 if not routepath.startswith('/'):
290 routepath = '/'+routepath
284 routepath = '/'+routepath
291
285
292 return (
286 return (
293 route.name,
287 route.name,
294 routepath,
288 routepath,
295 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
289 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
296 for arg in _argument_prog.findall(pattern)]
290 for arg in _argument_prog.findall(pattern)]
297 )
291 )
298
292
299 def get_routes():
293 def get_routes():
300 # pyramid routes
294 # pyramid routes
301 for route in mapper.get_routes():
295 for route in mapper.get_routes():
302 if not route.name.startswith('__'):
296 if not route.name.startswith('__'):
303 yield _extract_route_information(route)
297 yield _extract_route_information(route)
304
298
305 if asbool(registry.settings.get('generate_js_files', 'false')):
299 if asbool(registry.settings.get('generate_js_files', 'false')):
306 static_path = AssetResolver().resolve('rhodecode:public').abspath()
300 static_path = AssetResolver().resolve('rhodecode:public').abspath()
307 jsroutes = get_routes()
301 jsroutes = get_routes()
308 jsroutes_file_content = generate_jsroutes_content(jsroutes)
302 jsroutes_file_content = generate_jsroutes_content(jsroutes)
309 jsroutes_file_path = os.path.join(
303 jsroutes_file_path = os.path.join(
310 static_path, 'js', 'rhodecode', 'routes.js')
304 static_path, 'js', 'rhodecode', 'routes.js')
311
305
312 try:
306 try:
313 with io.open(jsroutes_file_path, 'w', encoding='utf-8') as f:
307 with io.open(jsroutes_file_path, 'w', encoding='utf-8') as f:
314 f.write(jsroutes_file_content)
308 f.write(jsroutes_file_content)
315 except Exception:
309 except Exception:
316 log.exception('Failed to write routes.js into %s', jsroutes_file_path)
310 log.exception('Failed to write routes.js into %s', jsroutes_file_path)
317
311
318
312
319 class Subscriber(object):
313 class Subscriber(object):
320 """
314 """
321 Base class for subscribers to the pyramid event system.
315 Base class for subscribers to the pyramid event system.
322 """
316 """
323 def __call__(self, event):
317 def __call__(self, event):
324 self.run(event)
318 self.run(event)
325
319
326 def run(self, event):
320 def run(self, event):
327 raise NotImplementedError('Subclass has to implement this.')
321 raise NotImplementedError('Subclass has to implement this.')
328
322
329
323
330 class AsyncSubscriber(Subscriber):
324 class AsyncSubscriber(Subscriber):
331 """
325 """
332 Subscriber that handles the execution of events in a separate task to not
326 Subscriber that handles the execution of events in a separate task to not
333 block the execution of the code which triggers the event. It puts the
327 block the execution of the code which triggers the event. It puts the
334 received events into a queue from which the worker process takes them in
328 received events into a queue from which the worker process takes them in
335 order.
329 order.
336 """
330 """
337 def __init__(self):
331 def __init__(self):
338 self._stop = False
332 self._stop = False
339 self._eventq = Queue.Queue()
333 self._eventq = Queue.Queue()
340 self._worker = self.create_worker()
334 self._worker = self.create_worker()
341 self._worker.start()
335 self._worker.start()
342
336
343 def __call__(self, event):
337 def __call__(self, event):
344 self._eventq.put(event)
338 self._eventq.put(event)
345
339
346 def create_worker(self):
340 def create_worker(self):
347 worker = Thread(target=self.do_work)
341 worker = Thread(target=self.do_work)
348 worker.daemon = True
342 worker.daemon = True
349 return worker
343 return worker
350
344
351 def stop_worker(self):
345 def stop_worker(self):
352 self._stop = False
346 self._stop = False
353 self._eventq.put(None)
347 self._eventq.put(None)
354 self._worker.join()
348 self._worker.join()
355
349
356 def do_work(self):
350 def do_work(self):
357 while not self._stop:
351 while not self._stop:
358 event = self._eventq.get()
352 event = self._eventq.get()
359 if event is not None:
353 if event is not None:
360 self.run(event)
354 self.run(event)
361
355
362
356
363 class AsyncSubprocessSubscriber(AsyncSubscriber):
357 class AsyncSubprocessSubscriber(AsyncSubscriber):
364 """
358 """
365 Subscriber that uses the subprocess32 module to execute a command if an
359 Subscriber that uses the subprocess32 module to execute a command if an
366 event is received. Events are handled asynchronously.
360 event is received. Events are handled asynchronously.
367 """
361 """
368
362
369 def __init__(self, cmd, timeout=None):
363 def __init__(self, cmd, timeout=None):
370 super(AsyncSubprocessSubscriber, self).__init__()
364 super(AsyncSubprocessSubscriber, self).__init__()
371 self._cmd = cmd
365 self._cmd = cmd
372 self._timeout = timeout
366 self._timeout = timeout
373
367
374 def run(self, event):
368 def run(self, event):
375 cmd = self._cmd
369 cmd = self._cmd
376 timeout = self._timeout
370 timeout = self._timeout
377 log.debug('Executing command %s.', cmd)
371 log.debug('Executing command %s.', cmd)
378
372
379 try:
373 try:
380 output = subprocess32.check_output(
374 output = subprocess32.check_output(
381 cmd, timeout=timeout, stderr=subprocess32.STDOUT)
375 cmd, timeout=timeout, stderr=subprocess32.STDOUT)
382 log.debug('Command finished %s', cmd)
376 log.debug('Command finished %s', cmd)
383 if output:
377 if output:
384 log.debug('Command output: %s', output)
378 log.debug('Command output: %s', output)
385 except subprocess32.TimeoutExpired as e:
379 except subprocess32.TimeoutExpired as e:
386 log.exception('Timeout while executing command.')
380 log.exception('Timeout while executing command.')
387 if e.output:
381 if e.output:
388 log.error('Command output: %s', e.output)
382 log.error('Command output: %s', e.output)
389 except subprocess32.CalledProcessError as e:
383 except subprocess32.CalledProcessError as e:
390 log.exception('Error while executing command.')
384 log.exception('Error while executing command.')
391 if e.output:
385 if e.output:
392 log.error('Command output: %s', e.output)
386 log.error('Command output: %s', e.output)
393 except:
387 except:
394 log.exception(
388 log.exception(
395 'Exception while executing command %s.', cmd)
389 'Exception while executing command %s.', cmd)
General Comments 0
You need to be logged in to leave comments. Login now