##// END OF EJS Templates
UserGroup on UserGroup permissions implementation....
marcink -
r3788:d9b89874 beta
parent child Browse files
Show More
@@ -1,368 +1,375 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.users_groups
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 User Groups crud controller for pylons
7 7
8 8 :created_on: Jan 25, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29
30 30 from formencode import htmlfill
31 31 from pylons import request, session, tmpl_context as c, url, config
32 32 from pylons.controllers.util import abort, redirect
33 33 from pylons.i18n.translation import _
34 34
35 35 from rhodecode.lib import helpers as h
36 from rhodecode.lib.exceptions import UserGroupsAssignedException
36 from rhodecode.lib.exceptions import UserGroupsAssignedException,\
37 RepoGroupAssignmentError
37 38 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
38 39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator,\
39 40 HasUserGroupPermissionAnyDecorator, HasPermissionAnyDecorator
40 41 from rhodecode.lib.base import BaseController, render
41 42 from rhodecode.model.scm import UserGroupList
42 43 from rhodecode.model.users_group import UserGroupModel
43 44 from rhodecode.model.repo import RepoModel
44 45 from rhodecode.model.db import User, UserGroup, UserGroupToPerm,\
45 46 UserGroupRepoToPerm, UserGroupRepoGroupToPerm
46 47 from rhodecode.model.forms import UserGroupForm, UserGroupPermsForm,\
47 48 CustomDefaultPermissionsForm
48 49 from rhodecode.model.meta import Session
49 50 from rhodecode.lib.utils import action_logger
50 51 from sqlalchemy.orm import joinedload
51 52 from webob.exc import HTTPInternalServerError
52 53
53 54 log = logging.getLogger(__name__)
54 55
55 56
56 57 class UsersGroupsController(BaseController):
57 58 """REST Controller styled on the Atom Publishing Protocol"""
58 59 # To properly map this controller, ensure your config/routing.py
59 60 # file has a resource setup:
60 61 # map.resource('users_group', 'users_groups')
61 62
62 63 @LoginRequired()
63 64 def __before__(self):
64 65 super(UsersGroupsController, self).__before__()
65 66 c.available_permissions = config['available_permissions']
66 67
67 68 def __load_data(self, user_group_id):
68 69 ugroup_repo_perms = UserGroupRepoToPerm.query()\
69 70 .options(joinedload(UserGroupRepoToPerm.permission))\
70 71 .options(joinedload(UserGroupRepoToPerm.repository))\
71 72 .filter(UserGroupRepoToPerm.users_group_id == user_group_id)\
72 73 .all()
73 74
74 75 for gr in ugroup_repo_perms:
75 76 c.users_group.permissions['repositories'][gr.repository.repo_name] \
76 77 = gr.permission.permission_name
77 78
78 79 ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
79 80 .options(joinedload(UserGroupRepoGroupToPerm.permission))\
80 81 .options(joinedload(UserGroupRepoGroupToPerm.group))\
81 82 .filter(UserGroupRepoGroupToPerm.users_group_id == user_group_id)\
82 83 .all()
83 84
84 85 for gr in ugroup_group_perms:
85 86 c.users_group.permissions['repositories_groups'][gr.group.group_name] \
86 87 = gr.permission.permission_name
87 88
88 89 c.group_members_obj = sorted((x.user for x in c.users_group.members),
89 90 key=lambda u: u.username.lower())
90 91
91 92 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
92 93 c.available_members = sorted(((x.user_id, x.username) for x in
93 94 User.query().all()),
94 95 key=lambda u: u[1].lower())
95 96 repo_model = RepoModel()
96 97 c.users_array = repo_model.get_users_js()
97
98 # commented out due to not now supporting assignment for user group
99 # on user group
100 c.users_groups_array = "[]" # repo_model.get_users_groups_js()
98 c.users_groups_array = repo_model.get_users_groups_js()
101 99 c.available_permissions = config['available_permissions']
102 100
103 101 def __load_defaults(self, user_group_id):
104 102 """
105 103 Load defaults settings for edit, and update
106 104
107 105 :param user_group_id:
108 106 """
109 107 user_group = UserGroup.get_or_404(user_group_id)
110 108 data = user_group.get_dict()
111 109
112 110 ug_model = UserGroupModel()
113 111
114 112 data.update({
115 113 'create_repo_perm': ug_model.has_perm(user_group,
116 114 'hg.create.repository'),
117 115 'create_user_group_perm': ug_model.has_perm(user_group,
118 116 'hg.usergroup.create.true'),
119 117 'fork_repo_perm': ug_model.has_perm(user_group,
120 118 'hg.fork.repository'),
121 119 })
122 120
123 121 # fill user group users
124 122 for p in user_group.user_user_group_to_perm:
125 123 data.update({'u_perm_%s' % p.user.username:
126 124 p.permission.permission_name})
127 125
126 for p in user_group.user_group_user_group_to_perm:
127 data.update({'g_perm_%s' % p.user_group.users_group_name:
128 p.permission.permission_name})
129
128 130 return data
129 131
130 132 def index(self, format='html'):
131 133 """GET /users_groups: All items in the collection"""
132 134 # url('users_groups')
133 135
134 136 group_iter = UserGroupList(UserGroup().query().all(),
135 137 perm_set=['usergroup.admin'])
136 138 sk = lambda g: g.users_group_name
137 139 c.users_groups_list = sorted(group_iter, key=sk)
138 140 return render('admin/users_groups/users_groups.html')
139 141
140 142 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
141 143 def create(self):
142 144 """POST /users_groups: Create a new item"""
143 145 # url('users_groups')
144 146
145 147 users_group_form = UserGroupForm()()
146 148 try:
147 149 form_result = users_group_form.to_python(dict(request.POST))
148 150 UserGroupModel().create(name=form_result['users_group_name'],
149 151 owner=self.rhodecode_user.user_id,
150 152 active=form_result['users_group_active'])
151 153
152 154 gr = form_result['users_group_name']
153 155 action_logger(self.rhodecode_user,
154 156 'admin_created_users_group:%s' % gr,
155 157 None, self.ip_addr, self.sa)
156 158 h.flash(_('Created user group %s') % gr, category='success')
157 159 Session().commit()
158 160 except formencode.Invalid, errors:
159 161 return htmlfill.render(
160 162 render('admin/users_groups/users_group_add.html'),
161 163 defaults=errors.value,
162 164 errors=errors.error_dict or {},
163 165 prefix_error=False,
164 166 encoding="UTF-8")
165 167 except Exception:
166 168 log.error(traceback.format_exc())
167 169 h.flash(_('Error occurred during creation of user group %s') \
168 170 % request.POST.get('users_group_name'), category='error')
169 171
170 172 return redirect(url('users_groups'))
171 173
172 174 @HasPermissionAnyDecorator('hg.admin', 'hg.usergroup.create.true')
173 175 def new(self, format='html'):
174 176 """GET /users_groups/new: Form to create a new item"""
175 177 # url('new_users_group')
176 178 return render('admin/users_groups/users_group_add.html')
177 179
178 180 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
179 181 def update(self, id):
180 182 """PUT /users_groups/id: Update an existing item"""
181 183 # Forms posted to this method should contain a hidden field:
182 184 # <input type="hidden" name="_method" value="PUT" />
183 185 # Or using helpers:
184 186 # h.form(url('users_group', id=ID),
185 187 # method='put')
186 188 # url('users_group', id=ID)
187 189
188 190 c.users_group = UserGroup.get_or_404(id)
189 191 self.__load_data(id)
190 192
191 193 available_members = [safe_unicode(x[0]) for x in c.available_members]
192 194
193 195 users_group_form = UserGroupForm(edit=True,
194 196 old_data=c.users_group.get_dict(),
195 197 available_members=available_members)()
196 198
197 199 try:
198 200 form_result = users_group_form.to_python(request.POST)
199 201 UserGroupModel().update(c.users_group, form_result)
200 202 gr = form_result['users_group_name']
201 203 action_logger(self.rhodecode_user,
202 204 'admin_updated_users_group:%s' % gr,
203 205 None, self.ip_addr, self.sa)
204 206 h.flash(_('Updated user group %s') % gr, category='success')
205 207 Session().commit()
206 208 except formencode.Invalid, errors:
207 209 ug_model = UserGroupModel()
208 210 defaults = errors.value
209 211 e = errors.error_dict or {}
210 212 defaults.update({
211 213 'create_repo_perm': ug_model.has_perm(id,
212 214 'hg.create.repository'),
213 215 'fork_repo_perm': ug_model.has_perm(id,
214 216 'hg.fork.repository'),
215 217 '_method': 'put'
216 218 })
217 219
218 220 return htmlfill.render(
219 221 render('admin/users_groups/users_group_edit.html'),
220 222 defaults=defaults,
221 223 errors=e,
222 224 prefix_error=False,
223 225 encoding="UTF-8")
224 226 except Exception:
225 227 log.error(traceback.format_exc())
226 228 h.flash(_('Error occurred during update of user group %s') \
227 229 % request.POST.get('users_group_name'), category='error')
228 230
229 231 return redirect(url('edit_users_group', id=id))
230 232
231 233 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
232 234 def delete(self, id):
233 235 """DELETE /users_groups/id: Delete an existing item"""
234 236 # Forms posted to this method should contain a hidden field:
235 237 # <input type="hidden" name="_method" value="DELETE" />
236 238 # Or using helpers:
237 239 # h.form(url('users_group', id=ID),
238 240 # method='delete')
239 241 # url('users_group', id=ID)
240 242 usr_gr = UserGroup.get_or_404(id)
241 243 try:
242 244 UserGroupModel().delete(usr_gr)
243 245 Session().commit()
244 246 h.flash(_('Successfully deleted user group'), category='success')
245 247 except UserGroupsAssignedException, e:
246 248 h.flash(e, category='error')
247 249 except Exception:
248 250 log.error(traceback.format_exc())
249 251 h.flash(_('An error occurred during deletion of user group'),
250 252 category='error')
251 253 return redirect(url('users_groups'))
252 254
253 255 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
254 256 def set_user_group_perm_member(self, id):
255 257 """
256 258 grant permission for given usergroup
257 259
258 260 :param id:
259 261 """
260 262 user_group = UserGroup.get_or_404(id)
261 263 form = UserGroupPermsForm()().to_python(request.POST)
262 264
263 265 # set the permissions !
264 UserGroupModel()._update_permissions(user_group, form['perms_new'],
265 form['perms_updates'])
266 try:
267 UserGroupModel()._update_permissions(user_group, form['perms_new'],
268 form['perms_updates'])
269 except RepoGroupAssignmentError:
270 h.flash(_('Target group cannot be the same'), category='error')
271 return redirect(url('edit_users_group', id=id))
266 272 #TODO: implement this
267 273 #action_logger(self.rhodecode_user, 'admin_changed_repo_permissions',
268 274 # repo_name, self.ip_addr, self.sa)
269 275 Session().commit()
270 276 h.flash(_('User Group permissions updated'), category='success')
271 277 return redirect(url('edit_users_group', id=id))
272 278
273 279 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
274 280 def delete_user_group_perm_member(self, id):
275 281 """
276 282 DELETE an existing repository group permission user
277 283
278 284 :param group_name:
279 285 """
280 286 try:
281 287 obj_type = request.POST.get('obj_type')
282 288 obj_id = None
283 289 if obj_type == 'user':
284 290 obj_id = safe_int(request.POST.get('user_id'))
285 291 elif obj_type == 'user_group':
286 292 obj_id = safe_int(request.POST.get('user_group_id'))
287 293
288 294 if not c.rhodecode_user.is_admin:
289 295 if obj_type == 'user' and c.rhodecode_user.user_id == obj_id:
290 296 msg = _('Cannot revoke permission for yourself as admin')
291 297 h.flash(msg, category='warning')
292 298 raise Exception('revoke admin permission on self')
293 299 if obj_type == 'user':
294 300 UserGroupModel().revoke_user_permission(user_group=id,
295 301 user=obj_id)
296 302 elif obj_type == 'user_group':
297 pass
303 UserGroupModel().revoke_users_group_permission(target_user_group=id,
304 user_group=obj_id)
298 305 Session().commit()
299 306 except Exception:
300 307 log.error(traceback.format_exc())
301 308 h.flash(_('An error occurred during revoking of permission'),
302 309 category='error')
303 310 raise HTTPInternalServerError()
304 311
305 312 def show(self, id, format='html'):
306 313 """GET /users_groups/id: Show a specific item"""
307 314 # url('users_group', id=ID)
308 315
309 316 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
310 317 def edit(self, id, format='html'):
311 318 """GET /users_groups/id/edit: Form to edit an existing item"""
312 319 # url('edit_users_group', id=ID)
313 320
314 321 c.users_group = UserGroup.get_or_404(id)
315 322 self.__load_data(id)
316 323
317 324 defaults = self.__load_defaults(id)
318 325
319 326 return htmlfill.render(
320 327 render('admin/users_groups/users_group_edit.html'),
321 328 defaults=defaults,
322 329 encoding="UTF-8",
323 330 force_defaults=False
324 331 )
325 332
326 333 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
327 334 def update_perm(self, id):
328 335 """PUT /users_perm/id: Update an existing item"""
329 336 # url('users_group_perm', id=ID, method='put')
330 337
331 338 users_group = UserGroup.get_or_404(id)
332 339
333 340 try:
334 341 form = CustomDefaultPermissionsForm()()
335 342 form_result = form.to_python(request.POST)
336 343
337 344 inherit_perms = form_result['inherit_default_permissions']
338 345 users_group.inherit_default_permissions = inherit_perms
339 346 Session().add(users_group)
340 347 usergroup_model = UserGroupModel()
341 348
342 349 defs = UserGroupToPerm.query()\
343 350 .filter(UserGroupToPerm.users_group == users_group)\
344 351 .all()
345 352 for ug in defs:
346 353 Session().delete(ug)
347 354
348 355 if form_result['create_repo_perm']:
349 356 usergroup_model.grant_perm(id, 'hg.create.repository')
350 357 else:
351 358 usergroup_model.grant_perm(id, 'hg.create.none')
352 359 if form_result['create_user_group_perm']:
353 360 usergroup_model.grant_perm(id, 'hg.usergroup.create.true')
354 361 else:
355 362 usergroup_model.grant_perm(id, 'hg.usergroup.create.false')
356 363 if form_result['fork_repo_perm']:
357 364 usergroup_model.grant_perm(id, 'hg.fork.repository')
358 365 else:
359 366 usergroup_model.grant_perm(id, 'hg.fork.none')
360 367
361 368 h.flash(_("Updated permissions"), category='success')
362 369 Session().commit()
363 370 except Exception:
364 371 log.error(traceback.format_exc())
365 372 h.flash(_('An error occurred during permissions saving'),
366 373 category='error')
367 374
368 375 return redirect(url('edit_users_group', id=id))
@@ -1,80 +1,84 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.exceptions
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Set of custom exceptions used in RhodeCode
7 7
8 8 :created_on: Nov 17, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 from webob.exc import HTTPClientError
27 27
28 28
29 29 class LdapUsernameError(Exception):
30 30 pass
31 31
32 32
33 33 class LdapPasswordError(Exception):
34 34 pass
35 35
36 36
37 37 class LdapConnectionError(Exception):
38 38 pass
39 39
40 40
41 41 class LdapImportError(Exception):
42 42 pass
43 43
44 44
45 45 class DefaultUserException(Exception):
46 46 pass
47 47
48 48
49 49 class UserOwnsReposException(Exception):
50 50 pass
51 51
52 52
53 53 class UserGroupsAssignedException(Exception):
54 54 pass
55 55
56 56
57 57 class StatusChangeOnClosedPullRequestError(Exception):
58 58 pass
59 59
60 60
61 61 class AttachedForksError(Exception):
62 62 pass
63 63
64 64
65 class RepoGroupAssignmentError(Exception):
66 pass
67
68
65 69 class HTTPLockedRC(HTTPClientError):
66 70 """
67 71 Special Exception For locked Repos in RhodeCode, the return code can
68 72 be overwritten by _code keyword argument passed into constructors
69 73 """
70 74 code = 423
71 75 title = explanation = 'Repository Locked'
72 76
73 77 def __init__(self, reponame, username, *args, **kwargs):
74 78 from rhodecode import CONFIG
75 79 from rhodecode.lib.utils2 import safe_int
76 80 _code = CONFIG.get('lock_ret_code')
77 81 self.code = safe_int(_code, self.code)
78 82 self.title = self.explanation = ('Repository `%s` locked by '
79 83 'user `%s`' % (reponame, username))
80 84 super(HTTPLockedRC, self).__init__(*args, **kwargs)
@@ -1,93 +1,94 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 vcs.exceptions
4 4 ~~~~~~~~~~~~~~
5 5
6 6 Custom exceptions module
7 7
8 8 :created_on: Apr 8, 2010
9 9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
10 10 """
11 11
12 12
13 13 class VCSError(Exception):
14 14 pass
15 15
16 16
17 17 class RepositoryError(VCSError):
18 18 pass
19 19
20 20
21 21 class EmptyRepositoryError(RepositoryError):
22 22 pass
23 23
24 24
25 25 class TagAlreadyExistError(RepositoryError):
26 26 pass
27 27
28 28
29 29 class TagDoesNotExistError(RepositoryError):
30 30 pass
31 31
32 32
33 33 class BranchAlreadyExistError(RepositoryError):
34 34 pass
35 35
36 36
37 37 class BranchDoesNotExistError(RepositoryError):
38 38 pass
39 39
40 40
41 41 class ChangesetError(RepositoryError):
42 42 pass
43 43
44 44
45 45 class ChangesetDoesNotExistError(ChangesetError):
46 46 pass
47 47
48 48
49 49 class CommitError(RepositoryError):
50 50 pass
51 51
52 52
53 53 class NothingChangedError(CommitError):
54 54 pass
55 55
56 56
57 57 class NodeError(VCSError):
58 58 pass
59 59
60 60
61 61 class RemovedFileNodeError(NodeError):
62 62 pass
63 63
64 64
65 65 class NodeAlreadyExistsError(CommitError):
66 66 pass
67 67
68 68
69 69 class NodeAlreadyChangedError(CommitError):
70 70 pass
71 71
72 72
73 73 class NodeDoesNotExistError(CommitError):
74 74 pass
75 75
76 76
77 77 class NodeNotChangedError(CommitError):
78 78 pass
79 79
80 80
81 81 class NodeAlreadyAddedError(CommitError):
82 82 pass
83 83
84 84
85 85 class NodeAlreadyRemovedError(CommitError):
86 86 pass
87 87
88 88
89 89 class ImproperArchiveTypeError(VCSError):
90 90 pass
91 91
92
92 93 class CommandError(VCSError):
93 94 pass
@@ -1,2129 +1,2131 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode
7 7
8 8 :created_on: Apr 08, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import logging
28 28 import datetime
29 29 import traceback
30 30 import hashlib
31 31 import time
32 32 from collections import defaultdict
33 33
34 34 from sqlalchemy import *
35 35 from sqlalchemy.ext.hybrid import hybrid_property
36 36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
37 37 from sqlalchemy.exc import DatabaseError
38 38 from beaker.cache import cache_region, region_invalidate
39 39 from webob.exc import HTTPNotFound
40 40
41 41 from pylons.i18n.translation import lazy_ugettext as _
42 42
43 43 from rhodecode.lib.vcs import get_backend
44 44 from rhodecode.lib.vcs.utils.helpers import get_scm
45 45 from rhodecode.lib.vcs.exceptions import VCSError
46 46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
48 48
49 49 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
50 50 safe_unicode, remove_suffix, remove_prefix, time_to_datetime, _set_extras
51 51 from rhodecode.lib.compat import json
52 52 from rhodecode.lib.caching_query import FromCache
53 53
54 54 from rhodecode.model.meta import Base, Session
55 55
56 56 URL_SEP = '/'
57 57 log = logging.getLogger(__name__)
58 58
59 59 #==============================================================================
60 60 # BASE CLASSES
61 61 #==============================================================================
62 62
63 63 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
64 64
65 65
66 66 class BaseModel(object):
67 67 """
68 68 Base Model for all classess
69 69 """
70 70
71 71 @classmethod
72 72 def _get_keys(cls):
73 73 """return column names for this model """
74 74 return class_mapper(cls).c.keys()
75 75
76 76 def get_dict(self):
77 77 """
78 78 return dict with keys and values corresponding
79 79 to this model data """
80 80
81 81 d = {}
82 82 for k in self._get_keys():
83 83 d[k] = getattr(self, k)
84 84
85 85 # also use __json__() if present to get additional fields
86 86 _json_attr = getattr(self, '__json__', None)
87 87 if _json_attr:
88 88 # update with attributes from __json__
89 89 if callable(_json_attr):
90 90 _json_attr = _json_attr()
91 91 for k, val in _json_attr.iteritems():
92 92 d[k] = val
93 93 return d
94 94
95 95 def get_appstruct(self):
96 96 """return list with keys and values tupples corresponding
97 97 to this model data """
98 98
99 99 l = []
100 100 for k in self._get_keys():
101 101 l.append((k, getattr(self, k),))
102 102 return l
103 103
104 104 def populate_obj(self, populate_dict):
105 105 """populate model with data from given populate_dict"""
106 106
107 107 for k in self._get_keys():
108 108 if k in populate_dict:
109 109 setattr(self, k, populate_dict[k])
110 110
111 111 @classmethod
112 112 def query(cls):
113 113 return Session().query(cls)
114 114
115 115 @classmethod
116 116 def get(cls, id_):
117 117 if id_:
118 118 return cls.query().get(id_)
119 119
120 120 @classmethod
121 121 def get_or_404(cls, id_):
122 122 try:
123 123 id_ = int(id_)
124 124 except (TypeError, ValueError):
125 125 raise HTTPNotFound
126 126
127 127 res = cls.query().get(id_)
128 128 if not res:
129 129 raise HTTPNotFound
130 130 return res
131 131
132 132 @classmethod
133 133 def getAll(cls):
134 134 # deprecated and left for backward compatibility
135 135 return cls.get_all()
136 136
137 137 @classmethod
138 138 def get_all(cls):
139 139 return cls.query().all()
140 140
141 141 @classmethod
142 142 def delete(cls, id_):
143 143 obj = cls.query().get(id_)
144 144 Session().delete(obj)
145 145
146 146 def __repr__(self):
147 147 if hasattr(self, '__unicode__'):
148 148 # python repr needs to return str
149 149 return safe_str(self.__unicode__())
150 150 return '<DB:%s>' % (self.__class__.__name__)
151 151
152 152
153 153 class RhodeCodeSetting(Base, BaseModel):
154 154 __tablename__ = 'rhodecode_settings'
155 155 __table_args__ = (
156 156 UniqueConstraint('app_settings_name'),
157 157 {'extend_existing': True, 'mysql_engine': 'InnoDB',
158 158 'mysql_charset': 'utf8'}
159 159 )
160 160 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
161 161 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
162 162 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
163 163
164 164 def __init__(self, k='', v=''):
165 165 self.app_settings_name = k
166 166 self.app_settings_value = v
167 167
168 168 @validates('_app_settings_value')
169 169 def validate_settings_value(self, key, val):
170 170 assert type(val) == unicode
171 171 return val
172 172
173 173 @hybrid_property
174 174 def app_settings_value(self):
175 175 v = self._app_settings_value
176 176 if self.app_settings_name in ["ldap_active",
177 177 "default_repo_enable_statistics",
178 178 "default_repo_enable_locking",
179 179 "default_repo_private",
180 180 "default_repo_enable_downloads"]:
181 181 v = str2bool(v)
182 182 return v
183 183
184 184 @app_settings_value.setter
185 185 def app_settings_value(self, val):
186 186 """
187 187 Setter that will always make sure we use unicode in app_settings_value
188 188
189 189 :param val:
190 190 """
191 191 self._app_settings_value = safe_unicode(val)
192 192
193 193 def __unicode__(self):
194 194 return u"<%s('%s:%s')>" % (
195 195 self.__class__.__name__,
196 196 self.app_settings_name, self.app_settings_value
197 197 )
198 198
199 199 @classmethod
200 200 def get_by_name(cls, key):
201 201 return cls.query()\
202 202 .filter(cls.app_settings_name == key).scalar()
203 203
204 204 @classmethod
205 205 def get_by_name_or_create(cls, key):
206 206 res = cls.get_by_name(key)
207 207 if not res:
208 208 res = cls(key)
209 209 return res
210 210
211 211 @classmethod
212 212 def get_app_settings(cls, cache=False):
213 213
214 214 ret = cls.query()
215 215
216 216 if cache:
217 217 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
218 218
219 219 if not ret:
220 220 raise Exception('Could not get application settings !')
221 221 settings = {}
222 222 for each in ret:
223 223 settings['rhodecode_' + each.app_settings_name] = \
224 224 each.app_settings_value
225 225
226 226 return settings
227 227
228 228 @classmethod
229 229 def get_ldap_settings(cls, cache=False):
230 230 ret = cls.query()\
231 231 .filter(cls.app_settings_name.startswith('ldap_')).all()
232 232 fd = {}
233 233 for row in ret:
234 234 fd.update({row.app_settings_name: row.app_settings_value})
235 235
236 236 return fd
237 237
238 238 @classmethod
239 239 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
240 240 ret = cls.query()\
241 241 .filter(cls.app_settings_name.startswith('default_')).all()
242 242 fd = {}
243 243 for row in ret:
244 244 key = row.app_settings_name
245 245 if strip_prefix:
246 246 key = remove_prefix(key, prefix='default_')
247 247 fd.update({key: row.app_settings_value})
248 248
249 249 return fd
250 250
251 251
252 252 class RhodeCodeUi(Base, BaseModel):
253 253 __tablename__ = 'rhodecode_ui'
254 254 __table_args__ = (
255 255 UniqueConstraint('ui_key'),
256 256 {'extend_existing': True, 'mysql_engine': 'InnoDB',
257 257 'mysql_charset': 'utf8'}
258 258 )
259 259
260 260 HOOK_UPDATE = 'changegroup.update'
261 261 HOOK_REPO_SIZE = 'changegroup.repo_size'
262 262 HOOK_PUSH = 'changegroup.push_logger'
263 263 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
264 264 HOOK_PULL = 'outgoing.pull_logger'
265 265 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
266 266
267 267 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
268 268 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
269 269 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
270 270 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
271 271 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
272 272
273 273 @classmethod
274 274 def get_by_key(cls, key):
275 275 return cls.query().filter(cls.ui_key == key).scalar()
276 276
277 277 @classmethod
278 278 def get_builtin_hooks(cls):
279 279 q = cls.query()
280 280 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
281 281 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
282 282 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
283 283 return q.all()
284 284
285 285 @classmethod
286 286 def get_custom_hooks(cls):
287 287 q = cls.query()
288 288 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
289 289 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
290 290 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
291 291 q = q.filter(cls.ui_section == 'hooks')
292 292 return q.all()
293 293
294 294 @classmethod
295 295 def get_repos_location(cls):
296 296 return cls.get_by_key('/').ui_value
297 297
298 298 @classmethod
299 299 def create_or_update_hook(cls, key, val):
300 300 new_ui = cls.get_by_key(key) or cls()
301 301 new_ui.ui_section = 'hooks'
302 302 new_ui.ui_active = True
303 303 new_ui.ui_key = key
304 304 new_ui.ui_value = val
305 305
306 306 Session().add(new_ui)
307 307
308 308 def __repr__(self):
309 309 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
310 310 self.ui_value)
311 311
312 312
313 313 class User(Base, BaseModel):
314 314 __tablename__ = 'users'
315 315 __table_args__ = (
316 316 UniqueConstraint('username'), UniqueConstraint('email'),
317 317 Index('u_username_idx', 'username'),
318 318 Index('u_email_idx', 'email'),
319 319 {'extend_existing': True, 'mysql_engine': 'InnoDB',
320 320 'mysql_charset': 'utf8'}
321 321 )
322 322 DEFAULT_USER = 'default'
323 323
324 324 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
325 325 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
326 326 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
327 327 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
328 328 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
329 329 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
330 330 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
331 331 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
332 332 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
333 333 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
334 334 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
335 335 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
336 336
337 337 user_log = relationship('UserLog')
338 338 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
339 339
340 340 repositories = relationship('Repository')
341 341 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
342 342 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
343 343
344 344 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
345 345 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
346 346
347 347 group_member = relationship('UserGroupMember', cascade='all')
348 348
349 349 notifications = relationship('UserNotification', cascade='all')
350 350 # notifications assigned to this user
351 351 user_created_notifications = relationship('Notification', cascade='all')
352 352 # comments created by this user
353 353 user_comments = relationship('ChangesetComment', cascade='all')
354 354 #extra emails for this user
355 355 user_emails = relationship('UserEmailMap', cascade='all')
356 356
357 357 @hybrid_property
358 358 def email(self):
359 359 return self._email
360 360
361 361 @email.setter
362 362 def email(self, val):
363 363 self._email = val.lower() if val else None
364 364
365 365 @property
366 366 def firstname(self):
367 367 # alias for future
368 368 return self.name
369 369
370 370 @property
371 371 def emails(self):
372 372 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
373 373 return [self.email] + [x.email for x in other]
374 374
375 375 @property
376 376 def ip_addresses(self):
377 377 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
378 378 return [x.ip_addr for x in ret]
379 379
380 380 @property
381 381 def username_and_name(self):
382 382 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
383 383
384 384 @property
385 385 def full_name(self):
386 386 return '%s %s' % (self.firstname, self.lastname)
387 387
388 388 @property
389 389 def full_name_or_username(self):
390 390 return ('%s %s' % (self.firstname, self.lastname)
391 391 if (self.firstname and self.lastname) else self.username)
392 392
393 393 @property
394 394 def full_contact(self):
395 395 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
396 396
397 397 @property
398 398 def short_contact(self):
399 399 return '%s %s' % (self.firstname, self.lastname)
400 400
401 401 @property
402 402 def is_admin(self):
403 403 return self.admin
404 404
405 405 @property
406 406 def AuthUser(self):
407 407 """
408 408 Returns instance of AuthUser for this user
409 409 """
410 410 from rhodecode.lib.auth import AuthUser
411 411 return AuthUser(user_id=self.user_id, api_key=self.api_key,
412 412 username=self.username)
413 413
414 414 def __unicode__(self):
415 415 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
416 416 self.user_id, self.username)
417 417
418 418 @classmethod
419 419 def get_by_username(cls, username, case_insensitive=False, cache=False):
420 420 if case_insensitive:
421 421 q = cls.query().filter(cls.username.ilike(username))
422 422 else:
423 423 q = cls.query().filter(cls.username == username)
424 424
425 425 if cache:
426 426 q = q.options(FromCache(
427 427 "sql_cache_short",
428 428 "get_user_%s" % _hash_key(username)
429 429 )
430 430 )
431 431 return q.scalar()
432 432
433 433 @classmethod
434 434 def get_by_api_key(cls, api_key, cache=False):
435 435 q = cls.query().filter(cls.api_key == api_key)
436 436
437 437 if cache:
438 438 q = q.options(FromCache("sql_cache_short",
439 439 "get_api_key_%s" % api_key))
440 440 return q.scalar()
441 441
442 442 @classmethod
443 443 def get_by_email(cls, email, case_insensitive=False, cache=False):
444 444 if case_insensitive:
445 445 q = cls.query().filter(cls.email.ilike(email))
446 446 else:
447 447 q = cls.query().filter(cls.email == email)
448 448
449 449 if cache:
450 450 q = q.options(FromCache("sql_cache_short",
451 451 "get_email_key_%s" % email))
452 452
453 453 ret = q.scalar()
454 454 if ret is None:
455 455 q = UserEmailMap.query()
456 456 # try fetching in alternate email map
457 457 if case_insensitive:
458 458 q = q.filter(UserEmailMap.email.ilike(email))
459 459 else:
460 460 q = q.filter(UserEmailMap.email == email)
461 461 q = q.options(joinedload(UserEmailMap.user))
462 462 if cache:
463 463 q = q.options(FromCache("sql_cache_short",
464 464 "get_email_map_key_%s" % email))
465 465 ret = getattr(q.scalar(), 'user', None)
466 466
467 467 return ret
468 468
469 469 @classmethod
470 470 def get_from_cs_author(cls, author):
471 471 """
472 472 Tries to get User objects out of commit author string
473 473
474 474 :param author:
475 475 """
476 476 from rhodecode.lib.helpers import email, author_name
477 477 # Valid email in the attribute passed, see if they're in the system
478 478 _email = email(author)
479 479 if _email:
480 480 user = cls.get_by_email(_email, case_insensitive=True)
481 481 if user:
482 482 return user
483 483 # Maybe we can match by username?
484 484 _author = author_name(author)
485 485 user = cls.get_by_username(_author, case_insensitive=True)
486 486 if user:
487 487 return user
488 488
489 489 def update_lastlogin(self):
490 490 """Update user lastlogin"""
491 491 self.last_login = datetime.datetime.now()
492 492 Session().add(self)
493 493 log.debug('updated user %s lastlogin' % self.username)
494 494
495 495 @classmethod
496 496 def get_first_admin(cls):
497 497 user = User.query().filter(User.admin == True).first()
498 498 if user is None:
499 499 raise Exception('Missing administrative account!')
500 500 return user
501 501
502 502 @classmethod
503 503 def get_default_user(cls, cache=False):
504 504 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
505 505 if user is None:
506 506 raise Exception('Missing default account!')
507 507 return user
508 508
509 509 def get_api_data(self):
510 510 """
511 511 Common function for generating user related data for API
512 512 """
513 513 user = self
514 514 data = dict(
515 515 user_id=user.user_id,
516 516 username=user.username,
517 517 firstname=user.name,
518 518 lastname=user.lastname,
519 519 email=user.email,
520 520 emails=user.emails,
521 521 api_key=user.api_key,
522 522 active=user.active,
523 523 admin=user.admin,
524 524 ldap_dn=user.ldap_dn,
525 525 last_login=user.last_login,
526 526 ip_addresses=user.ip_addresses
527 527 )
528 528 return data
529 529
530 530 def __json__(self):
531 531 data = dict(
532 532 full_name=self.full_name,
533 533 full_name_or_username=self.full_name_or_username,
534 534 short_contact=self.short_contact,
535 535 full_contact=self.full_contact
536 536 )
537 537 data.update(self.get_api_data())
538 538 return data
539 539
540 540
541 541 class UserEmailMap(Base, BaseModel):
542 542 __tablename__ = 'user_email_map'
543 543 __table_args__ = (
544 544 Index('uem_email_idx', 'email'),
545 545 UniqueConstraint('email'),
546 546 {'extend_existing': True, 'mysql_engine': 'InnoDB',
547 547 'mysql_charset': 'utf8'}
548 548 )
549 549 __mapper_args__ = {}
550 550
551 551 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
552 552 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
553 553 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
554 554 user = relationship('User', lazy='joined')
555 555
556 556 @validates('_email')
557 557 def validate_email(self, key, email):
558 558 # check if this email is not main one
559 559 main_email = Session().query(User).filter(User.email == email).scalar()
560 560 if main_email is not None:
561 561 raise AttributeError('email %s is present is user table' % email)
562 562 return email
563 563
564 564 @hybrid_property
565 565 def email(self):
566 566 return self._email
567 567
568 568 @email.setter
569 569 def email(self, val):
570 570 self._email = val.lower() if val else None
571 571
572 572
573 573 class UserIpMap(Base, BaseModel):
574 574 __tablename__ = 'user_ip_map'
575 575 __table_args__ = (
576 576 UniqueConstraint('user_id', 'ip_addr'),
577 577 {'extend_existing': True, 'mysql_engine': 'InnoDB',
578 578 'mysql_charset': 'utf8'}
579 579 )
580 580 __mapper_args__ = {}
581 581
582 582 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
583 583 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
584 584 ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
585 585 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
586 586 user = relationship('User', lazy='joined')
587 587
588 588 @classmethod
589 589 def _get_ip_range(cls, ip_addr):
590 590 from rhodecode.lib import ipaddr
591 591 net = ipaddr.IPNetwork(address=ip_addr)
592 592 return [str(net.network), str(net.broadcast)]
593 593
594 594 def __json__(self):
595 595 return dict(
596 596 ip_addr=self.ip_addr,
597 597 ip_range=self._get_ip_range(self.ip_addr)
598 598 )
599 599
600 600
601 601 class UserLog(Base, BaseModel):
602 602 __tablename__ = 'user_logs'
603 603 __table_args__ = (
604 604 {'extend_existing': True, 'mysql_engine': 'InnoDB',
605 605 'mysql_charset': 'utf8'},
606 606 )
607 607 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
608 608 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
609 609 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
610 610 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
611 611 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
612 612 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
613 613 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
614 614 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
615 615
616 616 @property
617 617 def action_as_day(self):
618 618 return datetime.date(*self.action_date.timetuple()[:3])
619 619
620 620 user = relationship('User')
621 621 repository = relationship('Repository', cascade='')
622 622
623 623
624 624 class UserGroup(Base, BaseModel):
625 625 __tablename__ = 'users_groups'
626 626 __table_args__ = (
627 627 {'extend_existing': True, 'mysql_engine': 'InnoDB',
628 628 'mysql_charset': 'utf8'},
629 629 )
630 630
631 631 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
632 632 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
633 633 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
634 634 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
635 635 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
636 636
637 637 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
638 638 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
639 639 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
640 640 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
641 641 user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
642 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
643
642 644 user = relationship('User')
643 645
644 646 def __unicode__(self):
645 647 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
646 648 self.users_group_id,
647 649 self.users_group_name)
648 650
649 651 @classmethod
650 652 def get_by_group_name(cls, group_name, cache=False,
651 653 case_insensitive=False):
652 654 if case_insensitive:
653 655 q = cls.query().filter(cls.users_group_name.ilike(group_name))
654 656 else:
655 657 q = cls.query().filter(cls.users_group_name == group_name)
656 658 if cache:
657 659 q = q.options(FromCache(
658 660 "sql_cache_short",
659 661 "get_user_%s" % _hash_key(group_name)
660 662 )
661 663 )
662 664 return q.scalar()
663 665
664 666 @classmethod
665 667 def get(cls, users_group_id, cache=False):
666 668 users_group = cls.query()
667 669 if cache:
668 670 users_group = users_group.options(FromCache("sql_cache_short",
669 671 "get_users_group_%s" % users_group_id))
670 672 return users_group.get(users_group_id)
671 673
672 674 def get_api_data(self):
673 675 users_group = self
674 676
675 677 data = dict(
676 678 users_group_id=users_group.users_group_id,
677 679 group_name=users_group.users_group_name,
678 680 active=users_group.users_group_active,
679 681 )
680 682
681 683 return data
682 684
683 685
684 686 class UserGroupMember(Base, BaseModel):
685 687 __tablename__ = 'users_groups_members'
686 688 __table_args__ = (
687 689 {'extend_existing': True, 'mysql_engine': 'InnoDB',
688 690 'mysql_charset': 'utf8'},
689 691 )
690 692
691 693 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
692 694 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
693 695 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
694 696
695 697 user = relationship('User', lazy='joined')
696 698 users_group = relationship('UserGroup')
697 699
698 700 def __init__(self, gr_id='', u_id=''):
699 701 self.users_group_id = gr_id
700 702 self.user_id = u_id
701 703
702 704
703 705 class RepositoryField(Base, BaseModel):
704 706 __tablename__ = 'repositories_fields'
705 707 __table_args__ = (
706 708 UniqueConstraint('repository_id', 'field_key'), # no-multi field
707 709 {'extend_existing': True, 'mysql_engine': 'InnoDB',
708 710 'mysql_charset': 'utf8'},
709 711 )
710 712 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
711 713
712 714 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
713 715 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
714 716 field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None))
715 717 field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
716 718 field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
717 719 field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
718 720 field_type = Column("field_type", String(256), nullable=False, unique=None)
719 721 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
720 722
721 723 repository = relationship('Repository')
722 724
723 725 @property
724 726 def field_key_prefixed(self):
725 727 return 'ex_%s' % self.field_key
726 728
727 729 @classmethod
728 730 def un_prefix_key(cls, key):
729 731 if key.startswith(cls.PREFIX):
730 732 return key[len(cls.PREFIX):]
731 733 return key
732 734
733 735 @classmethod
734 736 def get_by_key_name(cls, key, repo):
735 737 row = cls.query()\
736 738 .filter(cls.repository == repo)\
737 739 .filter(cls.field_key == key).scalar()
738 740 return row
739 741
740 742
741 743 class Repository(Base, BaseModel):
742 744 __tablename__ = 'repositories'
743 745 __table_args__ = (
744 746 UniqueConstraint('repo_name'),
745 747 Index('r_repo_name_idx', 'repo_name'),
746 748 {'extend_existing': True, 'mysql_engine': 'InnoDB',
747 749 'mysql_charset': 'utf8'},
748 750 )
749 751
750 752 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
751 753 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
752 754 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
753 755 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
754 756 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
755 757 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
756 758 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
757 759 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
758 760 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
759 761 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
760 762 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
761 763 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
762 764 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
763 765 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
764 766 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
765 767
766 768 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
767 769 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
768 770
769 771 user = relationship('User')
770 772 fork = relationship('Repository', remote_side=repo_id)
771 773 group = relationship('RepoGroup')
772 774 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
773 775 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
774 776 stats = relationship('Statistics', cascade='all', uselist=False)
775 777
776 778 followers = relationship('UserFollowing',
777 779 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
778 780 cascade='all')
779 781 extra_fields = relationship('RepositoryField',
780 782 cascade="all, delete, delete-orphan")
781 783
782 784 logs = relationship('UserLog')
783 785 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
784 786
785 787 pull_requests_org = relationship('PullRequest',
786 788 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
787 789 cascade="all, delete, delete-orphan")
788 790
789 791 pull_requests_other = relationship('PullRequest',
790 792 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
791 793 cascade="all, delete, delete-orphan")
792 794
793 795 def __unicode__(self):
794 796 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
795 797 self.repo_name)
796 798
797 799 @hybrid_property
798 800 def locked(self):
799 801 # always should return [user_id, timelocked]
800 802 if self._locked:
801 803 _lock_info = self._locked.split(':')
802 804 return int(_lock_info[0]), _lock_info[1]
803 805 return [None, None]
804 806
805 807 @locked.setter
806 808 def locked(self, val):
807 809 if val and isinstance(val, (list, tuple)):
808 810 self._locked = ':'.join(map(str, val))
809 811 else:
810 812 self._locked = None
811 813
812 814 @hybrid_property
813 815 def changeset_cache(self):
814 816 from rhodecode.lib.vcs.backends.base import EmptyChangeset
815 817 dummy = EmptyChangeset().__json__()
816 818 if not self._changeset_cache:
817 819 return dummy
818 820 try:
819 821 return json.loads(self._changeset_cache)
820 822 except TypeError:
821 823 return dummy
822 824
823 825 @changeset_cache.setter
824 826 def changeset_cache(self, val):
825 827 try:
826 828 self._changeset_cache = json.dumps(val)
827 829 except Exception:
828 830 log.error(traceback.format_exc())
829 831
830 832 @classmethod
831 833 def url_sep(cls):
832 834 return URL_SEP
833 835
834 836 @classmethod
835 837 def normalize_repo_name(cls, repo_name):
836 838 """
837 839 Normalizes os specific repo_name to the format internally stored inside
838 840 dabatabase using URL_SEP
839 841
840 842 :param cls:
841 843 :param repo_name:
842 844 """
843 845 return cls.url_sep().join(repo_name.split(os.sep))
844 846
845 847 @classmethod
846 848 def get_by_repo_name(cls, repo_name):
847 849 q = Session().query(cls).filter(cls.repo_name == repo_name)
848 850 q = q.options(joinedload(Repository.fork))\
849 851 .options(joinedload(Repository.user))\
850 852 .options(joinedload(Repository.group))
851 853 return q.scalar()
852 854
853 855 @classmethod
854 856 def get_by_full_path(cls, repo_full_path):
855 857 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
856 858 repo_name = cls.normalize_repo_name(repo_name)
857 859 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
858 860
859 861 @classmethod
860 862 def get_repo_forks(cls, repo_id):
861 863 return cls.query().filter(Repository.fork_id == repo_id)
862 864
863 865 @classmethod
864 866 def base_path(cls):
865 867 """
866 868 Returns base path when all repos are stored
867 869
868 870 :param cls:
869 871 """
870 872 q = Session().query(RhodeCodeUi)\
871 873 .filter(RhodeCodeUi.ui_key == cls.url_sep())
872 874 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
873 875 return q.one().ui_value
874 876
875 877 @property
876 878 def forks(self):
877 879 """
878 880 Return forks of this repo
879 881 """
880 882 return Repository.get_repo_forks(self.repo_id)
881 883
882 884 @property
883 885 def parent(self):
884 886 """
885 887 Returns fork parent
886 888 """
887 889 return self.fork
888 890
889 891 @property
890 892 def just_name(self):
891 893 return self.repo_name.split(Repository.url_sep())[-1]
892 894
893 895 @property
894 896 def groups_with_parents(self):
895 897 groups = []
896 898 if self.group is None:
897 899 return groups
898 900
899 901 cur_gr = self.group
900 902 groups.insert(0, cur_gr)
901 903 while 1:
902 904 gr = getattr(cur_gr, 'parent_group', None)
903 905 cur_gr = cur_gr.parent_group
904 906 if gr is None:
905 907 break
906 908 groups.insert(0, gr)
907 909
908 910 return groups
909 911
910 912 @property
911 913 def groups_and_repo(self):
912 914 return self.groups_with_parents, self.just_name, self.repo_name
913 915
914 916 @LazyProperty
915 917 def repo_path(self):
916 918 """
917 919 Returns base full path for that repository means where it actually
918 920 exists on a filesystem
919 921 """
920 922 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
921 923 Repository.url_sep())
922 924 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
923 925 return q.one().ui_value
924 926
925 927 @property
926 928 def repo_full_path(self):
927 929 p = [self.repo_path]
928 930 # we need to split the name by / since this is how we store the
929 931 # names in the database, but that eventually needs to be converted
930 932 # into a valid system path
931 933 p += self.repo_name.split(Repository.url_sep())
932 934 return os.path.join(*map(safe_unicode, p))
933 935
934 936 @property
935 937 def cache_keys(self):
936 938 """
937 939 Returns associated cache keys for that repo
938 940 """
939 941 return CacheInvalidation.query()\
940 942 .filter(CacheInvalidation.cache_args == self.repo_name)\
941 943 .order_by(CacheInvalidation.cache_key)\
942 944 .all()
943 945
944 946 def get_new_name(self, repo_name):
945 947 """
946 948 returns new full repository name based on assigned group and new new
947 949
948 950 :param group_name:
949 951 """
950 952 path_prefix = self.group.full_path_splitted if self.group else []
951 953 return Repository.url_sep().join(path_prefix + [repo_name])
952 954
953 955 @property
954 956 def _ui(self):
955 957 """
956 958 Creates an db based ui object for this repository
957 959 """
958 960 from rhodecode.lib.utils import make_ui
959 961 return make_ui('db', clear_session=False)
960 962
961 963 @classmethod
962 964 def is_valid(cls, repo_name):
963 965 """
964 966 returns True if given repo name is a valid filesystem repository
965 967
966 968 :param cls:
967 969 :param repo_name:
968 970 """
969 971 from rhodecode.lib.utils import is_valid_repo
970 972
971 973 return is_valid_repo(repo_name, cls.base_path())
972 974
973 975 def get_api_data(self):
974 976 """
975 977 Common function for generating repo api data
976 978
977 979 """
978 980 repo = self
979 981 data = dict(
980 982 repo_id=repo.repo_id,
981 983 repo_name=repo.repo_name,
982 984 repo_type=repo.repo_type,
983 985 clone_uri=repo.clone_uri,
984 986 private=repo.private,
985 987 created_on=repo.created_on,
986 988 description=repo.description,
987 989 landing_rev=repo.landing_rev,
988 990 owner=repo.user.username,
989 991 fork_of=repo.fork.repo_name if repo.fork else None,
990 992 enable_statistics=repo.enable_statistics,
991 993 enable_locking=repo.enable_locking,
992 994 enable_downloads=repo.enable_downloads,
993 995 last_changeset=repo.changeset_cache,
994 996 locked_by=User.get(self.locked[0]).get_api_data() \
995 997 if self.locked[0] else None,
996 998 locked_date=time_to_datetime(self.locked[1]) \
997 999 if self.locked[1] else None
998 1000 )
999 1001 rc_config = RhodeCodeSetting.get_app_settings()
1000 1002 repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
1001 1003 if repository_fields:
1002 1004 for f in self.extra_fields:
1003 1005 data[f.field_key_prefixed] = f.field_value
1004 1006
1005 1007 return data
1006 1008
1007 1009 @classmethod
1008 1010 def lock(cls, repo, user_id):
1009 1011 repo.locked = [user_id, time.time()]
1010 1012 Session().add(repo)
1011 1013 Session().commit()
1012 1014
1013 1015 @classmethod
1014 1016 def unlock(cls, repo):
1015 1017 repo.locked = None
1016 1018 Session().add(repo)
1017 1019 Session().commit()
1018 1020
1019 1021 @classmethod
1020 1022 def getlock(cls, repo):
1021 1023 return repo.locked
1022 1024
1023 1025 @property
1024 1026 def last_db_change(self):
1025 1027 return self.updated_on
1026 1028
1027 1029 def clone_url(self, **override):
1028 1030 from pylons import url
1029 1031 from urlparse import urlparse
1030 1032 import urllib
1031 1033 parsed_url = urlparse(url('home', qualified=True))
1032 1034 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
1033 1035 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
1034 1036 args = {
1035 1037 'user': '',
1036 1038 'pass': '',
1037 1039 'scheme': parsed_url.scheme,
1038 1040 'netloc': parsed_url.netloc,
1039 1041 'prefix': decoded_path,
1040 1042 'path': self.repo_name
1041 1043 }
1042 1044
1043 1045 args.update(override)
1044 1046 return default_clone_uri % args
1045 1047
1046 1048 #==========================================================================
1047 1049 # SCM PROPERTIES
1048 1050 #==========================================================================
1049 1051
1050 1052 def get_changeset(self, rev=None):
1051 1053 return get_changeset_safe(self.scm_instance, rev)
1052 1054
1053 1055 def get_landing_changeset(self):
1054 1056 """
1055 1057 Returns landing changeset, or if that doesn't exist returns the tip
1056 1058 """
1057 1059 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
1058 1060 return cs
1059 1061
1060 1062 def update_changeset_cache(self, cs_cache=None):
1061 1063 """
1062 1064 Update cache of last changeset for repository, keys should be::
1063 1065
1064 1066 short_id
1065 1067 raw_id
1066 1068 revision
1067 1069 message
1068 1070 date
1069 1071 author
1070 1072
1071 1073 :param cs_cache:
1072 1074 """
1073 1075 from rhodecode.lib.vcs.backends.base import BaseChangeset
1074 1076 if cs_cache is None:
1075 1077 cs_cache = EmptyChangeset()
1076 1078 # use no-cache version here
1077 1079 scm_repo = self.scm_instance_no_cache()
1078 1080 if scm_repo:
1079 1081 cs_cache = scm_repo.get_changeset()
1080 1082
1081 1083 if isinstance(cs_cache, BaseChangeset):
1082 1084 cs_cache = cs_cache.__json__()
1083 1085
1084 1086 if (cs_cache != self.changeset_cache or not self.changeset_cache):
1085 1087 _default = datetime.datetime.fromtimestamp(0)
1086 1088 last_change = cs_cache.get('date') or _default
1087 1089 log.debug('updated repo %s with new cs cache %s'
1088 1090 % (self.repo_name, cs_cache))
1089 1091 self.updated_on = last_change
1090 1092 self.changeset_cache = cs_cache
1091 1093 Session().add(self)
1092 1094 Session().commit()
1093 1095 else:
1094 1096 log.debug('Skipping repo:%s already with latest changes'
1095 1097 % self.repo_name)
1096 1098
1097 1099 @property
1098 1100 def tip(self):
1099 1101 return self.get_changeset('tip')
1100 1102
1101 1103 @property
1102 1104 def author(self):
1103 1105 return self.tip.author
1104 1106
1105 1107 @property
1106 1108 def last_change(self):
1107 1109 return self.scm_instance.last_change
1108 1110
1109 1111 def get_comments(self, revisions=None):
1110 1112 """
1111 1113 Returns comments for this repository grouped by revisions
1112 1114
1113 1115 :param revisions: filter query by revisions only
1114 1116 """
1115 1117 cmts = ChangesetComment.query()\
1116 1118 .filter(ChangesetComment.repo == self)
1117 1119 if revisions:
1118 1120 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1119 1121 grouped = defaultdict(list)
1120 1122 for cmt in cmts.all():
1121 1123 grouped[cmt.revision].append(cmt)
1122 1124 return grouped
1123 1125
1124 1126 def statuses(self, revisions=None):
1125 1127 """
1126 1128 Returns statuses for this repository
1127 1129
1128 1130 :param revisions: list of revisions to get statuses for
1129 1131 :type revisions: list
1130 1132 """
1131 1133
1132 1134 statuses = ChangesetStatus.query()\
1133 1135 .filter(ChangesetStatus.repo == self)\
1134 1136 .filter(ChangesetStatus.version == 0)
1135 1137 if revisions:
1136 1138 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1137 1139 grouped = {}
1138 1140
1139 1141 #maybe we have open new pullrequest without a status ?
1140 1142 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1141 1143 status_lbl = ChangesetStatus.get_status_lbl(stat)
1142 1144 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1143 1145 for rev in pr.revisions:
1144 1146 pr_id = pr.pull_request_id
1145 1147 pr_repo = pr.other_repo.repo_name
1146 1148 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1147 1149
1148 1150 for stat in statuses.all():
1149 1151 pr_id = pr_repo = None
1150 1152 if stat.pull_request:
1151 1153 pr_id = stat.pull_request.pull_request_id
1152 1154 pr_repo = stat.pull_request.other_repo.repo_name
1153 1155 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1154 1156 pr_id, pr_repo]
1155 1157 return grouped
1156 1158
1157 1159 def _repo_size(self):
1158 1160 from rhodecode.lib import helpers as h
1159 1161 log.debug('calculating repository size...')
1160 1162 return h.format_byte_size(self.scm_instance.size)
1161 1163
1162 1164 #==========================================================================
1163 1165 # SCM CACHE INSTANCE
1164 1166 #==========================================================================
1165 1167
1166 1168 def set_invalidate(self):
1167 1169 """
1168 1170 Mark caches of this repo as invalid.
1169 1171 """
1170 1172 CacheInvalidation.set_invalidate(self.repo_name)
1171 1173
1172 1174 def scm_instance_no_cache(self):
1173 1175 return self.__get_instance()
1174 1176
1175 1177 @property
1176 1178 def scm_instance(self):
1177 1179 import rhodecode
1178 1180 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1179 1181 if full_cache:
1180 1182 return self.scm_instance_cached()
1181 1183 return self.__get_instance()
1182 1184
1183 1185 def scm_instance_cached(self, valid_cache_keys=None):
1184 1186 @cache_region('long_term')
1185 1187 def _c(repo_name):
1186 1188 return self.__get_instance()
1187 1189 rn = self.repo_name
1188 1190
1189 1191 valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys)
1190 1192 if not valid:
1191 1193 log.debug('Cache for %s invalidated, getting new object' % (rn))
1192 1194 region_invalidate(_c, None, rn)
1193 1195 else:
1194 1196 log.debug('Getting obj for %s from cache' % (rn))
1195 1197 return _c(rn)
1196 1198
1197 1199 def __get_instance(self):
1198 1200 repo_full_path = self.repo_full_path
1199 1201 try:
1200 1202 alias = get_scm(repo_full_path)[0]
1201 1203 log.debug('Creating instance of %s repository from %s'
1202 1204 % (alias, repo_full_path))
1203 1205 backend = get_backend(alias)
1204 1206 except VCSError:
1205 1207 log.error(traceback.format_exc())
1206 1208 log.error('Perhaps this repository is in db and not in '
1207 1209 'filesystem run rescan repositories with '
1208 1210 '"destroy old data " option from admin panel')
1209 1211 return
1210 1212
1211 1213 if alias == 'hg':
1212 1214
1213 1215 repo = backend(safe_str(repo_full_path), create=False,
1214 1216 baseui=self._ui)
1215 1217 # skip hidden web repository
1216 1218 if repo._get_hidden():
1217 1219 return
1218 1220 else:
1219 1221 repo = backend(repo_full_path, create=False)
1220 1222
1221 1223 return repo
1222 1224
1223 1225
1224 1226 class RepoGroup(Base, BaseModel):
1225 1227 __tablename__ = 'groups'
1226 1228 __table_args__ = (
1227 1229 UniqueConstraint('group_name', 'group_parent_id'),
1228 1230 CheckConstraint('group_id != group_parent_id'),
1229 1231 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1230 1232 'mysql_charset': 'utf8'},
1231 1233 )
1232 1234 __mapper_args__ = {'order_by': 'group_name'}
1233 1235
1234 1236 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1235 1237 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1236 1238 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1237 1239 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1238 1240 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1239 1241 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1240 1242
1241 1243 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1242 1244 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1243 1245 parent_group = relationship('RepoGroup', remote_side=group_id)
1244 1246 user = relationship('User')
1245 1247
1246 1248 def __init__(self, group_name='', parent_group=None):
1247 1249 self.group_name = group_name
1248 1250 self.parent_group = parent_group
1249 1251
1250 1252 def __unicode__(self):
1251 1253 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
1252 1254 self.group_name)
1253 1255
1254 1256 @classmethod
1255 1257 def groups_choices(cls, groups=None, show_empty_group=True):
1256 1258 from webhelpers.html import literal as _literal
1257 1259 if not groups:
1258 1260 groups = cls.query().all()
1259 1261
1260 1262 repo_groups = []
1261 1263 if show_empty_group:
1262 1264 repo_groups = [('-1', '-- %s --' % _('top level'))]
1263 1265 sep = ' &raquo; '
1264 1266 _name = lambda k: _literal(sep.join(k))
1265 1267
1266 1268 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1267 1269 for x in groups])
1268 1270
1269 1271 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1270 1272 return repo_groups
1271 1273
1272 1274 @classmethod
1273 1275 def url_sep(cls):
1274 1276 return URL_SEP
1275 1277
1276 1278 @classmethod
1277 1279 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1278 1280 if case_insensitive:
1279 1281 gr = cls.query()\
1280 1282 .filter(cls.group_name.ilike(group_name))
1281 1283 else:
1282 1284 gr = cls.query()\
1283 1285 .filter(cls.group_name == group_name)
1284 1286 if cache:
1285 1287 gr = gr.options(FromCache(
1286 1288 "sql_cache_short",
1287 1289 "get_group_%s" % _hash_key(group_name)
1288 1290 )
1289 1291 )
1290 1292 return gr.scalar()
1291 1293
1292 1294 @property
1293 1295 def parents(self):
1294 1296 parents_recursion_limit = 5
1295 1297 groups = []
1296 1298 if self.parent_group is None:
1297 1299 return groups
1298 1300 cur_gr = self.parent_group
1299 1301 groups.insert(0, cur_gr)
1300 1302 cnt = 0
1301 1303 while 1:
1302 1304 cnt += 1
1303 1305 gr = getattr(cur_gr, 'parent_group', None)
1304 1306 cur_gr = cur_gr.parent_group
1305 1307 if gr is None:
1306 1308 break
1307 1309 if cnt == parents_recursion_limit:
1308 1310 # this will prevent accidental infinit loops
1309 1311 log.error('group nested more than %s' %
1310 1312 parents_recursion_limit)
1311 1313 break
1312 1314
1313 1315 groups.insert(0, gr)
1314 1316 return groups
1315 1317
1316 1318 @property
1317 1319 def children(self):
1318 1320 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1319 1321
1320 1322 @property
1321 1323 def name(self):
1322 1324 return self.group_name.split(RepoGroup.url_sep())[-1]
1323 1325
1324 1326 @property
1325 1327 def full_path(self):
1326 1328 return self.group_name
1327 1329
1328 1330 @property
1329 1331 def full_path_splitted(self):
1330 1332 return self.group_name.split(RepoGroup.url_sep())
1331 1333
1332 1334 @property
1333 1335 def repositories(self):
1334 1336 return Repository.query()\
1335 1337 .filter(Repository.group == self)\
1336 1338 .order_by(Repository.repo_name)
1337 1339
1338 1340 @property
1339 1341 def repositories_recursive_count(self):
1340 1342 cnt = self.repositories.count()
1341 1343
1342 1344 def children_count(group):
1343 1345 cnt = 0
1344 1346 for child in group.children:
1345 1347 cnt += child.repositories.count()
1346 1348 cnt += children_count(child)
1347 1349 return cnt
1348 1350
1349 1351 return cnt + children_count(self)
1350 1352
1351 1353 def _recursive_objects(self, include_repos=True):
1352 1354 all_ = []
1353 1355
1354 1356 def _get_members(root_gr):
1355 1357 if include_repos:
1356 1358 for r in root_gr.repositories:
1357 1359 all_.append(r)
1358 1360 childs = root_gr.children.all()
1359 1361 if childs:
1360 1362 for gr in childs:
1361 1363 all_.append(gr)
1362 1364 _get_members(gr)
1363 1365
1364 1366 _get_members(self)
1365 1367 return [self] + all_
1366 1368
1367 1369 def recursive_groups_and_repos(self):
1368 1370 """
1369 1371 Recursive return all groups, with repositories in those groups
1370 1372 """
1371 1373 return self._recursive_objects()
1372 1374
1373 1375 def recursive_groups(self):
1374 1376 """
1375 1377 Returns all children groups for this group including children of children
1376 1378 """
1377 1379 return self._recursive_objects(include_repos=False)
1378 1380
1379 1381 def get_new_name(self, group_name):
1380 1382 """
1381 1383 returns new full group name based on parent and new name
1382 1384
1383 1385 :param group_name:
1384 1386 """
1385 1387 path_prefix = (self.parent_group.full_path_splitted if
1386 1388 self.parent_group else [])
1387 1389 return RepoGroup.url_sep().join(path_prefix + [group_name])
1388 1390
1389 1391
1390 1392 class Permission(Base, BaseModel):
1391 1393 __tablename__ = 'permissions'
1392 1394 __table_args__ = (
1393 1395 Index('p_perm_name_idx', 'permission_name'),
1394 1396 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1395 1397 'mysql_charset': 'utf8'},
1396 1398 )
1397 1399 PERMS = [
1398 1400 ('hg.admin', _('RhodeCode Administrator')),
1399 1401
1400 1402 ('repository.none', _('Repository no access')),
1401 1403 ('repository.read', _('Repository read access')),
1402 1404 ('repository.write', _('Repository write access')),
1403 1405 ('repository.admin', _('Repository admin access')),
1404 1406
1405 1407 ('group.none', _('Repository group no access')),
1406 1408 ('group.read', _('Repository group read access')),
1407 1409 ('group.write', _('Repository group write access')),
1408 1410 ('group.admin', _('Repository group admin access')),
1409 1411
1410 1412 ('usergroup.none', _('User group no access')),
1411 1413 ('usergroup.read', _('User group read access')),
1412 1414 ('usergroup.write', _('User group write access')),
1413 1415 ('usergroup.admin', _('User group admin access')),
1414 1416
1415 1417 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
1416 1418 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
1417 1419
1418 1420 ('hg.usergroup.create.false', _('User Group creation disabled')),
1419 1421 ('hg.usergroup.create.true', _('User Group creation enabled')),
1420 1422
1421 1423 ('hg.create.none', _('Repository creation disabled')),
1422 1424 ('hg.create.repository', _('Repository creation enabled')),
1423 1425
1424 1426 ('hg.fork.none', _('Repository forking disabled')),
1425 1427 ('hg.fork.repository', _('Repository forking enabled')),
1426 1428
1427 1429 ('hg.register.none', _('Registration disabled')),
1428 1430 ('hg.register.manual_activate', _('User Registration with manual account activation')),
1429 1431 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
1430 1432
1431 1433 ('hg.extern_activate.manual', _('Manual activation of external account')),
1432 1434 ('hg.extern_activate.auto', _('Automatic activation of external account')),
1433 1435
1434 1436 ]
1435 1437
1436 1438 #definition of system default permissions for DEFAULT user
1437 1439 DEFAULT_USER_PERMISSIONS = [
1438 1440 'repository.read',
1439 1441 'group.read',
1440 1442 'usergroup.read',
1441 1443 'hg.create.repository',
1442 1444 'hg.fork.repository',
1443 1445 'hg.register.manual_activate',
1444 1446 'hg.extern_activate.auto',
1445 1447 ]
1446 1448
1447 1449 # defines which permissions are more important higher the more important
1448 1450 # Weight defines which permissions are more important.
1449 1451 # The higher number the more important.
1450 1452 PERM_WEIGHTS = {
1451 1453 'repository.none': 0,
1452 1454 'repository.read': 1,
1453 1455 'repository.write': 3,
1454 1456 'repository.admin': 4,
1455 1457
1456 1458 'group.none': 0,
1457 1459 'group.read': 1,
1458 1460 'group.write': 3,
1459 1461 'group.admin': 4,
1460 1462
1461 1463 'usergroup.none': 0,
1462 1464 'usergroup.read': 1,
1463 1465 'usergroup.write': 3,
1464 1466 'usergroup.admin': 4,
1465 1467 'hg.repogroup.create.false': 0,
1466 1468 'hg.repogroup.create.true': 1,
1467 1469
1468 1470 'hg.usergroup.create.false': 0,
1469 1471 'hg.usergroup.create.true': 1,
1470 1472
1471 1473 'hg.fork.none': 0,
1472 1474 'hg.fork.repository': 1,
1473 1475 'hg.create.none': 0,
1474 1476 'hg.create.repository': 1
1475 1477 }
1476 1478
1477 1479 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1478 1480 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1479 1481 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1480 1482
1481 1483 def __unicode__(self):
1482 1484 return u"<%s('%s:%s')>" % (
1483 1485 self.__class__.__name__, self.permission_id, self.permission_name
1484 1486 )
1485 1487
1486 1488 @classmethod
1487 1489 def get_by_key(cls, key):
1488 1490 return cls.query().filter(cls.permission_name == key).scalar()
1489 1491
1490 1492 @classmethod
1491 1493 def get_default_perms(cls, default_user_id):
1492 1494 q = Session().query(UserRepoToPerm, Repository, cls)\
1493 1495 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1494 1496 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1495 1497 .filter(UserRepoToPerm.user_id == default_user_id)
1496 1498
1497 1499 return q.all()
1498 1500
1499 1501 @classmethod
1500 1502 def get_default_group_perms(cls, default_user_id):
1501 1503 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1502 1504 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1503 1505 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1504 1506 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1505 1507
1506 1508 return q.all()
1507 1509
1508 1510 @classmethod
1509 1511 def get_default_user_group_perms(cls, default_user_id):
1510 1512 q = Session().query(UserUserGroupToPerm, UserGroup, cls)\
1511 1513 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
1512 1514 .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\
1513 1515 .filter(UserUserGroupToPerm.user_id == default_user_id)
1514 1516
1515 1517 return q.all()
1516 1518
1517 1519
1518 1520 class UserRepoToPerm(Base, BaseModel):
1519 1521 __tablename__ = 'repo_to_perm'
1520 1522 __table_args__ = (
1521 1523 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1522 1524 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1523 1525 'mysql_charset': 'utf8'}
1524 1526 )
1525 1527 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1526 1528 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1527 1529 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1528 1530 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1529 1531
1530 1532 user = relationship('User')
1531 1533 repository = relationship('Repository')
1532 1534 permission = relationship('Permission')
1533 1535
1534 1536 @classmethod
1535 1537 def create(cls, user, repository, permission):
1536 1538 n = cls()
1537 1539 n.user = user
1538 1540 n.repository = repository
1539 1541 n.permission = permission
1540 1542 Session().add(n)
1541 1543 return n
1542 1544
1543 1545 def __unicode__(self):
1544 1546 return u'<%s => %s >' % (self.user, self.repository)
1545 1547
1546 1548
1547 1549 class UserUserGroupToPerm(Base, BaseModel):
1548 1550 __tablename__ = 'user_user_group_to_perm'
1549 1551 __table_args__ = (
1550 1552 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
1551 1553 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1552 1554 'mysql_charset': 'utf8'}
1553 1555 )
1554 1556 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1555 1557 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1556 1558 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1557 1559 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1558 1560
1559 1561 user = relationship('User')
1560 1562 user_group = relationship('UserGroup')
1561 1563 permission = relationship('Permission')
1562 1564
1563 1565 @classmethod
1564 1566 def create(cls, user, user_group, permission):
1565 1567 n = cls()
1566 1568 n.user = user
1567 1569 n.user_group = user_group
1568 1570 n.permission = permission
1569 1571 Session().add(n)
1570 1572 return n
1571 1573
1572 1574 def __unicode__(self):
1573 1575 return u'<%s => %s >' % (self.user, self.user_group)
1574 1576
1575 1577
1576 1578 class UserToPerm(Base, BaseModel):
1577 1579 __tablename__ = 'user_to_perm'
1578 1580 __table_args__ = (
1579 1581 UniqueConstraint('user_id', 'permission_id'),
1580 1582 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1581 1583 'mysql_charset': 'utf8'}
1582 1584 )
1583 1585 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1584 1586 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1585 1587 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1586 1588
1587 1589 user = relationship('User')
1588 1590 permission = relationship('Permission', lazy='joined')
1589 1591
1590 1592 def __unicode__(self):
1591 1593 return u'<%s => %s >' % (self.user, self.permission)
1592 1594
1593 1595
1594 1596 class UserGroupRepoToPerm(Base, BaseModel):
1595 1597 __tablename__ = 'users_group_repo_to_perm'
1596 1598 __table_args__ = (
1597 1599 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1598 1600 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1599 1601 'mysql_charset': 'utf8'}
1600 1602 )
1601 1603 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1602 1604 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1603 1605 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1604 1606 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1605 1607
1606 1608 users_group = relationship('UserGroup')
1607 1609 permission = relationship('Permission')
1608 1610 repository = relationship('Repository')
1609 1611
1610 1612 @classmethod
1611 1613 def create(cls, users_group, repository, permission):
1612 1614 n = cls()
1613 1615 n.users_group = users_group
1614 1616 n.repository = repository
1615 1617 n.permission = permission
1616 1618 Session().add(n)
1617 1619 return n
1618 1620
1619 1621 def __unicode__(self):
1620 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1622 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
1621 1623
1622 1624
1623 #TODO; not sure if this will be ever used
1624 1625 class UserGroupUserGroupToPerm(Base, BaseModel):
1625 1626 __tablename__ = 'user_group_user_group_to_perm'
1626 1627 __table_args__ = (
1627 UniqueConstraint('user_group_id', 'user_group_id', 'permission_id'),
1628 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
1629 CheckConstraint('target_user_group_id != user_group_id'),
1628 1630 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1629 1631 'mysql_charset': 'utf8'}
1630 1632 )
1631 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1632 target_user_group_id = Column("target_users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1633 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1634 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1633 1635 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1634 1636 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1635 1637
1636 target_user_group = relationship('UserGroup', remote_side=target_user_group_id, primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
1637 user_group = relationship('UserGroup', remote_side=user_group_id, primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
1638 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
1639 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
1638 1640 permission = relationship('Permission')
1639 1641
1640 1642 @classmethod
1641 1643 def create(cls, target_user_group, user_group, permission):
1642 1644 n = cls()
1643 1645 n.target_user_group = target_user_group
1644 1646 n.user_group = user_group
1645 1647 n.permission = permission
1646 1648 Session().add(n)
1647 1649 return n
1648 1650
1649 1651 def __unicode__(self):
1650 return u'<UserGroup:%s => %s >' % (self.target_user_group, self.user_group)
1652 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
1651 1653
1652 1654
1653 1655 class UserGroupToPerm(Base, BaseModel):
1654 1656 __tablename__ = 'users_group_to_perm'
1655 1657 __table_args__ = (
1656 1658 UniqueConstraint('users_group_id', 'permission_id',),
1657 1659 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1658 1660 'mysql_charset': 'utf8'}
1659 1661 )
1660 1662 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1661 1663 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1662 1664 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1663 1665
1664 1666 users_group = relationship('UserGroup')
1665 1667 permission = relationship('Permission')
1666 1668
1667 1669
1668 1670 class UserRepoGroupToPerm(Base, BaseModel):
1669 1671 __tablename__ = 'user_repo_group_to_perm'
1670 1672 __table_args__ = (
1671 1673 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1672 1674 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1673 1675 'mysql_charset': 'utf8'}
1674 1676 )
1675 1677
1676 1678 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1677 1679 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1678 1680 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1679 1681 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1680 1682
1681 1683 user = relationship('User')
1682 1684 group = relationship('RepoGroup')
1683 1685 permission = relationship('Permission')
1684 1686
1685 1687
1686 1688 class UserGroupRepoGroupToPerm(Base, BaseModel):
1687 1689 __tablename__ = 'users_group_repo_group_to_perm'
1688 1690 __table_args__ = (
1689 1691 UniqueConstraint('users_group_id', 'group_id'),
1690 1692 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1691 1693 'mysql_charset': 'utf8'}
1692 1694 )
1693 1695
1694 1696 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1695 1697 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1696 1698 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1697 1699 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1698 1700
1699 1701 users_group = relationship('UserGroup')
1700 1702 permission = relationship('Permission')
1701 1703 group = relationship('RepoGroup')
1702 1704
1703 1705
1704 1706 class Statistics(Base, BaseModel):
1705 1707 __tablename__ = 'statistics'
1706 1708 __table_args__ = (
1707 1709 UniqueConstraint('repository_id'),
1708 1710 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1709 1711 'mysql_charset': 'utf8'}
1710 1712 )
1711 1713 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1712 1714 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1713 1715 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1714 1716 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1715 1717 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1716 1718 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1717 1719
1718 1720 repository = relationship('Repository', single_parent=True)
1719 1721
1720 1722
1721 1723 class UserFollowing(Base, BaseModel):
1722 1724 __tablename__ = 'user_followings'
1723 1725 __table_args__ = (
1724 1726 UniqueConstraint('user_id', 'follows_repository_id'),
1725 1727 UniqueConstraint('user_id', 'follows_user_id'),
1726 1728 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1727 1729 'mysql_charset': 'utf8'}
1728 1730 )
1729 1731
1730 1732 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1731 1733 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1732 1734 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1733 1735 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1734 1736 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1735 1737
1736 1738 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1737 1739
1738 1740 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1739 1741 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1740 1742
1741 1743 @classmethod
1742 1744 def get_repo_followers(cls, repo_id):
1743 1745 return cls.query().filter(cls.follows_repo_id == repo_id)
1744 1746
1745 1747
1746 1748 class CacheInvalidation(Base, BaseModel):
1747 1749 __tablename__ = 'cache_invalidation'
1748 1750 __table_args__ = (
1749 1751 UniqueConstraint('cache_key'),
1750 1752 Index('key_idx', 'cache_key'),
1751 1753 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1752 1754 'mysql_charset': 'utf8'},
1753 1755 )
1754 1756 # cache_id, not used
1755 1757 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1756 1758 # cache_key as created by _get_cache_key
1757 1759 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1758 1760 # cache_args is a repo_name
1759 1761 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1760 1762 # instance sets cache_active True when it is caching,
1761 1763 # other instances set cache_active to False to indicate that this cache is invalid
1762 1764 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1763 1765
1764 1766 def __init__(self, cache_key, repo_name=''):
1765 1767 self.cache_key = cache_key
1766 1768 self.cache_args = repo_name
1767 1769 self.cache_active = False
1768 1770
1769 1771 def __unicode__(self):
1770 1772 return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__,
1771 1773 self.cache_id, self.cache_key, self.cache_active)
1772 1774
1773 1775 def _cache_key_partition(self):
1774 1776 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
1775 1777 return prefix, repo_name, suffix
1776 1778
1777 1779 def get_prefix(self):
1778 1780 """
1779 1781 get prefix that might have been used in _get_cache_key to
1780 1782 generate self.cache_key. Only used for informational purposes
1781 1783 in repo_edit.html.
1782 1784 """
1783 1785 # prefix, repo_name, suffix
1784 1786 return self._cache_key_partition()[0]
1785 1787
1786 1788 def get_suffix(self):
1787 1789 """
1788 1790 get suffix that might have been used in _get_cache_key to
1789 1791 generate self.cache_key. Only used for informational purposes
1790 1792 in repo_edit.html.
1791 1793 """
1792 1794 # prefix, repo_name, suffix
1793 1795 return self._cache_key_partition()[2]
1794 1796
1795 1797 @classmethod
1796 1798 def clear_cache(cls):
1797 1799 """
1798 1800 Delete all cache keys from database.
1799 1801 Should only be run when all instances are down and all entries thus stale.
1800 1802 """
1801 1803 cls.query().delete()
1802 1804 Session().commit()
1803 1805
1804 1806 @classmethod
1805 1807 def _get_cache_key(cls, key):
1806 1808 """
1807 1809 Wrapper for generating a unique cache key for this instance and "key".
1808 1810 key must / will start with a repo_name which will be stored in .cache_args .
1809 1811 """
1810 1812 import rhodecode
1811 1813 prefix = rhodecode.CONFIG.get('instance_id', '')
1812 1814 return "%s%s" % (prefix, key)
1813 1815
1814 1816 @classmethod
1815 1817 def set_invalidate(cls, repo_name):
1816 1818 """
1817 1819 Mark all caches of a repo as invalid in the database.
1818 1820 """
1819 1821 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1820 1822
1821 1823 try:
1822 1824 for inv_obj in inv_objs:
1823 1825 log.debug('marking %s key for invalidation based on repo_name=%s'
1824 1826 % (inv_obj, safe_str(repo_name)))
1825 1827 inv_obj.cache_active = False
1826 1828 Session().add(inv_obj)
1827 1829 Session().commit()
1828 1830 except Exception:
1829 1831 log.error(traceback.format_exc())
1830 1832 Session().rollback()
1831 1833
1832 1834 @classmethod
1833 1835 def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None):
1834 1836 """
1835 1837 Mark this cache key as active and currently cached.
1836 1838 Return True if the existing cache registration still was valid.
1837 1839 Return False to indicate that it had been invalidated and caches should be refreshed.
1838 1840 """
1839 1841
1840 1842 key = (repo_name + '_' + kind) if kind else repo_name
1841 1843 cache_key = cls._get_cache_key(key)
1842 1844
1843 1845 if valid_cache_keys and cache_key in valid_cache_keys:
1844 1846 return True
1845 1847
1846 1848 try:
1847 1849 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
1848 1850 if not inv_obj:
1849 1851 inv_obj = CacheInvalidation(cache_key, repo_name)
1850 1852 was_valid = inv_obj.cache_active
1851 1853 inv_obj.cache_active = True
1852 1854 Session().add(inv_obj)
1853 1855 Session().commit()
1854 1856 return was_valid
1855 1857 except Exception:
1856 1858 log.error(traceback.format_exc())
1857 1859 Session().rollback()
1858 1860 return False
1859 1861
1860 1862 @classmethod
1861 1863 def get_valid_cache_keys(cls):
1862 1864 """
1863 1865 Return opaque object with information of which caches still are valid
1864 1866 and can be used without checking for invalidation.
1865 1867 """
1866 1868 return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all())
1867 1869
1868 1870
1869 1871 class ChangesetComment(Base, BaseModel):
1870 1872 __tablename__ = 'changeset_comments'
1871 1873 __table_args__ = (
1872 1874 Index('cc_revision_idx', 'revision'),
1873 1875 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1874 1876 'mysql_charset': 'utf8'},
1875 1877 )
1876 1878 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1877 1879 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1878 1880 revision = Column('revision', String(40), nullable=True)
1879 1881 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1880 1882 line_no = Column('line_no', Unicode(10), nullable=True)
1881 1883 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1882 1884 f_path = Column('f_path', Unicode(1000), nullable=True)
1883 1885 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1884 1886 text = Column('text', UnicodeText(25000), nullable=False)
1885 1887 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1886 1888 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1887 1889
1888 1890 author = relationship('User', lazy='joined')
1889 1891 repo = relationship('Repository')
1890 1892 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1891 1893 pull_request = relationship('PullRequest', lazy='joined')
1892 1894
1893 1895 @classmethod
1894 1896 def get_users(cls, revision=None, pull_request_id=None):
1895 1897 """
1896 1898 Returns user associated with this ChangesetComment. ie those
1897 1899 who actually commented
1898 1900
1899 1901 :param cls:
1900 1902 :param revision:
1901 1903 """
1902 1904 q = Session().query(User)\
1903 1905 .join(ChangesetComment.author)
1904 1906 if revision:
1905 1907 q = q.filter(cls.revision == revision)
1906 1908 elif pull_request_id:
1907 1909 q = q.filter(cls.pull_request_id == pull_request_id)
1908 1910 return q.all()
1909 1911
1910 1912
1911 1913 class ChangesetStatus(Base, BaseModel):
1912 1914 __tablename__ = 'changeset_statuses'
1913 1915 __table_args__ = (
1914 1916 Index('cs_revision_idx', 'revision'),
1915 1917 Index('cs_version_idx', 'version'),
1916 1918 UniqueConstraint('repo_id', 'revision', 'version'),
1917 1919 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1918 1920 'mysql_charset': 'utf8'}
1919 1921 )
1920 1922 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1921 1923 STATUS_APPROVED = 'approved'
1922 1924 STATUS_REJECTED = 'rejected'
1923 1925 STATUS_UNDER_REVIEW = 'under_review'
1924 1926
1925 1927 STATUSES = [
1926 1928 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1927 1929 (STATUS_APPROVED, _("Approved")),
1928 1930 (STATUS_REJECTED, _("Rejected")),
1929 1931 (STATUS_UNDER_REVIEW, _("Under Review")),
1930 1932 ]
1931 1933
1932 1934 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1933 1935 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1934 1936 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1935 1937 revision = Column('revision', String(40), nullable=False)
1936 1938 status = Column('status', String(128), nullable=False, default=DEFAULT)
1937 1939 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1938 1940 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1939 1941 version = Column('version', Integer(), nullable=False, default=0)
1940 1942 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1941 1943
1942 1944 author = relationship('User', lazy='joined')
1943 1945 repo = relationship('Repository')
1944 1946 comment = relationship('ChangesetComment', lazy='joined')
1945 1947 pull_request = relationship('PullRequest', lazy='joined')
1946 1948
1947 1949 def __unicode__(self):
1948 1950 return u"<%s('%s:%s')>" % (
1949 1951 self.__class__.__name__,
1950 1952 self.status, self.author
1951 1953 )
1952 1954
1953 1955 @classmethod
1954 1956 def get_status_lbl(cls, value):
1955 1957 return dict(cls.STATUSES).get(value)
1956 1958
1957 1959 @property
1958 1960 def status_lbl(self):
1959 1961 return ChangesetStatus.get_status_lbl(self.status)
1960 1962
1961 1963
1962 1964 class PullRequest(Base, BaseModel):
1963 1965 __tablename__ = 'pull_requests'
1964 1966 __table_args__ = (
1965 1967 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1966 1968 'mysql_charset': 'utf8'},
1967 1969 )
1968 1970
1969 1971 STATUS_NEW = u'new'
1970 1972 STATUS_OPEN = u'open'
1971 1973 STATUS_CLOSED = u'closed'
1972 1974
1973 1975 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1974 1976 title = Column('title', Unicode(256), nullable=True)
1975 1977 description = Column('description', UnicodeText(10240), nullable=True)
1976 1978 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1977 1979 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1978 1980 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1979 1981 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1980 1982 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1981 1983 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1982 1984 org_ref = Column('org_ref', Unicode(256), nullable=False)
1983 1985 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1984 1986 other_ref = Column('other_ref', Unicode(256), nullable=False)
1985 1987
1986 1988 @hybrid_property
1987 1989 def revisions(self):
1988 1990 return self._revisions.split(':')
1989 1991
1990 1992 @revisions.setter
1991 1993 def revisions(self, val):
1992 1994 self._revisions = ':'.join(val)
1993 1995
1994 1996 @property
1995 1997 def org_ref_parts(self):
1996 1998 return self.org_ref.split(':')
1997 1999
1998 2000 @property
1999 2001 def other_ref_parts(self):
2000 2002 return self.other_ref.split(':')
2001 2003
2002 2004 author = relationship('User', lazy='joined')
2003 2005 reviewers = relationship('PullRequestReviewers',
2004 2006 cascade="all, delete, delete-orphan")
2005 2007 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
2006 2008 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
2007 2009 statuses = relationship('ChangesetStatus')
2008 2010 comments = relationship('ChangesetComment',
2009 2011 cascade="all, delete, delete-orphan")
2010 2012
2011 2013 def is_closed(self):
2012 2014 return self.status == self.STATUS_CLOSED
2013 2015
2014 2016 @property
2015 2017 def last_review_status(self):
2016 2018 return self.statuses[-1].status if self.statuses else ''
2017 2019
2018 2020 def __json__(self):
2019 2021 return dict(
2020 2022 revisions=self.revisions
2021 2023 )
2022 2024
2023 2025
2024 2026 class PullRequestReviewers(Base, BaseModel):
2025 2027 __tablename__ = 'pull_request_reviewers'
2026 2028 __table_args__ = (
2027 2029 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2028 2030 'mysql_charset': 'utf8'},
2029 2031 )
2030 2032
2031 2033 def __init__(self, user=None, pull_request=None):
2032 2034 self.user = user
2033 2035 self.pull_request = pull_request
2034 2036
2035 2037 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
2036 2038 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
2037 2039 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
2038 2040
2039 2041 user = relationship('User')
2040 2042 pull_request = relationship('PullRequest')
2041 2043
2042 2044
2043 2045 class Notification(Base, BaseModel):
2044 2046 __tablename__ = 'notifications'
2045 2047 __table_args__ = (
2046 2048 Index('notification_type_idx', 'type'),
2047 2049 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2048 2050 'mysql_charset': 'utf8'},
2049 2051 )
2050 2052
2051 2053 TYPE_CHANGESET_COMMENT = u'cs_comment'
2052 2054 TYPE_MESSAGE = u'message'
2053 2055 TYPE_MENTION = u'mention'
2054 2056 TYPE_REGISTRATION = u'registration'
2055 2057 TYPE_PULL_REQUEST = u'pull_request'
2056 2058 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
2057 2059
2058 2060 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
2059 2061 subject = Column('subject', Unicode(512), nullable=True)
2060 2062 body = Column('body', UnicodeText(50000), nullable=True)
2061 2063 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
2062 2064 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2063 2065 type_ = Column('type', Unicode(256))
2064 2066
2065 2067 created_by_user = relationship('User')
2066 2068 notifications_to_users = relationship('UserNotification', lazy='joined',
2067 2069 cascade="all, delete, delete-orphan")
2068 2070
2069 2071 @property
2070 2072 def recipients(self):
2071 2073 return [x.user for x in UserNotification.query()\
2072 2074 .filter(UserNotification.notification == self)\
2073 2075 .order_by(UserNotification.user_id.asc()).all()]
2074 2076
2075 2077 @classmethod
2076 2078 def create(cls, created_by, subject, body, recipients, type_=None):
2077 2079 if type_ is None:
2078 2080 type_ = Notification.TYPE_MESSAGE
2079 2081
2080 2082 notification = cls()
2081 2083 notification.created_by_user = created_by
2082 2084 notification.subject = subject
2083 2085 notification.body = body
2084 2086 notification.type_ = type_
2085 2087 notification.created_on = datetime.datetime.now()
2086 2088
2087 2089 for u in recipients:
2088 2090 assoc = UserNotification()
2089 2091 assoc.notification = notification
2090 2092 u.notifications.append(assoc)
2091 2093 Session().add(notification)
2092 2094 return notification
2093 2095
2094 2096 @property
2095 2097 def description(self):
2096 2098 from rhodecode.model.notification import NotificationModel
2097 2099 return NotificationModel().make_description(self)
2098 2100
2099 2101
2100 2102 class UserNotification(Base, BaseModel):
2101 2103 __tablename__ = 'user_to_notification'
2102 2104 __table_args__ = (
2103 2105 UniqueConstraint('user_id', 'notification_id'),
2104 2106 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2105 2107 'mysql_charset': 'utf8'}
2106 2108 )
2107 2109 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
2108 2110 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2109 2111 read = Column('read', Boolean, default=False)
2110 2112 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
2111 2113
2112 2114 user = relationship('User', lazy="joined")
2113 2115 notification = relationship('Notification', lazy="joined",
2114 2116 order_by=lambda: Notification.created_on.desc(),)
2115 2117
2116 2118 def mark_as_read(self):
2117 2119 self.read = True
2118 2120 Session().add(self)
2119 2121
2120 2122
2121 2123 class DbMigrateVersion(Base, BaseModel):
2122 2124 __tablename__ = 'db_migrate_version'
2123 2125 __table_args__ = (
2124 2126 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2125 2127 'mysql_charset': 'utf8'},
2126 2128 )
2127 2129 repository_id = Column('repository_id', String(250), primary_key=True)
2128 2130 repository_path = Column('repository_path', Text)
2129 2131 version = Column('version', Integer)
@@ -1,782 +1,803 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.user
4 4 ~~~~~~~~~~~~~~~~~~~~
5 5
6 6 users model for RhodeCode
7 7
8 8 :created_on: Apr 9, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import itertools
29 29 import collections
30 30 from pylons import url
31 31 from pylons.i18n.translation import _
32 32
33 33 from sqlalchemy.exc import DatabaseError
34 34 from sqlalchemy.orm import joinedload
35 35
36 36 from rhodecode.lib.utils2 import safe_unicode, generate_api_key
37 37 from rhodecode.lib.caching_query import FromCache
38 38 from rhodecode.model import BaseModel
39 39 from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
40 40 UserToPerm, UserGroupRepoToPerm, UserGroupToPerm, UserGroupMember, \
41 41 Notification, RepoGroup, UserRepoGroupToPerm, UserGroupRepoGroupToPerm, \
42 UserEmailMap, UserIpMap
42 UserEmailMap, UserIpMap, UserGroupUserGroupToPerm, UserGroup
43 43 from rhodecode.lib.exceptions import DefaultUserException, \
44 44 UserOwnsReposException
45 45 from rhodecode.model.meta import Session
46 46
47 47
48 48 log = logging.getLogger(__name__)
49 49
50 50 PERM_WEIGHTS = Permission.PERM_WEIGHTS
51 51
52 52
53 53 class UserModel(BaseModel):
54 54 cls = User
55 55
56 56 def get(self, user_id, cache=False):
57 57 user = self.sa.query(User)
58 58 if cache:
59 59 user = user.options(FromCache("sql_cache_short",
60 60 "get_user_%s" % user_id))
61 61 return user.get(user_id)
62 62
63 63 def get_user(self, user):
64 64 return self._get_user(user)
65 65
66 66 def get_by_username(self, username, cache=False, case_insensitive=False):
67 67
68 68 if case_insensitive:
69 69 user = self.sa.query(User).filter(User.username.ilike(username))
70 70 else:
71 71 user = self.sa.query(User)\
72 72 .filter(User.username == username)
73 73 if cache:
74 74 user = user.options(FromCache("sql_cache_short",
75 75 "get_user_%s" % username))
76 76 return user.scalar()
77 77
78 78 def get_by_email(self, email, cache=False, case_insensitive=False):
79 79 return User.get_by_email(email, case_insensitive, cache)
80 80
81 81 def get_by_api_key(self, api_key, cache=False):
82 82 return User.get_by_api_key(api_key, cache)
83 83
84 84 def create(self, form_data):
85 85 from rhodecode.lib.auth import get_crypt_password
86 86 try:
87 87 new_user = User()
88 88 for k, v in form_data.items():
89 89 if k == 'password':
90 90 v = get_crypt_password(v)
91 91 if k == 'firstname':
92 92 k = 'name'
93 93 setattr(new_user, k, v)
94 94
95 95 new_user.api_key = generate_api_key(form_data['username'])
96 96 self.sa.add(new_user)
97 97 return new_user
98 98 except Exception:
99 99 log.error(traceback.format_exc())
100 100 raise
101 101
102 102 def create_or_update(self, username, password, email, firstname='',
103 103 lastname='', active=True, admin=False, ldap_dn=None):
104 104 """
105 105 Creates a new instance if not found, or updates current one
106 106
107 107 :param username:
108 108 :param password:
109 109 :param email:
110 110 :param active:
111 111 :param firstname:
112 112 :param lastname:
113 113 :param active:
114 114 :param admin:
115 115 :param ldap_dn:
116 116 """
117 117
118 118 from rhodecode.lib.auth import get_crypt_password
119 119
120 120 log.debug('Checking for %s account in RhodeCode database' % username)
121 121 user = User.get_by_username(username, case_insensitive=True)
122 122 if user is None:
123 123 log.debug('creating new user %s' % username)
124 124 new_user = User()
125 125 edit = False
126 126 else:
127 127 log.debug('updating user %s' % username)
128 128 new_user = user
129 129 edit = True
130 130
131 131 try:
132 132 new_user.username = username
133 133 new_user.admin = admin
134 134 # set password only if creating an user or password is changed
135 135 if not edit or user.password != password:
136 136 new_user.password = get_crypt_password(password)
137 137 new_user.api_key = generate_api_key(username)
138 138 new_user.email = email
139 139 new_user.active = active
140 140 new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None
141 141 new_user.name = firstname
142 142 new_user.lastname = lastname
143 143 self.sa.add(new_user)
144 144 return new_user
145 145 except (DatabaseError,):
146 146 log.error(traceback.format_exc())
147 147 raise
148 148
149 149 def create_for_container_auth(self, username, attrs):
150 150 """
151 151 Creates the given user if it's not already in the database
152 152
153 153 :param username:
154 154 :param attrs:
155 155 """
156 156 if self.get_by_username(username, case_insensitive=True) is None:
157 157
158 158 # autogenerate email for container account without one
159 159 generate_email = lambda usr: '%s@container_auth.account' % usr
160 160
161 161 try:
162 162 new_user = User()
163 163 new_user.username = username
164 164 new_user.password = None
165 165 new_user.api_key = generate_api_key(username)
166 166 new_user.email = attrs['email']
167 167 new_user.active = attrs.get('active', True)
168 168 new_user.name = attrs['name'] or generate_email(username)
169 169 new_user.lastname = attrs['lastname']
170 170
171 171 self.sa.add(new_user)
172 172 return new_user
173 173 except (DatabaseError,):
174 174 log.error(traceback.format_exc())
175 175 self.sa.rollback()
176 176 raise
177 177 log.debug('User %s already exists. Skipping creation of account'
178 178 ' for container auth.', username)
179 179 return None
180 180
181 181 def create_ldap(self, username, password, user_dn, attrs):
182 182 """
183 183 Checks if user is in database, if not creates this user marked
184 184 as ldap user
185 185
186 186 :param username:
187 187 :param password:
188 188 :param user_dn:
189 189 :param attrs:
190 190 """
191 191 from rhodecode.lib.auth import get_crypt_password
192 192 log.debug('Checking for such ldap account in RhodeCode database')
193 193 if self.get_by_username(username, case_insensitive=True) is None:
194 194
195 195 # autogenerate email for ldap account without one
196 196 generate_email = lambda usr: '%s@ldap.account' % usr
197 197
198 198 try:
199 199 new_user = User()
200 200 username = username.lower()
201 201 # add ldap account always lowercase
202 202 new_user.username = username
203 203 new_user.password = get_crypt_password(password)
204 204 new_user.api_key = generate_api_key(username)
205 205 new_user.email = attrs['email'] or generate_email(username)
206 206 new_user.active = attrs.get('active', True)
207 207 new_user.ldap_dn = safe_unicode(user_dn)
208 208 new_user.name = attrs['name']
209 209 new_user.lastname = attrs['lastname']
210 210
211 211 self.sa.add(new_user)
212 212 return new_user
213 213 except (DatabaseError,):
214 214 log.error(traceback.format_exc())
215 215 self.sa.rollback()
216 216 raise
217 217 log.debug('this %s user exists skipping creation of ldap account',
218 218 username)
219 219 return None
220 220
221 221 def create_registration(self, form_data):
222 222 from rhodecode.model.notification import NotificationModel
223 223
224 224 try:
225 225 form_data['admin'] = False
226 226 new_user = self.create(form_data)
227 227
228 228 self.sa.add(new_user)
229 229 self.sa.flush()
230 230
231 231 # notification to admins
232 232 subject = _('New user registration')
233 233 body = ('New user registration\n'
234 234 '---------------------\n'
235 235 '- Username: %s\n'
236 236 '- Full Name: %s\n'
237 237 '- Email: %s\n')
238 238 body = body % (new_user.username, new_user.full_name,
239 239 new_user.email)
240 240 edit_url = url('edit_user', id=new_user.user_id, qualified=True)
241 241 kw = {'registered_user_url': edit_url}
242 242 NotificationModel().create(created_by=new_user, subject=subject,
243 243 body=body, recipients=None,
244 244 type_=Notification.TYPE_REGISTRATION,
245 245 email_kwargs=kw)
246 246
247 247 except Exception:
248 248 log.error(traceback.format_exc())
249 249 raise
250 250
251 251 def update(self, user_id, form_data, skip_attrs=[]):
252 252 from rhodecode.lib.auth import get_crypt_password
253 253 try:
254 254 user = self.get(user_id, cache=False)
255 255 if user.username == 'default':
256 256 raise DefaultUserException(
257 257 _("You can't Edit this user since it's"
258 258 " crucial for entire application"))
259 259
260 260 for k, v in form_data.items():
261 261 if k in skip_attrs:
262 262 continue
263 263 if k == 'new_password' and v:
264 264 user.password = get_crypt_password(v)
265 265 user.api_key = generate_api_key(user.username)
266 266 else:
267 267 if k == 'firstname':
268 268 k = 'name'
269 269 setattr(user, k, v)
270 270 self.sa.add(user)
271 271 except Exception:
272 272 log.error(traceback.format_exc())
273 273 raise
274 274
275 275 def update_user(self, user, **kwargs):
276 276 from rhodecode.lib.auth import get_crypt_password
277 277 try:
278 278 user = self._get_user(user)
279 279 if user.username == 'default':
280 280 raise DefaultUserException(
281 281 _("You can't Edit this user since it's"
282 282 " crucial for entire application")
283 283 )
284 284
285 285 for k, v in kwargs.items():
286 286 if k == 'password' and v:
287 287 v = get_crypt_password(v)
288 288 user.api_key = generate_api_key(user.username)
289 289
290 290 setattr(user, k, v)
291 291 self.sa.add(user)
292 292 return user
293 293 except Exception:
294 294 log.error(traceback.format_exc())
295 295 raise
296 296
297 297 def delete(self, user):
298 298 user = self._get_user(user)
299 299
300 300 try:
301 301 if user.username == 'default':
302 302 raise DefaultUserException(
303 303 _(u"You can't remove this user since it's"
304 304 " crucial for entire application")
305 305 )
306 306 if user.repositories:
307 307 repos = [x.repo_name for x in user.repositories]
308 308 raise UserOwnsReposException(
309 309 _(u'user "%s" still owns %s repositories and cannot be '
310 310 'removed. Switch owners or remove those repositories. %s')
311 311 % (user.username, len(repos), ', '.join(repos))
312 312 )
313 313 self.sa.delete(user)
314 314 except Exception:
315 315 log.error(traceback.format_exc())
316 316 raise
317 317
318 318 def reset_password_link(self, data):
319 319 from rhodecode.lib.celerylib import tasks, run_task
320 320 from rhodecode.model.notification import EmailNotificationModel
321 321 user_email = data['email']
322 322 try:
323 323 user = User.get_by_email(user_email)
324 324 if user:
325 325 log.debug('password reset user found %s' % user)
326 326 link = url('reset_password_confirmation', key=user.api_key,
327 327 qualified=True)
328 328 reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
329 329 body = EmailNotificationModel().get_email_tmpl(reg_type,
330 330 **{'user': user.short_contact,
331 331 'reset_url': link})
332 332 log.debug('sending email')
333 333 run_task(tasks.send_email, user_email,
334 334 _("Password reset link"), body, body)
335 335 log.info('send new password mail to %s' % user_email)
336 336 else:
337 337 log.debug("password reset email %s not found" % user_email)
338 338 except Exception:
339 339 log.error(traceback.format_exc())
340 340 return False
341 341
342 342 return True
343 343
344 344 def reset_password(self, data):
345 345 from rhodecode.lib.celerylib import tasks, run_task
346 346 from rhodecode.lib import auth
347 347 user_email = data['email']
348 348 try:
349 349 try:
350 350 user = User.get_by_email(user_email)
351 351 new_passwd = auth.PasswordGenerator().gen_password(8,
352 352 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
353 353 if user:
354 354 user.password = auth.get_crypt_password(new_passwd)
355 355 user.api_key = auth.generate_api_key(user.username)
356 356 Session().add(user)
357 357 Session().commit()
358 358 log.info('change password for %s' % user_email)
359 359 if new_passwd is None:
360 360 raise Exception('unable to generate new password')
361 361 except Exception:
362 362 log.error(traceback.format_exc())
363 363 Session().rollback()
364 364
365 365 run_task(tasks.send_email, user_email,
366 366 _('Your new password'),
367 367 _('Your new RhodeCode password:%s') % (new_passwd))
368 368 log.info('send new password mail to %s' % user_email)
369 369
370 370 except Exception:
371 371 log.error('Failed to update user password')
372 372 log.error(traceback.format_exc())
373 373
374 374 return True
375 375
376 376 def fill_data(self, auth_user, user_id=None, api_key=None):
377 377 """
378 378 Fetches auth_user by user_id,or api_key if present.
379 379 Fills auth_user attributes with those taken from database.
380 380 Additionally set's is_authenitated if lookup fails
381 381 present in database
382 382
383 383 :param auth_user: instance of user to set attributes
384 384 :param user_id: user id to fetch by
385 385 :param api_key: api key to fetch by
386 386 """
387 387 if user_id is None and api_key is None:
388 388 raise Exception('You need to pass user_id or api_key')
389 389
390 390 try:
391 391 if api_key:
392 392 dbuser = self.get_by_api_key(api_key)
393 393 else:
394 394 dbuser = self.get(user_id)
395 395
396 396 if dbuser is not None and dbuser.active:
397 397 log.debug('filling %s data' % dbuser)
398 398 for k, v in dbuser.get_dict().items():
399 399 setattr(auth_user, k, v)
400 400 else:
401 401 return False
402 402
403 403 except Exception:
404 404 log.error(traceback.format_exc())
405 405 auth_user.is_authenticated = False
406 406 return False
407 407
408 408 return True
409 409
410 410 def fill_perms(self, user, explicit=True, algo='higherwin'):
411 411 """
412 412 Fills user permission attribute with permissions taken from database
413 413 works for permissions given for repositories, and for permissions that
414 414 are granted to groups
415 415
416 416 :param user: user instance to fill his perms
417 417 :param explicit: In case there are permissions both for user and a group
418 418 that user is part of, explicit flag will defiine if user will
419 419 explicitly override permissions from group, if it's False it will
420 420 make decision based on the algo
421 421 :param algo: algorithm to decide what permission should be choose if
422 422 it's multiple defined, eg user in two different groups. It also
423 423 decides if explicit flag is turned off how to specify the permission
424 424 for case when user is in a group + have defined separate permission
425 425 """
426 426 RK = 'repositories'
427 427 GK = 'repositories_groups'
428 428 UK = 'user_groups'
429 429 GLOBAL = 'global'
430 430 user.permissions[RK] = {}
431 431 user.permissions[GK] = {}
432 432 user.permissions[UK] = {}
433 433 user.permissions[GLOBAL] = set()
434 434
435 435 def _choose_perm(new_perm, cur_perm):
436 436 new_perm_val = PERM_WEIGHTS[new_perm]
437 437 cur_perm_val = PERM_WEIGHTS[cur_perm]
438 438 if algo == 'higherwin':
439 439 if new_perm_val > cur_perm_val:
440 440 return new_perm
441 441 return cur_perm
442 442 elif algo == 'lowerwin':
443 443 if new_perm_val < cur_perm_val:
444 444 return new_perm
445 445 return cur_perm
446 446
447 447 #======================================================================
448 448 # fetch default permissions
449 449 #======================================================================
450 450 default_user = User.get_by_username('default', cache=True)
451 451 default_user_id = default_user.user_id
452 452
453 453 default_repo_perms = Permission.get_default_perms(default_user_id)
454 454 default_repo_groups_perms = Permission.get_default_group_perms(default_user_id)
455 455 default_user_group_perms = Permission.get_default_user_group_perms(default_user_id)
456 456
457 457 if user.is_admin:
458 458 #==================================================================
459 459 # admin user have all default rights for repositories
460 460 # and groups set to admin
461 461 #==================================================================
462 462 user.permissions[GLOBAL].add('hg.admin')
463 463
464 464 # repositories
465 465 for perm in default_repo_perms:
466 466 r_k = perm.UserRepoToPerm.repository.repo_name
467 467 p = 'repository.admin'
468 468 user.permissions[RK][r_k] = p
469 469
470 470 # repository groups
471 471 for perm in default_repo_groups_perms:
472 472 rg_k = perm.UserRepoGroupToPerm.group.group_name
473 473 p = 'group.admin'
474 474 user.permissions[GK][rg_k] = p
475 475
476 476 # user groups
477 477 for perm in default_user_group_perms:
478 478 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
479 479 p = 'usergroup.admin'
480 480 user.permissions[UK][u_k] = p
481 481 return user
482 482
483 483 #==================================================================
484 484 # SET DEFAULTS GLOBAL, REPOS, REPOSITORY GROUPS
485 485 #==================================================================
486 486 uid = user.user_id
487 487
488 488 # default global permissions taken fron the default user
489 489 default_global_perms = self.sa.query(UserToPerm)\
490 490 .filter(UserToPerm.user_id == default_user_id)
491 491
492 492 for perm in default_global_perms:
493 493 user.permissions[GLOBAL].add(perm.permission.permission_name)
494 494
495 495 # defaults for repositories, taken from default user
496 496 for perm in default_repo_perms:
497 497 r_k = perm.UserRepoToPerm.repository.repo_name
498 498 if perm.Repository.private and not (perm.Repository.user_id == uid):
499 499 # disable defaults for private repos,
500 500 p = 'repository.none'
501 501 elif perm.Repository.user_id == uid:
502 502 # set admin if owner
503 503 p = 'repository.admin'
504 504 else:
505 505 p = perm.Permission.permission_name
506 506
507 507 user.permissions[RK][r_k] = p
508 508
509 509 # defaults for repository groups taken from default user permission
510 510 # on given group
511 511 for perm in default_repo_groups_perms:
512 512 rg_k = perm.UserRepoGroupToPerm.group.group_name
513 513 p = perm.Permission.permission_name
514 514 user.permissions[GK][rg_k] = p
515 515
516 516 # defaults for user groups taken from default user permission
517 517 # on given user group
518 518 for perm in default_user_group_perms:
519 519 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
520 520 p = perm.Permission.permission_name
521 521 user.permissions[UK][u_k] = p
522 522
523 523 #======================================================================
524 524 # !! OVERRIDE GLOBALS !! with user permissions if any found
525 525 #======================================================================
526 526 # those can be configured from groups or users explicitly
527 527 _configurable = set([
528 528 'hg.fork.none', 'hg.fork.repository',
529 529 'hg.create.none', 'hg.create.repository',
530 530 'hg.usergroup.create.false', 'hg.usergroup.create.true'
531 531 ])
532 532
533 533 # USER GROUPS comes first
534 534 # user group global permissions
535 535 user_perms_from_users_groups = self.sa.query(UserGroupToPerm)\
536 536 .options(joinedload(UserGroupToPerm.permission))\
537 537 .join((UserGroupMember, UserGroupToPerm.users_group_id ==
538 538 UserGroupMember.users_group_id))\
539 539 .filter(UserGroupMember.user_id == uid)\
540 540 .order_by(UserGroupToPerm.users_group_id)\
541 541 .all()
542 542 #need to group here by groups since user can be in more than one group
543 543 _grouped = [[x, list(y)] for x, y in
544 544 itertools.groupby(user_perms_from_users_groups,
545 545 lambda x:x.users_group)]
546 546 for gr, perms in _grouped:
547 547 # since user can be in multiple groups iterate over them and
548 548 # select the lowest permissions first (more explicit)
549 549 ##TODO: do this^^
550 550 if not gr.inherit_default_permissions:
551 551 # NEED TO IGNORE all configurable permissions and
552 552 # replace them with explicitly set
553 553 user.permissions[GLOBAL] = user.permissions[GLOBAL]\
554 554 .difference(_configurable)
555 555 for perm in perms:
556 556 user.permissions[GLOBAL].add(perm.permission.permission_name)
557 557
558 558 # user specific global permissions
559 559 user_perms = self.sa.query(UserToPerm)\
560 560 .options(joinedload(UserToPerm.permission))\
561 561 .filter(UserToPerm.user_id == uid).all()
562 562
563 563 if not user.inherit_default_permissions:
564 564 # NEED TO IGNORE all configurable permissions and
565 565 # replace them with explicitly set
566 566 user.permissions[GLOBAL] = user.permissions[GLOBAL]\
567 567 .difference(_configurable)
568 568
569 569 for perm in user_perms:
570 570 user.permissions[GLOBAL].add(perm.permission.permission_name)
571 571 ## END GLOBAL PERMISSIONS
572 572
573
574 573 #======================================================================
575 574 # !! PERMISSIONS FOR REPOSITORIES !!
576 575 #======================================================================
577 576 #======================================================================
578 577 # check if user is part of user groups for this repository and
579 578 # fill in his permission from it. _choose_perm decides of which
580 579 # permission should be selected based on selected method
581 580 #======================================================================
582 581
583 582 # user group for repositories permissions
584 583 user_repo_perms_from_users_groups = \
585 584 self.sa.query(UserGroupRepoToPerm, Permission, Repository,)\
586 585 .join((Repository, UserGroupRepoToPerm.repository_id ==
587 586 Repository.repo_id))\
588 587 .join((Permission, UserGroupRepoToPerm.permission_id ==
589 588 Permission.permission_id))\
590 589 .join((UserGroupMember, UserGroupRepoToPerm.users_group_id ==
591 590 UserGroupMember.users_group_id))\
592 591 .filter(UserGroupMember.user_id == uid)\
593 592 .all()
594 593
595 594 multiple_counter = collections.defaultdict(int)
596 595 for perm in user_repo_perms_from_users_groups:
597 596 r_k = perm.UserGroupRepoToPerm.repository.repo_name
598 597 multiple_counter[r_k] += 1
599 598 p = perm.Permission.permission_name
600 599 cur_perm = user.permissions[RK][r_k]
601 600
602 601 if perm.Repository.user_id == uid:
603 602 # set admin if owner
604 603 p = 'repository.admin'
605 604 else:
606 605 if multiple_counter[r_k] > 1:
607 606 p = _choose_perm(p, cur_perm)
608 607 user.permissions[RK][r_k] = p
609 608
610 609 # user explicit permissions for repositories, overrides any specified
611 610 # by the group permission
612 611 user_repo_perms = Permission.get_default_perms(uid)
613 612 for perm in user_repo_perms:
614 613 r_k = perm.UserRepoToPerm.repository.repo_name
615 614 cur_perm = user.permissions[RK][r_k]
616 615 # set admin if owner
617 616 if perm.Repository.user_id == uid:
618 617 p = 'repository.admin'
619 618 else:
620 619 p = perm.Permission.permission_name
621 620 if not explicit:
622 621 p = _choose_perm(p, cur_perm)
623 622 user.permissions[RK][r_k] = p
624 623
625 624 #======================================================================
626 625 # !! PERMISSIONS FOR REPOSITORY GROUPS !!
627 626 #======================================================================
628 627 #======================================================================
629 628 # check if user is part of user groups for this repository groups and
630 629 # fill in his permission from it. _choose_perm decides of which
631 630 # permission should be selected based on selected method
632 631 #======================================================================
633 632 # user group for repo groups permissions
634 633 user_repo_group_perms_from_users_groups = \
635 634 self.sa.query(UserGroupRepoGroupToPerm, Permission, RepoGroup)\
636 635 .join((RepoGroup, UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id))\
637 636 .join((Permission, UserGroupRepoGroupToPerm.permission_id
638 637 == Permission.permission_id))\
639 638 .join((UserGroupMember, UserGroupRepoGroupToPerm.users_group_id
640 639 == UserGroupMember.users_group_id))\
641 640 .filter(UserGroupMember.user_id == uid)\
642 641 .all()
643 642
644 643 multiple_counter = collections.defaultdict(int)
645 644 for perm in user_repo_group_perms_from_users_groups:
646 645 g_k = perm.UserGroupRepoGroupToPerm.group.group_name
647 646 multiple_counter[g_k] += 1
648 647 p = perm.Permission.permission_name
649 648 cur_perm = user.permissions[GK][g_k]
650 649 if multiple_counter[g_k] > 1:
651 650 p = _choose_perm(p, cur_perm)
652 651 user.permissions[GK][g_k] = p
653 652
654 653 # user explicit permissions for repository groups
655 654 user_repo_groups_perms = Permission.get_default_group_perms(uid)
656 655 for perm in user_repo_groups_perms:
657 656 rg_k = perm.UserRepoGroupToPerm.group.group_name
658 657 p = perm.Permission.permission_name
659 658 cur_perm = user.permissions[GK][rg_k]
660 659 if not explicit:
661 660 p = _choose_perm(p, cur_perm)
662 661 user.permissions[GK][rg_k] = p
663 662
664 663 #======================================================================
665 664 # !! PERMISSIONS FOR USER GROUPS !!
666 665 #======================================================================
666 # user group for user group permissions
667 user_group_user_groups_perms = \
668 self.sa.query(UserGroupUserGroupToPerm, Permission, UserGroup)\
669 .join((UserGroup, UserGroupUserGroupToPerm.target_user_group_id
670 == UserGroup.users_group_id))\
671 .join((Permission, UserGroupUserGroupToPerm.permission_id
672 == Permission.permission_id))\
673 .join((UserGroupMember, UserGroupUserGroupToPerm.user_group_id
674 == UserGroupMember.users_group_id))\
675 .filter(UserGroupMember.user_id == uid)\
676 .all()
677
678 multiple_counter = collections.defaultdict(int)
679 for perm in user_group_user_groups_perms:
680 g_k = perm.UserGroupUserGroupToPerm.target_user_group.users_group_name
681 multiple_counter[g_k] += 1
682 p = perm.Permission.permission_name
683 cur_perm = user.permissions[UK][g_k]
684 if multiple_counter[g_k] > 1:
685 p = _choose_perm(p, cur_perm)
686 user.permissions[UK][g_k] = p
687
667 688 #user explicit permission for user groups
668 689 user_user_groups_perms = Permission.get_default_user_group_perms(uid)
669 690 for perm in user_user_groups_perms:
670 691 u_k = perm.UserUserGroupToPerm.user_group.users_group_name
671 692 p = perm.Permission.permission_name
672 693 cur_perm = user.permissions[UK][u_k]
673 694 if not explicit:
674 695 p = _choose_perm(p, cur_perm)
675 696 user.permissions[UK][u_k] = p
676 697
677 698 return user
678 699
679 700 def has_perm(self, user, perm):
680 701 perm = self._get_perm(perm)
681 702 user = self._get_user(user)
682 703
683 704 return UserToPerm.query().filter(UserToPerm.user == user)\
684 705 .filter(UserToPerm.permission == perm).scalar() is not None
685 706
686 707 def grant_perm(self, user, perm):
687 708 """
688 709 Grant user global permissions
689 710
690 711 :param user:
691 712 :param perm:
692 713 """
693 714 user = self._get_user(user)
694 715 perm = self._get_perm(perm)
695 716 # if this permission is already granted skip it
696 717 _perm = UserToPerm.query()\
697 718 .filter(UserToPerm.user == user)\
698 719 .filter(UserToPerm.permission == perm)\
699 720 .scalar()
700 721 if _perm:
701 722 return
702 723 new = UserToPerm()
703 724 new.user = user
704 725 new.permission = perm
705 726 self.sa.add(new)
706 727
707 728 def revoke_perm(self, user, perm):
708 729 """
709 730 Revoke users global permissions
710 731
711 732 :param user:
712 733 :param perm:
713 734 """
714 735 user = self._get_user(user)
715 736 perm = self._get_perm(perm)
716 737
717 738 obj = UserToPerm.query()\
718 739 .filter(UserToPerm.user == user)\
719 740 .filter(UserToPerm.permission == perm)\
720 741 .scalar()
721 742 if obj:
722 743 self.sa.delete(obj)
723 744
724 745 def add_extra_email(self, user, email):
725 746 """
726 747 Adds email address to UserEmailMap
727 748
728 749 :param user:
729 750 :param email:
730 751 """
731 752 from rhodecode.model import forms
732 753 form = forms.UserExtraEmailForm()()
733 754 data = form.to_python(dict(email=email))
734 755 user = self._get_user(user)
735 756
736 757 obj = UserEmailMap()
737 758 obj.user = user
738 759 obj.email = data['email']
739 760 self.sa.add(obj)
740 761 return obj
741 762
742 763 def delete_extra_email(self, user, email_id):
743 764 """
744 765 Removes email address from UserEmailMap
745 766
746 767 :param user:
747 768 :param email_id:
748 769 """
749 770 user = self._get_user(user)
750 771 obj = UserEmailMap.query().get(email_id)
751 772 if obj:
752 773 self.sa.delete(obj)
753 774
754 775 def add_extra_ip(self, user, ip):
755 776 """
756 777 Adds ip address to UserIpMap
757 778
758 779 :param user:
759 780 :param ip:
760 781 """
761 782 from rhodecode.model import forms
762 783 form = forms.UserExtraIpForm()()
763 784 data = form.to_python(dict(ip=ip))
764 785 user = self._get_user(user)
765 786
766 787 obj = UserIpMap()
767 788 obj.user = user
768 789 obj.ip_addr = data['ip']
769 790 self.sa.add(obj)
770 791 return obj
771 792
772 793 def delete_extra_ip(self, user, ip_id):
773 794 """
774 795 Removes ip address from UserIpMap
775 796
776 797 :param user:
777 798 :param ip_id:
778 799 """
779 800 user = self._get_user(user)
780 801 obj = UserIpMap.query().get(ip_id)
781 802 if obj:
782 803 self.sa.delete(obj)
@@ -1,299 +1,343 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.users_group
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 user group model for RhodeCode
7 7
8 8 :created_on: Oct 1, 2011
9 9 :author: nvinot
10 10 :copyright: (C) 2011-2011 Nicolas Vinot <aeris@imirhil.fr>
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 27 import logging
28 28 import traceback
29 29
30 30 from rhodecode.model import BaseModel
31 31 from rhodecode.model.db import UserGroupMember, UserGroup,\
32 UserGroupRepoToPerm, Permission, UserGroupToPerm, User, UserUserGroupToPerm
33 from rhodecode.lib.exceptions import UserGroupsAssignedException
32 UserGroupRepoToPerm, Permission, UserGroupToPerm, User, UserUserGroupToPerm,\
33 UserGroupUserGroupToPerm
34 from rhodecode.lib.exceptions import UserGroupsAssignedException,\
35 RepoGroupAssignmentError
34 36
35 37 log = logging.getLogger(__name__)
36 38
37 39
38 40 class UserGroupModel(BaseModel):
39 41
40 42 cls = UserGroup
41 43
42 44 def _get_user_group(self, users_group):
43 45 return self._get_instance(UserGroup, users_group,
44 46 callback=UserGroup.get_by_group_name)
45 47
46 48 def _create_default_perms(self, user_group):
47 49 # create default permission
48 50 default_perm = 'usergroup.read'
49 51 def_user = User.get_default_user()
50 52 for p in def_user.user_perms:
51 53 if p.permission.permission_name.startswith('usergroup.'):
52 54 default_perm = p.permission.permission_name
53 55 break
54 56
55 57 user_group_to_perm = UserUserGroupToPerm()
56 58 user_group_to_perm.permission = Permission.get_by_key(default_perm)
57 59
58 60 user_group_to_perm.user_group = user_group
59 61 user_group_to_perm.user_id = def_user.user_id
60 62 return user_group_to_perm
61 63
62 64 def _update_permissions(self, user_group, perms_new=None,
63 65 perms_updates=None):
64 66 if not perms_new:
65 67 perms_new = []
66 68 if not perms_updates:
67 69 perms_updates = []
68 70
69 71 # update permissions
70 72 for member, perm, member_type in perms_updates:
71 73 if member_type == 'user':
72 74 # this updates existing one
73 75 self.grant_user_permission(
74 76 user_group=user_group, user=member, perm=perm
75 77 )
76 78 else:
77 79 self.grant_users_group_permission(
78 user_group=user_group, group_name=member, perm=perm
80 target_user_group=user_group, user_group=member, perm=perm
79 81 )
80 82 # set new permissions
81 83 for member, perm, member_type in perms_new:
82 84 if member_type == 'user':
83 85 self.grant_user_permission(
84 86 user_group=user_group, user=member, perm=perm
85 87 )
86 88 else:
87 89 self.grant_users_group_permission(
88 user_group=user_group, group_name=member, perm=perm
90 target_user_group=user_group, user_group=member, perm=perm
89 91 )
90 92
91 93 def get(self, users_group_id, cache=False):
92 94 return UserGroup.get(users_group_id)
93 95
94 96 def get_group(self, users_group):
95 97 return self._get_user_group(users_group)
96 98
97 99 def get_by_name(self, name, cache=False, case_insensitive=False):
98 100 return UserGroup.get_by_group_name(name, cache, case_insensitive)
99 101
100 102 def create(self, name, owner, active=True):
101 103 try:
102 104 new_user_group = UserGroup()
103 105 new_user_group.user = self._get_user(owner)
104 106 new_user_group.users_group_name = name
105 107 new_user_group.users_group_active = active
106 108 self.sa.add(new_user_group)
107 109 perm_obj = self._create_default_perms(new_user_group)
108 110 self.sa.add(perm_obj)
109 111
110 112 self.grant_user_permission(user_group=new_user_group,
111 113 user=owner, perm='usergroup.admin')
112 114
113 115 return new_user_group
114 116 except Exception:
115 117 log.error(traceback.format_exc())
116 118 raise
117 119
118 120 def update(self, users_group, form_data):
119 121
120 122 try:
121 123 users_group = self._get_user_group(users_group)
122 124
123 125 for k, v in form_data.items():
124 126 if k == 'users_group_members':
125 127 users_group.members = []
126 128 self.sa.flush()
127 129 members_list = []
128 130 if v:
129 131 v = [v] if isinstance(v, basestring) else v
130 132 for u_id in set(v):
131 133 member = UserGroupMember(users_group.users_group_id, u_id)
132 134 members_list.append(member)
133 135 setattr(users_group, 'members', members_list)
134 136 setattr(users_group, k, v)
135 137
136 138 self.sa.add(users_group)
137 139 except Exception:
138 140 log.error(traceback.format_exc())
139 141 raise
140 142
141 143 def delete(self, users_group, force=False):
142 144 """
143 145 Deletes repository group, unless force flag is used
144 146 raises exception if there are members in that group, else deletes
145 147 group and users
146 148
147 149 :param users_group:
148 150 :param force:
149 151 """
150 152 try:
151 153 users_group = self._get_user_group(users_group)
152 154
153 155 # check if this group is not assigned to repo
154 156 assigned_groups = UserGroupRepoToPerm.query()\
155 157 .filter(UserGroupRepoToPerm.users_group == users_group).all()
156 158
157 159 if assigned_groups and not force:
158 160 raise UserGroupsAssignedException('RepoGroup assigned to %s' %
159 161 assigned_groups)
160 162
161 163 self.sa.delete(users_group)
162 164 except Exception:
163 165 log.error(traceback.format_exc())
164 166 raise
165 167
166 168 def add_user_to_group(self, users_group, user):
167 169 users_group = self._get_user_group(users_group)
168 170 user = self._get_user(user)
169 171
170 172 for m in users_group.members:
171 173 u = m.user
172 174 if u.user_id == user.user_id:
173 175 return True
174 176
175 177 try:
176 178 users_group_member = UserGroupMember()
177 179 users_group_member.user = user
178 180 users_group_member.users_group = users_group
179 181
180 182 users_group.members.append(users_group_member)
181 183 user.group_member.append(users_group_member)
182 184
183 185 self.sa.add(users_group_member)
184 186 return users_group_member
185 187 except Exception:
186 188 log.error(traceback.format_exc())
187 189 raise
188 190
189 191 def remove_user_from_group(self, users_group, user):
190 192 users_group = self._get_user_group(users_group)
191 193 user = self._get_user(user)
192 194
193 195 users_group_member = None
194 196 for m in users_group.members:
195 197 if m.user.user_id == user.user_id:
196 198 # Found this user's membership row
197 199 users_group_member = m
198 200 break
199 201
200 202 if users_group_member:
201 203 try:
202 204 self.sa.delete(users_group_member)
203 205 return True
204 206 except Exception:
205 207 log.error(traceback.format_exc())
206 208 raise
207 209 else:
208 210 # User isn't in that group
209 211 return False
210 212
211 213 def has_perm(self, users_group, perm):
212 214 users_group = self._get_user_group(users_group)
213 215 perm = self._get_perm(perm)
214 216
215 217 return UserGroupToPerm.query()\
216 218 .filter(UserGroupToPerm.users_group == users_group)\
217 219 .filter(UserGroupToPerm.permission == perm).scalar() is not None
218 220
219 221 def grant_perm(self, users_group, perm):
220 222 users_group = self._get_user_group(users_group)
221 223 perm = self._get_perm(perm)
222 224
223 225 # if this permission is already granted skip it
224 226 _perm = UserGroupToPerm.query()\
225 227 .filter(UserGroupToPerm.users_group == users_group)\
226 228 .filter(UserGroupToPerm.permission == perm)\
227 229 .scalar()
228 230 if _perm:
229 231 return
230 232
231 233 new = UserGroupToPerm()
232 234 new.users_group = users_group
233 235 new.permission = perm
234 236 self.sa.add(new)
235 237
236 238 def revoke_perm(self, users_group, perm):
237 239 users_group = self._get_user_group(users_group)
238 240 perm = self._get_perm(perm)
239 241
240 242 obj = UserGroupToPerm.query()\
241 243 .filter(UserGroupToPerm.users_group == users_group)\
242 244 .filter(UserGroupToPerm.permission == perm).scalar()
243 245 if obj:
244 246 self.sa.delete(obj)
245 247
246 248 def grant_user_permission(self, user_group, user, perm):
247 249 """
248 250 Grant permission for user on given user group, or update
249 251 existing one if found
250 252
251 253 :param user_group: Instance of UserGroup, users_group_id,
252 254 or users_group_name
253 255 :param user: Instance of User, user_id or username
254 256 :param perm: Instance of Permission, or permission_name
255 257 """
256 258
257 259 user_group = self._get_user_group(user_group)
258 260 user = self._get_user(user)
259 261 permission = self._get_perm(perm)
260 262
261 263 # check if we have that permission already
262 264 obj = self.sa.query(UserUserGroupToPerm)\
263 265 .filter(UserUserGroupToPerm.user == user)\
264 266 .filter(UserUserGroupToPerm.user_group == user_group)\
265 267 .scalar()
266 268 if obj is None:
267 269 # create new !
268 270 obj = UserUserGroupToPerm()
269 271 obj.user_group = user_group
270 272 obj.user = user
271 273 obj.permission = permission
272 274 self.sa.add(obj)
273 275 log.debug('Granted perm %s to %s on %s' % (perm, user, user_group))
274 276
275 277 def revoke_user_permission(self, user_group, user):
276 278 """
277 279 Revoke permission for user on given repository group
278 280
279 281 :param user_group: Instance of ReposGroup, repositories_group_id,
280 282 or repositories_group name
281 283 :param user: Instance of User, user_id or username
282 284 """
283 285
284 286 user_group = self._get_user_group(user_group)
285 287 user = self._get_user(user)
286 288
287 289 obj = self.sa.query(UserUserGroupToPerm)\
288 290 .filter(UserUserGroupToPerm.user == user)\
289 291 .filter(UserUserGroupToPerm.user_group == user_group)\
290 292 .scalar()
291 293 if obj:
292 294 self.sa.delete(obj)
293 295 log.debug('Revoked perm on %s on %s' % (user_group, user))
294 296
295 def grant_users_group_permission(self, user_group, group_name, perm):
296 raise NotImplementedError()
297 def grant_users_group_permission(self, target_user_group, user_group, perm):
298 """
299 Grant user group permission for given target_user_group
300
301 :param target_user_group:
302 :param user_group:
303 :param perm:
304 """
305 target_user_group = self._get_user_group(target_user_group)
306 user_group = self._get_user_group(user_group)
307 permission = self._get_perm(perm)
308 # forbid assigning same user group to itself
309 if target_user_group == user_group:
310 raise RepoGroupAssignmentError('target repo:%s cannot be '
311 'assigned to itself' % target_user_group)
297 312
298 def revoke_users_group_permission(self, user_group, group_name):
299 raise NotImplementedError()
313 # check if we have that permission already
314 obj = self.sa.query(UserGroupUserGroupToPerm)\
315 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
316 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
317 .scalar()
318 if obj is None:
319 # create new !
320 obj = UserGroupUserGroupToPerm()
321 obj.user_group = user_group
322 obj.target_user_group = target_user_group
323 obj.permission = permission
324 self.sa.add(obj)
325 log.debug('Granted perm %s to %s on %s' % (perm, target_user_group, user_group))
326
327 def revoke_users_group_permission(self, target_user_group, user_group):
328 """
329 Revoke user group permission for given target_user_group
330
331 :param target_user_group:
332 :param user_group:
333 """
334 target_user_group = self._get_user_group(target_user_group)
335 user_group = self._get_user_group(user_group)
336
337 obj = self.sa.query(UserGroupUserGroupToPerm)\
338 .filter(UserGroupUserGroupToPerm.target_user_group == target_user_group)\
339 .filter(UserGroupUserGroupToPerm.user_group == user_group)\
340 .scalar()
341 if obj:
342 self.sa.delete(obj)
343 log.debug('Revoked perm on %s on %s' % (target_user_group, user_group))
@@ -1,106 +1,107 b''
1 1 <table id="permissions_manage" class="noborder">
2 2 <tr>
3 3 <td>${_('none')}</td>
4 4 <td>${_('read')}</td>
5 5 <td>${_('write')}</td>
6 6 <td>${_('admin')}</td>
7 7 <td>${_('member')}</td>
8 8 <td></td>
9 9 </tr>
10 10 ## USERS
11 11 %for r2p in c.repos_group.repo_group_to_perm:
12 12 ##forbid revoking permission from yourself
13 13 <tr id="id${id(r2p.user.username)}">
14 14 %if c.rhodecode_user.user_id != r2p.user.user_id or c.rhodecode_user.is_admin:
15 15 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.none')}</td>
16 16 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.read')}</td>
17 17 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.write')}</td>
18 18 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.admin')}</td>
19 19 <td style="white-space: nowrap;">
20 20 <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username if r2p.user.username != 'default' else _('default')}
21 21 </td>
22 22 <td>
23 23 %if r2p.user.username !='default':
24 24 <span class="delete_icon action_button" onclick="ajaxActionRevoke(${r2p.user.user_id}, 'user', '${'id%s'%id(r2p.user.username)}')">
25 25 ${_('revoke')}
26 26 </span>
27 27 %endif
28 28 </td>
29 29 %else:
30 30 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.none', disabled="disabled")}</td>
31 31 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.read', disabled="disabled")}</td>
32 32 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.write', disabled="disabled")}</td>
33 33 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.admin', disabled="disabled")}</td>
34 34 <td style="white-space: nowrap;">
35 35 <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username if r2p.user.username != 'default' else _('default')}
36 36 </td>
37 37 <td>
38 38 </td>
39 39 %endif
40 40 </tr>
41 41 %endfor
42 42
43 43 ## USER GROUPS
44 44 %for g2p in c.repos_group.users_group_to_perm:
45 45 <tr id="id${id(g2p.users_group.users_group_name)}">
46 46 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.none')}</td>
47 47 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.read')}</td>
48 48 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.write')}</td>
49 49 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.admin')}</td>
50 50 <td style="white-space: nowrap;">
51 51 <img class="perm-gravatar" src="${h.url('/images/icons/group.png')}"/>${g2p.users_group.users_group_name}
52 52 </td>
53 53 <td>
54 54 <span class="delete_icon action_button" onclick="ajaxActionRevoke(${g2p.users_group.users_group_id}, 'user_group', '${'id%s'%id(g2p.users_group.users_group_name)}')">
55 55 ${_('revoke')}
56 56 </span>
57 57 </td>
58 58 </tr>
59 59 %endfor
60 <%
60
61 <%
61 62 _tmpl = h.literal("""' \
62 63 <td><input type="radio" value="group.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
63 64 <td><input type="radio" value="group.read" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
64 65 <td><input type="radio" value="group.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
65 66 <td><input type="radio" value="group.admin" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
66 67 <td class="ac"> \
67 68 <div class="perm_ac" id="perm_ac_{0}"> \
68 69 <input class="yui-ac-input" id="perm_new_member_name_{0}" name="perm_new_member_name_{0}" value="" type="text"> \
69 70 <input id="perm_new_member_type_{0}" name="perm_new_member_type_{0}" value="" type="hidden"> \
70 71 <div id="perm_container_{0}"></div> \
71 72 </div> \
72 73 </td> \
73 74 <td></td>'""")
74 75 %>
75 76 ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
76 77 <tr class="new_members last_new_member" id="add_perm_input"></tr>
77 78 <tr>
78 79 <td colspan="6">
79 80 <span id="add_perm" class="add_icon" style="cursor: pointer;">
80 81 ${_('Add another member')}
81 82 </span>
82 83 </td>
83 84 </tr>
84 85 <tr>
85 86 <td colspan="6">
86 87 ${h.checkbox('recursive',value="True", label=_('apply to children'))}
87 88 <span class="help-block">${_('Set or revoke permission to all children of that group, including non-private repositories and other groups')}</span>
88 89 </td>
89 90 </tr>
90 91 </table>
91 92 <script type="text/javascript">
92 93 function ajaxActionRevoke(obj_id, obj_type, field_id) {
93 94 url = "${h.url('delete_repo_group_perm_member', group_name=c.repos_group.group_name)}";
94 95 ajaxActionRevokePermission(url, obj_id, obj_type, field_id, {recursive:YUD.get('recursive').checked});
95 96 };
96 97
97 98 YUE.onDOMReady(function () {
98 99 if (!YUD.hasClass('perm_new_member_name', 'error')) {
99 100 YUD.setStyle('add_perm_input', 'display', 'none');
100 101 }
101 102 YAHOO.util.Event.addListener('add_perm', 'click', function () {
102 103 addPermAction(${_tmpl}, ${c.users_array|n}, ${c.users_groups_array|n});
103 104 });
104 105 });
105 106
106 107 </script>
@@ -1,83 +1,101 b''
1 1 <table id="permissions_manage" class="noborder">
2 2 <tr>
3 3 <td>${_('none')}</td>
4 4 <td>${_('read')}</td>
5 5 <td>${_('write')}</td>
6 6 <td>${_('admin')}</td>
7 7 <td>${_('member')}</td>
8 8 <td></td>
9 9 </tr>
10 10 ## USERS
11 11 %for r2p in c.users_group.user_user_group_to_perm:
12 12 ##forbid revoking permission from yourself
13 13 <tr id="id${id(r2p.user.username)}">
14 14 %if c.rhodecode_user.user_id != r2p.user.user_id or c.rhodecode_user.is_admin:
15 15 <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.none')}</td>
16 16 <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.read')}</td>
17 17 <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.write')}</td>
18 18 <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.admin')}</td>
19 19 <td style="white-space: nowrap;">
20 20 <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username if r2p.user.username != 'default' else _('default')}
21 21 </td>
22 22 <td>
23 23 %if r2p.user.username !='default':
24 24 <span class="delete_icon action_button" onclick="ajaxActionRevoke(${r2p.user.user_id}, 'user', '${'id%s'%id(r2p.user.username)}')">
25 25 ${_('revoke')}
26 26 </span>
27 27 %endif
28 28 </td>
29 29 %else:
30 30 <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.none', disabled="disabled")}</td>
31 31 <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.read', disabled="disabled")}</td>
32 32 <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.write', disabled="disabled")}</td>
33 33 <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.admin', disabled="disabled")}</td>
34 34 <td style="white-space: nowrap;">
35 35 <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username if r2p.user.username != 'default' else _('default')}
36 36 </td>
37 37 <td>
38 38 </td>
39 39 %endif
40 40 </tr>
41 41 %endfor
42 42
43 ## USER GROUPS
44 %for g2p in c.users_group.user_group_user_group_to_perm:
45 <tr id="id${id(g2p.user_group.users_group_name)}">
46 <td>${h.radio('g_perm_%s' % g2p.user_group.users_group_name,'usergroup.none')}</td>
47 <td>${h.radio('g_perm_%s' % g2p.user_group.users_group_name,'usergroup.read')}</td>
48 <td>${h.radio('g_perm_%s' % g2p.user_group.users_group_name,'usergroup.write')}</td>
49 <td>${h.radio('g_perm_%s' % g2p.user_group.users_group_name,'usergroup.admin')}</td>
50 <td style="white-space: nowrap;">
51 <img class="perm-gravatar" src="${h.url('/images/icons/group.png')}"/>${g2p.user_group.users_group_name}
52 </td>
53 <td>
54 <span class="delete_icon action_button" onclick="ajaxActionRevoke(${g2p.user_group.users_group_id}, 'user_group', '${'id%s'%id(g2p.user_group.users_group_name)}')">
55 ${_('revoke')}
56 </span>
57 </td>
58 </tr>
59 %endfor
60
43 61 <%
44 62 _tmpl = h.literal("""' \
45 63 <td><input type="radio" value="usergroup.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
46 64 <td><input type="radio" value="usergroup.read" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
47 65 <td><input type="radio" value="usergroup.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
48 66 <td><input type="radio" value="usergroup.admin" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
49 67 <td class="ac"> \
50 68 <div class="perm_ac" id="perm_ac_{0}"> \
51 69 <input class="yui-ac-input" id="perm_new_member_name_{0}" name="perm_new_member_name_{0}" value="" type="text"> \
52 70 <input id="perm_new_member_type_{0}" name="perm_new_member_type_{0}" value="" type="hidden"> \
53 71 <div id="perm_container_{0}"></div> \
54 72 </div> \
55 73 </td> \
56 74 <td></td>'""")
57 75 %>
58 76 ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
59 77 <tr class="new_members last_new_member" id="add_perm_input"></tr>
60 78 <tr>
61 79 <td colspan="6">
62 80 <span id="add_perm" class="add_icon" style="cursor: pointer;">
63 81 ${_('Add another member')}
64 82 </span>
65 83 </td>
66 84 </tr>
67 85 </table>
68 86 <script type="text/javascript">
69 87 function ajaxActionRevoke(obj_id, obj_type, field_id) {
70 88 url = "${h.url('delete_user_group_perm_member', id=c.users_group.users_group_id)}";
71 89 ajaxActionRevokePermission(url, obj_id, obj_type, field_id);
72 90 };
73 91
74 92 YUE.onDOMReady(function () {
75 93 if (!YUD.hasClass('perm_new_member_name', 'error')) {
76 94 YUD.setStyle('add_perm_input', 'display', 'none');
77 95 }
78 96 YAHOO.util.Event.addListener('add_perm', 'click', function () {
79 97 addPermAction(${_tmpl}, ${c.users_array|n}, ${c.users_groups_array|n});
80 98 });
81 99 });
82 100
83 101 </script>
General Comments 0
You need to be logged in to leave comments. Login now