##// END OF EJS Templates
admin users and admin users group: add number of inactive users/users_group, resolves #5454
Bartłomiej Wołyńczyk -
r2727:15796c8e default
parent child Browse files
Show More
@@ -1,246 +1,257 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22
23 23 import formencode
24 24 import formencode.htmlfill
25 25
26 26 from pyramid.httpexceptions import HTTPFound
27 27 from pyramid.view import view_config
28 28 from pyramid.response import Response
29 29 from pyramid.renderers import render
30 30
31 31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 32 from rhodecode.lib.auth import (
33 33 LoginRequired, NotAnonymous, CSRFRequired, HasPermissionAnyDecorator)
34 34 from rhodecode.lib import helpers as h, audit_logger
35 35 from rhodecode.lib.utils2 import safe_unicode
36 36
37 37 from rhodecode.model.forms import UserGroupForm
38 38 from rhodecode.model.permission import PermissionModel
39 39 from rhodecode.model.scm import UserGroupList
40 40 from rhodecode.model.db import (
41 41 or_, count, User, UserGroup, UserGroupMember)
42 42 from rhodecode.model.meta import Session
43 43 from rhodecode.model.user_group import UserGroupModel
44 from rhodecode.model.db import true
44 45
45 46 log = logging.getLogger(__name__)
46 47
47 48
48 49 class AdminUserGroupsView(BaseAppView, DataGridAppView):
49 50
50 51 def load_default_context(self):
51 52 c = self._get_local_tmpl_context()
52 53
53 54 PermissionModel().set_global_permission_choices(
54 55 c, gettext_translator=self.request.translate)
55 56
56 57 return c
57 58
58 59 # permission check in data loading of
59 60 # `user_groups_list_data` via UserGroupList
60 61 @LoginRequired()
61 62 @NotAnonymous()
62 63 @view_config(
63 64 route_name='user_groups', request_method='GET',
64 65 renderer='rhodecode:templates/admin/user_groups/user_groups.mako')
65 66 def user_groups_list(self):
66 67 c = self.load_default_context()
67 68 return self._get_template_context(c)
68 69
69 70 # permission check inside
70 71 @LoginRequired()
71 72 @NotAnonymous()
72 73 @view_config(
73 74 route_name='user_groups_data', request_method='GET',
74 75 renderer='json_ext', xhr=True)
75 76 def user_groups_list_data(self):
76 77 self.load_default_context()
77 78 column_map = {
78 79 'active': 'users_group_active',
79 80 'description': 'user_group_description',
80 81 'members': 'members_total',
81 82 'owner': 'user_username',
82 83 'sync': 'group_data'
83 84 }
84 85 draw, start, limit = self._extract_chunk(self.request)
85 86 search_q, order_by, order_dir = self._extract_ordering(
86 87 self.request, column_map=column_map)
87 88
88 89 _render = self.request.get_partial_renderer(
89 90 'rhodecode:templates/data_table/_dt_elements.mako')
90 91
91 92 def user_group_name(user_group_name):
92 93 return _render("user_group_name", user_group_name)
93 94
94 95 def user_group_actions(user_group_id, user_group_name):
95 96 return _render("user_group_actions", user_group_id, user_group_name)
96 97
97 98 def user_profile(username):
98 99 return _render('user_profile', username)
99 100
100 101 auth_user_group_list = UserGroupList(
101 102 UserGroup.query().all(), perm_set=['usergroup.admin'])
102 103
103 104 allowed_ids = [-1]
104 105 for user_group in auth_user_group_list:
105 106 allowed_ids.append(user_group.users_group_id)
106 107
107 108 user_groups_data_total_count = UserGroup.query()\
108 109 .filter(UserGroup.users_group_id.in_(allowed_ids))\
109 110 .count()
110 111
112 user_groups_data_total_inactive_count = UserGroup.query()\
113 .filter(UserGroup.users_group_id.in_(allowed_ids))\
114 .filter(UserGroup.users_group_active != true()).count()
115
111 116 member_count = count(UserGroupMember.user_id)
112 117 base_q = Session.query(
113 118 UserGroup.users_group_name,
114 119 UserGroup.user_group_description,
115 120 UserGroup.users_group_active,
116 121 UserGroup.users_group_id,
117 122 UserGroup.group_data,
118 123 User,
119 124 member_count.label('member_count')
120 125 ) \
121 126 .filter(UserGroup.users_group_id.in_(allowed_ids)) \
122 127 .outerjoin(UserGroupMember) \
123 128 .join(User, User.user_id == UserGroup.user_id) \
124 129 .group_by(UserGroup, User)
125 130
131 base_q_inactive = base_q.filter(UserGroup.users_group_active != true())
132
126 133 if search_q:
127 134 like_expression = u'%{}%'.format(safe_unicode(search_q))
128 135 base_q = base_q.filter(or_(
129 136 UserGroup.users_group_name.ilike(like_expression),
130 137 ))
138 base_q_inactive = base_q.filter(UserGroup.users_group_active != true())
131 139
132 140 user_groups_data_total_filtered_count = base_q.count()
141 user_groups_data_total_filtered_inactive_count = base_q_inactive.count()
133 142
134 143 if order_by == 'members_total':
135 144 sort_col = member_count
136 145 elif order_by == 'user_username':
137 146 sort_col = User.username
138 147 else:
139 148 sort_col = getattr(UserGroup, order_by, None)
140 149
141 150 if isinstance(sort_col, count) or sort_col:
142 151 if order_dir == 'asc':
143 152 sort_col = sort_col.asc()
144 153 else:
145 154 sort_col = sort_col.desc()
146 155
147 156 base_q = base_q.order_by(sort_col)
148 157 base_q = base_q.offset(start).limit(limit)
149 158
150 159 # authenticated access to user groups
151 160 auth_user_group_list = base_q.all()
152 161
153 162 user_groups_data = []
154 163 for user_gr in auth_user_group_list:
155 164 user_groups_data.append({
156 165 "users_group_name": user_group_name(user_gr.users_group_name),
157 166 "name_raw": h.escape(user_gr.users_group_name),
158 167 "description": h.escape(user_gr.user_group_description),
159 168 "members": user_gr.member_count,
160 169 # NOTE(marcink): because of advanced query we
161 170 # need to load it like that
162 171 "sync": UserGroup._load_sync(
163 172 UserGroup._load_group_data(user_gr.group_data)),
164 173 "active": h.bool2icon(user_gr.users_group_active),
165 174 "owner": user_profile(user_gr.User.username),
166 175 "action": user_group_actions(
167 176 user_gr.users_group_id, user_gr.users_group_name)
168 177 })
169 178
170 179 data = ({
171 180 'draw': draw,
172 181 'data': user_groups_data,
173 182 'recordsTotal': user_groups_data_total_count,
183 'recordsTotalInactive': user_groups_data_total_inactive_count,
174 184 'recordsFiltered': user_groups_data_total_filtered_count,
185 'recordsFilteredInactive': user_groups_data_total_filtered_inactive_count,
175 186 })
176 187
177 188 return data
178 189
179 190 @LoginRequired()
180 191 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
181 192 @view_config(
182 193 route_name='user_groups_new', request_method='GET',
183 194 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
184 195 def user_groups_new(self):
185 196 c = self.load_default_context()
186 197 return self._get_template_context(c)
187 198
188 199 @LoginRequired()
189 200 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
190 201 @CSRFRequired()
191 202 @view_config(
192 203 route_name='user_groups_create', request_method='POST',
193 204 renderer='rhodecode:templates/admin/user_groups/user_group_add.mako')
194 205 def user_groups_create(self):
195 206 _ = self.request.translate
196 207 c = self.load_default_context()
197 208 users_group_form = UserGroupForm(self.request.translate)()
198 209
199 210 user_group_name = self.request.POST.get('users_group_name')
200 211 try:
201 212 form_result = users_group_form.to_python(dict(self.request.POST))
202 213 user_group = UserGroupModel().create(
203 214 name=form_result['users_group_name'],
204 215 description=form_result['user_group_description'],
205 216 owner=self._rhodecode_user.user_id,
206 217 active=form_result['users_group_active'])
207 218 Session().flush()
208 219 creation_data = user_group.get_api_data()
209 220 user_group_name = form_result['users_group_name']
210 221
211 222 audit_logger.store_web(
212 223 'user_group.create', action_data={'data': creation_data},
213 224 user=self._rhodecode_user)
214 225
215 226 user_group_link = h.link_to(
216 227 h.escape(user_group_name),
217 228 h.route_path(
218 229 'edit_user_group', user_group_id=user_group.users_group_id))
219 230 h.flash(h.literal(_('Created user group %(user_group_link)s')
220 231 % {'user_group_link': user_group_link}),
221 232 category='success')
222 233 Session().commit()
223 234 user_group_id = user_group.users_group_id
224 235 except formencode.Invalid as errors:
225 236
226 237 data = render(
227 238 'rhodecode:templates/admin/user_groups/user_group_add.mako',
228 239 self._get_template_context(c), self.request)
229 240 html = formencode.htmlfill.render(
230 241 data,
231 242 defaults=errors.value,
232 243 errors=errors.error_dict or {},
233 244 prefix_error=False,
234 245 encoding="UTF-8",
235 246 force_defaults=False
236 247 )
237 248 return Response(html)
238 249
239 250 except Exception:
240 251 log.exception("Exception creating user group")
241 252 h.flash(_('Error occurred during creation of user group %s') \
242 253 % user_group_name, category='error')
243 254 raise HTTPFound(h.route_path('user_groups_new'))
244 255
245 256 raise HTTPFound(
246 257 h.route_path('edit_user_group', user_group_id=user_group_id))
@@ -1,1191 +1,1200 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import datetime
23 23 import formencode
24 24 import formencode.htmlfill
25 25
26 26 from pyramid.httpexceptions import HTTPFound
27 27 from pyramid.view import view_config
28 28 from pyramid.renderers import render
29 29 from pyramid.response import Response
30 30
31 31 from rhodecode.apps._base import BaseAppView, DataGridAppView, UserAppView
32 32 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
33 33 from rhodecode.authentication.plugins import auth_rhodecode
34 34 from rhodecode.events import trigger
35 from rhodecode.model.db import true
35 36
36 37 from rhodecode.lib import audit_logger
37 38 from rhodecode.lib.exceptions import (
38 39 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
39 40 UserOwnsUserGroupsException, DefaultUserException)
40 41 from rhodecode.lib.ext_json import json
41 42 from rhodecode.lib.auth import (
42 43 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
43 44 from rhodecode.lib import helpers as h
44 45 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
45 46 from rhodecode.model.auth_token import AuthTokenModel
46 47 from rhodecode.model.forms import (
47 48 UserForm, UserIndividualPermissionsForm, UserPermissionsForm,
48 49 UserExtraEmailForm, UserExtraIpForm)
49 50 from rhodecode.model.permission import PermissionModel
50 51 from rhodecode.model.repo_group import RepoGroupModel
51 52 from rhodecode.model.ssh_key import SshKeyModel
52 53 from rhodecode.model.user import UserModel
53 54 from rhodecode.model.user_group import UserGroupModel
54 55 from rhodecode.model.db import (
55 56 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
56 57 UserApiKeys, UserSshKeys, RepoGroup)
57 58 from rhodecode.model.meta import Session
58 59
59 60 log = logging.getLogger(__name__)
60 61
61 62
62 63 class AdminUsersView(BaseAppView, DataGridAppView):
63 64
64 65 def load_default_context(self):
65 66 c = self._get_local_tmpl_context()
66 67 return c
67 68
68 69 @LoginRequired()
69 70 @HasPermissionAllDecorator('hg.admin')
70 71 @view_config(
71 72 route_name='users', request_method='GET',
72 73 renderer='rhodecode:templates/admin/users/users.mako')
73 74 def users_list(self):
74 75 c = self.load_default_context()
75 76 return self._get_template_context(c)
76 77
77 78 @LoginRequired()
78 79 @HasPermissionAllDecorator('hg.admin')
79 80 @view_config(
80 81 # renderer defined below
81 82 route_name='users_data', request_method='GET',
82 83 renderer='json_ext', xhr=True)
83 84 def users_list_data(self):
84 85 self.load_default_context()
85 86 column_map = {
86 87 'first_name': 'name',
87 88 'last_name': 'lastname',
88 89 }
89 90 draw, start, limit = self._extract_chunk(self.request)
90 91 search_q, order_by, order_dir = self._extract_ordering(
91 92 self.request, column_map=column_map)
92
93 93 _render = self.request.get_partial_renderer(
94 94 'rhodecode:templates/data_table/_dt_elements.mako')
95 95
96 96 def user_actions(user_id, username):
97 97 return _render("user_actions", user_id, username)
98 98
99 99 users_data_total_count = User.query()\
100 100 .filter(User.username != User.DEFAULT_USER) \
101 101 .count()
102 102
103 users_data_total_inactive_count = User.query()\
104 .filter(User.username != User.DEFAULT_USER) \
105 .filter(User.active != true())\
106 .count()
107
103 108 # json generate
104 109 base_q = User.query().filter(User.username != User.DEFAULT_USER)
110 base_inactive_q = base_q.filter(User.active != true())
105 111
106 112 if search_q:
107 113 like_expression = u'%{}%'.format(safe_unicode(search_q))
108 114 base_q = base_q.filter(or_(
109 115 User.username.ilike(like_expression),
110 116 User._email.ilike(like_expression),
111 117 User.name.ilike(like_expression),
112 118 User.lastname.ilike(like_expression),
113 119 ))
120 base_inactive_q = base_q.filter(User.active != true())
114 121
115 122 users_data_total_filtered_count = base_q.count()
123 users_data_total_filtered_inactive_count = base_inactive_q.count()
116 124
117 125 sort_col = getattr(User, order_by, None)
118 126 if sort_col:
119 127 if order_dir == 'asc':
120 128 # handle null values properly to order by NULL last
121 129 if order_by in ['last_activity']:
122 130 sort_col = coalesce(sort_col, datetime.date.max)
123 131 sort_col = sort_col.asc()
124 132 else:
125 133 # handle null values properly to order by NULL last
126 134 if order_by in ['last_activity']:
127 135 sort_col = coalesce(sort_col, datetime.date.min)
128 136 sort_col = sort_col.desc()
129 137
130 138 base_q = base_q.order_by(sort_col)
131 139 base_q = base_q.offset(start).limit(limit)
132 140
133 141 users_list = base_q.all()
134 142
135 143 users_data = []
136 144 for user in users_list:
137 145 users_data.append({
138 146 "username": h.gravatar_with_user(self.request, user.username),
139 147 "email": user.email,
140 148 "first_name": user.first_name,
141 149 "last_name": user.last_name,
142 150 "last_login": h.format_date(user.last_login),
143 151 "last_activity": h.format_date(user.last_activity),
144 152 "active": h.bool2icon(user.active),
145 153 "active_raw": user.active,
146 154 "admin": h.bool2icon(user.admin),
147 155 "extern_type": user.extern_type,
148 156 "extern_name": user.extern_name,
149 157 "action": user_actions(user.user_id, user.username),
150 158 })
151
152 159 data = ({
153 160 'draw': draw,
154 161 'data': users_data,
155 162 'recordsTotal': users_data_total_count,
156 163 'recordsFiltered': users_data_total_filtered_count,
164 'recordsTotalInactive': users_data_total_inactive_count,
165 'recordsFilteredInactive': users_data_total_filtered_inactive_count
157 166 })
158 167
159 168 return data
160 169
161 170 def _set_personal_repo_group_template_vars(self, c_obj):
162 171 DummyUser = AttributeDict({
163 172 'username': '${username}',
164 173 'user_id': '${user_id}',
165 174 })
166 175 c_obj.default_create_repo_group = RepoGroupModel() \
167 176 .get_default_create_personal_repo_group()
168 177 c_obj.personal_repo_group_name = RepoGroupModel() \
169 178 .get_personal_group_name(DummyUser)
170 179
171 180 @LoginRequired()
172 181 @HasPermissionAllDecorator('hg.admin')
173 182 @view_config(
174 183 route_name='users_new', request_method='GET',
175 184 renderer='rhodecode:templates/admin/users/user_add.mako')
176 185 def users_new(self):
177 186 _ = self.request.translate
178 187 c = self.load_default_context()
179 188 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
180 189 self._set_personal_repo_group_template_vars(c)
181 190 return self._get_template_context(c)
182 191
183 192 @LoginRequired()
184 193 @HasPermissionAllDecorator('hg.admin')
185 194 @CSRFRequired()
186 195 @view_config(
187 196 route_name='users_create', request_method='POST',
188 197 renderer='rhodecode:templates/admin/users/user_add.mako')
189 198 def users_create(self):
190 199 _ = self.request.translate
191 200 c = self.load_default_context()
192 201 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
193 202 user_model = UserModel()
194 203 user_form = UserForm(self.request.translate)()
195 204 try:
196 205 form_result = user_form.to_python(dict(self.request.POST))
197 206 user = user_model.create(form_result)
198 207 Session().flush()
199 208 creation_data = user.get_api_data()
200 209 username = form_result['username']
201 210
202 211 audit_logger.store_web(
203 212 'user.create', action_data={'data': creation_data},
204 213 user=c.rhodecode_user)
205 214
206 215 user_link = h.link_to(
207 216 h.escape(username),
208 217 h.route_path('user_edit', user_id=user.user_id))
209 218 h.flash(h.literal(_('Created user %(user_link)s')
210 219 % {'user_link': user_link}), category='success')
211 220 Session().commit()
212 221 except formencode.Invalid as errors:
213 222 self._set_personal_repo_group_template_vars(c)
214 223 data = render(
215 224 'rhodecode:templates/admin/users/user_add.mako',
216 225 self._get_template_context(c), self.request)
217 226 html = formencode.htmlfill.render(
218 227 data,
219 228 defaults=errors.value,
220 229 errors=errors.error_dict or {},
221 230 prefix_error=False,
222 231 encoding="UTF-8",
223 232 force_defaults=False
224 233 )
225 234 return Response(html)
226 235 except UserCreationError as e:
227 236 h.flash(e, 'error')
228 237 except Exception:
229 238 log.exception("Exception creation of user")
230 239 h.flash(_('Error occurred during creation of user %s')
231 240 % self.request.POST.get('username'), category='error')
232 241 raise HTTPFound(h.route_path('users'))
233 242
234 243
235 244 class UsersView(UserAppView):
236 245 ALLOW_SCOPED_TOKENS = False
237 246 """
238 247 This view has alternative version inside EE, if modified please take a look
239 248 in there as well.
240 249 """
241 250
242 251 def load_default_context(self):
243 252 c = self._get_local_tmpl_context()
244 253 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
245 254 c.allowed_languages = [
246 255 ('en', 'English (en)'),
247 256 ('de', 'German (de)'),
248 257 ('fr', 'French (fr)'),
249 258 ('it', 'Italian (it)'),
250 259 ('ja', 'Japanese (ja)'),
251 260 ('pl', 'Polish (pl)'),
252 261 ('pt', 'Portuguese (pt)'),
253 262 ('ru', 'Russian (ru)'),
254 263 ('zh', 'Chinese (zh)'),
255 264 ]
256 265 req = self.request
257 266
258 267 c.available_permissions = req.registry.settings['available_permissions']
259 268 PermissionModel().set_global_permission_choices(
260 269 c, gettext_translator=req.translate)
261 270
262 271 return c
263 272
264 273 @LoginRequired()
265 274 @HasPermissionAllDecorator('hg.admin')
266 275 @CSRFRequired()
267 276 @view_config(
268 277 route_name='user_update', request_method='POST',
269 278 renderer='rhodecode:templates/admin/users/user_edit.mako')
270 279 def user_update(self):
271 280 _ = self.request.translate
272 281 c = self.load_default_context()
273 282
274 283 user_id = self.db_user_id
275 284 c.user = self.db_user
276 285
277 286 c.active = 'profile'
278 287 c.extern_type = c.user.extern_type
279 288 c.extern_name = c.user.extern_name
280 289 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
281 290 available_languages = [x[0] for x in c.allowed_languages]
282 291 _form = UserForm(self.request.translate, edit=True,
283 292 available_languages=available_languages,
284 293 old_data={'user_id': user_id,
285 294 'email': c.user.email})()
286 295 form_result = {}
287 296 old_values = c.user.get_api_data()
288 297 try:
289 298 form_result = _form.to_python(dict(self.request.POST))
290 299 skip_attrs = ['extern_type', 'extern_name']
291 300 # TODO: plugin should define if username can be updated
292 301 if c.extern_type != "rhodecode":
293 302 # forbid updating username for external accounts
294 303 skip_attrs.append('username')
295 304
296 305 UserModel().update_user(
297 306 user_id, skip_attrs=skip_attrs, **form_result)
298 307
299 308 audit_logger.store_web(
300 309 'user.edit', action_data={'old_data': old_values},
301 310 user=c.rhodecode_user)
302 311
303 312 Session().commit()
304 313 h.flash(_('User updated successfully'), category='success')
305 314 except formencode.Invalid as errors:
306 315 data = render(
307 316 'rhodecode:templates/admin/users/user_edit.mako',
308 317 self._get_template_context(c), self.request)
309 318 html = formencode.htmlfill.render(
310 319 data,
311 320 defaults=errors.value,
312 321 errors=errors.error_dict or {},
313 322 prefix_error=False,
314 323 encoding="UTF-8",
315 324 force_defaults=False
316 325 )
317 326 return Response(html)
318 327 except UserCreationError as e:
319 328 h.flash(e, 'error')
320 329 except Exception:
321 330 log.exception("Exception updating user")
322 331 h.flash(_('Error occurred during update of user %s')
323 332 % form_result.get('username'), category='error')
324 333 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
325 334
326 335 @LoginRequired()
327 336 @HasPermissionAllDecorator('hg.admin')
328 337 @CSRFRequired()
329 338 @view_config(
330 339 route_name='user_delete', request_method='POST',
331 340 renderer='rhodecode:templates/admin/users/user_edit.mako')
332 341 def user_delete(self):
333 342 _ = self.request.translate
334 343 c = self.load_default_context()
335 344 c.user = self.db_user
336 345
337 346 _repos = c.user.repositories
338 347 _repo_groups = c.user.repository_groups
339 348 _user_groups = c.user.user_groups
340 349
341 350 handle_repos = None
342 351 handle_repo_groups = None
343 352 handle_user_groups = None
344 353 # dummy call for flash of handle
345 354 set_handle_flash_repos = lambda: None
346 355 set_handle_flash_repo_groups = lambda: None
347 356 set_handle_flash_user_groups = lambda: None
348 357
349 358 if _repos and self.request.POST.get('user_repos'):
350 359 do = self.request.POST['user_repos']
351 360 if do == 'detach':
352 361 handle_repos = 'detach'
353 362 set_handle_flash_repos = lambda: h.flash(
354 363 _('Detached %s repositories') % len(_repos),
355 364 category='success')
356 365 elif do == 'delete':
357 366 handle_repos = 'delete'
358 367 set_handle_flash_repos = lambda: h.flash(
359 368 _('Deleted %s repositories') % len(_repos),
360 369 category='success')
361 370
362 371 if _repo_groups and self.request.POST.get('user_repo_groups'):
363 372 do = self.request.POST['user_repo_groups']
364 373 if do == 'detach':
365 374 handle_repo_groups = 'detach'
366 375 set_handle_flash_repo_groups = lambda: h.flash(
367 376 _('Detached %s repository groups') % len(_repo_groups),
368 377 category='success')
369 378 elif do == 'delete':
370 379 handle_repo_groups = 'delete'
371 380 set_handle_flash_repo_groups = lambda: h.flash(
372 381 _('Deleted %s repository groups') % len(_repo_groups),
373 382 category='success')
374 383
375 384 if _user_groups and self.request.POST.get('user_user_groups'):
376 385 do = self.request.POST['user_user_groups']
377 386 if do == 'detach':
378 387 handle_user_groups = 'detach'
379 388 set_handle_flash_user_groups = lambda: h.flash(
380 389 _('Detached %s user groups') % len(_user_groups),
381 390 category='success')
382 391 elif do == 'delete':
383 392 handle_user_groups = 'delete'
384 393 set_handle_flash_user_groups = lambda: h.flash(
385 394 _('Deleted %s user groups') % len(_user_groups),
386 395 category='success')
387 396
388 397 old_values = c.user.get_api_data()
389 398 try:
390 399 UserModel().delete(c.user, handle_repos=handle_repos,
391 400 handle_repo_groups=handle_repo_groups,
392 401 handle_user_groups=handle_user_groups)
393 402
394 403 audit_logger.store_web(
395 404 'user.delete', action_data={'old_data': old_values},
396 405 user=c.rhodecode_user)
397 406
398 407 Session().commit()
399 408 set_handle_flash_repos()
400 409 set_handle_flash_repo_groups()
401 410 set_handle_flash_user_groups()
402 411 h.flash(_('Successfully deleted user'), category='success')
403 412 except (UserOwnsReposException, UserOwnsRepoGroupsException,
404 413 UserOwnsUserGroupsException, DefaultUserException) as e:
405 414 h.flash(e, category='warning')
406 415 except Exception:
407 416 log.exception("Exception during deletion of user")
408 417 h.flash(_('An error occurred during deletion of user'),
409 418 category='error')
410 419 raise HTTPFound(h.route_path('users'))
411 420
412 421 @LoginRequired()
413 422 @HasPermissionAllDecorator('hg.admin')
414 423 @view_config(
415 424 route_name='user_edit', request_method='GET',
416 425 renderer='rhodecode:templates/admin/users/user_edit.mako')
417 426 def user_edit(self):
418 427 _ = self.request.translate
419 428 c = self.load_default_context()
420 429 c.user = self.db_user
421 430
422 431 c.active = 'profile'
423 432 c.extern_type = c.user.extern_type
424 433 c.extern_name = c.user.extern_name
425 434 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
426 435
427 436 defaults = c.user.get_dict()
428 437 defaults.update({'language': c.user.user_data.get('language')})
429 438
430 439 data = render(
431 440 'rhodecode:templates/admin/users/user_edit.mako',
432 441 self._get_template_context(c), self.request)
433 442 html = formencode.htmlfill.render(
434 443 data,
435 444 defaults=defaults,
436 445 encoding="UTF-8",
437 446 force_defaults=False
438 447 )
439 448 return Response(html)
440 449
441 450 @LoginRequired()
442 451 @HasPermissionAllDecorator('hg.admin')
443 452 @view_config(
444 453 route_name='user_edit_advanced', request_method='GET',
445 454 renderer='rhodecode:templates/admin/users/user_edit.mako')
446 455 def user_edit_advanced(self):
447 456 _ = self.request.translate
448 457 c = self.load_default_context()
449 458
450 459 user_id = self.db_user_id
451 460 c.user = self.db_user
452 461
453 462 c.active = 'advanced'
454 463 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
455 464 c.personal_repo_group_name = RepoGroupModel()\
456 465 .get_personal_group_name(c.user)
457 466
458 467 c.user_to_review_rules = sorted(
459 468 (x.user for x in c.user.user_review_rules),
460 469 key=lambda u: u.username.lower())
461 470
462 471 c.first_admin = User.get_first_super_admin()
463 472 defaults = c.user.get_dict()
464 473
465 474 # Interim workaround if the user participated on any pull requests as a
466 475 # reviewer.
467 476 has_review = len(c.user.reviewer_pull_requests)
468 477 c.can_delete_user = not has_review
469 478 c.can_delete_user_message = ''
470 479 inactive_link = h.link_to(
471 480 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
472 481 if has_review == 1:
473 482 c.can_delete_user_message = h.literal(_(
474 483 'The user participates as reviewer in {} pull request and '
475 484 'cannot be deleted. \nYou can set the user to '
476 485 '"{}" instead of deleting it.').format(
477 486 has_review, inactive_link))
478 487 elif has_review:
479 488 c.can_delete_user_message = h.literal(_(
480 489 'The user participates as reviewer in {} pull requests and '
481 490 'cannot be deleted. \nYou can set the user to '
482 491 '"{}" instead of deleting it.').format(
483 492 has_review, inactive_link))
484 493
485 494 data = render(
486 495 'rhodecode:templates/admin/users/user_edit.mako',
487 496 self._get_template_context(c), self.request)
488 497 html = formencode.htmlfill.render(
489 498 data,
490 499 defaults=defaults,
491 500 encoding="UTF-8",
492 501 force_defaults=False
493 502 )
494 503 return Response(html)
495 504
496 505 @LoginRequired()
497 506 @HasPermissionAllDecorator('hg.admin')
498 507 @view_config(
499 508 route_name='user_edit_global_perms', request_method='GET',
500 509 renderer='rhodecode:templates/admin/users/user_edit.mako')
501 510 def user_edit_global_perms(self):
502 511 _ = self.request.translate
503 512 c = self.load_default_context()
504 513 c.user = self.db_user
505 514
506 515 c.active = 'global_perms'
507 516
508 517 c.default_user = User.get_default_user()
509 518 defaults = c.user.get_dict()
510 519 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
511 520 defaults.update(c.default_user.get_default_perms())
512 521 defaults.update(c.user.get_default_perms())
513 522
514 523 data = render(
515 524 'rhodecode:templates/admin/users/user_edit.mako',
516 525 self._get_template_context(c), self.request)
517 526 html = formencode.htmlfill.render(
518 527 data,
519 528 defaults=defaults,
520 529 encoding="UTF-8",
521 530 force_defaults=False
522 531 )
523 532 return Response(html)
524 533
525 534 @LoginRequired()
526 535 @HasPermissionAllDecorator('hg.admin')
527 536 @CSRFRequired()
528 537 @view_config(
529 538 route_name='user_edit_global_perms_update', request_method='POST',
530 539 renderer='rhodecode:templates/admin/users/user_edit.mako')
531 540 def user_edit_global_perms_update(self):
532 541 _ = self.request.translate
533 542 c = self.load_default_context()
534 543
535 544 user_id = self.db_user_id
536 545 c.user = self.db_user
537 546
538 547 c.active = 'global_perms'
539 548 try:
540 549 # first stage that verifies the checkbox
541 550 _form = UserIndividualPermissionsForm(self.request.translate)
542 551 form_result = _form.to_python(dict(self.request.POST))
543 552 inherit_perms = form_result['inherit_default_permissions']
544 553 c.user.inherit_default_permissions = inherit_perms
545 554 Session().add(c.user)
546 555
547 556 if not inherit_perms:
548 557 # only update the individual ones if we un check the flag
549 558 _form = UserPermissionsForm(
550 559 self.request.translate,
551 560 [x[0] for x in c.repo_create_choices],
552 561 [x[0] for x in c.repo_create_on_write_choices],
553 562 [x[0] for x in c.repo_group_create_choices],
554 563 [x[0] for x in c.user_group_create_choices],
555 564 [x[0] for x in c.fork_choices],
556 565 [x[0] for x in c.inherit_default_permission_choices])()
557 566
558 567 form_result = _form.to_python(dict(self.request.POST))
559 568 form_result.update({'perm_user_id': c.user.user_id})
560 569
561 570 PermissionModel().update_user_permissions(form_result)
562 571
563 572 # TODO(marcink): implement global permissions
564 573 # audit_log.store_web('user.edit.permissions')
565 574
566 575 Session().commit()
567 576 h.flash(_('User global permissions updated successfully'),
568 577 category='success')
569 578
570 579 except formencode.Invalid as errors:
571 580 data = render(
572 581 'rhodecode:templates/admin/users/user_edit.mako',
573 582 self._get_template_context(c), self.request)
574 583 html = formencode.htmlfill.render(
575 584 data,
576 585 defaults=errors.value,
577 586 errors=errors.error_dict or {},
578 587 prefix_error=False,
579 588 encoding="UTF-8",
580 589 force_defaults=False
581 590 )
582 591 return Response(html)
583 592 except Exception:
584 593 log.exception("Exception during permissions saving")
585 594 h.flash(_('An error occurred during permissions saving'),
586 595 category='error')
587 596 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
588 597
589 598 @LoginRequired()
590 599 @HasPermissionAllDecorator('hg.admin')
591 600 @CSRFRequired()
592 601 @view_config(
593 602 route_name='user_force_password_reset', request_method='POST',
594 603 renderer='rhodecode:templates/admin/users/user_edit.mako')
595 604 def user_force_password_reset(self):
596 605 """
597 606 toggle reset password flag for this user
598 607 """
599 608 _ = self.request.translate
600 609 c = self.load_default_context()
601 610
602 611 user_id = self.db_user_id
603 612 c.user = self.db_user
604 613
605 614 try:
606 615 old_value = c.user.user_data.get('force_password_change')
607 616 c.user.update_userdata(force_password_change=not old_value)
608 617
609 618 if old_value:
610 619 msg = _('Force password change disabled for user')
611 620 audit_logger.store_web(
612 621 'user.edit.password_reset.disabled',
613 622 user=c.rhodecode_user)
614 623 else:
615 624 msg = _('Force password change enabled for user')
616 625 audit_logger.store_web(
617 626 'user.edit.password_reset.enabled',
618 627 user=c.rhodecode_user)
619 628
620 629 Session().commit()
621 630 h.flash(msg, category='success')
622 631 except Exception:
623 632 log.exception("Exception during password reset for user")
624 633 h.flash(_('An error occurred during password reset for user'),
625 634 category='error')
626 635
627 636 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
628 637
629 638 @LoginRequired()
630 639 @HasPermissionAllDecorator('hg.admin')
631 640 @CSRFRequired()
632 641 @view_config(
633 642 route_name='user_create_personal_repo_group', request_method='POST',
634 643 renderer='rhodecode:templates/admin/users/user_edit.mako')
635 644 def user_create_personal_repo_group(self):
636 645 """
637 646 Create personal repository group for this user
638 647 """
639 648 from rhodecode.model.repo_group import RepoGroupModel
640 649
641 650 _ = self.request.translate
642 651 c = self.load_default_context()
643 652
644 653 user_id = self.db_user_id
645 654 c.user = self.db_user
646 655
647 656 personal_repo_group = RepoGroup.get_user_personal_repo_group(
648 657 c.user.user_id)
649 658 if personal_repo_group:
650 659 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
651 660
652 661 personal_repo_group_name = RepoGroupModel().get_personal_group_name(
653 662 c.user)
654 663 named_personal_group = RepoGroup.get_by_group_name(
655 664 personal_repo_group_name)
656 665 try:
657 666
658 667 if named_personal_group and named_personal_group.user_id == c.user.user_id:
659 668 # migrate the same named group, and mark it as personal
660 669 named_personal_group.personal = True
661 670 Session().add(named_personal_group)
662 671 Session().commit()
663 672 msg = _('Linked repository group `%s` as personal' % (
664 673 personal_repo_group_name,))
665 674 h.flash(msg, category='success')
666 675 elif not named_personal_group:
667 676 RepoGroupModel().create_personal_repo_group(c.user)
668 677
669 678 msg = _('Created repository group `%s`' % (
670 679 personal_repo_group_name,))
671 680 h.flash(msg, category='success')
672 681 else:
673 682 msg = _('Repository group `%s` is already taken' % (
674 683 personal_repo_group_name,))
675 684 h.flash(msg, category='warning')
676 685 except Exception:
677 686 log.exception("Exception during repository group creation")
678 687 msg = _(
679 688 'An error occurred during repository group creation for user')
680 689 h.flash(msg, category='error')
681 690 Session().rollback()
682 691
683 692 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
684 693
685 694 @LoginRequired()
686 695 @HasPermissionAllDecorator('hg.admin')
687 696 @view_config(
688 697 route_name='edit_user_auth_tokens', request_method='GET',
689 698 renderer='rhodecode:templates/admin/users/user_edit.mako')
690 699 def auth_tokens(self):
691 700 _ = self.request.translate
692 701 c = self.load_default_context()
693 702 c.user = self.db_user
694 703
695 704 c.active = 'auth_tokens'
696 705
697 706 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
698 707 c.role_values = [
699 708 (x, AuthTokenModel.cls._get_role_name(x))
700 709 for x in AuthTokenModel.cls.ROLES]
701 710 c.role_options = [(c.role_values, _("Role"))]
702 711 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
703 712 c.user.user_id, show_expired=True)
704 713 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
705 714 return self._get_template_context(c)
706 715
707 716 def maybe_attach_token_scope(self, token):
708 717 # implemented in EE edition
709 718 pass
710 719
711 720 @LoginRequired()
712 721 @HasPermissionAllDecorator('hg.admin')
713 722 @CSRFRequired()
714 723 @view_config(
715 724 route_name='edit_user_auth_tokens_add', request_method='POST')
716 725 def auth_tokens_add(self):
717 726 _ = self.request.translate
718 727 c = self.load_default_context()
719 728
720 729 user_id = self.db_user_id
721 730 c.user = self.db_user
722 731
723 732 user_data = c.user.get_api_data()
724 733 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
725 734 description = self.request.POST.get('description')
726 735 role = self.request.POST.get('role')
727 736
728 737 token = AuthTokenModel().create(
729 738 c.user.user_id, description, lifetime, role)
730 739 token_data = token.get_api_data()
731 740
732 741 self.maybe_attach_token_scope(token)
733 742 audit_logger.store_web(
734 743 'user.edit.token.add', action_data={
735 744 'data': {'token': token_data, 'user': user_data}},
736 745 user=self._rhodecode_user, )
737 746 Session().commit()
738 747
739 748 h.flash(_("Auth token successfully created"), category='success')
740 749 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
741 750
742 751 @LoginRequired()
743 752 @HasPermissionAllDecorator('hg.admin')
744 753 @CSRFRequired()
745 754 @view_config(
746 755 route_name='edit_user_auth_tokens_delete', request_method='POST')
747 756 def auth_tokens_delete(self):
748 757 _ = self.request.translate
749 758 c = self.load_default_context()
750 759
751 760 user_id = self.db_user_id
752 761 c.user = self.db_user
753 762
754 763 user_data = c.user.get_api_data()
755 764
756 765 del_auth_token = self.request.POST.get('del_auth_token')
757 766
758 767 if del_auth_token:
759 768 token = UserApiKeys.get_or_404(del_auth_token)
760 769 token_data = token.get_api_data()
761 770
762 771 AuthTokenModel().delete(del_auth_token, c.user.user_id)
763 772 audit_logger.store_web(
764 773 'user.edit.token.delete', action_data={
765 774 'data': {'token': token_data, 'user': user_data}},
766 775 user=self._rhodecode_user,)
767 776 Session().commit()
768 777 h.flash(_("Auth token successfully deleted"), category='success')
769 778
770 779 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
771 780
772 781 @LoginRequired()
773 782 @HasPermissionAllDecorator('hg.admin')
774 783 @view_config(
775 784 route_name='edit_user_ssh_keys', request_method='GET',
776 785 renderer='rhodecode:templates/admin/users/user_edit.mako')
777 786 def ssh_keys(self):
778 787 _ = self.request.translate
779 788 c = self.load_default_context()
780 789 c.user = self.db_user
781 790
782 791 c.active = 'ssh_keys'
783 792 c.default_key = self.request.GET.get('default_key')
784 793 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
785 794 return self._get_template_context(c)
786 795
787 796 @LoginRequired()
788 797 @HasPermissionAllDecorator('hg.admin')
789 798 @view_config(
790 799 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
791 800 renderer='rhodecode:templates/admin/users/user_edit.mako')
792 801 def ssh_keys_generate_keypair(self):
793 802 _ = self.request.translate
794 803 c = self.load_default_context()
795 804
796 805 c.user = self.db_user
797 806
798 807 c.active = 'ssh_keys_generate'
799 808 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
800 809 c.private, c.public = SshKeyModel().generate_keypair(comment=comment)
801 810
802 811 return self._get_template_context(c)
803 812
804 813 @LoginRequired()
805 814 @HasPermissionAllDecorator('hg.admin')
806 815 @CSRFRequired()
807 816 @view_config(
808 817 route_name='edit_user_ssh_keys_add', request_method='POST')
809 818 def ssh_keys_add(self):
810 819 _ = self.request.translate
811 820 c = self.load_default_context()
812 821
813 822 user_id = self.db_user_id
814 823 c.user = self.db_user
815 824
816 825 user_data = c.user.get_api_data()
817 826 key_data = self.request.POST.get('key_data')
818 827 description = self.request.POST.get('description')
819 828
820 829 fingerprint = 'unknown'
821 830 try:
822 831 if not key_data:
823 832 raise ValueError('Please add a valid public key')
824 833
825 834 key = SshKeyModel().parse_key(key_data.strip())
826 835 fingerprint = key.hash_md5()
827 836
828 837 ssh_key = SshKeyModel().create(
829 838 c.user.user_id, fingerprint, key_data, description)
830 839 ssh_key_data = ssh_key.get_api_data()
831 840
832 841 audit_logger.store_web(
833 842 'user.edit.ssh_key.add', action_data={
834 843 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
835 844 user=self._rhodecode_user, )
836 845 Session().commit()
837 846
838 847 # Trigger an event on change of keys.
839 848 trigger(SshKeyFileChangeEvent(), self.request.registry)
840 849
841 850 h.flash(_("Ssh Key successfully created"), category='success')
842 851
843 852 except IntegrityError:
844 853 log.exception("Exception during ssh key saving")
845 854 err = 'Such key with fingerprint `{}` already exists, ' \
846 855 'please use a different one'.format(fingerprint)
847 856 h.flash(_('An error occurred during ssh key saving: {}').format(err),
848 857 category='error')
849 858 except Exception as e:
850 859 log.exception("Exception during ssh key saving")
851 860 h.flash(_('An error occurred during ssh key saving: {}').format(e),
852 861 category='error')
853 862
854 863 return HTTPFound(
855 864 h.route_path('edit_user_ssh_keys', user_id=user_id))
856 865
857 866 @LoginRequired()
858 867 @HasPermissionAllDecorator('hg.admin')
859 868 @CSRFRequired()
860 869 @view_config(
861 870 route_name='edit_user_ssh_keys_delete', request_method='POST')
862 871 def ssh_keys_delete(self):
863 872 _ = self.request.translate
864 873 c = self.load_default_context()
865 874
866 875 user_id = self.db_user_id
867 876 c.user = self.db_user
868 877
869 878 user_data = c.user.get_api_data()
870 879
871 880 del_ssh_key = self.request.POST.get('del_ssh_key')
872 881
873 882 if del_ssh_key:
874 883 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
875 884 ssh_key_data = ssh_key.get_api_data()
876 885
877 886 SshKeyModel().delete(del_ssh_key, c.user.user_id)
878 887 audit_logger.store_web(
879 888 'user.edit.ssh_key.delete', action_data={
880 889 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
881 890 user=self._rhodecode_user,)
882 891 Session().commit()
883 892 # Trigger an event on change of keys.
884 893 trigger(SshKeyFileChangeEvent(), self.request.registry)
885 894 h.flash(_("Ssh key successfully deleted"), category='success')
886 895
887 896 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
888 897
889 898 @LoginRequired()
890 899 @HasPermissionAllDecorator('hg.admin')
891 900 @view_config(
892 901 route_name='edit_user_emails', request_method='GET',
893 902 renderer='rhodecode:templates/admin/users/user_edit.mako')
894 903 def emails(self):
895 904 _ = self.request.translate
896 905 c = self.load_default_context()
897 906 c.user = self.db_user
898 907
899 908 c.active = 'emails'
900 909 c.user_email_map = UserEmailMap.query() \
901 910 .filter(UserEmailMap.user == c.user).all()
902 911
903 912 return self._get_template_context(c)
904 913
905 914 @LoginRequired()
906 915 @HasPermissionAllDecorator('hg.admin')
907 916 @CSRFRequired()
908 917 @view_config(
909 918 route_name='edit_user_emails_add', request_method='POST')
910 919 def emails_add(self):
911 920 _ = self.request.translate
912 921 c = self.load_default_context()
913 922
914 923 user_id = self.db_user_id
915 924 c.user = self.db_user
916 925
917 926 email = self.request.POST.get('new_email')
918 927 user_data = c.user.get_api_data()
919 928 try:
920 929
921 930 form = UserExtraEmailForm(self.request.translate)()
922 931 data = form.to_python({'email': email})
923 932 email = data['email']
924 933
925 934 UserModel().add_extra_email(c.user.user_id, email)
926 935 audit_logger.store_web(
927 936 'user.edit.email.add',
928 937 action_data={'email': email, 'user': user_data},
929 938 user=self._rhodecode_user)
930 939 Session().commit()
931 940 h.flash(_("Added new email address `%s` for user account") % email,
932 941 category='success')
933 942 except formencode.Invalid as error:
934 943 h.flash(h.escape(error.error_dict['email']), category='error')
935 944 except IntegrityError:
936 945 log.warning("Email %s already exists", email)
937 946 h.flash(_('Email `{}` is already registered for another user.').format(email),
938 947 category='error')
939 948 except Exception:
940 949 log.exception("Exception during email saving")
941 950 h.flash(_('An error occurred during email saving'),
942 951 category='error')
943 952 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
944 953
945 954 @LoginRequired()
946 955 @HasPermissionAllDecorator('hg.admin')
947 956 @CSRFRequired()
948 957 @view_config(
949 958 route_name='edit_user_emails_delete', request_method='POST')
950 959 def emails_delete(self):
951 960 _ = self.request.translate
952 961 c = self.load_default_context()
953 962
954 963 user_id = self.db_user_id
955 964 c.user = self.db_user
956 965
957 966 email_id = self.request.POST.get('del_email_id')
958 967 user_model = UserModel()
959 968
960 969 email = UserEmailMap.query().get(email_id).email
961 970 user_data = c.user.get_api_data()
962 971 user_model.delete_extra_email(c.user.user_id, email_id)
963 972 audit_logger.store_web(
964 973 'user.edit.email.delete',
965 974 action_data={'email': email, 'user': user_data},
966 975 user=self._rhodecode_user)
967 976 Session().commit()
968 977 h.flash(_("Removed email address from user account"),
969 978 category='success')
970 979 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
971 980
972 981 @LoginRequired()
973 982 @HasPermissionAllDecorator('hg.admin')
974 983 @view_config(
975 984 route_name='edit_user_ips', request_method='GET',
976 985 renderer='rhodecode:templates/admin/users/user_edit.mako')
977 986 def ips(self):
978 987 _ = self.request.translate
979 988 c = self.load_default_context()
980 989 c.user = self.db_user
981 990
982 991 c.active = 'ips'
983 992 c.user_ip_map = UserIpMap.query() \
984 993 .filter(UserIpMap.user == c.user).all()
985 994
986 995 c.inherit_default_ips = c.user.inherit_default_permissions
987 996 c.default_user_ip_map = UserIpMap.query() \
988 997 .filter(UserIpMap.user == User.get_default_user()).all()
989 998
990 999 return self._get_template_context(c)
991 1000
992 1001 @LoginRequired()
993 1002 @HasPermissionAllDecorator('hg.admin')
994 1003 @CSRFRequired()
995 1004 @view_config(
996 1005 route_name='edit_user_ips_add', request_method='POST')
997 1006 # NOTE(marcink): this view is allowed for default users, as we can
998 1007 # edit their IP white list
999 1008 def ips_add(self):
1000 1009 _ = self.request.translate
1001 1010 c = self.load_default_context()
1002 1011
1003 1012 user_id = self.db_user_id
1004 1013 c.user = self.db_user
1005 1014
1006 1015 user_model = UserModel()
1007 1016 desc = self.request.POST.get('description')
1008 1017 try:
1009 1018 ip_list = user_model.parse_ip_range(
1010 1019 self.request.POST.get('new_ip'))
1011 1020 except Exception as e:
1012 1021 ip_list = []
1013 1022 log.exception("Exception during ip saving")
1014 1023 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1015 1024 category='error')
1016 1025 added = []
1017 1026 user_data = c.user.get_api_data()
1018 1027 for ip in ip_list:
1019 1028 try:
1020 1029 form = UserExtraIpForm(self.request.translate)()
1021 1030 data = form.to_python({'ip': ip})
1022 1031 ip = data['ip']
1023 1032
1024 1033 user_model.add_extra_ip(c.user.user_id, ip, desc)
1025 1034 audit_logger.store_web(
1026 1035 'user.edit.ip.add',
1027 1036 action_data={'ip': ip, 'user': user_data},
1028 1037 user=self._rhodecode_user)
1029 1038 Session().commit()
1030 1039 added.append(ip)
1031 1040 except formencode.Invalid as error:
1032 1041 msg = error.error_dict['ip']
1033 1042 h.flash(msg, category='error')
1034 1043 except Exception:
1035 1044 log.exception("Exception during ip saving")
1036 1045 h.flash(_('An error occurred during ip saving'),
1037 1046 category='error')
1038 1047 if added:
1039 1048 h.flash(
1040 1049 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1041 1050 category='success')
1042 1051 if 'default_user' in self.request.POST:
1043 1052 # case for editing global IP list we do it for 'DEFAULT' user
1044 1053 raise HTTPFound(h.route_path('admin_permissions_ips'))
1045 1054 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1046 1055
1047 1056 @LoginRequired()
1048 1057 @HasPermissionAllDecorator('hg.admin')
1049 1058 @CSRFRequired()
1050 1059 @view_config(
1051 1060 route_name='edit_user_ips_delete', request_method='POST')
1052 1061 # NOTE(marcink): this view is allowed for default users, as we can
1053 1062 # edit their IP white list
1054 1063 def ips_delete(self):
1055 1064 _ = self.request.translate
1056 1065 c = self.load_default_context()
1057 1066
1058 1067 user_id = self.db_user_id
1059 1068 c.user = self.db_user
1060 1069
1061 1070 ip_id = self.request.POST.get('del_ip_id')
1062 1071 user_model = UserModel()
1063 1072 user_data = c.user.get_api_data()
1064 1073 ip = UserIpMap.query().get(ip_id).ip_addr
1065 1074 user_model.delete_extra_ip(c.user.user_id, ip_id)
1066 1075 audit_logger.store_web(
1067 1076 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1068 1077 user=self._rhodecode_user)
1069 1078 Session().commit()
1070 1079 h.flash(_("Removed ip address from user whitelist"), category='success')
1071 1080
1072 1081 if 'default_user' in self.request.POST:
1073 1082 # case for editing global IP list we do it for 'DEFAULT' user
1074 1083 raise HTTPFound(h.route_path('admin_permissions_ips'))
1075 1084 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1076 1085
1077 1086 @LoginRequired()
1078 1087 @HasPermissionAllDecorator('hg.admin')
1079 1088 @view_config(
1080 1089 route_name='edit_user_groups_management', request_method='GET',
1081 1090 renderer='rhodecode:templates/admin/users/user_edit.mako')
1082 1091 def groups_management(self):
1083 1092 c = self.load_default_context()
1084 1093 c.user = self.db_user
1085 1094 c.data = c.user.group_member
1086 1095
1087 1096 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1088 1097 for group in c.user.group_member]
1089 1098 c.groups = json.dumps(groups)
1090 1099 c.active = 'groups'
1091 1100
1092 1101 return self._get_template_context(c)
1093 1102
1094 1103 @LoginRequired()
1095 1104 @HasPermissionAllDecorator('hg.admin')
1096 1105 @CSRFRequired()
1097 1106 @view_config(
1098 1107 route_name='edit_user_groups_management_updates', request_method='POST')
1099 1108 def groups_management_updates(self):
1100 1109 _ = self.request.translate
1101 1110 c = self.load_default_context()
1102 1111
1103 1112 user_id = self.db_user_id
1104 1113 c.user = self.db_user
1105 1114
1106 1115 user_groups = set(self.request.POST.getall('users_group_id'))
1107 1116 user_groups_objects = []
1108 1117
1109 1118 for ugid in user_groups:
1110 1119 user_groups_objects.append(
1111 1120 UserGroupModel().get_group(safe_int(ugid)))
1112 1121 user_group_model = UserGroupModel()
1113 1122 added_to_groups, removed_from_groups = \
1114 1123 user_group_model.change_groups(c.user, user_groups_objects)
1115 1124
1116 1125 user_data = c.user.get_api_data()
1117 1126 for user_group_id in added_to_groups:
1118 1127 user_group = UserGroup.get(user_group_id)
1119 1128 old_values = user_group.get_api_data()
1120 1129 audit_logger.store_web(
1121 1130 'user_group.edit.member.add',
1122 1131 action_data={'user': user_data, 'old_data': old_values},
1123 1132 user=self._rhodecode_user)
1124 1133
1125 1134 for user_group_id in removed_from_groups:
1126 1135 user_group = UserGroup.get(user_group_id)
1127 1136 old_values = user_group.get_api_data()
1128 1137 audit_logger.store_web(
1129 1138 'user_group.edit.member.delete',
1130 1139 action_data={'user': user_data, 'old_data': old_values},
1131 1140 user=self._rhodecode_user)
1132 1141
1133 1142 Session().commit()
1134 1143 c.active = 'user_groups_management'
1135 1144 h.flash(_("Groups successfully changed"), category='success')
1136 1145
1137 1146 return HTTPFound(h.route_path(
1138 1147 'edit_user_groups_management', user_id=user_id))
1139 1148
1140 1149 @LoginRequired()
1141 1150 @HasPermissionAllDecorator('hg.admin')
1142 1151 @view_config(
1143 1152 route_name='edit_user_audit_logs', request_method='GET',
1144 1153 renderer='rhodecode:templates/admin/users/user_edit.mako')
1145 1154 def user_audit_logs(self):
1146 1155 _ = self.request.translate
1147 1156 c = self.load_default_context()
1148 1157 c.user = self.db_user
1149 1158
1150 1159 c.active = 'audit'
1151 1160
1152 1161 p = safe_int(self.request.GET.get('page', 1), 1)
1153 1162
1154 1163 filter_term = self.request.GET.get('filter')
1155 1164 user_log = UserModel().get_user_log(c.user, filter_term)
1156 1165
1157 1166 def url_generator(**kw):
1158 1167 if filter_term:
1159 1168 kw['filter'] = filter_term
1160 1169 return self.request.current_route_path(_query=kw)
1161 1170
1162 1171 c.audit_logs = h.Page(
1163 1172 user_log, page=p, items_per_page=10, url=url_generator)
1164 1173 c.filter_term = filter_term
1165 1174 return self._get_template_context(c)
1166 1175
1167 1176 @LoginRequired()
1168 1177 @HasPermissionAllDecorator('hg.admin')
1169 1178 @view_config(
1170 1179 route_name='edit_user_perms_summary', request_method='GET',
1171 1180 renderer='rhodecode:templates/admin/users/user_edit.mako')
1172 1181 def user_perms_summary(self):
1173 1182 _ = self.request.translate
1174 1183 c = self.load_default_context()
1175 1184 c.user = self.db_user
1176 1185
1177 1186 c.active = 'perms_summary'
1178 1187 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1179 1188
1180 1189 return self._get_template_context(c)
1181 1190
1182 1191 @LoginRequired()
1183 1192 @HasPermissionAllDecorator('hg.admin')
1184 1193 @view_config(
1185 1194 route_name='edit_user_perms_summary_json', request_method='GET',
1186 1195 renderer='json_ext')
1187 1196 def user_perms_summary_json(self):
1188 1197 self.load_default_context()
1189 1198 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1190 1199
1191 1200 return perm_user.permissions
@@ -1,108 +1,115 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('User groups administration')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="user_group_count">0</span> ${_('user groups')}
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="user_group_count">0</span>
14 14 </%def>
15 15
16 16 <%def name="menu_bar_nav()">
17 17 ${self.menu_items(active='admin')}
18 18 </%def>
19 19
20 20 <%def name="main()">
21 21 <div class="box">
22 22
23 23 <div class="title">
24 24 ${self.breadcrumbs()}
25 25 <ul class="links">
26 26 %if h.HasPermissionAny('hg.admin', 'hg.usergroup.create.true')():
27 27 <li>
28 28 <a href="${h.route_path('user_groups_new')}" class="btn btn-small btn-success">${_(u'Add User Group')}</a>
29 29 </li>
30 30 %endif
31 31 </ul>
32 32 </div>
33 33
34 34 <div id="repos_list_wrap">
35 35 <table id="user_group_list_table" class="display"></table>
36 36 </div>
37 37
38 38 </div>
39 39 <script>
40 40 $(document).ready(function() {
41 var getDatatableCount = function(){
42 var table = $('#user_group_list_table').dataTable();
43 var page = table.api().page.info();
44 var active = page.recordsDisplay;
45 var total = page.recordsTotal;
46
47 var _text = _gettext("{0} out of {1} users").format(active, total);
48 $('#user_group_count').text(_text);
49 };
41 var $userGroupsListTable = $('#user_group_list_table');
50 42
51 43 // user list
52 $('#user_group_list_table').DataTable({
44 $userGroupsListTable.DataTable({
53 45 processing: true,
54 46 serverSide: true,
55 ajax: "${h.route_path('user_groups_data')}",
47 ajax: {
48 "url": "${h.route_path('user_groups_data')}",
49 "dataSrc": function (json) {
50 var filteredCount = json.recordsFiltered;
51 var filteredInactiveCount = json.recordsFilteredInactive;
52 var totalInactive = json.recordsTotalInactive;
53 var total = json.recordsTotal;
54
55 var _text = _gettext(
56 "{0} ({1} inactive) of {2} user groups ({3} inactive)").format(
57 filteredCount, filteredInactiveCount, total, totalInactive);
58
59 if (total === filteredCount) {
60 _text = _gettext(
61 "{0} user groups ({1} inactive)").format(total, totalInactive);
62 }
63 $('#user_group_count').text(_text);
64 return json.data;
65 },
66 },
67
56 68 dom: 'rtp',
57 69 pageLength: ${c.visual.admin_grid_items},
58 70 order: [[ 0, "asc" ]],
59 71 columns: [
60 72 { data: {"_": "users_group_name",
61 73 "sort": "users_group_name"}, title: "${_('Name')}", className: "td-componentname" },
62 74 { data: {"_": "description",
63 75 "sort": "description"}, title: "${_('Description')}", className: "td-description" },
64 76 { data: {"_": "members",
65 77 "sort": "members"}, title: "${_('Members')}", className: "td-number" },
66 78 { data: {"_": "sync",
67 79 "sort": "sync"}, title: "${_('Sync')}", className: "td-sync" },
68 80 { data: {"_": "active",
69 81 "sort": "active"}, title: "${_('Active')}", className: "td-active" },
70 82 { data: {"_": "owner",
71 83 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
72 84 { data: {"_": "action",
73 85 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false}
74 86 ],
75 87 language: {
76 88 paginate: DEFAULT_GRID_PAGINATION,
77 89 sProcessing: _gettext('loading...'),
78 90 emptyTable: _gettext("No user groups available yet.")
79 91 }
80 92 });
81 93
82 $('#user_group_list_table').on('xhr.dt', function(e, settings, json, xhr){
83 $('#user_group_list_table').css('opacity', 1);
94 $userGroupsListTable.on('xhr.dt', function(e, settings, json, xhr){
95 $userGroupsListTable.css('opacity', 1);
84 96 });
85 97
86 $('#user_group_list_table').on('preXhr.dt', function(e, settings, data){
87 $('#user_group_list_table').css('opacity', 0.3);
88 });
89
90 // refresh counters on draw
91 $('#user_group_list_table').on('draw.dt', function(){
92 getDatatableCount();
98 $userGroupsListTable.on('preXhr.dt', function(e, settings, data){
99 $userGroupsListTable.css('opacity', 0.3);
93 100 });
94 101
95 102 // filter
96 103 $('#q_filter').on('keyup',
97 104 $.debounce(250, function() {
98 105 $('#user_group_list_table').DataTable().search(
99 106 $('#q_filter').val()
100 107 ).draw();
101 108 })
102 109 );
103 110
104 111 });
105 112
106 113 </script>
107 114
108 115 </%def>
@@ -1,120 +1,123 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Users administration')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
13 13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="user_count">0</span>
14 14 </%def>
15 15
16 16 <%def name="menu_bar_nav()">
17 17 ${self.menu_items(active='admin')}
18 18 </%def>
19 19
20 20 <%def name="main()">
21 21
22 22 <div class="box">
23 23
24 24 <div class="title">
25 25 ${self.breadcrumbs()}
26 26 <ul class="links">
27 27 <li>
28 28 <a href="${h.route_path('users_new')}" class="btn btn-small btn-success">${_(u'Add User')}</a>
29 29 </li>
30 30 </ul>
31 31 </div>
32 32
33 33 <div id="repos_list_wrap">
34 34 <table id="user_list_table" class="display"></table>
35 35 </div>
36 36 </div>
37 37
38 38 <script type="text/javascript">
39 39
40 40 $(document).ready(function() {
41 41 var $userListTable = $('#user_list_table');
42
43 var getDatatableCount = function(){
44 var table = $userListTable.dataTable();
45 var page = table.api().page.info();
46 var active = page.recordsDisplay;
47 var total = page.recordsTotal;
48
49 var _text = _gettext("{0} out of {1} users").format(active, total);
50 $('#user_count').text(_text);
51 };
52
53 42 // user list
54 43 $userListTable.DataTable({
55 44 processing: true,
56 45 serverSide: true,
57 ajax: "${h.route_path('users_data')}",
46 ajax: {
47 "url": "${h.route_path('users_data')}",
48 "dataSrc": function ( json ) {
49 var filteredCount = json.recordsFiltered;
50 var filteredInactiveCount = json.recordsFilteredInactive;
51 var totalInactive = json.recordsTotalInactive;
52 var total = json.recordsTotal;
53
54 var _text = _gettext(
55 "{0} ({1} inactive) of {2} users ({3} inactive)").format(
56 filteredCount, filteredInactiveCount, total, totalInactive);
57
58 if(total === filteredCount){
59 _text = _gettext(
60 "{0} users ({1} inactive)").format(total, totalInactive);
61 }
62 $('#user_count').text(_text);
63 return json.data;
64 }
65 },
58 66 dom: 'rtp',
59 67 pageLength: ${c.visual.admin_grid_items},
60 68 order: [[ 0, "asc" ]],
61 69 columns: [
62 70 { data: {"_": "username",
63 71 "sort": "username"}, title: "${_('Username')}", className: "td-user" },
64 72 { data: {"_": "email",
65 73 "sort": "email"}, title: "${_('Email')}", className: "td-email" },
66 74 { data: {"_": "first_name",
67 75 "sort": "first_name"}, title: "${_('First Name')}", className: "td-user" },
68 76 { data: {"_": "last_name",
69 77 "sort": "last_name"}, title: "${_('Last Name')}", className: "td-user" },
70 78 { data: {"_": "last_activity",
71 79 "sort": "last_activity",
72 80 "type": Number}, title: "${_('Last activity')}", className: "td-time" },
73 81 { data: {"_": "active",
74 82 "sort": "active"}, title: "${_('Active')}", className: "td-active" },
75 83 { data: {"_": "admin",
76 84 "sort": "admin"}, title: "${_('Admin')}", className: "td-admin" },
77 85 { data: {"_": "extern_type",
78 86 "sort": "extern_type"}, title: "${_('Auth type')}", className: "td-type" },
79 87 { data: {"_": "action",
80 88 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false }
81 89 ],
82 90 language: {
83 91 paginate: DEFAULT_GRID_PAGINATION,
84 92 sProcessing: _gettext('loading...'),
85 93 emptyTable: _gettext("No users available yet.")
86 94 },
87 95
88 96 "createdRow": function ( row, data, index ) {
89 97 if (!data['active_raw']){
90 98 $(row).addClass('closed')
91 99 }
92 100 }
93 101 });
94 102
95 103 $userListTable.on('xhr.dt', function(e, settings, json, xhr){
96 104 $userListTable.css('opacity', 1);
97 105 });
98 106
99 107 $userListTable.on('preXhr.dt', function(e, settings, data){
100 108 $userListTable.css('opacity', 0.3);
101 109 });
102 110
103 // refresh counters on draw
104 $userListTable.on('draw.dt', function(){
105 getDatatableCount();
106 });
107
108 111 // filter
109 112 $('#q_filter').on('keyup',
110 113 $.debounce(250, function() {
111 114 $userListTable.DataTable().search(
112 115 $('#q_filter').val()
113 116 ).draw();
114 117 })
115 118 );
116 119
117 120 });
118 121 </script>
119 122
120 123 </%def>
General Comments 0
You need to be logged in to leave comments. Login now