##// END OF EJS Templates
repo-auth-tokens: UX, set and disable to VCS scope if selected an repo from select2
marcink -
r2118:3991f1f4 default
parent child Browse files
Show More
@@ -1,1177 +1,1178 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 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.apps._base import BaseAppView, DataGridAppView, UserAppView
31 from rhodecode.apps._base import BaseAppView, DataGridAppView, UserAppView
32 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
32 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
33 from rhodecode.authentication.plugins import auth_rhodecode
33 from rhodecode.authentication.plugins import auth_rhodecode
34 from rhodecode.events import trigger
34 from rhodecode.events import trigger
35
35
36 from rhodecode.lib import audit_logger
36 from rhodecode.lib import audit_logger
37 from rhodecode.lib.exceptions import (
37 from rhodecode.lib.exceptions import (
38 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
38 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
39 UserOwnsUserGroupsException, DefaultUserException)
39 UserOwnsUserGroupsException, DefaultUserException)
40 from rhodecode.lib.ext_json import json
40 from rhodecode.lib.ext_json import json
41 from rhodecode.lib.auth import (
41 from rhodecode.lib.auth import (
42 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
42 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
43 from rhodecode.lib import helpers as h
43 from rhodecode.lib import helpers as h
44 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
44 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
45 from rhodecode.model.auth_token import AuthTokenModel
45 from rhodecode.model.auth_token import AuthTokenModel
46 from rhodecode.model.forms import (
46 from rhodecode.model.forms import (
47 UserForm, UserIndividualPermissionsForm, UserPermissionsForm)
47 UserForm, UserIndividualPermissionsForm, UserPermissionsForm)
48 from rhodecode.model.permission import PermissionModel
48 from rhodecode.model.permission import PermissionModel
49 from rhodecode.model.repo_group import RepoGroupModel
49 from rhodecode.model.repo_group import RepoGroupModel
50 from rhodecode.model.ssh_key import SshKeyModel
50 from rhodecode.model.ssh_key import SshKeyModel
51 from rhodecode.model.user import UserModel
51 from rhodecode.model.user import UserModel
52 from rhodecode.model.user_group import UserGroupModel
52 from rhodecode.model.user_group import UserGroupModel
53 from rhodecode.model.db import (
53 from rhodecode.model.db import (
54 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
54 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
55 UserApiKeys, UserSshKeys, RepoGroup)
55 UserApiKeys, UserSshKeys, RepoGroup)
56 from rhodecode.model.meta import Session
56 from rhodecode.model.meta import Session
57
57
58 log = logging.getLogger(__name__)
58 log = logging.getLogger(__name__)
59
59
60
60
61 class AdminUsersView(BaseAppView, DataGridAppView):
61 class AdminUsersView(BaseAppView, DataGridAppView):
62
62
63 def load_default_context(self):
63 def load_default_context(self):
64 c = self._get_local_tmpl_context()
64 c = self._get_local_tmpl_context()
65 self._register_global_c(c)
65 self._register_global_c(c)
66 return c
66 return c
67
67
68 @LoginRequired()
68 @LoginRequired()
69 @HasPermissionAllDecorator('hg.admin')
69 @HasPermissionAllDecorator('hg.admin')
70 @view_config(
70 @view_config(
71 route_name='users', request_method='GET',
71 route_name='users', request_method='GET',
72 renderer='rhodecode:templates/admin/users/users.mako')
72 renderer='rhodecode:templates/admin/users/users.mako')
73 def users_list(self):
73 def users_list(self):
74 c = self.load_default_context()
74 c = self.load_default_context()
75 return self._get_template_context(c)
75 return self._get_template_context(c)
76
76
77 @LoginRequired()
77 @LoginRequired()
78 @HasPermissionAllDecorator('hg.admin')
78 @HasPermissionAllDecorator('hg.admin')
79 @view_config(
79 @view_config(
80 # renderer defined below
80 # renderer defined below
81 route_name='users_data', request_method='GET',
81 route_name='users_data', request_method='GET',
82 renderer='json_ext', xhr=True)
82 renderer='json_ext', xhr=True)
83 def users_list_data(self):
83 def users_list_data(self):
84 column_map = {
84 column_map = {
85 'first_name': 'name',
85 'first_name': 'name',
86 'last_name': 'lastname',
86 'last_name': 'lastname',
87 }
87 }
88 draw, start, limit = self._extract_chunk(self.request)
88 draw, start, limit = self._extract_chunk(self.request)
89 search_q, order_by, order_dir = self._extract_ordering(
89 search_q, order_by, order_dir = self._extract_ordering(
90 self.request, column_map=column_map)
90 self.request, column_map=column_map)
91
91
92 _render = self.request.get_partial_renderer(
92 _render = self.request.get_partial_renderer(
93 'data_table/_dt_elements.mako')
93 'data_table/_dt_elements.mako')
94
94
95 def user_actions(user_id, username):
95 def user_actions(user_id, username):
96 return _render("user_actions", user_id, username)
96 return _render("user_actions", user_id, username)
97
97
98 users_data_total_count = User.query()\
98 users_data_total_count = User.query()\
99 .filter(User.username != User.DEFAULT_USER) \
99 .filter(User.username != User.DEFAULT_USER) \
100 .count()
100 .count()
101
101
102 # json generate
102 # json generate
103 base_q = User.query().filter(User.username != User.DEFAULT_USER)
103 base_q = User.query().filter(User.username != User.DEFAULT_USER)
104
104
105 if search_q:
105 if search_q:
106 like_expression = u'%{}%'.format(safe_unicode(search_q))
106 like_expression = u'%{}%'.format(safe_unicode(search_q))
107 base_q = base_q.filter(or_(
107 base_q = base_q.filter(or_(
108 User.username.ilike(like_expression),
108 User.username.ilike(like_expression),
109 User._email.ilike(like_expression),
109 User._email.ilike(like_expression),
110 User.name.ilike(like_expression),
110 User.name.ilike(like_expression),
111 User.lastname.ilike(like_expression),
111 User.lastname.ilike(like_expression),
112 ))
112 ))
113
113
114 users_data_total_filtered_count = base_q.count()
114 users_data_total_filtered_count = base_q.count()
115
115
116 sort_col = getattr(User, order_by, None)
116 sort_col = getattr(User, order_by, None)
117 if sort_col:
117 if sort_col:
118 if order_dir == 'asc':
118 if order_dir == 'asc':
119 # handle null values properly to order by NULL last
119 # handle null values properly to order by NULL last
120 if order_by in ['last_activity']:
120 if order_by in ['last_activity']:
121 sort_col = coalesce(sort_col, datetime.date.max)
121 sort_col = coalesce(sort_col, datetime.date.max)
122 sort_col = sort_col.asc()
122 sort_col = sort_col.asc()
123 else:
123 else:
124 # handle null values properly to order by NULL last
124 # handle null values properly to order by NULL last
125 if order_by in ['last_activity']:
125 if order_by in ['last_activity']:
126 sort_col = coalesce(sort_col, datetime.date.min)
126 sort_col = coalesce(sort_col, datetime.date.min)
127 sort_col = sort_col.desc()
127 sort_col = sort_col.desc()
128
128
129 base_q = base_q.order_by(sort_col)
129 base_q = base_q.order_by(sort_col)
130 base_q = base_q.offset(start).limit(limit)
130 base_q = base_q.offset(start).limit(limit)
131
131
132 users_list = base_q.all()
132 users_list = base_q.all()
133
133
134 users_data = []
134 users_data = []
135 for user in users_list:
135 for user in users_list:
136 users_data.append({
136 users_data.append({
137 "username": h.gravatar_with_user(self.request, user.username),
137 "username": h.gravatar_with_user(self.request, user.username),
138 "email": user.email,
138 "email": user.email,
139 "first_name": user.first_name,
139 "first_name": user.first_name,
140 "last_name": user.last_name,
140 "last_name": user.last_name,
141 "last_login": h.format_date(user.last_login),
141 "last_login": h.format_date(user.last_login),
142 "last_activity": h.format_date(user.last_activity),
142 "last_activity": h.format_date(user.last_activity),
143 "active": h.bool2icon(user.active),
143 "active": h.bool2icon(user.active),
144 "active_raw": user.active,
144 "active_raw": user.active,
145 "admin": h.bool2icon(user.admin),
145 "admin": h.bool2icon(user.admin),
146 "extern_type": user.extern_type,
146 "extern_type": user.extern_type,
147 "extern_name": user.extern_name,
147 "extern_name": user.extern_name,
148 "action": user_actions(user.user_id, user.username),
148 "action": user_actions(user.user_id, user.username),
149 })
149 })
150
150
151 data = ({
151 data = ({
152 'draw': draw,
152 'draw': draw,
153 'data': users_data,
153 'data': users_data,
154 'recordsTotal': users_data_total_count,
154 'recordsTotal': users_data_total_count,
155 'recordsFiltered': users_data_total_filtered_count,
155 'recordsFiltered': users_data_total_filtered_count,
156 })
156 })
157
157
158 return data
158 return data
159
159
160 def _set_personal_repo_group_template_vars(self, c_obj):
160 def _set_personal_repo_group_template_vars(self, c_obj):
161 DummyUser = AttributeDict({
161 DummyUser = AttributeDict({
162 'username': '${username}',
162 'username': '${username}',
163 'user_id': '${user_id}',
163 'user_id': '${user_id}',
164 })
164 })
165 c_obj.default_create_repo_group = RepoGroupModel() \
165 c_obj.default_create_repo_group = RepoGroupModel() \
166 .get_default_create_personal_repo_group()
166 .get_default_create_personal_repo_group()
167 c_obj.personal_repo_group_name = RepoGroupModel() \
167 c_obj.personal_repo_group_name = RepoGroupModel() \
168 .get_personal_group_name(DummyUser)
168 .get_personal_group_name(DummyUser)
169
169
170 @LoginRequired()
170 @LoginRequired()
171 @HasPermissionAllDecorator('hg.admin')
171 @HasPermissionAllDecorator('hg.admin')
172 @view_config(
172 @view_config(
173 route_name='users_new', request_method='GET',
173 route_name='users_new', request_method='GET',
174 renderer='rhodecode:templates/admin/users/user_add.mako')
174 renderer='rhodecode:templates/admin/users/user_add.mako')
175 def users_new(self):
175 def users_new(self):
176 _ = self.request.translate
176 _ = self.request.translate
177 c = self.load_default_context()
177 c = self.load_default_context()
178 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
178 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
179 self._set_personal_repo_group_template_vars(c)
179 self._set_personal_repo_group_template_vars(c)
180 return self._get_template_context(c)
180 return self._get_template_context(c)
181
181
182 @LoginRequired()
182 @LoginRequired()
183 @HasPermissionAllDecorator('hg.admin')
183 @HasPermissionAllDecorator('hg.admin')
184 @CSRFRequired()
184 @CSRFRequired()
185 @view_config(
185 @view_config(
186 route_name='users_create', request_method='POST',
186 route_name='users_create', request_method='POST',
187 renderer='rhodecode:templates/admin/users/user_add.mako')
187 renderer='rhodecode:templates/admin/users/user_add.mako')
188 def users_create(self):
188 def users_create(self):
189 _ = self.request.translate
189 _ = self.request.translate
190 c = self.load_default_context()
190 c = self.load_default_context()
191 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
191 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
192 user_model = UserModel()
192 user_model = UserModel()
193 user_form = UserForm()()
193 user_form = UserForm()()
194 try:
194 try:
195 form_result = user_form.to_python(dict(self.request.POST))
195 form_result = user_form.to_python(dict(self.request.POST))
196 user = user_model.create(form_result)
196 user = user_model.create(form_result)
197 Session().flush()
197 Session().flush()
198 creation_data = user.get_api_data()
198 creation_data = user.get_api_data()
199 username = form_result['username']
199 username = form_result['username']
200
200
201 audit_logger.store_web(
201 audit_logger.store_web(
202 'user.create', action_data={'data': creation_data},
202 'user.create', action_data={'data': creation_data},
203 user=c.rhodecode_user)
203 user=c.rhodecode_user)
204
204
205 user_link = h.link_to(
205 user_link = h.link_to(
206 h.escape(username),
206 h.escape(username),
207 h.route_path('user_edit', user_id=user.user_id))
207 h.route_path('user_edit', user_id=user.user_id))
208 h.flash(h.literal(_('Created user %(user_link)s')
208 h.flash(h.literal(_('Created user %(user_link)s')
209 % {'user_link': user_link}), category='success')
209 % {'user_link': user_link}), category='success')
210 Session().commit()
210 Session().commit()
211 except formencode.Invalid as errors:
211 except formencode.Invalid as errors:
212 self._set_personal_repo_group_template_vars(c)
212 self._set_personal_repo_group_template_vars(c)
213 data = render(
213 data = render(
214 'rhodecode:templates/admin/users/user_add.mako',
214 'rhodecode:templates/admin/users/user_add.mako',
215 self._get_template_context(c), self.request)
215 self._get_template_context(c), self.request)
216 html = formencode.htmlfill.render(
216 html = formencode.htmlfill.render(
217 data,
217 data,
218 defaults=errors.value,
218 defaults=errors.value,
219 errors=errors.error_dict or {},
219 errors=errors.error_dict or {},
220 prefix_error=False,
220 prefix_error=False,
221 encoding="UTF-8",
221 encoding="UTF-8",
222 force_defaults=False
222 force_defaults=False
223 )
223 )
224 return Response(html)
224 return Response(html)
225 except UserCreationError as e:
225 except UserCreationError as e:
226 h.flash(e, 'error')
226 h.flash(e, 'error')
227 except Exception:
227 except Exception:
228 log.exception("Exception creation of user")
228 log.exception("Exception creation of user")
229 h.flash(_('Error occurred during creation of user %s')
229 h.flash(_('Error occurred during creation of user %s')
230 % self.request.POST.get('username'), category='error')
230 % self.request.POST.get('username'), category='error')
231 raise HTTPFound(h.route_path('users'))
231 raise HTTPFound(h.route_path('users'))
232
232
233
233
234 class UsersView(UserAppView):
234 class UsersView(UserAppView):
235 ALLOW_SCOPED_TOKENS = False
235 ALLOW_SCOPED_TOKENS = False
236 """
236 """
237 This view has alternative version inside EE, if modified please take a look
237 This view has alternative version inside EE, if modified please take a look
238 in there as well.
238 in there as well.
239 """
239 """
240
240
241 def load_default_context(self):
241 def load_default_context(self):
242 c = self._get_local_tmpl_context()
242 c = self._get_local_tmpl_context()
243 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
243 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
244 c.allowed_languages = [
244 c.allowed_languages = [
245 ('en', 'English (en)'),
245 ('en', 'English (en)'),
246 ('de', 'German (de)'),
246 ('de', 'German (de)'),
247 ('fr', 'French (fr)'),
247 ('fr', 'French (fr)'),
248 ('it', 'Italian (it)'),
248 ('it', 'Italian (it)'),
249 ('ja', 'Japanese (ja)'),
249 ('ja', 'Japanese (ja)'),
250 ('pl', 'Polish (pl)'),
250 ('pl', 'Polish (pl)'),
251 ('pt', 'Portuguese (pt)'),
251 ('pt', 'Portuguese (pt)'),
252 ('ru', 'Russian (ru)'),
252 ('ru', 'Russian (ru)'),
253 ('zh', 'Chinese (zh)'),
253 ('zh', 'Chinese (zh)'),
254 ]
254 ]
255 req = self.request
255 req = self.request
256
256
257 c.available_permissions = req.registry.settings['available_permissions']
257 c.available_permissions = req.registry.settings['available_permissions']
258 PermissionModel().set_global_permission_choices(
258 PermissionModel().set_global_permission_choices(
259 c, gettext_translator=req.translate)
259 c, gettext_translator=req.translate)
260
260
261 self._register_global_c(c)
261 self._register_global_c(c)
262 return c
262 return c
263
263
264 @LoginRequired()
264 @LoginRequired()
265 @HasPermissionAllDecorator('hg.admin')
265 @HasPermissionAllDecorator('hg.admin')
266 @CSRFRequired()
266 @CSRFRequired()
267 @view_config(
267 @view_config(
268 route_name='user_update', request_method='POST',
268 route_name='user_update', request_method='POST',
269 renderer='rhodecode:templates/admin/users/user_edit.mako')
269 renderer='rhodecode:templates/admin/users/user_edit.mako')
270 def user_update(self):
270 def user_update(self):
271 _ = self.request.translate
271 _ = self.request.translate
272 c = self.load_default_context()
272 c = self.load_default_context()
273
273
274 user_id = self.db_user_id
274 user_id = self.db_user_id
275 c.user = self.db_user
275 c.user = self.db_user
276
276
277 c.active = 'profile'
277 c.active = 'profile'
278 c.extern_type = c.user.extern_type
278 c.extern_type = c.user.extern_type
279 c.extern_name = c.user.extern_name
279 c.extern_name = c.user.extern_name
280 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
280 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
281 available_languages = [x[0] for x in c.allowed_languages]
281 available_languages = [x[0] for x in c.allowed_languages]
282 _form = UserForm(edit=True, available_languages=available_languages,
282 _form = UserForm(edit=True, available_languages=available_languages,
283 old_data={'user_id': user_id,
283 old_data={'user_id': user_id,
284 'email': c.user.email})()
284 'email': c.user.email})()
285 form_result = {}
285 form_result = {}
286 old_values = c.user.get_api_data()
286 old_values = c.user.get_api_data()
287 try:
287 try:
288 form_result = _form.to_python(dict(self.request.POST))
288 form_result = _form.to_python(dict(self.request.POST))
289 skip_attrs = ['extern_type', 'extern_name']
289 skip_attrs = ['extern_type', 'extern_name']
290 # TODO: plugin should define if username can be updated
290 # TODO: plugin should define if username can be updated
291 if c.extern_type != "rhodecode":
291 if c.extern_type != "rhodecode":
292 # forbid updating username for external accounts
292 # forbid updating username for external accounts
293 skip_attrs.append('username')
293 skip_attrs.append('username')
294
294
295 UserModel().update_user(
295 UserModel().update_user(
296 user_id, skip_attrs=skip_attrs, **form_result)
296 user_id, skip_attrs=skip_attrs, **form_result)
297
297
298 audit_logger.store_web(
298 audit_logger.store_web(
299 'user.edit', action_data={'old_data': old_values},
299 'user.edit', action_data={'old_data': old_values},
300 user=c.rhodecode_user)
300 user=c.rhodecode_user)
301
301
302 Session().commit()
302 Session().commit()
303 h.flash(_('User updated successfully'), category='success')
303 h.flash(_('User updated successfully'), category='success')
304 except formencode.Invalid as errors:
304 except formencode.Invalid as errors:
305 data = render(
305 data = render(
306 'rhodecode:templates/admin/users/user_edit.mako',
306 'rhodecode:templates/admin/users/user_edit.mako',
307 self._get_template_context(c), self.request)
307 self._get_template_context(c), self.request)
308 html = formencode.htmlfill.render(
308 html = formencode.htmlfill.render(
309 data,
309 data,
310 defaults=errors.value,
310 defaults=errors.value,
311 errors=errors.error_dict or {},
311 errors=errors.error_dict or {},
312 prefix_error=False,
312 prefix_error=False,
313 encoding="UTF-8",
313 encoding="UTF-8",
314 force_defaults=False
314 force_defaults=False
315 )
315 )
316 return Response(html)
316 return Response(html)
317 except UserCreationError as e:
317 except UserCreationError as e:
318 h.flash(e, 'error')
318 h.flash(e, 'error')
319 except Exception:
319 except Exception:
320 log.exception("Exception updating user")
320 log.exception("Exception updating user")
321 h.flash(_('Error occurred during update of user %s')
321 h.flash(_('Error occurred during update of user %s')
322 % form_result.get('username'), category='error')
322 % form_result.get('username'), category='error')
323 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
323 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
324
324
325 @LoginRequired()
325 @LoginRequired()
326 @HasPermissionAllDecorator('hg.admin')
326 @HasPermissionAllDecorator('hg.admin')
327 @CSRFRequired()
327 @CSRFRequired()
328 @view_config(
328 @view_config(
329 route_name='user_delete', request_method='POST',
329 route_name='user_delete', request_method='POST',
330 renderer='rhodecode:templates/admin/users/user_edit.mako')
330 renderer='rhodecode:templates/admin/users/user_edit.mako')
331 def user_delete(self):
331 def user_delete(self):
332 _ = self.request.translate
332 _ = self.request.translate
333 c = self.load_default_context()
333 c = self.load_default_context()
334 c.user = self.db_user
334 c.user = self.db_user
335
335
336 _repos = c.user.repositories
336 _repos = c.user.repositories
337 _repo_groups = c.user.repository_groups
337 _repo_groups = c.user.repository_groups
338 _user_groups = c.user.user_groups
338 _user_groups = c.user.user_groups
339
339
340 handle_repos = None
340 handle_repos = None
341 handle_repo_groups = None
341 handle_repo_groups = None
342 handle_user_groups = None
342 handle_user_groups = None
343 # dummy call for flash of handle
343 # dummy call for flash of handle
344 set_handle_flash_repos = lambda: None
344 set_handle_flash_repos = lambda: None
345 set_handle_flash_repo_groups = lambda: None
345 set_handle_flash_repo_groups = lambda: None
346 set_handle_flash_user_groups = lambda: None
346 set_handle_flash_user_groups = lambda: None
347
347
348 if _repos and self.request.POST.get('user_repos'):
348 if _repos and self.request.POST.get('user_repos'):
349 do = self.request.POST['user_repos']
349 do = self.request.POST['user_repos']
350 if do == 'detach':
350 if do == 'detach':
351 handle_repos = 'detach'
351 handle_repos = 'detach'
352 set_handle_flash_repos = lambda: h.flash(
352 set_handle_flash_repos = lambda: h.flash(
353 _('Detached %s repositories') % len(_repos),
353 _('Detached %s repositories') % len(_repos),
354 category='success')
354 category='success')
355 elif do == 'delete':
355 elif do == 'delete':
356 handle_repos = 'delete'
356 handle_repos = 'delete'
357 set_handle_flash_repos = lambda: h.flash(
357 set_handle_flash_repos = lambda: h.flash(
358 _('Deleted %s repositories') % len(_repos),
358 _('Deleted %s repositories') % len(_repos),
359 category='success')
359 category='success')
360
360
361 if _repo_groups and self.request.POST.get('user_repo_groups'):
361 if _repo_groups and self.request.POST.get('user_repo_groups'):
362 do = self.request.POST['user_repo_groups']
362 do = self.request.POST['user_repo_groups']
363 if do == 'detach':
363 if do == 'detach':
364 handle_repo_groups = 'detach'
364 handle_repo_groups = 'detach'
365 set_handle_flash_repo_groups = lambda: h.flash(
365 set_handle_flash_repo_groups = lambda: h.flash(
366 _('Detached %s repository groups') % len(_repo_groups),
366 _('Detached %s repository groups') % len(_repo_groups),
367 category='success')
367 category='success')
368 elif do == 'delete':
368 elif do == 'delete':
369 handle_repo_groups = 'delete'
369 handle_repo_groups = 'delete'
370 set_handle_flash_repo_groups = lambda: h.flash(
370 set_handle_flash_repo_groups = lambda: h.flash(
371 _('Deleted %s repository groups') % len(_repo_groups),
371 _('Deleted %s repository groups') % len(_repo_groups),
372 category='success')
372 category='success')
373
373
374 if _user_groups and self.request.POST.get('user_user_groups'):
374 if _user_groups and self.request.POST.get('user_user_groups'):
375 do = self.request.POST['user_user_groups']
375 do = self.request.POST['user_user_groups']
376 if do == 'detach':
376 if do == 'detach':
377 handle_user_groups = 'detach'
377 handle_user_groups = 'detach'
378 set_handle_flash_user_groups = lambda: h.flash(
378 set_handle_flash_user_groups = lambda: h.flash(
379 _('Detached %s user groups') % len(_user_groups),
379 _('Detached %s user groups') % len(_user_groups),
380 category='success')
380 category='success')
381 elif do == 'delete':
381 elif do == 'delete':
382 handle_user_groups = 'delete'
382 handle_user_groups = 'delete'
383 set_handle_flash_user_groups = lambda: h.flash(
383 set_handle_flash_user_groups = lambda: h.flash(
384 _('Deleted %s user groups') % len(_user_groups),
384 _('Deleted %s user groups') % len(_user_groups),
385 category='success')
385 category='success')
386
386
387 old_values = c.user.get_api_data()
387 old_values = c.user.get_api_data()
388 try:
388 try:
389 UserModel().delete(c.user, handle_repos=handle_repos,
389 UserModel().delete(c.user, handle_repos=handle_repos,
390 handle_repo_groups=handle_repo_groups,
390 handle_repo_groups=handle_repo_groups,
391 handle_user_groups=handle_user_groups)
391 handle_user_groups=handle_user_groups)
392
392
393 audit_logger.store_web(
393 audit_logger.store_web(
394 'user.delete', action_data={'old_data': old_values},
394 'user.delete', action_data={'old_data': old_values},
395 user=c.rhodecode_user)
395 user=c.rhodecode_user)
396
396
397 Session().commit()
397 Session().commit()
398 set_handle_flash_repos()
398 set_handle_flash_repos()
399 set_handle_flash_repo_groups()
399 set_handle_flash_repo_groups()
400 set_handle_flash_user_groups()
400 set_handle_flash_user_groups()
401 h.flash(_('Successfully deleted user'), category='success')
401 h.flash(_('Successfully deleted user'), category='success')
402 except (UserOwnsReposException, UserOwnsRepoGroupsException,
402 except (UserOwnsReposException, UserOwnsRepoGroupsException,
403 UserOwnsUserGroupsException, DefaultUserException) as e:
403 UserOwnsUserGroupsException, DefaultUserException) as e:
404 h.flash(e, category='warning')
404 h.flash(e, category='warning')
405 except Exception:
405 except Exception:
406 log.exception("Exception during deletion of user")
406 log.exception("Exception during deletion of user")
407 h.flash(_('An error occurred during deletion of user'),
407 h.flash(_('An error occurred during deletion of user'),
408 category='error')
408 category='error')
409 raise HTTPFound(h.route_path('users'))
409 raise HTTPFound(h.route_path('users'))
410
410
411 @LoginRequired()
411 @LoginRequired()
412 @HasPermissionAllDecorator('hg.admin')
412 @HasPermissionAllDecorator('hg.admin')
413 @view_config(
413 @view_config(
414 route_name='user_edit', request_method='GET',
414 route_name='user_edit', request_method='GET',
415 renderer='rhodecode:templates/admin/users/user_edit.mako')
415 renderer='rhodecode:templates/admin/users/user_edit.mako')
416 def user_edit(self):
416 def user_edit(self):
417 _ = self.request.translate
417 _ = self.request.translate
418 c = self.load_default_context()
418 c = self.load_default_context()
419 c.user = self.db_user
419 c.user = self.db_user
420
420
421 c.active = 'profile'
421 c.active = 'profile'
422 c.extern_type = c.user.extern_type
422 c.extern_type = c.user.extern_type
423 c.extern_name = c.user.extern_name
423 c.extern_name = c.user.extern_name
424 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
424 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
425
425
426 defaults = c.user.get_dict()
426 defaults = c.user.get_dict()
427 defaults.update({'language': c.user.user_data.get('language')})
427 defaults.update({'language': c.user.user_data.get('language')})
428
428
429 data = render(
429 data = render(
430 'rhodecode:templates/admin/users/user_edit.mako',
430 'rhodecode:templates/admin/users/user_edit.mako',
431 self._get_template_context(c), self.request)
431 self._get_template_context(c), self.request)
432 html = formencode.htmlfill.render(
432 html = formencode.htmlfill.render(
433 data,
433 data,
434 defaults=defaults,
434 defaults=defaults,
435 encoding="UTF-8",
435 encoding="UTF-8",
436 force_defaults=False
436 force_defaults=False
437 )
437 )
438 return Response(html)
438 return Response(html)
439
439
440 @LoginRequired()
440 @LoginRequired()
441 @HasPermissionAllDecorator('hg.admin')
441 @HasPermissionAllDecorator('hg.admin')
442 @view_config(
442 @view_config(
443 route_name='user_edit_advanced', request_method='GET',
443 route_name='user_edit_advanced', request_method='GET',
444 renderer='rhodecode:templates/admin/users/user_edit.mako')
444 renderer='rhodecode:templates/admin/users/user_edit.mako')
445 def user_edit_advanced(self):
445 def user_edit_advanced(self):
446 _ = self.request.translate
446 _ = self.request.translate
447 c = self.load_default_context()
447 c = self.load_default_context()
448
448
449 user_id = self.db_user_id
449 user_id = self.db_user_id
450 c.user = self.db_user
450 c.user = self.db_user
451
451
452 c.active = 'advanced'
452 c.active = 'advanced'
453 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
453 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
454 c.personal_repo_group_name = RepoGroupModel()\
454 c.personal_repo_group_name = RepoGroupModel()\
455 .get_personal_group_name(c.user)
455 .get_personal_group_name(c.user)
456
456
457 c.user_to_review_rules = sorted(
457 c.user_to_review_rules = sorted(
458 (x.user for x in c.user.user_review_rules),
458 (x.user for x in c.user.user_review_rules),
459 key=lambda u: u.username.lower())
459 key=lambda u: u.username.lower())
460
460
461 c.first_admin = User.get_first_super_admin()
461 c.first_admin = User.get_first_super_admin()
462 defaults = c.user.get_dict()
462 defaults = c.user.get_dict()
463
463
464 # Interim workaround if the user participated on any pull requests as a
464 # Interim workaround if the user participated on any pull requests as a
465 # reviewer.
465 # reviewer.
466 has_review = len(c.user.reviewer_pull_requests)
466 has_review = len(c.user.reviewer_pull_requests)
467 c.can_delete_user = not has_review
467 c.can_delete_user = not has_review
468 c.can_delete_user_message = ''
468 c.can_delete_user_message = ''
469 inactive_link = h.link_to(
469 inactive_link = h.link_to(
470 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
470 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
471 if has_review == 1:
471 if has_review == 1:
472 c.can_delete_user_message = h.literal(_(
472 c.can_delete_user_message = h.literal(_(
473 'The user participates as reviewer in {} pull request and '
473 'The user participates as reviewer in {} pull request and '
474 'cannot be deleted. \nYou can set the user to '
474 'cannot be deleted. \nYou can set the user to '
475 '"{}" instead of deleting it.').format(
475 '"{}" instead of deleting it.').format(
476 has_review, inactive_link))
476 has_review, inactive_link))
477 elif has_review:
477 elif has_review:
478 c.can_delete_user_message = h.literal(_(
478 c.can_delete_user_message = h.literal(_(
479 'The user participates as reviewer in {} pull requests and '
479 'The user participates as reviewer in {} pull requests and '
480 'cannot be deleted. \nYou can set the user to '
480 'cannot be deleted. \nYou can set the user to '
481 '"{}" instead of deleting it.').format(
481 '"{}" instead of deleting it.').format(
482 has_review, inactive_link))
482 has_review, inactive_link))
483
483
484 data = render(
484 data = render(
485 'rhodecode:templates/admin/users/user_edit.mako',
485 'rhodecode:templates/admin/users/user_edit.mako',
486 self._get_template_context(c), self.request)
486 self._get_template_context(c), self.request)
487 html = formencode.htmlfill.render(
487 html = formencode.htmlfill.render(
488 data,
488 data,
489 defaults=defaults,
489 defaults=defaults,
490 encoding="UTF-8",
490 encoding="UTF-8",
491 force_defaults=False
491 force_defaults=False
492 )
492 )
493 return Response(html)
493 return Response(html)
494
494
495 @LoginRequired()
495 @LoginRequired()
496 @HasPermissionAllDecorator('hg.admin')
496 @HasPermissionAllDecorator('hg.admin')
497 @view_config(
497 @view_config(
498 route_name='user_edit_global_perms', request_method='GET',
498 route_name='user_edit_global_perms', request_method='GET',
499 renderer='rhodecode:templates/admin/users/user_edit.mako')
499 renderer='rhodecode:templates/admin/users/user_edit.mako')
500 def user_edit_global_perms(self):
500 def user_edit_global_perms(self):
501 _ = self.request.translate
501 _ = self.request.translate
502 c = self.load_default_context()
502 c = self.load_default_context()
503 c.user = self.db_user
503 c.user = self.db_user
504
504
505 c.active = 'global_perms'
505 c.active = 'global_perms'
506
506
507 c.default_user = User.get_default_user()
507 c.default_user = User.get_default_user()
508 defaults = c.user.get_dict()
508 defaults = c.user.get_dict()
509 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
509 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
510 defaults.update(c.default_user.get_default_perms())
510 defaults.update(c.default_user.get_default_perms())
511 defaults.update(c.user.get_default_perms())
511 defaults.update(c.user.get_default_perms())
512
512
513 data = render(
513 data = render(
514 'rhodecode:templates/admin/users/user_edit.mako',
514 'rhodecode:templates/admin/users/user_edit.mako',
515 self._get_template_context(c), self.request)
515 self._get_template_context(c), self.request)
516 html = formencode.htmlfill.render(
516 html = formencode.htmlfill.render(
517 data,
517 data,
518 defaults=defaults,
518 defaults=defaults,
519 encoding="UTF-8",
519 encoding="UTF-8",
520 force_defaults=False
520 force_defaults=False
521 )
521 )
522 return Response(html)
522 return Response(html)
523
523
524 @LoginRequired()
524 @LoginRequired()
525 @HasPermissionAllDecorator('hg.admin')
525 @HasPermissionAllDecorator('hg.admin')
526 @CSRFRequired()
526 @CSRFRequired()
527 @view_config(
527 @view_config(
528 route_name='user_edit_global_perms_update', request_method='POST',
528 route_name='user_edit_global_perms_update', request_method='POST',
529 renderer='rhodecode:templates/admin/users/user_edit.mako')
529 renderer='rhodecode:templates/admin/users/user_edit.mako')
530 def user_edit_global_perms_update(self):
530 def user_edit_global_perms_update(self):
531 _ = self.request.translate
531 _ = self.request.translate
532 c = self.load_default_context()
532 c = self.load_default_context()
533
533
534 user_id = self.db_user_id
534 user_id = self.db_user_id
535 c.user = self.db_user
535 c.user = self.db_user
536
536
537 c.active = 'global_perms'
537 c.active = 'global_perms'
538 try:
538 try:
539 # first stage that verifies the checkbox
539 # first stage that verifies the checkbox
540 _form = UserIndividualPermissionsForm()
540 _form = UserIndividualPermissionsForm()
541 form_result = _form.to_python(dict(self.request.POST))
541 form_result = _form.to_python(dict(self.request.POST))
542 inherit_perms = form_result['inherit_default_permissions']
542 inherit_perms = form_result['inherit_default_permissions']
543 c.user.inherit_default_permissions = inherit_perms
543 c.user.inherit_default_permissions = inherit_perms
544 Session().add(c.user)
544 Session().add(c.user)
545
545
546 if not inherit_perms:
546 if not inherit_perms:
547 # only update the individual ones if we un check the flag
547 # only update the individual ones if we un check the flag
548 _form = UserPermissionsForm(
548 _form = UserPermissionsForm(
549 [x[0] for x in c.repo_create_choices],
549 [x[0] for x in c.repo_create_choices],
550 [x[0] for x in c.repo_create_on_write_choices],
550 [x[0] for x in c.repo_create_on_write_choices],
551 [x[0] for x in c.repo_group_create_choices],
551 [x[0] for x in c.repo_group_create_choices],
552 [x[0] for x in c.user_group_create_choices],
552 [x[0] for x in c.user_group_create_choices],
553 [x[0] for x in c.fork_choices],
553 [x[0] for x in c.fork_choices],
554 [x[0] for x in c.inherit_default_permission_choices])()
554 [x[0] for x in c.inherit_default_permission_choices])()
555
555
556 form_result = _form.to_python(dict(self.request.POST))
556 form_result = _form.to_python(dict(self.request.POST))
557 form_result.update({'perm_user_id': c.user.user_id})
557 form_result.update({'perm_user_id': c.user.user_id})
558
558
559 PermissionModel().update_user_permissions(form_result)
559 PermissionModel().update_user_permissions(form_result)
560
560
561 # TODO(marcink): implement global permissions
561 # TODO(marcink): implement global permissions
562 # audit_log.store_web('user.edit.permissions')
562 # audit_log.store_web('user.edit.permissions')
563
563
564 Session().commit()
564 Session().commit()
565 h.flash(_('User global permissions updated successfully'),
565 h.flash(_('User global permissions updated successfully'),
566 category='success')
566 category='success')
567
567
568 except formencode.Invalid as errors:
568 except formencode.Invalid as errors:
569 data = render(
569 data = render(
570 'rhodecode:templates/admin/users/user_edit.mako',
570 'rhodecode:templates/admin/users/user_edit.mako',
571 self._get_template_context(c), self.request)
571 self._get_template_context(c), self.request)
572 html = formencode.htmlfill.render(
572 html = formencode.htmlfill.render(
573 data,
573 data,
574 defaults=errors.value,
574 defaults=errors.value,
575 errors=errors.error_dict or {},
575 errors=errors.error_dict or {},
576 prefix_error=False,
576 prefix_error=False,
577 encoding="UTF-8",
577 encoding="UTF-8",
578 force_defaults=False
578 force_defaults=False
579 )
579 )
580 return Response(html)
580 return Response(html)
581 except Exception:
581 except Exception:
582 log.exception("Exception during permissions saving")
582 log.exception("Exception during permissions saving")
583 h.flash(_('An error occurred during permissions saving'),
583 h.flash(_('An error occurred during permissions saving'),
584 category='error')
584 category='error')
585 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
585 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
586
586
587 @LoginRequired()
587 @LoginRequired()
588 @HasPermissionAllDecorator('hg.admin')
588 @HasPermissionAllDecorator('hg.admin')
589 @CSRFRequired()
589 @CSRFRequired()
590 @view_config(
590 @view_config(
591 route_name='user_force_password_reset', request_method='POST',
591 route_name='user_force_password_reset', request_method='POST',
592 renderer='rhodecode:templates/admin/users/user_edit.mako')
592 renderer='rhodecode:templates/admin/users/user_edit.mako')
593 def user_force_password_reset(self):
593 def user_force_password_reset(self):
594 """
594 """
595 toggle reset password flag for this user
595 toggle reset password flag for this user
596 """
596 """
597 _ = self.request.translate
597 _ = self.request.translate
598 c = self.load_default_context()
598 c = self.load_default_context()
599
599
600 user_id = self.db_user_id
600 user_id = self.db_user_id
601 c.user = self.db_user
601 c.user = self.db_user
602
602
603 try:
603 try:
604 old_value = c.user.user_data.get('force_password_change')
604 old_value = c.user.user_data.get('force_password_change')
605 c.user.update_userdata(force_password_change=not old_value)
605 c.user.update_userdata(force_password_change=not old_value)
606
606
607 if old_value:
607 if old_value:
608 msg = _('Force password change disabled for user')
608 msg = _('Force password change disabled for user')
609 audit_logger.store_web(
609 audit_logger.store_web(
610 'user.edit.password_reset.disabled',
610 'user.edit.password_reset.disabled',
611 user=c.rhodecode_user)
611 user=c.rhodecode_user)
612 else:
612 else:
613 msg = _('Force password change enabled for user')
613 msg = _('Force password change enabled for user')
614 audit_logger.store_web(
614 audit_logger.store_web(
615 'user.edit.password_reset.enabled',
615 'user.edit.password_reset.enabled',
616 user=c.rhodecode_user)
616 user=c.rhodecode_user)
617
617
618 Session().commit()
618 Session().commit()
619 h.flash(msg, category='success')
619 h.flash(msg, category='success')
620 except Exception:
620 except Exception:
621 log.exception("Exception during password reset for user")
621 log.exception("Exception during password reset for user")
622 h.flash(_('An error occurred during password reset for user'),
622 h.flash(_('An error occurred during password reset for user'),
623 category='error')
623 category='error')
624
624
625 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
625 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
626
626
627 @LoginRequired()
627 @LoginRequired()
628 @HasPermissionAllDecorator('hg.admin')
628 @HasPermissionAllDecorator('hg.admin')
629 @CSRFRequired()
629 @CSRFRequired()
630 @view_config(
630 @view_config(
631 route_name='user_create_personal_repo_group', request_method='POST',
631 route_name='user_create_personal_repo_group', request_method='POST',
632 renderer='rhodecode:templates/admin/users/user_edit.mako')
632 renderer='rhodecode:templates/admin/users/user_edit.mako')
633 def user_create_personal_repo_group(self):
633 def user_create_personal_repo_group(self):
634 """
634 """
635 Create personal repository group for this user
635 Create personal repository group for this user
636 """
636 """
637 from rhodecode.model.repo_group import RepoGroupModel
637 from rhodecode.model.repo_group import RepoGroupModel
638
638
639 _ = self.request.translate
639 _ = self.request.translate
640 c = self.load_default_context()
640 c = self.load_default_context()
641
641
642 user_id = self.db_user_id
642 user_id = self.db_user_id
643 c.user = self.db_user
643 c.user = self.db_user
644
644
645 personal_repo_group = RepoGroup.get_user_personal_repo_group(
645 personal_repo_group = RepoGroup.get_user_personal_repo_group(
646 c.user.user_id)
646 c.user.user_id)
647 if personal_repo_group:
647 if personal_repo_group:
648 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
648 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
649
649
650 personal_repo_group_name = RepoGroupModel().get_personal_group_name(
650 personal_repo_group_name = RepoGroupModel().get_personal_group_name(
651 c.user)
651 c.user)
652 named_personal_group = RepoGroup.get_by_group_name(
652 named_personal_group = RepoGroup.get_by_group_name(
653 personal_repo_group_name)
653 personal_repo_group_name)
654 try:
654 try:
655
655
656 if named_personal_group and named_personal_group.user_id == c.user.user_id:
656 if named_personal_group and named_personal_group.user_id == c.user.user_id:
657 # migrate the same named group, and mark it as personal
657 # migrate the same named group, and mark it as personal
658 named_personal_group.personal = True
658 named_personal_group.personal = True
659 Session().add(named_personal_group)
659 Session().add(named_personal_group)
660 Session().commit()
660 Session().commit()
661 msg = _('Linked repository group `%s` as personal' % (
661 msg = _('Linked repository group `%s` as personal' % (
662 personal_repo_group_name,))
662 personal_repo_group_name,))
663 h.flash(msg, category='success')
663 h.flash(msg, category='success')
664 elif not named_personal_group:
664 elif not named_personal_group:
665 RepoGroupModel().create_personal_repo_group(c.user)
665 RepoGroupModel().create_personal_repo_group(c.user)
666
666
667 msg = _('Created repository group `%s`' % (
667 msg = _('Created repository group `%s`' % (
668 personal_repo_group_name,))
668 personal_repo_group_name,))
669 h.flash(msg, category='success')
669 h.flash(msg, category='success')
670 else:
670 else:
671 msg = _('Repository group `%s` is already taken' % (
671 msg = _('Repository group `%s` is already taken' % (
672 personal_repo_group_name,))
672 personal_repo_group_name,))
673 h.flash(msg, category='warning')
673 h.flash(msg, category='warning')
674 except Exception:
674 except Exception:
675 log.exception("Exception during repository group creation")
675 log.exception("Exception during repository group creation")
676 msg = _(
676 msg = _(
677 'An error occurred during repository group creation for user')
677 'An error occurred during repository group creation for user')
678 h.flash(msg, category='error')
678 h.flash(msg, category='error')
679 Session().rollback()
679 Session().rollback()
680
680
681 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
681 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
682
682
683 @LoginRequired()
683 @LoginRequired()
684 @HasPermissionAllDecorator('hg.admin')
684 @HasPermissionAllDecorator('hg.admin')
685 @view_config(
685 @view_config(
686 route_name='edit_user_auth_tokens', request_method='GET',
686 route_name='edit_user_auth_tokens', request_method='GET',
687 renderer='rhodecode:templates/admin/users/user_edit.mako')
687 renderer='rhodecode:templates/admin/users/user_edit.mako')
688 def auth_tokens(self):
688 def auth_tokens(self):
689 _ = self.request.translate
689 _ = self.request.translate
690 c = self.load_default_context()
690 c = self.load_default_context()
691 c.user = self.db_user
691 c.user = self.db_user
692
692
693 c.active = 'auth_tokens'
693 c.active = 'auth_tokens'
694
694
695 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
695 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
696 c.role_values = [
696 c.role_values = [
697 (x, AuthTokenModel.cls._get_role_name(x))
697 (x, AuthTokenModel.cls._get_role_name(x))
698 for x in AuthTokenModel.cls.ROLES]
698 for x in AuthTokenModel.cls.ROLES]
699 c.role_options = [(c.role_values, _("Role"))]
699 c.role_options = [(c.role_values, _("Role"))]
700 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
700 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
701 c.user.user_id, show_expired=True)
701 c.user.user_id, show_expired=True)
702 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
702 return self._get_template_context(c)
703 return self._get_template_context(c)
703
704
704 def maybe_attach_token_scope(self, token):
705 def maybe_attach_token_scope(self, token):
705 # implemented in EE edition
706 # implemented in EE edition
706 pass
707 pass
707
708
708 @LoginRequired()
709 @LoginRequired()
709 @HasPermissionAllDecorator('hg.admin')
710 @HasPermissionAllDecorator('hg.admin')
710 @CSRFRequired()
711 @CSRFRequired()
711 @view_config(
712 @view_config(
712 route_name='edit_user_auth_tokens_add', request_method='POST')
713 route_name='edit_user_auth_tokens_add', request_method='POST')
713 def auth_tokens_add(self):
714 def auth_tokens_add(self):
714 _ = self.request.translate
715 _ = self.request.translate
715 c = self.load_default_context()
716 c = self.load_default_context()
716
717
717 user_id = self.db_user_id
718 user_id = self.db_user_id
718 c.user = self.db_user
719 c.user = self.db_user
719
720
720 user_data = c.user.get_api_data()
721 user_data = c.user.get_api_data()
721 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
722 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
722 description = self.request.POST.get('description')
723 description = self.request.POST.get('description')
723 role = self.request.POST.get('role')
724 role = self.request.POST.get('role')
724
725
725 token = AuthTokenModel().create(
726 token = AuthTokenModel().create(
726 c.user.user_id, description, lifetime, role)
727 c.user.user_id, description, lifetime, role)
727 token_data = token.get_api_data()
728 token_data = token.get_api_data()
728
729
729 self.maybe_attach_token_scope(token)
730 self.maybe_attach_token_scope(token)
730 audit_logger.store_web(
731 audit_logger.store_web(
731 'user.edit.token.add', action_data={
732 'user.edit.token.add', action_data={
732 'data': {'token': token_data, 'user': user_data}},
733 'data': {'token': token_data, 'user': user_data}},
733 user=self._rhodecode_user, )
734 user=self._rhodecode_user, )
734 Session().commit()
735 Session().commit()
735
736
736 h.flash(_("Auth token successfully created"), category='success')
737 h.flash(_("Auth token successfully created"), category='success')
737 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
738 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
738
739
739 @LoginRequired()
740 @LoginRequired()
740 @HasPermissionAllDecorator('hg.admin')
741 @HasPermissionAllDecorator('hg.admin')
741 @CSRFRequired()
742 @CSRFRequired()
742 @view_config(
743 @view_config(
743 route_name='edit_user_auth_tokens_delete', request_method='POST')
744 route_name='edit_user_auth_tokens_delete', request_method='POST')
744 def auth_tokens_delete(self):
745 def auth_tokens_delete(self):
745 _ = self.request.translate
746 _ = self.request.translate
746 c = self.load_default_context()
747 c = self.load_default_context()
747
748
748 user_id = self.db_user_id
749 user_id = self.db_user_id
749 c.user = self.db_user
750 c.user = self.db_user
750
751
751 user_data = c.user.get_api_data()
752 user_data = c.user.get_api_data()
752
753
753 del_auth_token = self.request.POST.get('del_auth_token')
754 del_auth_token = self.request.POST.get('del_auth_token')
754
755
755 if del_auth_token:
756 if del_auth_token:
756 token = UserApiKeys.get_or_404(del_auth_token)
757 token = UserApiKeys.get_or_404(del_auth_token)
757 token_data = token.get_api_data()
758 token_data = token.get_api_data()
758
759
759 AuthTokenModel().delete(del_auth_token, c.user.user_id)
760 AuthTokenModel().delete(del_auth_token, c.user.user_id)
760 audit_logger.store_web(
761 audit_logger.store_web(
761 'user.edit.token.delete', action_data={
762 'user.edit.token.delete', action_data={
762 'data': {'token': token_data, 'user': user_data}},
763 'data': {'token': token_data, 'user': user_data}},
763 user=self._rhodecode_user,)
764 user=self._rhodecode_user,)
764 Session().commit()
765 Session().commit()
765 h.flash(_("Auth token successfully deleted"), category='success')
766 h.flash(_("Auth token successfully deleted"), category='success')
766
767
767 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
768 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
768
769
769 @LoginRequired()
770 @LoginRequired()
770 @HasPermissionAllDecorator('hg.admin')
771 @HasPermissionAllDecorator('hg.admin')
771 @view_config(
772 @view_config(
772 route_name='edit_user_ssh_keys', request_method='GET',
773 route_name='edit_user_ssh_keys', request_method='GET',
773 renderer='rhodecode:templates/admin/users/user_edit.mako')
774 renderer='rhodecode:templates/admin/users/user_edit.mako')
774 def ssh_keys(self):
775 def ssh_keys(self):
775 _ = self.request.translate
776 _ = self.request.translate
776 c = self.load_default_context()
777 c = self.load_default_context()
777 c.user = self.db_user
778 c.user = self.db_user
778
779
779 c.active = 'ssh_keys'
780 c.active = 'ssh_keys'
780 c.default_key = self.request.GET.get('default_key')
781 c.default_key = self.request.GET.get('default_key')
781 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
782 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
782 return self._get_template_context(c)
783 return self._get_template_context(c)
783
784
784 @LoginRequired()
785 @LoginRequired()
785 @HasPermissionAllDecorator('hg.admin')
786 @HasPermissionAllDecorator('hg.admin')
786 @view_config(
787 @view_config(
787 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
788 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
788 renderer='rhodecode:templates/admin/users/user_edit.mako')
789 renderer='rhodecode:templates/admin/users/user_edit.mako')
789 def ssh_keys_generate_keypair(self):
790 def ssh_keys_generate_keypair(self):
790 _ = self.request.translate
791 _ = self.request.translate
791 c = self.load_default_context()
792 c = self.load_default_context()
792
793
793 c.user = self.db_user
794 c.user = self.db_user
794
795
795 c.active = 'ssh_keys_generate'
796 c.active = 'ssh_keys_generate'
796 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
797 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
797 c.private, c.public = SshKeyModel().generate_keypair(comment=comment)
798 c.private, c.public = SshKeyModel().generate_keypair(comment=comment)
798
799
799 return self._get_template_context(c)
800 return self._get_template_context(c)
800
801
801 @LoginRequired()
802 @LoginRequired()
802 @HasPermissionAllDecorator('hg.admin')
803 @HasPermissionAllDecorator('hg.admin')
803 @CSRFRequired()
804 @CSRFRequired()
804 @view_config(
805 @view_config(
805 route_name='edit_user_ssh_keys_add', request_method='POST')
806 route_name='edit_user_ssh_keys_add', request_method='POST')
806 def ssh_keys_add(self):
807 def ssh_keys_add(self):
807 _ = self.request.translate
808 _ = self.request.translate
808 c = self.load_default_context()
809 c = self.load_default_context()
809
810
810 user_id = self.db_user_id
811 user_id = self.db_user_id
811 c.user = self.db_user
812 c.user = self.db_user
812
813
813 user_data = c.user.get_api_data()
814 user_data = c.user.get_api_data()
814 key_data = self.request.POST.get('key_data')
815 key_data = self.request.POST.get('key_data')
815 description = self.request.POST.get('description')
816 description = self.request.POST.get('description')
816
817
817 try:
818 try:
818 if not key_data:
819 if not key_data:
819 raise ValueError('Please add a valid public key')
820 raise ValueError('Please add a valid public key')
820
821
821 key = SshKeyModel().parse_key(key_data.strip())
822 key = SshKeyModel().parse_key(key_data.strip())
822 fingerprint = key.hash_md5()
823 fingerprint = key.hash_md5()
823
824
824 ssh_key = SshKeyModel().create(
825 ssh_key = SshKeyModel().create(
825 c.user.user_id, fingerprint, key_data, description)
826 c.user.user_id, fingerprint, key_data, description)
826 ssh_key_data = ssh_key.get_api_data()
827 ssh_key_data = ssh_key.get_api_data()
827
828
828 audit_logger.store_web(
829 audit_logger.store_web(
829 'user.edit.ssh_key.add', action_data={
830 'user.edit.ssh_key.add', action_data={
830 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
831 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
831 user=self._rhodecode_user, )
832 user=self._rhodecode_user, )
832 Session().commit()
833 Session().commit()
833
834
834 # Trigger an event on change of keys.
835 # Trigger an event on change of keys.
835 trigger(SshKeyFileChangeEvent(), self.request.registry)
836 trigger(SshKeyFileChangeEvent(), self.request.registry)
836
837
837 h.flash(_("Ssh Key successfully created"), category='success')
838 h.flash(_("Ssh Key successfully created"), category='success')
838
839
839 except IntegrityError:
840 except IntegrityError:
840 log.exception("Exception during ssh key saving")
841 log.exception("Exception during ssh key saving")
841 h.flash(_('An error occurred during ssh key saving: {}').format(
842 h.flash(_('An error occurred during ssh key saving: {}').format(
842 'Such key already exists, please use a different one'),
843 'Such key already exists, please use a different one'),
843 category='error')
844 category='error')
844 except Exception as e:
845 except Exception as e:
845 log.exception("Exception during ssh key saving")
846 log.exception("Exception during ssh key saving")
846 h.flash(_('An error occurred during ssh key saving: {}').format(e),
847 h.flash(_('An error occurred during ssh key saving: {}').format(e),
847 category='error')
848 category='error')
848
849
849 return HTTPFound(
850 return HTTPFound(
850 h.route_path('edit_user_ssh_keys', user_id=user_id))
851 h.route_path('edit_user_ssh_keys', user_id=user_id))
851
852
852 @LoginRequired()
853 @LoginRequired()
853 @HasPermissionAllDecorator('hg.admin')
854 @HasPermissionAllDecorator('hg.admin')
854 @CSRFRequired()
855 @CSRFRequired()
855 @view_config(
856 @view_config(
856 route_name='edit_user_ssh_keys_delete', request_method='POST')
857 route_name='edit_user_ssh_keys_delete', request_method='POST')
857 def ssh_keys_delete(self):
858 def ssh_keys_delete(self):
858 _ = self.request.translate
859 _ = self.request.translate
859 c = self.load_default_context()
860 c = self.load_default_context()
860
861
861 user_id = self.db_user_id
862 user_id = self.db_user_id
862 c.user = self.db_user
863 c.user = self.db_user
863
864
864 user_data = c.user.get_api_data()
865 user_data = c.user.get_api_data()
865
866
866 del_ssh_key = self.request.POST.get('del_ssh_key')
867 del_ssh_key = self.request.POST.get('del_ssh_key')
867
868
868 if del_ssh_key:
869 if del_ssh_key:
869 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
870 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
870 ssh_key_data = ssh_key.get_api_data()
871 ssh_key_data = ssh_key.get_api_data()
871
872
872 SshKeyModel().delete(del_ssh_key, c.user.user_id)
873 SshKeyModel().delete(del_ssh_key, c.user.user_id)
873 audit_logger.store_web(
874 audit_logger.store_web(
874 'user.edit.ssh_key.delete', action_data={
875 'user.edit.ssh_key.delete', action_data={
875 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
876 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
876 user=self._rhodecode_user,)
877 user=self._rhodecode_user,)
877 Session().commit()
878 Session().commit()
878 # Trigger an event on change of keys.
879 # Trigger an event on change of keys.
879 trigger(SshKeyFileChangeEvent(), self.request.registry)
880 trigger(SshKeyFileChangeEvent(), self.request.registry)
880 h.flash(_("Ssh key successfully deleted"), category='success')
881 h.flash(_("Ssh key successfully deleted"), category='success')
881
882
882 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
883 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
883
884
884 @LoginRequired()
885 @LoginRequired()
885 @HasPermissionAllDecorator('hg.admin')
886 @HasPermissionAllDecorator('hg.admin')
886 @view_config(
887 @view_config(
887 route_name='edit_user_emails', request_method='GET',
888 route_name='edit_user_emails', request_method='GET',
888 renderer='rhodecode:templates/admin/users/user_edit.mako')
889 renderer='rhodecode:templates/admin/users/user_edit.mako')
889 def emails(self):
890 def emails(self):
890 _ = self.request.translate
891 _ = self.request.translate
891 c = self.load_default_context()
892 c = self.load_default_context()
892 c.user = self.db_user
893 c.user = self.db_user
893
894
894 c.active = 'emails'
895 c.active = 'emails'
895 c.user_email_map = UserEmailMap.query() \
896 c.user_email_map = UserEmailMap.query() \
896 .filter(UserEmailMap.user == c.user).all()
897 .filter(UserEmailMap.user == c.user).all()
897
898
898 return self._get_template_context(c)
899 return self._get_template_context(c)
899
900
900 @LoginRequired()
901 @LoginRequired()
901 @HasPermissionAllDecorator('hg.admin')
902 @HasPermissionAllDecorator('hg.admin')
902 @CSRFRequired()
903 @CSRFRequired()
903 @view_config(
904 @view_config(
904 route_name='edit_user_emails_add', request_method='POST')
905 route_name='edit_user_emails_add', request_method='POST')
905 def emails_add(self):
906 def emails_add(self):
906 _ = self.request.translate
907 _ = self.request.translate
907 c = self.load_default_context()
908 c = self.load_default_context()
908
909
909 user_id = self.db_user_id
910 user_id = self.db_user_id
910 c.user = self.db_user
911 c.user = self.db_user
911
912
912 email = self.request.POST.get('new_email')
913 email = self.request.POST.get('new_email')
913 user_data = c.user.get_api_data()
914 user_data = c.user.get_api_data()
914 try:
915 try:
915 UserModel().add_extra_email(c.user.user_id, email)
916 UserModel().add_extra_email(c.user.user_id, email)
916 audit_logger.store_web(
917 audit_logger.store_web(
917 'user.edit.email.add',
918 'user.edit.email.add',
918 action_data={'email': email, 'user': user_data},
919 action_data={'email': email, 'user': user_data},
919 user=self._rhodecode_user)
920 user=self._rhodecode_user)
920 Session().commit()
921 Session().commit()
921 h.flash(_("Added new email address `%s` for user account") % email,
922 h.flash(_("Added new email address `%s` for user account") % email,
922 category='success')
923 category='success')
923 except formencode.Invalid as error:
924 except formencode.Invalid as error:
924 h.flash(h.escape(error.error_dict['email']), category='error')
925 h.flash(h.escape(error.error_dict['email']), category='error')
925 except IntegrityError:
926 except IntegrityError:
926 log.warning("Email %s already exists", email)
927 log.warning("Email %s already exists", email)
927 h.flash(_('Email `{}` is already registered for another user.').format(email),
928 h.flash(_('Email `{}` is already registered for another user.').format(email),
928 category='error')
929 category='error')
929 except Exception:
930 except Exception:
930 log.exception("Exception during email saving")
931 log.exception("Exception during email saving")
931 h.flash(_('An error occurred during email saving'),
932 h.flash(_('An error occurred during email saving'),
932 category='error')
933 category='error')
933 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
934 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
934
935
935 @LoginRequired()
936 @LoginRequired()
936 @HasPermissionAllDecorator('hg.admin')
937 @HasPermissionAllDecorator('hg.admin')
937 @CSRFRequired()
938 @CSRFRequired()
938 @view_config(
939 @view_config(
939 route_name='edit_user_emails_delete', request_method='POST')
940 route_name='edit_user_emails_delete', request_method='POST')
940 def emails_delete(self):
941 def emails_delete(self):
941 _ = self.request.translate
942 _ = self.request.translate
942 c = self.load_default_context()
943 c = self.load_default_context()
943
944
944 user_id = self.db_user_id
945 user_id = self.db_user_id
945 c.user = self.db_user
946 c.user = self.db_user
946
947
947 email_id = self.request.POST.get('del_email_id')
948 email_id = self.request.POST.get('del_email_id')
948 user_model = UserModel()
949 user_model = UserModel()
949
950
950 email = UserEmailMap.query().get(email_id).email
951 email = UserEmailMap.query().get(email_id).email
951 user_data = c.user.get_api_data()
952 user_data = c.user.get_api_data()
952 user_model.delete_extra_email(c.user.user_id, email_id)
953 user_model.delete_extra_email(c.user.user_id, email_id)
953 audit_logger.store_web(
954 audit_logger.store_web(
954 'user.edit.email.delete',
955 'user.edit.email.delete',
955 action_data={'email': email, 'user': user_data},
956 action_data={'email': email, 'user': user_data},
956 user=self._rhodecode_user)
957 user=self._rhodecode_user)
957 Session().commit()
958 Session().commit()
958 h.flash(_("Removed email address from user account"),
959 h.flash(_("Removed email address from user account"),
959 category='success')
960 category='success')
960 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
961 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
961
962
962 @LoginRequired()
963 @LoginRequired()
963 @HasPermissionAllDecorator('hg.admin')
964 @HasPermissionAllDecorator('hg.admin')
964 @view_config(
965 @view_config(
965 route_name='edit_user_ips', request_method='GET',
966 route_name='edit_user_ips', request_method='GET',
966 renderer='rhodecode:templates/admin/users/user_edit.mako')
967 renderer='rhodecode:templates/admin/users/user_edit.mako')
967 def ips(self):
968 def ips(self):
968 _ = self.request.translate
969 _ = self.request.translate
969 c = self.load_default_context()
970 c = self.load_default_context()
970 c.user = self.db_user
971 c.user = self.db_user
971
972
972 c.active = 'ips'
973 c.active = 'ips'
973 c.user_ip_map = UserIpMap.query() \
974 c.user_ip_map = UserIpMap.query() \
974 .filter(UserIpMap.user == c.user).all()
975 .filter(UserIpMap.user == c.user).all()
975
976
976 c.inherit_default_ips = c.user.inherit_default_permissions
977 c.inherit_default_ips = c.user.inherit_default_permissions
977 c.default_user_ip_map = UserIpMap.query() \
978 c.default_user_ip_map = UserIpMap.query() \
978 .filter(UserIpMap.user == User.get_default_user()).all()
979 .filter(UserIpMap.user == User.get_default_user()).all()
979
980
980 return self._get_template_context(c)
981 return self._get_template_context(c)
981
982
982 @LoginRequired()
983 @LoginRequired()
983 @HasPermissionAllDecorator('hg.admin')
984 @HasPermissionAllDecorator('hg.admin')
984 @CSRFRequired()
985 @CSRFRequired()
985 @view_config(
986 @view_config(
986 route_name='edit_user_ips_add', request_method='POST')
987 route_name='edit_user_ips_add', request_method='POST')
987 # NOTE(marcink): this view is allowed for default users, as we can
988 # NOTE(marcink): this view is allowed for default users, as we can
988 # edit their IP white list
989 # edit their IP white list
989 def ips_add(self):
990 def ips_add(self):
990 _ = self.request.translate
991 _ = self.request.translate
991 c = self.load_default_context()
992 c = self.load_default_context()
992
993
993 user_id = self.db_user_id
994 user_id = self.db_user_id
994 c.user = self.db_user
995 c.user = self.db_user
995
996
996 user_model = UserModel()
997 user_model = UserModel()
997 desc = self.request.POST.get('description')
998 desc = self.request.POST.get('description')
998 try:
999 try:
999 ip_list = user_model.parse_ip_range(
1000 ip_list = user_model.parse_ip_range(
1000 self.request.POST.get('new_ip'))
1001 self.request.POST.get('new_ip'))
1001 except Exception as e:
1002 except Exception as e:
1002 ip_list = []
1003 ip_list = []
1003 log.exception("Exception during ip saving")
1004 log.exception("Exception during ip saving")
1004 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1005 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1005 category='error')
1006 category='error')
1006 added = []
1007 added = []
1007 user_data = c.user.get_api_data()
1008 user_data = c.user.get_api_data()
1008 for ip in ip_list:
1009 for ip in ip_list:
1009 try:
1010 try:
1010 user_model.add_extra_ip(c.user.user_id, ip, desc)
1011 user_model.add_extra_ip(c.user.user_id, ip, desc)
1011 audit_logger.store_web(
1012 audit_logger.store_web(
1012 'user.edit.ip.add',
1013 'user.edit.ip.add',
1013 action_data={'ip': ip, 'user': user_data},
1014 action_data={'ip': ip, 'user': user_data},
1014 user=self._rhodecode_user)
1015 user=self._rhodecode_user)
1015 Session().commit()
1016 Session().commit()
1016 added.append(ip)
1017 added.append(ip)
1017 except formencode.Invalid as error:
1018 except formencode.Invalid as error:
1018 msg = error.error_dict['ip']
1019 msg = error.error_dict['ip']
1019 h.flash(msg, category='error')
1020 h.flash(msg, category='error')
1020 except Exception:
1021 except Exception:
1021 log.exception("Exception during ip saving")
1022 log.exception("Exception during ip saving")
1022 h.flash(_('An error occurred during ip saving'),
1023 h.flash(_('An error occurred during ip saving'),
1023 category='error')
1024 category='error')
1024 if added:
1025 if added:
1025 h.flash(
1026 h.flash(
1026 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1027 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1027 category='success')
1028 category='success')
1028 if 'default_user' in self.request.POST:
1029 if 'default_user' in self.request.POST:
1029 # case for editing global IP list we do it for 'DEFAULT' user
1030 # case for editing global IP list we do it for 'DEFAULT' user
1030 raise HTTPFound(h.route_path('admin_permissions_ips'))
1031 raise HTTPFound(h.route_path('admin_permissions_ips'))
1031 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1032 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1032
1033
1033 @LoginRequired()
1034 @LoginRequired()
1034 @HasPermissionAllDecorator('hg.admin')
1035 @HasPermissionAllDecorator('hg.admin')
1035 @CSRFRequired()
1036 @CSRFRequired()
1036 @view_config(
1037 @view_config(
1037 route_name='edit_user_ips_delete', request_method='POST')
1038 route_name='edit_user_ips_delete', request_method='POST')
1038 # NOTE(marcink): this view is allowed for default users, as we can
1039 # NOTE(marcink): this view is allowed for default users, as we can
1039 # edit their IP white list
1040 # edit their IP white list
1040 def ips_delete(self):
1041 def ips_delete(self):
1041 _ = self.request.translate
1042 _ = self.request.translate
1042 c = self.load_default_context()
1043 c = self.load_default_context()
1043
1044
1044 user_id = self.db_user_id
1045 user_id = self.db_user_id
1045 c.user = self.db_user
1046 c.user = self.db_user
1046
1047
1047 ip_id = self.request.POST.get('del_ip_id')
1048 ip_id = self.request.POST.get('del_ip_id')
1048 user_model = UserModel()
1049 user_model = UserModel()
1049 user_data = c.user.get_api_data()
1050 user_data = c.user.get_api_data()
1050 ip = UserIpMap.query().get(ip_id).ip_addr
1051 ip = UserIpMap.query().get(ip_id).ip_addr
1051 user_model.delete_extra_ip(c.user.user_id, ip_id)
1052 user_model.delete_extra_ip(c.user.user_id, ip_id)
1052 audit_logger.store_web(
1053 audit_logger.store_web(
1053 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1054 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1054 user=self._rhodecode_user)
1055 user=self._rhodecode_user)
1055 Session().commit()
1056 Session().commit()
1056 h.flash(_("Removed ip address from user whitelist"), category='success')
1057 h.flash(_("Removed ip address from user whitelist"), category='success')
1057
1058
1058 if 'default_user' in self.request.POST:
1059 if 'default_user' in self.request.POST:
1059 # case for editing global IP list we do it for 'DEFAULT' user
1060 # case for editing global IP list we do it for 'DEFAULT' user
1060 raise HTTPFound(h.route_path('admin_permissions_ips'))
1061 raise HTTPFound(h.route_path('admin_permissions_ips'))
1061 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1062 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1062
1063
1063 @LoginRequired()
1064 @LoginRequired()
1064 @HasPermissionAllDecorator('hg.admin')
1065 @HasPermissionAllDecorator('hg.admin')
1065 @view_config(
1066 @view_config(
1066 route_name='edit_user_groups_management', request_method='GET',
1067 route_name='edit_user_groups_management', request_method='GET',
1067 renderer='rhodecode:templates/admin/users/user_edit.mako')
1068 renderer='rhodecode:templates/admin/users/user_edit.mako')
1068 def groups_management(self):
1069 def groups_management(self):
1069 c = self.load_default_context()
1070 c = self.load_default_context()
1070 c.user = self.db_user
1071 c.user = self.db_user
1071 c.data = c.user.group_member
1072 c.data = c.user.group_member
1072
1073
1073 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1074 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1074 for group in c.user.group_member]
1075 for group in c.user.group_member]
1075 c.groups = json.dumps(groups)
1076 c.groups = json.dumps(groups)
1076 c.active = 'groups'
1077 c.active = 'groups'
1077
1078
1078 return self._get_template_context(c)
1079 return self._get_template_context(c)
1079
1080
1080 @LoginRequired()
1081 @LoginRequired()
1081 @HasPermissionAllDecorator('hg.admin')
1082 @HasPermissionAllDecorator('hg.admin')
1082 @CSRFRequired()
1083 @CSRFRequired()
1083 @view_config(
1084 @view_config(
1084 route_name='edit_user_groups_management_updates', request_method='POST')
1085 route_name='edit_user_groups_management_updates', request_method='POST')
1085 def groups_management_updates(self):
1086 def groups_management_updates(self):
1086 _ = self.request.translate
1087 _ = self.request.translate
1087 c = self.load_default_context()
1088 c = self.load_default_context()
1088
1089
1089 user_id = self.db_user_id
1090 user_id = self.db_user_id
1090 c.user = self.db_user
1091 c.user = self.db_user
1091
1092
1092 user_groups = set(self.request.POST.getall('users_group_id'))
1093 user_groups = set(self.request.POST.getall('users_group_id'))
1093 user_groups_objects = []
1094 user_groups_objects = []
1094
1095
1095 for ugid in user_groups:
1096 for ugid in user_groups:
1096 user_groups_objects.append(
1097 user_groups_objects.append(
1097 UserGroupModel().get_group(safe_int(ugid)))
1098 UserGroupModel().get_group(safe_int(ugid)))
1098 user_group_model = UserGroupModel()
1099 user_group_model = UserGroupModel()
1099 added_to_groups, removed_from_groups = \
1100 added_to_groups, removed_from_groups = \
1100 user_group_model.change_groups(c.user, user_groups_objects)
1101 user_group_model.change_groups(c.user, user_groups_objects)
1101
1102
1102 user_data = c.user.get_api_data()
1103 user_data = c.user.get_api_data()
1103 for user_group_id in added_to_groups:
1104 for user_group_id in added_to_groups:
1104 user_group = UserGroup.get(user_group_id)
1105 user_group = UserGroup.get(user_group_id)
1105 old_values = user_group.get_api_data()
1106 old_values = user_group.get_api_data()
1106 audit_logger.store_web(
1107 audit_logger.store_web(
1107 'user_group.edit.member.add',
1108 'user_group.edit.member.add',
1108 action_data={'user': user_data, 'old_data': old_values},
1109 action_data={'user': user_data, 'old_data': old_values},
1109 user=self._rhodecode_user)
1110 user=self._rhodecode_user)
1110
1111
1111 for user_group_id in removed_from_groups:
1112 for user_group_id in removed_from_groups:
1112 user_group = UserGroup.get(user_group_id)
1113 user_group = UserGroup.get(user_group_id)
1113 old_values = user_group.get_api_data()
1114 old_values = user_group.get_api_data()
1114 audit_logger.store_web(
1115 audit_logger.store_web(
1115 'user_group.edit.member.delete',
1116 'user_group.edit.member.delete',
1116 action_data={'user': user_data, 'old_data': old_values},
1117 action_data={'user': user_data, 'old_data': old_values},
1117 user=self._rhodecode_user)
1118 user=self._rhodecode_user)
1118
1119
1119 Session().commit()
1120 Session().commit()
1120 c.active = 'user_groups_management'
1121 c.active = 'user_groups_management'
1121 h.flash(_("Groups successfully changed"), category='success')
1122 h.flash(_("Groups successfully changed"), category='success')
1122
1123
1123 return HTTPFound(h.route_path(
1124 return HTTPFound(h.route_path(
1124 'edit_user_groups_management', user_id=user_id))
1125 'edit_user_groups_management', user_id=user_id))
1125
1126
1126 @LoginRequired()
1127 @LoginRequired()
1127 @HasPermissionAllDecorator('hg.admin')
1128 @HasPermissionAllDecorator('hg.admin')
1128 @view_config(
1129 @view_config(
1129 route_name='edit_user_audit_logs', request_method='GET',
1130 route_name='edit_user_audit_logs', request_method='GET',
1130 renderer='rhodecode:templates/admin/users/user_edit.mako')
1131 renderer='rhodecode:templates/admin/users/user_edit.mako')
1131 def user_audit_logs(self):
1132 def user_audit_logs(self):
1132 _ = self.request.translate
1133 _ = self.request.translate
1133 c = self.load_default_context()
1134 c = self.load_default_context()
1134 c.user = self.db_user
1135 c.user = self.db_user
1135
1136
1136 c.active = 'audit'
1137 c.active = 'audit'
1137
1138
1138 p = safe_int(self.request.GET.get('page', 1), 1)
1139 p = safe_int(self.request.GET.get('page', 1), 1)
1139
1140
1140 filter_term = self.request.GET.get('filter')
1141 filter_term = self.request.GET.get('filter')
1141 user_log = UserModel().get_user_log(c.user, filter_term)
1142 user_log = UserModel().get_user_log(c.user, filter_term)
1142
1143
1143 def url_generator(**kw):
1144 def url_generator(**kw):
1144 if filter_term:
1145 if filter_term:
1145 kw['filter'] = filter_term
1146 kw['filter'] = filter_term
1146 return self.request.current_route_path(_query=kw)
1147 return self.request.current_route_path(_query=kw)
1147
1148
1148 c.audit_logs = h.Page(
1149 c.audit_logs = h.Page(
1149 user_log, page=p, items_per_page=10, url=url_generator)
1150 user_log, page=p, items_per_page=10, url=url_generator)
1150 c.filter_term = filter_term
1151 c.filter_term = filter_term
1151 return self._get_template_context(c)
1152 return self._get_template_context(c)
1152
1153
1153 @LoginRequired()
1154 @LoginRequired()
1154 @HasPermissionAllDecorator('hg.admin')
1155 @HasPermissionAllDecorator('hg.admin')
1155 @view_config(
1156 @view_config(
1156 route_name='edit_user_perms_summary', request_method='GET',
1157 route_name='edit_user_perms_summary', request_method='GET',
1157 renderer='rhodecode:templates/admin/users/user_edit.mako')
1158 renderer='rhodecode:templates/admin/users/user_edit.mako')
1158 def user_perms_summary(self):
1159 def user_perms_summary(self):
1159 _ = self.request.translate
1160 _ = self.request.translate
1160 c = self.load_default_context()
1161 c = self.load_default_context()
1161 c.user = self.db_user
1162 c.user = self.db_user
1162
1163
1163 c.active = 'perms_summary'
1164 c.active = 'perms_summary'
1164 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1165 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1165
1166
1166 return self._get_template_context(c)
1167 return self._get_template_context(c)
1167
1168
1168 @LoginRequired()
1169 @LoginRequired()
1169 @HasPermissionAllDecorator('hg.admin')
1170 @HasPermissionAllDecorator('hg.admin')
1170 @view_config(
1171 @view_config(
1171 route_name='edit_user_perms_summary_json', request_method='GET',
1172 route_name='edit_user_perms_summary_json', request_method='GET',
1172 renderer='json_ext')
1173 renderer='json_ext')
1173 def user_perms_summary_json(self):
1174 def user_perms_summary_json(self):
1174 self.load_default_context()
1175 self.load_default_context()
1175 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1176 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1176
1177
1177 return perm_user.permissions
1178 return perm_user.permissions
@@ -1,579 +1,580 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 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
23
24 import formencode
24 import formencode
25 import formencode.htmlfill
25 import formencode.htmlfill
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.apps._base import BaseAppView, DataGridAppView
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 from rhodecode import forms
32 from rhodecode import forms
33 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
34 from rhodecode.lib import audit_logger
34 from rhodecode.lib import audit_logger
35 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.ext_json import json
36 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
36 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
37 from rhodecode.lib.channelstream import (
37 from rhodecode.lib.channelstream import (
38 channelstream_request, ChannelstreamException)
38 channelstream_request, ChannelstreamException)
39 from rhodecode.lib.utils2 import safe_int, md5, str2bool
39 from rhodecode.lib.utils2 import safe_int, md5, str2bool
40 from rhodecode.model.auth_token import AuthTokenModel
40 from rhodecode.model.auth_token import AuthTokenModel
41 from rhodecode.model.comment import CommentsModel
41 from rhodecode.model.comment import CommentsModel
42 from rhodecode.model.db import (
42 from rhodecode.model.db import (
43 Repository, UserEmailMap, UserApiKeys, UserFollowing, joinedload,
43 Repository, UserEmailMap, UserApiKeys, UserFollowing, joinedload,
44 PullRequest)
44 PullRequest)
45 from rhodecode.model.forms import UserForm
45 from rhodecode.model.forms import UserForm
46 from rhodecode.model.meta import Session
46 from rhodecode.model.meta import Session
47 from rhodecode.model.pull_request import PullRequestModel
47 from rhodecode.model.pull_request import PullRequestModel
48 from rhodecode.model.scm import RepoList
48 from rhodecode.model.scm import RepoList
49 from rhodecode.model.user import UserModel
49 from rhodecode.model.user import UserModel
50 from rhodecode.model.repo import RepoModel
50 from rhodecode.model.repo import RepoModel
51 from rhodecode.model.validation_schema.schemas import user_schema
51 from rhodecode.model.validation_schema.schemas import user_schema
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55
55
56 class MyAccountView(BaseAppView, DataGridAppView):
56 class MyAccountView(BaseAppView, DataGridAppView):
57 ALLOW_SCOPED_TOKENS = False
57 ALLOW_SCOPED_TOKENS = False
58 """
58 """
59 This view has alternative version inside EE, if modified please take a look
59 This view has alternative version inside EE, if modified please take a look
60 in there as well.
60 in there as well.
61 """
61 """
62
62
63 def load_default_context(self):
63 def load_default_context(self):
64 c = self._get_local_tmpl_context()
64 c = self._get_local_tmpl_context()
65 c.user = c.auth_user.get_instance()
65 c.user = c.auth_user.get_instance()
66 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
66 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
67 self._register_global_c(c)
67 self._register_global_c(c)
68 return c
68 return c
69
69
70 @LoginRequired()
70 @LoginRequired()
71 @NotAnonymous()
71 @NotAnonymous()
72 @view_config(
72 @view_config(
73 route_name='my_account_profile', request_method='GET',
73 route_name='my_account_profile', request_method='GET',
74 renderer='rhodecode:templates/admin/my_account/my_account.mako')
74 renderer='rhodecode:templates/admin/my_account/my_account.mako')
75 def my_account_profile(self):
75 def my_account_profile(self):
76 c = self.load_default_context()
76 c = self.load_default_context()
77 c.active = 'profile'
77 c.active = 'profile'
78 return self._get_template_context(c)
78 return self._get_template_context(c)
79
79
80 @LoginRequired()
80 @LoginRequired()
81 @NotAnonymous()
81 @NotAnonymous()
82 @view_config(
82 @view_config(
83 route_name='my_account_password', request_method='GET',
83 route_name='my_account_password', request_method='GET',
84 renderer='rhodecode:templates/admin/my_account/my_account.mako')
84 renderer='rhodecode:templates/admin/my_account/my_account.mako')
85 def my_account_password(self):
85 def my_account_password(self):
86 c = self.load_default_context()
86 c = self.load_default_context()
87 c.active = 'password'
87 c.active = 'password'
88 c.extern_type = c.user.extern_type
88 c.extern_type = c.user.extern_type
89
89
90 schema = user_schema.ChangePasswordSchema().bind(
90 schema = user_schema.ChangePasswordSchema().bind(
91 username=c.user.username)
91 username=c.user.username)
92
92
93 form = forms.Form(
93 form = forms.Form(
94 schema,
94 schema,
95 action=h.route_path('my_account_password_update'),
95 action=h.route_path('my_account_password_update'),
96 buttons=(forms.buttons.save, forms.buttons.reset))
96 buttons=(forms.buttons.save, forms.buttons.reset))
97
97
98 c.form = form
98 c.form = form
99 return self._get_template_context(c)
99 return self._get_template_context(c)
100
100
101 @LoginRequired()
101 @LoginRequired()
102 @NotAnonymous()
102 @NotAnonymous()
103 @CSRFRequired()
103 @CSRFRequired()
104 @view_config(
104 @view_config(
105 route_name='my_account_password_update', request_method='POST',
105 route_name='my_account_password_update', request_method='POST',
106 renderer='rhodecode:templates/admin/my_account/my_account.mako')
106 renderer='rhodecode:templates/admin/my_account/my_account.mako')
107 def my_account_password_update(self):
107 def my_account_password_update(self):
108 _ = self.request.translate
108 _ = self.request.translate
109 c = self.load_default_context()
109 c = self.load_default_context()
110 c.active = 'password'
110 c.active = 'password'
111 c.extern_type = c.user.extern_type
111 c.extern_type = c.user.extern_type
112
112
113 schema = user_schema.ChangePasswordSchema().bind(
113 schema = user_schema.ChangePasswordSchema().bind(
114 username=c.user.username)
114 username=c.user.username)
115
115
116 form = forms.Form(
116 form = forms.Form(
117 schema, buttons=(forms.buttons.save, forms.buttons.reset))
117 schema, buttons=(forms.buttons.save, forms.buttons.reset))
118
118
119 if c.extern_type != 'rhodecode':
119 if c.extern_type != 'rhodecode':
120 raise HTTPFound(self.request.route_path('my_account_password'))
120 raise HTTPFound(self.request.route_path('my_account_password'))
121
121
122 controls = self.request.POST.items()
122 controls = self.request.POST.items()
123 try:
123 try:
124 valid_data = form.validate(controls)
124 valid_data = form.validate(controls)
125 UserModel().update_user(c.user.user_id, **valid_data)
125 UserModel().update_user(c.user.user_id, **valid_data)
126 c.user.update_userdata(force_password_change=False)
126 c.user.update_userdata(force_password_change=False)
127 Session().commit()
127 Session().commit()
128 except forms.ValidationFailure as e:
128 except forms.ValidationFailure as e:
129 c.form = e
129 c.form = e
130 return self._get_template_context(c)
130 return self._get_template_context(c)
131
131
132 except Exception:
132 except Exception:
133 log.exception("Exception updating password")
133 log.exception("Exception updating password")
134 h.flash(_('Error occurred during update of user password'),
134 h.flash(_('Error occurred during update of user password'),
135 category='error')
135 category='error')
136 else:
136 else:
137 instance = c.auth_user.get_instance()
137 instance = c.auth_user.get_instance()
138 self.session.setdefault('rhodecode_user', {}).update(
138 self.session.setdefault('rhodecode_user', {}).update(
139 {'password': md5(instance.password)})
139 {'password': md5(instance.password)})
140 self.session.save()
140 self.session.save()
141 h.flash(_("Successfully updated password"), category='success')
141 h.flash(_("Successfully updated password"), category='success')
142
142
143 raise HTTPFound(self.request.route_path('my_account_password'))
143 raise HTTPFound(self.request.route_path('my_account_password'))
144
144
145 @LoginRequired()
145 @LoginRequired()
146 @NotAnonymous()
146 @NotAnonymous()
147 @view_config(
147 @view_config(
148 route_name='my_account_auth_tokens', request_method='GET',
148 route_name='my_account_auth_tokens', request_method='GET',
149 renderer='rhodecode:templates/admin/my_account/my_account.mako')
149 renderer='rhodecode:templates/admin/my_account/my_account.mako')
150 def my_account_auth_tokens(self):
150 def my_account_auth_tokens(self):
151 _ = self.request.translate
151 _ = self.request.translate
152
152
153 c = self.load_default_context()
153 c = self.load_default_context()
154 c.active = 'auth_tokens'
154 c.active = 'auth_tokens'
155 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
155 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
156 c.role_values = [
156 c.role_values = [
157 (x, AuthTokenModel.cls._get_role_name(x))
157 (x, AuthTokenModel.cls._get_role_name(x))
158 for x in AuthTokenModel.cls.ROLES]
158 for x in AuthTokenModel.cls.ROLES]
159 c.role_options = [(c.role_values, _("Role"))]
159 c.role_options = [(c.role_values, _("Role"))]
160 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
160 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
161 c.user.user_id, show_expired=True)
161 c.user.user_id, show_expired=True)
162 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
162 return self._get_template_context(c)
163 return self._get_template_context(c)
163
164
164 def maybe_attach_token_scope(self, token):
165 def maybe_attach_token_scope(self, token):
165 # implemented in EE edition
166 # implemented in EE edition
166 pass
167 pass
167
168
168 @LoginRequired()
169 @LoginRequired()
169 @NotAnonymous()
170 @NotAnonymous()
170 @CSRFRequired()
171 @CSRFRequired()
171 @view_config(
172 @view_config(
172 route_name='my_account_auth_tokens_add', request_method='POST',)
173 route_name='my_account_auth_tokens_add', request_method='POST',)
173 def my_account_auth_tokens_add(self):
174 def my_account_auth_tokens_add(self):
174 _ = self.request.translate
175 _ = self.request.translate
175 c = self.load_default_context()
176 c = self.load_default_context()
176
177
177 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
178 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
178 description = self.request.POST.get('description')
179 description = self.request.POST.get('description')
179 role = self.request.POST.get('role')
180 role = self.request.POST.get('role')
180
181
181 token = AuthTokenModel().create(
182 token = AuthTokenModel().create(
182 c.user.user_id, description, lifetime, role)
183 c.user.user_id, description, lifetime, role)
183 token_data = token.get_api_data()
184 token_data = token.get_api_data()
184
185
185 self.maybe_attach_token_scope(token)
186 self.maybe_attach_token_scope(token)
186 audit_logger.store_web(
187 audit_logger.store_web(
187 'user.edit.token.add', action_data={
188 'user.edit.token.add', action_data={
188 'data': {'token': token_data, 'user': 'self'}},
189 'data': {'token': token_data, 'user': 'self'}},
189 user=self._rhodecode_user, )
190 user=self._rhodecode_user, )
190 Session().commit()
191 Session().commit()
191
192
192 h.flash(_("Auth token successfully created"), category='success')
193 h.flash(_("Auth token successfully created"), category='success')
193 return HTTPFound(h.route_path('my_account_auth_tokens'))
194 return HTTPFound(h.route_path('my_account_auth_tokens'))
194
195
195 @LoginRequired()
196 @LoginRequired()
196 @NotAnonymous()
197 @NotAnonymous()
197 @CSRFRequired()
198 @CSRFRequired()
198 @view_config(
199 @view_config(
199 route_name='my_account_auth_tokens_delete', request_method='POST')
200 route_name='my_account_auth_tokens_delete', request_method='POST')
200 def my_account_auth_tokens_delete(self):
201 def my_account_auth_tokens_delete(self):
201 _ = self.request.translate
202 _ = self.request.translate
202 c = self.load_default_context()
203 c = self.load_default_context()
203
204
204 del_auth_token = self.request.POST.get('del_auth_token')
205 del_auth_token = self.request.POST.get('del_auth_token')
205
206
206 if del_auth_token:
207 if del_auth_token:
207 token = UserApiKeys.get_or_404(del_auth_token)
208 token = UserApiKeys.get_or_404(del_auth_token)
208 token_data = token.get_api_data()
209 token_data = token.get_api_data()
209
210
210 AuthTokenModel().delete(del_auth_token, c.user.user_id)
211 AuthTokenModel().delete(del_auth_token, c.user.user_id)
211 audit_logger.store_web(
212 audit_logger.store_web(
212 'user.edit.token.delete', action_data={
213 'user.edit.token.delete', action_data={
213 'data': {'token': token_data, 'user': 'self'}},
214 'data': {'token': token_data, 'user': 'self'}},
214 user=self._rhodecode_user,)
215 user=self._rhodecode_user,)
215 Session().commit()
216 Session().commit()
216 h.flash(_("Auth token successfully deleted"), category='success')
217 h.flash(_("Auth token successfully deleted"), category='success')
217
218
218 return HTTPFound(h.route_path('my_account_auth_tokens'))
219 return HTTPFound(h.route_path('my_account_auth_tokens'))
219
220
220 @LoginRequired()
221 @LoginRequired()
221 @NotAnonymous()
222 @NotAnonymous()
222 @view_config(
223 @view_config(
223 route_name='my_account_emails', request_method='GET',
224 route_name='my_account_emails', request_method='GET',
224 renderer='rhodecode:templates/admin/my_account/my_account.mako')
225 renderer='rhodecode:templates/admin/my_account/my_account.mako')
225 def my_account_emails(self):
226 def my_account_emails(self):
226 _ = self.request.translate
227 _ = self.request.translate
227
228
228 c = self.load_default_context()
229 c = self.load_default_context()
229 c.active = 'emails'
230 c.active = 'emails'
230
231
231 c.user_email_map = UserEmailMap.query()\
232 c.user_email_map = UserEmailMap.query()\
232 .filter(UserEmailMap.user == c.user).all()
233 .filter(UserEmailMap.user == c.user).all()
233 return self._get_template_context(c)
234 return self._get_template_context(c)
234
235
235 @LoginRequired()
236 @LoginRequired()
236 @NotAnonymous()
237 @NotAnonymous()
237 @CSRFRequired()
238 @CSRFRequired()
238 @view_config(
239 @view_config(
239 route_name='my_account_emails_add', request_method='POST')
240 route_name='my_account_emails_add', request_method='POST')
240 def my_account_emails_add(self):
241 def my_account_emails_add(self):
241 _ = self.request.translate
242 _ = self.request.translate
242 c = self.load_default_context()
243 c = self.load_default_context()
243
244
244 email = self.request.POST.get('new_email')
245 email = self.request.POST.get('new_email')
245
246
246 try:
247 try:
247 UserModel().add_extra_email(c.user.user_id, email)
248 UserModel().add_extra_email(c.user.user_id, email)
248 audit_logger.store_web(
249 audit_logger.store_web(
249 'user.edit.email.add', action_data={
250 'user.edit.email.add', action_data={
250 'data': {'email': email, 'user': 'self'}},
251 'data': {'email': email, 'user': 'self'}},
251 user=self._rhodecode_user,)
252 user=self._rhodecode_user,)
252
253
253 Session().commit()
254 Session().commit()
254 h.flash(_("Added new email address `%s` for user account") % email,
255 h.flash(_("Added new email address `%s` for user account") % email,
255 category='success')
256 category='success')
256 except formencode.Invalid as error:
257 except formencode.Invalid as error:
257 h.flash(h.escape(error.error_dict['email']), category='error')
258 h.flash(h.escape(error.error_dict['email']), category='error')
258 except Exception:
259 except Exception:
259 log.exception("Exception in my_account_emails")
260 log.exception("Exception in my_account_emails")
260 h.flash(_('An error occurred during email saving'),
261 h.flash(_('An error occurred during email saving'),
261 category='error')
262 category='error')
262 return HTTPFound(h.route_path('my_account_emails'))
263 return HTTPFound(h.route_path('my_account_emails'))
263
264
264 @LoginRequired()
265 @LoginRequired()
265 @NotAnonymous()
266 @NotAnonymous()
266 @CSRFRequired()
267 @CSRFRequired()
267 @view_config(
268 @view_config(
268 route_name='my_account_emails_delete', request_method='POST')
269 route_name='my_account_emails_delete', request_method='POST')
269 def my_account_emails_delete(self):
270 def my_account_emails_delete(self):
270 _ = self.request.translate
271 _ = self.request.translate
271 c = self.load_default_context()
272 c = self.load_default_context()
272
273
273 del_email_id = self.request.POST.get('del_email_id')
274 del_email_id = self.request.POST.get('del_email_id')
274 if del_email_id:
275 if del_email_id:
275 email = UserEmailMap.get_or_404(del_email_id).email
276 email = UserEmailMap.get_or_404(del_email_id).email
276 UserModel().delete_extra_email(c.user.user_id, del_email_id)
277 UserModel().delete_extra_email(c.user.user_id, del_email_id)
277 audit_logger.store_web(
278 audit_logger.store_web(
278 'user.edit.email.delete', action_data={
279 'user.edit.email.delete', action_data={
279 'data': {'email': email, 'user': 'self'}},
280 'data': {'email': email, 'user': 'self'}},
280 user=self._rhodecode_user,)
281 user=self._rhodecode_user,)
281 Session().commit()
282 Session().commit()
282 h.flash(_("Email successfully deleted"),
283 h.flash(_("Email successfully deleted"),
283 category='success')
284 category='success')
284 return HTTPFound(h.route_path('my_account_emails'))
285 return HTTPFound(h.route_path('my_account_emails'))
285
286
286 @LoginRequired()
287 @LoginRequired()
287 @NotAnonymous()
288 @NotAnonymous()
288 @CSRFRequired()
289 @CSRFRequired()
289 @view_config(
290 @view_config(
290 route_name='my_account_notifications_test_channelstream',
291 route_name='my_account_notifications_test_channelstream',
291 request_method='POST', renderer='json_ext')
292 request_method='POST', renderer='json_ext')
292 def my_account_notifications_test_channelstream(self):
293 def my_account_notifications_test_channelstream(self):
293 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
294 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
294 self._rhodecode_user.username, datetime.datetime.now())
295 self._rhodecode_user.username, datetime.datetime.now())
295 payload = {
296 payload = {
296 # 'channel': 'broadcast',
297 # 'channel': 'broadcast',
297 'type': 'message',
298 'type': 'message',
298 'timestamp': datetime.datetime.utcnow(),
299 'timestamp': datetime.datetime.utcnow(),
299 'user': 'system',
300 'user': 'system',
300 'pm_users': [self._rhodecode_user.username],
301 'pm_users': [self._rhodecode_user.username],
301 'message': {
302 'message': {
302 'message': message,
303 'message': message,
303 'level': 'info',
304 'level': 'info',
304 'topic': '/notifications'
305 'topic': '/notifications'
305 }
306 }
306 }
307 }
307
308
308 registry = self.request.registry
309 registry = self.request.registry
309 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
310 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
310 channelstream_config = rhodecode_plugins.get('channelstream', {})
311 channelstream_config = rhodecode_plugins.get('channelstream', {})
311
312
312 try:
313 try:
313 channelstream_request(channelstream_config, [payload], '/message')
314 channelstream_request(channelstream_config, [payload], '/message')
314 except ChannelstreamException as e:
315 except ChannelstreamException as e:
315 log.exception('Failed to send channelstream data')
316 log.exception('Failed to send channelstream data')
316 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
317 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
317 return {"response": 'Channelstream data sent. '
318 return {"response": 'Channelstream data sent. '
318 'You should see a new live message now.'}
319 'You should see a new live message now.'}
319
320
320 def _load_my_repos_data(self, watched=False):
321 def _load_my_repos_data(self, watched=False):
321 if watched:
322 if watched:
322 admin = False
323 admin = False
323 follows_repos = Session().query(UserFollowing)\
324 follows_repos = Session().query(UserFollowing)\
324 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
325 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
325 .options(joinedload(UserFollowing.follows_repository))\
326 .options(joinedload(UserFollowing.follows_repository))\
326 .all()
327 .all()
327 repo_list = [x.follows_repository for x in follows_repos]
328 repo_list = [x.follows_repository for x in follows_repos]
328 else:
329 else:
329 admin = True
330 admin = True
330 repo_list = Repository.get_all_repos(
331 repo_list = Repository.get_all_repos(
331 user_id=self._rhodecode_user.user_id)
332 user_id=self._rhodecode_user.user_id)
332 repo_list = RepoList(repo_list, perm_set=[
333 repo_list = RepoList(repo_list, perm_set=[
333 'repository.read', 'repository.write', 'repository.admin'])
334 'repository.read', 'repository.write', 'repository.admin'])
334
335
335 repos_data = RepoModel().get_repos_as_dict(
336 repos_data = RepoModel().get_repos_as_dict(
336 repo_list=repo_list, admin=admin)
337 repo_list=repo_list, admin=admin)
337 # json used to render the grid
338 # json used to render the grid
338 return json.dumps(repos_data)
339 return json.dumps(repos_data)
339
340
340 @LoginRequired()
341 @LoginRequired()
341 @NotAnonymous()
342 @NotAnonymous()
342 @view_config(
343 @view_config(
343 route_name='my_account_repos', request_method='GET',
344 route_name='my_account_repos', request_method='GET',
344 renderer='rhodecode:templates/admin/my_account/my_account.mako')
345 renderer='rhodecode:templates/admin/my_account/my_account.mako')
345 def my_account_repos(self):
346 def my_account_repos(self):
346 c = self.load_default_context()
347 c = self.load_default_context()
347 c.active = 'repos'
348 c.active = 'repos'
348
349
349 # json used to render the grid
350 # json used to render the grid
350 c.data = self._load_my_repos_data()
351 c.data = self._load_my_repos_data()
351 return self._get_template_context(c)
352 return self._get_template_context(c)
352
353
353 @LoginRequired()
354 @LoginRequired()
354 @NotAnonymous()
355 @NotAnonymous()
355 @view_config(
356 @view_config(
356 route_name='my_account_watched', request_method='GET',
357 route_name='my_account_watched', request_method='GET',
357 renderer='rhodecode:templates/admin/my_account/my_account.mako')
358 renderer='rhodecode:templates/admin/my_account/my_account.mako')
358 def my_account_watched(self):
359 def my_account_watched(self):
359 c = self.load_default_context()
360 c = self.load_default_context()
360 c.active = 'watched'
361 c.active = 'watched'
361
362
362 # json used to render the grid
363 # json used to render the grid
363 c.data = self._load_my_repos_data(watched=True)
364 c.data = self._load_my_repos_data(watched=True)
364 return self._get_template_context(c)
365 return self._get_template_context(c)
365
366
366 @LoginRequired()
367 @LoginRequired()
367 @NotAnonymous()
368 @NotAnonymous()
368 @view_config(
369 @view_config(
369 route_name='my_account_perms', request_method='GET',
370 route_name='my_account_perms', request_method='GET',
370 renderer='rhodecode:templates/admin/my_account/my_account.mako')
371 renderer='rhodecode:templates/admin/my_account/my_account.mako')
371 def my_account_perms(self):
372 def my_account_perms(self):
372 c = self.load_default_context()
373 c = self.load_default_context()
373 c.active = 'perms'
374 c.active = 'perms'
374
375
375 c.perm_user = c.auth_user
376 c.perm_user = c.auth_user
376 return self._get_template_context(c)
377 return self._get_template_context(c)
377
378
378 @LoginRequired()
379 @LoginRequired()
379 @NotAnonymous()
380 @NotAnonymous()
380 @view_config(
381 @view_config(
381 route_name='my_account_notifications', request_method='GET',
382 route_name='my_account_notifications', request_method='GET',
382 renderer='rhodecode:templates/admin/my_account/my_account.mako')
383 renderer='rhodecode:templates/admin/my_account/my_account.mako')
383 def my_notifications(self):
384 def my_notifications(self):
384 c = self.load_default_context()
385 c = self.load_default_context()
385 c.active = 'notifications'
386 c.active = 'notifications'
386
387
387 return self._get_template_context(c)
388 return self._get_template_context(c)
388
389
389 @LoginRequired()
390 @LoginRequired()
390 @NotAnonymous()
391 @NotAnonymous()
391 @CSRFRequired()
392 @CSRFRequired()
392 @view_config(
393 @view_config(
393 route_name='my_account_notifications_toggle_visibility',
394 route_name='my_account_notifications_toggle_visibility',
394 request_method='POST', renderer='json_ext')
395 request_method='POST', renderer='json_ext')
395 def my_notifications_toggle_visibility(self):
396 def my_notifications_toggle_visibility(self):
396 user = self._rhodecode_db_user
397 user = self._rhodecode_db_user
397 new_status = not user.user_data.get('notification_status', True)
398 new_status = not user.user_data.get('notification_status', True)
398 user.update_userdata(notification_status=new_status)
399 user.update_userdata(notification_status=new_status)
399 Session().commit()
400 Session().commit()
400 return user.user_data['notification_status']
401 return user.user_data['notification_status']
401
402
402 @LoginRequired()
403 @LoginRequired()
403 @NotAnonymous()
404 @NotAnonymous()
404 @view_config(
405 @view_config(
405 route_name='my_account_edit',
406 route_name='my_account_edit',
406 request_method='GET',
407 request_method='GET',
407 renderer='rhodecode:templates/admin/my_account/my_account.mako')
408 renderer='rhodecode:templates/admin/my_account/my_account.mako')
408 def my_account_edit(self):
409 def my_account_edit(self):
409 c = self.load_default_context()
410 c = self.load_default_context()
410 c.active = 'profile_edit'
411 c.active = 'profile_edit'
411
412
412 c.perm_user = c.auth_user
413 c.perm_user = c.auth_user
413 c.extern_type = c.user.extern_type
414 c.extern_type = c.user.extern_type
414 c.extern_name = c.user.extern_name
415 c.extern_name = c.user.extern_name
415
416
416 defaults = c.user.get_dict()
417 defaults = c.user.get_dict()
417
418
418 data = render('rhodecode:templates/admin/my_account/my_account.mako',
419 data = render('rhodecode:templates/admin/my_account/my_account.mako',
419 self._get_template_context(c), self.request)
420 self._get_template_context(c), self.request)
420 html = formencode.htmlfill.render(
421 html = formencode.htmlfill.render(
421 data,
422 data,
422 defaults=defaults,
423 defaults=defaults,
423 encoding="UTF-8",
424 encoding="UTF-8",
424 force_defaults=False
425 force_defaults=False
425 )
426 )
426 return Response(html)
427 return Response(html)
427
428
428 @LoginRequired()
429 @LoginRequired()
429 @NotAnonymous()
430 @NotAnonymous()
430 @CSRFRequired()
431 @CSRFRequired()
431 @view_config(
432 @view_config(
432 route_name='my_account_update',
433 route_name='my_account_update',
433 request_method='POST',
434 request_method='POST',
434 renderer='rhodecode:templates/admin/my_account/my_account.mako')
435 renderer='rhodecode:templates/admin/my_account/my_account.mako')
435 def my_account_update(self):
436 def my_account_update(self):
436 _ = self.request.translate
437 _ = self.request.translate
437 c = self.load_default_context()
438 c = self.load_default_context()
438 c.active = 'profile_edit'
439 c.active = 'profile_edit'
439
440
440 c.perm_user = c.auth_user
441 c.perm_user = c.auth_user
441 c.extern_type = c.user.extern_type
442 c.extern_type = c.user.extern_type
442 c.extern_name = c.user.extern_name
443 c.extern_name = c.user.extern_name
443
444
444 _form = UserForm(edit=True,
445 _form = UserForm(edit=True,
445 old_data={'user_id': self._rhodecode_user.user_id,
446 old_data={'user_id': self._rhodecode_user.user_id,
446 'email': self._rhodecode_user.email})()
447 'email': self._rhodecode_user.email})()
447 form_result = {}
448 form_result = {}
448 try:
449 try:
449 post_data = dict(self.request.POST)
450 post_data = dict(self.request.POST)
450 post_data['new_password'] = ''
451 post_data['new_password'] = ''
451 post_data['password_confirmation'] = ''
452 post_data['password_confirmation'] = ''
452 form_result = _form.to_python(post_data)
453 form_result = _form.to_python(post_data)
453 # skip updating those attrs for my account
454 # skip updating those attrs for my account
454 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
455 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
455 'new_password', 'password_confirmation']
456 'new_password', 'password_confirmation']
456 # TODO: plugin should define if username can be updated
457 # TODO: plugin should define if username can be updated
457 if c.extern_type != "rhodecode":
458 if c.extern_type != "rhodecode":
458 # forbid updating username for external accounts
459 # forbid updating username for external accounts
459 skip_attrs.append('username')
460 skip_attrs.append('username')
460
461
461 UserModel().update_user(
462 UserModel().update_user(
462 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
463 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
463 **form_result)
464 **form_result)
464 h.flash(_('Your account was updated successfully'),
465 h.flash(_('Your account was updated successfully'),
465 category='success')
466 category='success')
466 Session().commit()
467 Session().commit()
467
468
468 except formencode.Invalid as errors:
469 except formencode.Invalid as errors:
469 data = render(
470 data = render(
470 'rhodecode:templates/admin/my_account/my_account.mako',
471 'rhodecode:templates/admin/my_account/my_account.mako',
471 self._get_template_context(c), self.request)
472 self._get_template_context(c), self.request)
472
473
473 html = formencode.htmlfill.render(
474 html = formencode.htmlfill.render(
474 data,
475 data,
475 defaults=errors.value,
476 defaults=errors.value,
476 errors=errors.error_dict or {},
477 errors=errors.error_dict or {},
477 prefix_error=False,
478 prefix_error=False,
478 encoding="UTF-8",
479 encoding="UTF-8",
479 force_defaults=False)
480 force_defaults=False)
480 return Response(html)
481 return Response(html)
481
482
482 except Exception:
483 except Exception:
483 log.exception("Exception updating user")
484 log.exception("Exception updating user")
484 h.flash(_('Error occurred during update of user %s')
485 h.flash(_('Error occurred during update of user %s')
485 % form_result.get('username'), category='error')
486 % form_result.get('username'), category='error')
486 raise HTTPFound(h.route_path('my_account_profile'))
487 raise HTTPFound(h.route_path('my_account_profile'))
487
488
488 raise HTTPFound(h.route_path('my_account_profile'))
489 raise HTTPFound(h.route_path('my_account_profile'))
489
490
490 def _get_pull_requests_list(self, statuses):
491 def _get_pull_requests_list(self, statuses):
491 draw, start, limit = self._extract_chunk(self.request)
492 draw, start, limit = self._extract_chunk(self.request)
492 search_q, order_by, order_dir = self._extract_ordering(self.request)
493 search_q, order_by, order_dir = self._extract_ordering(self.request)
493 _render = self.request.get_partial_renderer(
494 _render = self.request.get_partial_renderer(
494 'data_table/_dt_elements.mako')
495 'data_table/_dt_elements.mako')
495
496
496 pull_requests = PullRequestModel().get_im_participating_in(
497 pull_requests = PullRequestModel().get_im_participating_in(
497 user_id=self._rhodecode_user.user_id,
498 user_id=self._rhodecode_user.user_id,
498 statuses=statuses,
499 statuses=statuses,
499 offset=start, length=limit, order_by=order_by,
500 offset=start, length=limit, order_by=order_by,
500 order_dir=order_dir)
501 order_dir=order_dir)
501
502
502 pull_requests_total_count = PullRequestModel().count_im_participating_in(
503 pull_requests_total_count = PullRequestModel().count_im_participating_in(
503 user_id=self._rhodecode_user.user_id, statuses=statuses)
504 user_id=self._rhodecode_user.user_id, statuses=statuses)
504
505
505 data = []
506 data = []
506 comments_model = CommentsModel()
507 comments_model = CommentsModel()
507 for pr in pull_requests:
508 for pr in pull_requests:
508 repo_id = pr.target_repo_id
509 repo_id = pr.target_repo_id
509 comments = comments_model.get_all_comments(
510 comments = comments_model.get_all_comments(
510 repo_id, pull_request=pr)
511 repo_id, pull_request=pr)
511 owned = pr.user_id == self._rhodecode_user.user_id
512 owned = pr.user_id == self._rhodecode_user.user_id
512
513
513 data.append({
514 data.append({
514 'target_repo': _render('pullrequest_target_repo',
515 'target_repo': _render('pullrequest_target_repo',
515 pr.target_repo.repo_name),
516 pr.target_repo.repo_name),
516 'name': _render('pullrequest_name',
517 'name': _render('pullrequest_name',
517 pr.pull_request_id, pr.target_repo.repo_name,
518 pr.pull_request_id, pr.target_repo.repo_name,
518 short=True),
519 short=True),
519 'name_raw': pr.pull_request_id,
520 'name_raw': pr.pull_request_id,
520 'status': _render('pullrequest_status',
521 'status': _render('pullrequest_status',
521 pr.calculated_review_status()),
522 pr.calculated_review_status()),
522 'title': _render(
523 'title': _render(
523 'pullrequest_title', pr.title, pr.description),
524 'pullrequest_title', pr.title, pr.description),
524 'description': h.escape(pr.description),
525 'description': h.escape(pr.description),
525 'updated_on': _render('pullrequest_updated_on',
526 'updated_on': _render('pullrequest_updated_on',
526 h.datetime_to_time(pr.updated_on)),
527 h.datetime_to_time(pr.updated_on)),
527 'updated_on_raw': h.datetime_to_time(pr.updated_on),
528 'updated_on_raw': h.datetime_to_time(pr.updated_on),
528 'created_on': _render('pullrequest_updated_on',
529 'created_on': _render('pullrequest_updated_on',
529 h.datetime_to_time(pr.created_on)),
530 h.datetime_to_time(pr.created_on)),
530 'created_on_raw': h.datetime_to_time(pr.created_on),
531 'created_on_raw': h.datetime_to_time(pr.created_on),
531 'author': _render('pullrequest_author',
532 'author': _render('pullrequest_author',
532 pr.author.full_contact, ),
533 pr.author.full_contact, ),
533 'author_raw': pr.author.full_name,
534 'author_raw': pr.author.full_name,
534 'comments': _render('pullrequest_comments', len(comments)),
535 'comments': _render('pullrequest_comments', len(comments)),
535 'comments_raw': len(comments),
536 'comments_raw': len(comments),
536 'closed': pr.is_closed(),
537 'closed': pr.is_closed(),
537 'owned': owned
538 'owned': owned
538 })
539 })
539
540
540 # json used to render the grid
541 # json used to render the grid
541 data = ({
542 data = ({
542 'draw': draw,
543 'draw': draw,
543 'data': data,
544 'data': data,
544 'recordsTotal': pull_requests_total_count,
545 'recordsTotal': pull_requests_total_count,
545 'recordsFiltered': pull_requests_total_count,
546 'recordsFiltered': pull_requests_total_count,
546 })
547 })
547 return data
548 return data
548
549
549 @LoginRequired()
550 @LoginRequired()
550 @NotAnonymous()
551 @NotAnonymous()
551 @view_config(
552 @view_config(
552 route_name='my_account_pullrequests',
553 route_name='my_account_pullrequests',
553 request_method='GET',
554 request_method='GET',
554 renderer='rhodecode:templates/admin/my_account/my_account.mako')
555 renderer='rhodecode:templates/admin/my_account/my_account.mako')
555 def my_account_pullrequests(self):
556 def my_account_pullrequests(self):
556 c = self.load_default_context()
557 c = self.load_default_context()
557 c.active = 'pullrequests'
558 c.active = 'pullrequests'
558 req_get = self.request.GET
559 req_get = self.request.GET
559
560
560 c.closed = str2bool(req_get.get('pr_show_closed'))
561 c.closed = str2bool(req_get.get('pr_show_closed'))
561
562
562 return self._get_template_context(c)
563 return self._get_template_context(c)
563
564
564 @LoginRequired()
565 @LoginRequired()
565 @NotAnonymous()
566 @NotAnonymous()
566 @view_config(
567 @view_config(
567 route_name='my_account_pullrequests_data',
568 route_name='my_account_pullrequests_data',
568 request_method='GET', renderer='json_ext')
569 request_method='GET', renderer='json_ext')
569 def my_account_pullrequests_data(self):
570 def my_account_pullrequests_data(self):
570 req_get = self.request.GET
571 req_get = self.request.GET
571 closed = str2bool(req_get.get('closed'))
572 closed = str2bool(req_get.get('closed'))
572
573
573 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
574 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
574 if closed:
575 if closed:
575 statuses += [PullRequest.STATUS_CLOSED]
576 statuses += [PullRequest.STATUS_CLOSED]
576
577
577 data = self._get_pull_requests_list(statuses=statuses)
578 data = self._get_pull_requests_list(statuses=statuses)
578 return data
579 return data
579
580
@@ -1,179 +1,188 b''
1 <div class="panel panel-default">
1 <div class="panel panel-default">
2 <div class="panel-heading">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('Authentication Tokens')}</h3>
3 <h3 class="panel-title">${_('Authentication Tokens')}</h3>
4 </div>
4 </div>
5 <div class="panel-body">
5 <div class="panel-body">
6 <div class="apikeys_wrap">
6 <div class="apikeys_wrap">
7 <p>
7 <p>
8 ${_('Each token can have a role. Token with a role can be used only in given context, '
8 ${_('Each token can have a role. Token with a role can be used only in given context, '
9 'e.g. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations only.')}
9 'e.g. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations only.')}
10 </p>
10 </p>
11 <table class="rctable auth_tokens">
11 <table class="rctable auth_tokens">
12 <tr>
12 <tr>
13 <th>${_('Token')}</th>
13 <th>${_('Token')}</th>
14 <th>${_('Scope')}</th>
14 <th>${_('Scope')}</th>
15 <th>${_('Description')}</th>
15 <th>${_('Description')}</th>
16 <th>${_('Role')}</th>
16 <th>${_('Role')}</th>
17 <th>${_('Expiration')}</th>
17 <th>${_('Expiration')}</th>
18 <th>${_('Action')}</th>
18 <th>${_('Action')}</th>
19 </tr>
19 </tr>
20 %if c.user_auth_tokens:
20 %if c.user_auth_tokens:
21 %for auth_token in c.user_auth_tokens:
21 %for auth_token in c.user_auth_tokens:
22 <tr class="${'expired' if auth_token.expired else ''}">
22 <tr class="${'expired' if auth_token.expired else ''}">
23 <td class="truncate-wrap td-authtoken">
23 <td class="truncate-wrap td-authtoken">
24 <div class="user_auth_tokens truncate autoexpand">
24 <div class="user_auth_tokens truncate autoexpand">
25 <code>${auth_token.api_key}</code>
25 <code>${auth_token.api_key}</code>
26 </div>
26 </div>
27 </td>
27 </td>
28 <td class="td">${auth_token.scope_humanized}</td>
28 <td class="td">${auth_token.scope_humanized}</td>
29 <td class="td-wrap">${auth_token.description}</td>
29 <td class="td-wrap">${auth_token.description}</td>
30 <td class="td-tags">
30 <td class="td-tags">
31 <span class="tag disabled">${auth_token.role_humanized}</span>
31 <span class="tag disabled">${auth_token.role_humanized}</span>
32 </td>
32 </td>
33 <td class="td-exp">
33 <td class="td-exp">
34 %if auth_token.expires == -1:
34 %if auth_token.expires == -1:
35 ${_('never')}
35 ${_('never')}
36 %else:
36 %else:
37 %if auth_token.expired:
37 %if auth_token.expired:
38 <span style="text-decoration: line-through">${h.age_component(h.time_to_utcdatetime(auth_token.expires))}</span>
38 <span style="text-decoration: line-through">${h.age_component(h.time_to_utcdatetime(auth_token.expires))}</span>
39 %else:
39 %else:
40 ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
40 ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
41 %endif
41 %endif
42 %endif
42 %endif
43 </td>
43 </td>
44 <td class="td-action">
44 <td class="td-action">
45 ${h.secure_form(h.route_path('my_account_auth_tokens_delete'), request=request)}
45 ${h.secure_form(h.route_path('my_account_auth_tokens_delete'), request=request)}
46 ${h.hidden('del_auth_token', auth_token.user_api_key_id)}
46 ${h.hidden('del_auth_token', auth_token.user_api_key_id)}
47 <button class="btn btn-link btn-danger" type="submit"
47 <button class="btn btn-link btn-danger" type="submit"
48 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.token_obfuscated}');">
48 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.token_obfuscated}');">
49 ${_('Delete')}
49 ${_('Delete')}
50 </button>
50 </button>
51 ${h.end_form()}
51 ${h.end_form()}
52 </td>
52 </td>
53 </tr>
53 </tr>
54 %endfor
54 %endfor
55 %else:
55 %else:
56 <tr><td><div class="ip">${_('No additional auth tokens specified')}</div></td></tr>
56 <tr><td><div class="ip">${_('No additional auth tokens specified')}</div></td></tr>
57 %endif
57 %endif
58 </table>
58 </table>
59 </div>
59 </div>
60
60
61 <div class="user_auth_tokens">
61 <div class="user_auth_tokens">
62 ${h.secure_form(h.route_path('my_account_auth_tokens_add'), request=request)}
62 ${h.secure_form(h.route_path('my_account_auth_tokens_add'), request=request)}
63 <div class="form form-vertical">
63 <div class="form form-vertical">
64 <!-- fields -->
64 <!-- fields -->
65 <div class="fields">
65 <div class="fields">
66 <div class="field">
66 <div class="field">
67 <div class="label">
67 <div class="label">
68 <label for="new_email">${_('New authentication token')}:</label>
68 <label for="new_email">${_('New authentication token')}:</label>
69 </div>
69 </div>
70 <div class="input">
70 <div class="input">
71 ${h.text('description', class_='medium', placeholder=_('Description'))}
71 ${h.text('description', class_='medium', placeholder=_('Description'))}
72 ${h.hidden('lifetime')}
72 ${h.hidden('lifetime')}
73 ${h.select('role', '', c.role_options)}
73 ${h.select('role', '', c.role_options)}
74
74
75 % if c.allow_scoped_tokens:
75 % if c.allow_scoped_tokens:
76 ${h.hidden('scope_repo_id')}
76 ${h.hidden('scope_repo_id')}
77 % else:
77 % else:
78 ${h.select('scope_repo_id_disabled', '', ['Scopes available in EE edition'], disabled='disabled')}
78 ${h.select('scope_repo_id_disabled', '', ['Scopes available in EE edition'], disabled='disabled')}
79 % endif
79 % endif
80 </div>
80 </div>
81 <p class="help-block">
81 <p class="help-block">
82 ${_('Repository scope works only with tokens with VCS type.')}
82 ${_('Repository scope works only with tokens with VCS type.')}
83 </p>
83 </p>
84 </div>
84 </div>
85 <div class="buttons">
85 <div class="buttons">
86 ${h.submit('save',_('Add'),class_="btn")}
86 ${h.submit('save',_('Add'),class_="btn")}
87 ${h.reset('reset',_('Reset'),class_="btn")}
87 ${h.reset('reset',_('Reset'),class_="btn")}
88 </div>
88 </div>
89 </div>
89 </div>
90 </div>
90 </div>
91 ${h.end_form()}
91 ${h.end_form()}
92 </div>
92 </div>
93 </div>
93 </div>
94 </div>
94 </div>
95 <script>
95 <script>
96 $(document).ready(function(){
96 $(document).ready(function(){
97
97
98 var select2Options = {
98 var select2Options = {
99 'containerCssClass': "drop-menu",
99 'containerCssClass': "drop-menu",
100 'dropdownCssClass': "drop-menu-dropdown",
100 'dropdownCssClass': "drop-menu-dropdown",
101 'dropdownAutoWidth': true
101 'dropdownAutoWidth': true
102 };
102 };
103 $("#role").select2(select2Options);
103 $("#role").select2(select2Options);
104
104
105 var preloadData = {
105 var preloadData = {
106 results: [
106 results: [
107 % for entry in c.lifetime_values:
107 % for entry in c.lifetime_values:
108 {id:${entry[0]}, text:"${entry[1]}"}${'' if loop.last else ','}
108 {id:${entry[0]}, text:"${entry[1]}"}${'' if loop.last else ','}
109 % endfor
109 % endfor
110 ]
110 ]
111 };
111 };
112
112
113 $("#lifetime").select2({
113 $("#lifetime").select2({
114 containerCssClass: "drop-menu",
114 containerCssClass: "drop-menu",
115 dropdownCssClass: "drop-menu-dropdown",
115 dropdownCssClass: "drop-menu-dropdown",
116 dropdownAutoWidth: true,
116 dropdownAutoWidth: true,
117 data: preloadData,
117 data: preloadData,
118 placeholder: "${_('Select or enter expiration date')}",
118 placeholder: "${_('Select or enter expiration date')}",
119 query: function(query) {
119 query: function(query) {
120 feedLifetimeOptions(query, preloadData);
120 feedLifetimeOptions(query, preloadData);
121 }
121 }
122 });
122 });
123
123
124
124
125 var repoFilter = function(data) {
125 var repoFilter = function(data) {
126 var results = [];
126 var results = [];
127
127
128 if (!data.results[0]) {
128 if (!data.results[0]) {
129 return data
129 return data
130 }
130 }
131
131
132 $.each(data.results[0].children, function() {
132 $.each(data.results[0].children, function() {
133 // replace name to ID for submision
133 // replace name to ID for submision
134 this.id = this.obj.repo_id;
134 this.id = this.obj.repo_id;
135 results.push(this);
135 results.push(this);
136 });
136 });
137
137
138 data.results[0].children = results;
138 data.results[0].children = results;
139 return data;
139 return data;
140 };
140 };
141
141
142 $("#scope_repo_id_disabled").select2(select2Options);
142 $("#scope_repo_id_disabled").select2(select2Options);
143
143
144 var selectVcsScope = function() {
145 // select vcs scope and disable input
146 $("#role").select2("val", "${c.role_vcs}").trigger('change');
147 $("#role").select2("readonly", true)
148 };
149
144 $("#scope_repo_id").select2({
150 $("#scope_repo_id").select2({
145 cachedDataSource: {},
151 cachedDataSource: {},
146 minimumInputLength: 2,
152 minimumInputLength: 2,
147 placeholder: "${_('repository scope')}",
153 placeholder: "${_('repository scope')}",
148 dropdownAutoWidth: true,
154 dropdownAutoWidth: true,
149 containerCssClass: "drop-menu",
155 containerCssClass: "drop-menu",
150 dropdownCssClass: "drop-menu-dropdown",
156 dropdownCssClass: "drop-menu-dropdown",
151 formatResult: formatResult,
157 formatResult: formatResult,
152 query: $.debounce(250, function(query){
158 query: $.debounce(250, function(query){
153 self = this;
159 self = this;
154 var cacheKey = query.term;
160 var cacheKey = query.term;
155 var cachedData = self.cachedDataSource[cacheKey];
161 var cachedData = self.cachedDataSource[cacheKey];
156
162
157 if (cachedData) {
163 if (cachedData) {
158 query.callback({results: cachedData.results});
164 query.callback({results: cachedData.results});
159 } else {
165 } else {
160 $.ajax({
166 $.ajax({
161 url: pyroutes.url('repo_list_data'),
167 url: pyroutes.url('repo_list_data'),
162 data: {'query': query.term},
168 data: {'query': query.term},
163 dataType: 'json',
169 dataType: 'json',
164 type: 'GET',
170 type: 'GET',
165 success: function(data) {
171 success: function(data) {
166 data = repoFilter(data);
172 data = repoFilter(data);
167 self.cachedDataSource[cacheKey] = data;
173 self.cachedDataSource[cacheKey] = data;
168 query.callback({results: data.results});
174 query.callback({results: data.results});
169 },
175 },
170 error: function(data, textStatus, errorThrown) {
176 error: function(data, textStatus, errorThrown) {
171 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
177 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
172 }
178 }
173 })
179 })
174 }
180 }
175 })
181 })
176 });
182 });
183 $("#scope_repo_id").on('select2-selecting', function(e){
184 selectVcsScope()
185 });
177
186
178 });
187 });
179 </script>
188 </script>
@@ -1,177 +1,186 b''
1 <div class="panel panel-default">
1 <div class="panel panel-default">
2 <div class="panel-heading">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('Authentication Tokens')}</h3>
3 <h3 class="panel-title">${_('Authentication Tokens')}</h3>
4 </div>
4 </div>
5 <div class="panel-body">
5 <div class="panel-body">
6 <div class="apikeys_wrap">
6 <div class="apikeys_wrap">
7 <p>
7 <p>
8 ${_('Each token can have a role. Token with a role can be used only in given context, '
8 ${_('Each token can have a role. Token with a role can be used only in given context, '
9 'e.g. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations only.')}
9 'e.g. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations only.')}
10 </p>
10 </p>
11 <table class="rctable auth_tokens">
11 <table class="rctable auth_tokens">
12 <tr>
12 <tr>
13 <th>${_('Token')}</th>
13 <th>${_('Token')}</th>
14 <th>${_('Scope')}</th>
14 <th>${_('Scope')}</th>
15 <th>${_('Description')}</th>
15 <th>${_('Description')}</th>
16 <th>${_('Role')}</th>
16 <th>${_('Role')}</th>
17 <th>${_('Expiration')}</th>
17 <th>${_('Expiration')}</th>
18 <th>${_('Action')}</th>
18 <th>${_('Action')}</th>
19 </tr>
19 </tr>
20 %if c.user_auth_tokens:
20 %if c.user_auth_tokens:
21 %for auth_token in c.user_auth_tokens:
21 %for auth_token in c.user_auth_tokens:
22 <tr class="${'expired' if auth_token.expired else ''}">
22 <tr class="${'expired' if auth_token.expired else ''}">
23 <td class="truncate-wrap td-authtoken"><div class="user_auth_tokens truncate autoexpand"><code>${auth_token.api_key}</code></div></td>
23 <td class="truncate-wrap td-authtoken"><div class="user_auth_tokens truncate autoexpand"><code>${auth_token.api_key}</code></div></td>
24 <td class="td">${auth_token.scope_humanized}</td>
24 <td class="td">${auth_token.scope_humanized}</td>
25 <td class="td-wrap">${auth_token.description}</td>
25 <td class="td-wrap">${auth_token.description}</td>
26 <td class="td-tags">
26 <td class="td-tags">
27 <span class="tag disabled">${auth_token.role_humanized}</span>
27 <span class="tag disabled">${auth_token.role_humanized}</span>
28 </td>
28 </td>
29 <td class="td-exp">
29 <td class="td-exp">
30 %if auth_token.expires == -1:
30 %if auth_token.expires == -1:
31 ${_('never')}
31 ${_('never')}
32 %else:
32 %else:
33 %if auth_token.expired:
33 %if auth_token.expired:
34 <span style="text-decoration: line-through">${h.age_component(h.time_to_utcdatetime(auth_token.expires))}</span>
34 <span style="text-decoration: line-through">${h.age_component(h.time_to_utcdatetime(auth_token.expires))}</span>
35 %else:
35 %else:
36 ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
36 ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
37 %endif
37 %endif
38 %endif
38 %endif
39 </td>
39 </td>
40 <td class="td-action">
40 <td class="td-action">
41 ${h.secure_form(h.route_path('edit_user_auth_tokens_delete', user_id=c.user.user_id), request=request)}
41 ${h.secure_form(h.route_path('edit_user_auth_tokens_delete', user_id=c.user.user_id), request=request)}
42 ${h.hidden('del_auth_token', auth_token.user_api_key_id)}
42 ${h.hidden('del_auth_token', auth_token.user_api_key_id)}
43 <button class="btn btn-link btn-danger" type="submit"
43 <button class="btn btn-link btn-danger" type="submit"
44 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.token_obfuscated}');">
44 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.token_obfuscated}');">
45 ${_('Delete')}
45 ${_('Delete')}
46 </button>
46 </button>
47 ${h.end_form()}
47 ${h.end_form()}
48 </td>
48 </td>
49 </tr>
49 </tr>
50 %endfor
50 %endfor
51 %else:
51 %else:
52 <tr><td><div class="ip">${_('No additional auth tokens specified')}</div></td></tr>
52 <tr><td><div class="ip">${_('No additional auth tokens specified')}</div></td></tr>
53 %endif
53 %endif
54 </table>
54 </table>
55 </div>
55 </div>
56
56
57 <div class="user_auth_tokens">
57 <div class="user_auth_tokens">
58 ${h.secure_form(h.route_path('edit_user_auth_tokens_add', user_id=c.user.user_id), request=request)}
58 ${h.secure_form(h.route_path('edit_user_auth_tokens_add', user_id=c.user.user_id), request=request)}
59 <div class="form form-vertical">
59 <div class="form form-vertical">
60 <!-- fields -->
60 <!-- fields -->
61 <div class="fields">
61 <div class="fields">
62 <div class="field">
62 <div class="field">
63 <div class="label">
63 <div class="label">
64 <label for="new_email">${_('New authentication token')}:</label>
64 <label for="new_email">${_('New authentication token')}:</label>
65 </div>
65 </div>
66 <div class="input">
66 <div class="input">
67 ${h.text('description', class_='medium', placeholder=_('Description'))}
67 ${h.text('description', class_='medium', placeholder=_('Description'))}
68 ${h.hidden('lifetime')}
68 ${h.hidden('lifetime')}
69 ${h.select('role', '', c.role_options)}
69 ${h.select('role', '', c.role_options)}
70
70
71 % if c.allow_scoped_tokens:
71 % if c.allow_scoped_tokens:
72 ${h.hidden('scope_repo_id')}
72 ${h.hidden('scope_repo_id')}
73 % else:
73 % else:
74 ${h.select('scope_repo_id_disabled', '', ['Scopes available in EE edition'], disabled='disabled')}
74 ${h.select('scope_repo_id_disabled', '', ['Scopes available in EE edition'], disabled='disabled')}
75 % endif
75 % endif
76 </div>
76 </div>
77 <p class="help-block">
77 <p class="help-block">
78 ${_('Repository scope works only with tokens with VCS type.')}
78 ${_('Repository scope works only with tokens with VCS type.')}
79 </p>
79 </p>
80 </div>
80 </div>
81 <div class="buttons">
81 <div class="buttons">
82 ${h.submit('save',_('Add'),class_="btn")}
82 ${h.submit('save',_('Add'),class_="btn")}
83 ${h.reset('reset',_('Reset'),class_="btn")}
83 ${h.reset('reset',_('Reset'),class_="btn")}
84 </div>
84 </div>
85 </div>
85 </div>
86 </div>
86 </div>
87 ${h.end_form()}
87 ${h.end_form()}
88 </div>
88 </div>
89 </div>
89 </div>
90 </div>
90 </div>
91
91
92 <script>
92 <script>
93
93
94 $(document).ready(function(){
94 $(document).ready(function(){
95
95
96 var select2Options = {
96 var select2Options = {
97 'containerCssClass': "drop-menu",
97 'containerCssClass': "drop-menu",
98 'dropdownCssClass': "drop-menu-dropdown",
98 'dropdownCssClass': "drop-menu-dropdown",
99 'dropdownAutoWidth': true
99 'dropdownAutoWidth': true
100 };
100 };
101 $("#role").select2(select2Options);
101 $("#role").select2(select2Options);
102
102
103 var preloadData = {
103 var preloadData = {
104 results: [
104 results: [
105 % for entry in c.lifetime_values:
105 % for entry in c.lifetime_values:
106 {id:${entry[0]}, text:"${entry[1]}"}${'' if loop.last else ','}
106 {id:${entry[0]}, text:"${entry[1]}"}${'' if loop.last else ','}
107 % endfor
107 % endfor
108 ]
108 ]
109 };
109 };
110
110
111 $("#lifetime").select2({
111 $("#lifetime").select2({
112 containerCssClass: "drop-menu",
112 containerCssClass: "drop-menu",
113 dropdownCssClass: "drop-menu-dropdown",
113 dropdownCssClass: "drop-menu-dropdown",
114 dropdownAutoWidth: true,
114 dropdownAutoWidth: true,
115 data: preloadData,
115 data: preloadData,
116 placeholder: "${_('Select or enter expiration date')}",
116 placeholder: "${_('Select or enter expiration date')}",
117 query: function(query) {
117 query: function(query) {
118 feedLifetimeOptions(query, preloadData);
118 feedLifetimeOptions(query, preloadData);
119 }
119 }
120 });
120 });
121
121
122
122
123 var repoFilter = function(data) {
123 var repoFilter = function(data) {
124 var results = [];
124 var results = [];
125
125
126 if (!data.results[0]) {
126 if (!data.results[0]) {
127 return data
127 return data
128 }
128 }
129
129
130 $.each(data.results[0].children, function() {
130 $.each(data.results[0].children, function() {
131 // replace name to ID for submision
131 // replace name to ID for submision
132 this.id = this.obj.repo_id;
132 this.id = this.obj.repo_id;
133 results.push(this);
133 results.push(this);
134 });
134 });
135
135
136 data.results[0].children = results;
136 data.results[0].children = results;
137 return data;
137 return data;
138 };
138 };
139
139
140 $("#scope_repo_id_disabled").select2(select2Options);
140 $("#scope_repo_id_disabled").select2(select2Options);
141
141
142 var selectVcsScope = function() {
143 // select vcs scope and disable input
144 $("#role").select2("val", "${c.role_vcs}").trigger('change');
145 $("#role").select2("readonly", true)
146 };
147
142 $("#scope_repo_id").select2({
148 $("#scope_repo_id").select2({
143 cachedDataSource: {},
149 cachedDataSource: {},
144 minimumInputLength: 2,
150 minimumInputLength: 2,
145 placeholder: "${_('repository scope')}",
151 placeholder: "${_('repository scope')}",
146 dropdownAutoWidth: true,
152 dropdownAutoWidth: true,
147 containerCssClass: "drop-menu",
153 containerCssClass: "drop-menu",
148 dropdownCssClass: "drop-menu-dropdown",
154 dropdownCssClass: "drop-menu-dropdown",
149 formatResult: formatResult,
155 formatResult: formatResult,
150 query: $.debounce(250, function(query){
156 query: $.debounce(250, function(query){
151 self = this;
157 self = this;
152 var cacheKey = query.term;
158 var cacheKey = query.term;
153 var cachedData = self.cachedDataSource[cacheKey];
159 var cachedData = self.cachedDataSource[cacheKey];
154
160
155 if (cachedData) {
161 if (cachedData) {
156 query.callback({results: cachedData.results});
162 query.callback({results: cachedData.results});
157 } else {
163 } else {
158 $.ajax({
164 $.ajax({
159 url: pyroutes.url('repo_list_data'),
165 url: pyroutes.url('repo_list_data'),
160 data: {'query': query.term},
166 data: {'query': query.term},
161 dataType: 'json',
167 dataType: 'json',
162 type: 'GET',
168 type: 'GET',
163 success: function(data) {
169 success: function(data) {
164 data = repoFilter(data);
170 data = repoFilter(data);
165 self.cachedDataSource[cacheKey] = data;
171 self.cachedDataSource[cacheKey] = data;
166 query.callback({results: data.results});
172 query.callback({results: data.results});
167 },
173 },
168 error: function(data, textStatus, errorThrown) {
174 error: function(data, textStatus, errorThrown) {
169 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
175 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
170 }
176 }
171 })
177 })
172 }
178 }
173 })
179 })
174 });
180 });
181 $("#scope_repo_id").on('select2-selecting', function(e){
182 selectVcsScope()
183 });
175
184
176 });
185 });
177 </script>
186 </script>
General Comments 0
You need to be logged in to leave comments. Login now