##// END OF EJS Templates
Renamed name to firstname in forms...
marcink -
r2544:6ce3387b beta
parent child Browse files
Show More
@@ -1,427 +1,426 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.settings
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 settings controller for rhodecode admin
7 7
8 8 :created_on: Jul 14, 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 formencode
29 29 import pkg_resources
30 30 import platform
31 31
32 32 from sqlalchemy import func
33 33 from formencode import htmlfill
34 34 from pylons import request, session, tmpl_context as c, url, config
35 35 from pylons.controllers.util import abort, redirect
36 36 from pylons.i18n.translation import _
37 37
38 38 from rhodecode.lib import helpers as h
39 39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
40 40 HasPermissionAnyDecorator, NotAnonymous
41 41 from rhodecode.lib.base import BaseController, render
42 42 from rhodecode.lib.celerylib import tasks, run_task
43 43 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
44 44 set_rhodecode_config, repo_name_slug
45 45 from rhodecode.model.db import RhodeCodeUi, Repository, RepoGroup, \
46 46 RhodeCodeSetting
47 47 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
48 48 ApplicationUiSettingsForm
49 49 from rhodecode.model.scm import ScmModel
50 50 from rhodecode.model.user import UserModel
51 51 from rhodecode.model.db import User
52 52 from rhodecode.model.notification import EmailNotificationModel
53 53 from rhodecode.model.meta import Session
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57
58 58 class SettingsController(BaseController):
59 59 """REST Controller styled on the Atom Publishing Protocol"""
60 60 # To properly map this controller, ensure your config/routing.py
61 61 # file has a resource setup:
62 62 # map.resource('setting', 'settings', controller='admin/settings',
63 63 # path_prefix='/admin', name_prefix='admin_')
64 64
65 65 @LoginRequired()
66 66 def __before__(self):
67 67 c.admin_user = session.get('admin_user')
68 68 c.admin_username = session.get('admin_username')
69 69 c.modules = sorted([(p.project_name, p.version)
70 70 for p in pkg_resources.working_set],
71 71 key=lambda k: k[0].lower())
72 72 c.py_version = platform.python_version()
73 73 c.platform = platform.platform()
74 74 super(SettingsController, self).__before__()
75 75
76 76 @HasPermissionAllDecorator('hg.admin')
77 77 def index(self, format='html'):
78 78 """GET /admin/settings: All items in the collection"""
79 79 # url('admin_settings')
80 80
81 81 defaults = RhodeCodeSetting.get_app_settings()
82 82 defaults.update(self.get_hg_ui_settings())
83 83
84 84 return htmlfill.render(
85 85 render('admin/settings/settings.html'),
86 86 defaults=defaults,
87 87 encoding="UTF-8",
88 88 force_defaults=False
89 89 )
90 90
91 91 @HasPermissionAllDecorator('hg.admin')
92 92 def create(self):
93 93 """POST /admin/settings: Create a new item"""
94 94 # url('admin_settings')
95 95
96 96 @HasPermissionAllDecorator('hg.admin')
97 97 def new(self, format='html'):
98 98 """GET /admin/settings/new: Form to create a new item"""
99 99 # url('admin_new_setting')
100 100
101 101 @HasPermissionAllDecorator('hg.admin')
102 102 def update(self, setting_id):
103 103 """PUT /admin/settings/setting_id: Update an existing item"""
104 104 # Forms posted to this method should contain a hidden field:
105 105 # <input type="hidden" name="_method" value="PUT" />
106 106 # Or using helpers:
107 107 # h.form(url('admin_setting', setting_id=ID),
108 108 # method='put')
109 109 # url('admin_setting', setting_id=ID)
110 110 if setting_id == 'mapping':
111 111 rm_obsolete = request.POST.get('destroy', False)
112 112 log.debug('Rescanning directories with destroy=%s' % rm_obsolete)
113 113 initial = ScmModel().repo_scan()
114 114 log.debug('invalidating all repositories')
115 115 for repo_name in initial.keys():
116 116 invalidate_cache('get_repo_cached_%s' % repo_name)
117 117
118 118 added, removed = repo2db_mapper(initial, rm_obsolete)
119 119
120 120 h.flash(_('Repositories successfully'
121 121 ' rescanned added: %s,removed: %s') % (added, removed),
122 122 category='success')
123 123
124 124 if setting_id == 'whoosh':
125 125 repo_location = self.get_hg_ui_settings()['paths_root_path']
126 126 full_index = request.POST.get('full_index', False)
127 127 run_task(tasks.whoosh_index, repo_location, full_index)
128 128
129 129 h.flash(_('Whoosh reindex task scheduled'), category='success')
130 130 if setting_id == 'global':
131 131
132 132 application_form = ApplicationSettingsForm()()
133 133 try:
134 134 form_result = application_form.to_python(dict(request.POST))
135 135
136 136 try:
137 137 hgsettings1 = RhodeCodeSetting.get_by_name('title')
138 138 hgsettings1.app_settings_value = \
139 139 form_result['rhodecode_title']
140 140
141 141 hgsettings2 = RhodeCodeSetting.get_by_name('realm')
142 142 hgsettings2.app_settings_value = \
143 143 form_result['rhodecode_realm']
144 144
145 145 hgsettings3 = RhodeCodeSetting.get_by_name('ga_code')
146 146 hgsettings3.app_settings_value = \
147 147 form_result['rhodecode_ga_code']
148 148
149 149 self.sa.add(hgsettings1)
150 150 self.sa.add(hgsettings2)
151 151 self.sa.add(hgsettings3)
152 152 self.sa.commit()
153 153 set_rhodecode_config(config)
154 154 h.flash(_('Updated application settings'),
155 155 category='success')
156 156
157 157 except Exception:
158 158 log.error(traceback.format_exc())
159 159 h.flash(_('error occurred during updating '
160 160 'application settings'),
161 161 category='error')
162 162
163 163 self.sa.rollback()
164 164
165 165 except formencode.Invalid, errors:
166 166 return htmlfill.render(
167 167 render('admin/settings/settings.html'),
168 168 defaults=errors.value,
169 169 errors=errors.error_dict or {},
170 170 prefix_error=False,
171 171 encoding="UTF-8")
172 172
173 173 if setting_id == 'mercurial':
174 174 application_form = ApplicationUiSettingsForm()()
175 175 try:
176 176 form_result = application_form.to_python(dict(request.POST))
177 177 # fix namespaces for hooks
178 178 _f = lambda s: s.replace('.', '_')
179 179 try:
180 180
181 181 hgsettings1 = self.sa.query(RhodeCodeUi)\
182 182 .filter(RhodeCodeUi.ui_key == 'push_ssl').one()
183 183 hgsettings1.ui_value = form_result['web_push_ssl']
184 184
185 185 hgsettings2 = self.sa.query(RhodeCodeUi)\
186 186 .filter(RhodeCodeUi.ui_key == '/').one()
187 187 hgsettings2.ui_value = form_result['paths_root_path']
188 188
189 189 #HOOKS
190 190 hgsettings3 = self.sa.query(RhodeCodeUi)\
191 191 .filter(RhodeCodeUi.ui_key == RhodeCodeUi.HOOK_UPDATE)\
192 192 .one()
193 193 hgsettings3.ui_active = bool(form_result[_f('hooks_%s' %
194 194 RhodeCodeUi.HOOK_UPDATE)])
195 195
196 196 hgsettings4 = self.sa.query(RhodeCodeUi)\
197 197 .filter(RhodeCodeUi.ui_key == RhodeCodeUi.HOOK_REPO_SIZE)\
198 198 .one()
199 199 hgsettings4.ui_active = bool(form_result[_f('hooks_%s' %
200 200 RhodeCodeUi.HOOK_REPO_SIZE)])
201 201
202 202 hgsettings5 = self.sa.query(RhodeCodeUi)\
203 203 .filter(RhodeCodeUi.ui_key == RhodeCodeUi.HOOK_PUSH)\
204 204 .one()
205 205 hgsettings5.ui_active = bool(form_result[_f('hooks_%s' %
206 206 RhodeCodeUi.HOOK_PUSH)])
207 207
208 208 hgsettings6 = self.sa.query(RhodeCodeUi)\
209 209 .filter(RhodeCodeUi.ui_key == RhodeCodeUi.HOOK_PULL)\
210 210 .one()
211 211 hgsettings6.ui_active = bool(form_result[_f('hooks_%s' %
212 212 RhodeCodeUi.HOOK_PULL)])
213 213
214 214 self.sa.add(hgsettings1)
215 215 self.sa.add(hgsettings2)
216 216 self.sa.add(hgsettings3)
217 217 self.sa.add(hgsettings4)
218 218 self.sa.add(hgsettings5)
219 219 self.sa.add(hgsettings6)
220 220 self.sa.commit()
221 221
222 222 h.flash(_('Updated mercurial settings'),
223 223 category='success')
224 224
225 225 except:
226 226 log.error(traceback.format_exc())
227 227 h.flash(_('error occurred during updating '
228 228 'application settings'), category='error')
229 229
230 230 self.sa.rollback()
231 231
232 232 except formencode.Invalid, errors:
233 233 return htmlfill.render(
234 234 render('admin/settings/settings.html'),
235 235 defaults=errors.value,
236 236 errors=errors.error_dict or {},
237 237 prefix_error=False,
238 238 encoding="UTF-8")
239 239
240 240 if setting_id == 'hooks':
241 241 ui_key = request.POST.get('new_hook_ui_key')
242 242 ui_value = request.POST.get('new_hook_ui_value')
243 243 try:
244 244
245 245 if ui_value and ui_key:
246 246 RhodeCodeUi.create_or_update_hook(ui_key, ui_value)
247 247 h.flash(_('Added new hook'),
248 248 category='success')
249 249
250 250 # check for edits
251 251 update = False
252 252 _d = request.POST.dict_of_lists()
253 253 for k, v in zip(_d.get('hook_ui_key', []),
254 254 _d.get('hook_ui_value_new', [])):
255 255 RhodeCodeUi.create_or_update_hook(k, v)
256 256 update = True
257 257
258 258 if update:
259 259 h.flash(_('Updated hooks'), category='success')
260 260 self.sa.commit()
261 261 except:
262 262 log.error(traceback.format_exc())
263 263 h.flash(_('error occurred during hook creation'),
264 264 category='error')
265 265
266 266 return redirect(url('admin_edit_setting', setting_id='hooks'))
267 267
268 268 if setting_id == 'email':
269 269 test_email = request.POST.get('test_email')
270 270 test_email_subj = 'RhodeCode TestEmail'
271 271 test_email_body = 'RhodeCode Email test'
272 272
273 273 test_email_html_body = EmailNotificationModel()\
274 274 .get_email_tmpl(EmailNotificationModel.TYPE_DEFAULT,
275 275 body=test_email_body)
276 276
277 277 recipients = [test_email] if [test_email] else None
278 278
279 279 run_task(tasks.send_email, recipients, test_email_subj,
280 280 test_email_body, test_email_html_body)
281 281
282 282 h.flash(_('Email task created'), category='success')
283 283 return redirect(url('admin_settings'))
284 284
285 285 @HasPermissionAllDecorator('hg.admin')
286 286 def delete(self, setting_id):
287 287 """DELETE /admin/settings/setting_id: Delete an existing item"""
288 288 # Forms posted to this method should contain a hidden field:
289 289 # <input type="hidden" name="_method" value="DELETE" />
290 290 # Or using helpers:
291 291 # h.form(url('admin_setting', setting_id=ID),
292 292 # method='delete')
293 293 # url('admin_setting', setting_id=ID)
294 294 if setting_id == 'hooks':
295 295 hook_id = request.POST.get('hook_id')
296 296 RhodeCodeUi.delete(hook_id)
297 297 self.sa.commit()
298 298
299 299 @HasPermissionAllDecorator('hg.admin')
300 300 def show(self, setting_id, format='html'):
301 301 """
302 302 GET /admin/settings/setting_id: Show a specific item"""
303 303 # url('admin_setting', setting_id=ID)
304 304
305 305 @HasPermissionAllDecorator('hg.admin')
306 306 def edit(self, setting_id, format='html'):
307 307 """
308 308 GET /admin/settings/setting_id/edit: Form to
309 309 edit an existing item"""
310 310 # url('admin_edit_setting', setting_id=ID)
311 311
312 312 c.hooks = RhodeCodeUi.get_builtin_hooks()
313 313 c.custom_hooks = RhodeCodeUi.get_custom_hooks()
314 314
315 315 return htmlfill.render(
316 316 render('admin/settings/hooks.html'),
317 317 defaults={},
318 318 encoding="UTF-8",
319 319 force_defaults=False
320 320 )
321 321
322 322 @NotAnonymous()
323 323 def my_account(self):
324 324 """
325 325 GET /_admin/my_account Displays info about my account
326 326 """
327 327 # url('admin_settings_my_account')
328 328
329 329 c.user = User.get(self.rhodecode_user.user_id)
330 330 all_repos = self.sa.query(Repository)\
331 331 .filter(Repository.user_id == c.user.user_id)\
332 332 .order_by(func.lower(Repository.repo_name)).all()
333 333
334 334 c.user_repos = ScmModel().get_repos(all_repos)
335 335
336 336 if c.user.username == 'default':
337 337 h.flash(_("You can't edit this user since it's"
338 338 " crucial for entire application"), category='warning')
339 339 return redirect(url('users'))
340 340
341 341 defaults = c.user.get_dict()
342 342
343 343 c.form = htmlfill.render(
344 344 render('admin/users/user_edit_my_account_form.html'),
345 345 defaults=defaults,
346 346 encoding="UTF-8",
347 347 force_defaults=False
348 348 )
349 349 return render('admin/users/user_edit_my_account.html')
350 350
351 351 def my_account_update(self):
352 352 """PUT /_admin/my_account_update: Update an existing item"""
353 353 # Forms posted to this method should contain a hidden field:
354 354 # <input type="hidden" name="_method" value="PUT" />
355 355 # Or using helpers:
356 356 # h.form(url('admin_settings_my_account_update'),
357 357 # method='put')
358 358 # url('admin_settings_my_account_update', id=ID)
359 user_model = UserModel()
360 359 uid = self.rhodecode_user.user_id
360 email = self.rhodecode_user.email
361 361 _form = UserForm(edit=True,
362 old_data={'user_id': uid,
363 'email': self.rhodecode_user.email})()
362 old_data={'user_id': uid, 'email': email})()
364 363 form_result = {}
365 364 try:
366 365 form_result = _form.to_python(dict(request.POST))
367 user_model.update_my_account(uid, form_result)
366 UserModel().update_my_account(uid, form_result)
368 367 h.flash(_('Your account was updated successfully'),
369 368 category='success')
370 369 Session.commit()
371 370 except formencode.Invalid, errors:
372 371 c.user = User.get(self.rhodecode_user.user_id)
373 372 all_repos = self.sa.query(Repository)\
374 373 .filter(Repository.user_id == c.user.user_id)\
375 374 .order_by(func.lower(Repository.repo_name))\
376 375 .all()
377 376 c.user_repos = ScmModel().get_repos(all_repos)
378 377
379 378 c.form = htmlfill.render(
380 379 render('admin/users/user_edit_my_account_form.html'),
381 380 defaults=errors.value,
382 381 errors=errors.error_dict or {},
383 382 prefix_error=False,
384 383 encoding="UTF-8")
385 384 return render('admin/users/user_edit_my_account.html')
386 385 except Exception:
387 386 log.error(traceback.format_exc())
388 387 h.flash(_('error occurred during update of user %s') \
389 388 % form_result.get('username'), category='error')
390 389
391 390 return redirect(url('my_account'))
392 391
393 392 @NotAnonymous()
394 393 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
395 394 def create_repository(self):
396 395 """GET /_admin/create_repository: Form to create a new item"""
397 396
398 397 c.repo_groups = RepoGroup.groups_choices()
399 398 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
400 399 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
401 400
402 401 new_repo = request.GET.get('repo', '')
403 402 c.new_repo = repo_name_slug(new_repo)
404 403
405 404 return render('admin/repos/repo_add_create_repository.html')
406 405
407 406 def get_hg_ui_settings(self):
408 407 ret = self.sa.query(RhodeCodeUi).all()
409 408
410 409 if not ret:
411 410 raise Exception('Could not get application ui settings !')
412 411 settings = {}
413 412 for each in ret:
414 413 k = each.ui_key
415 414 v = each.ui_value
416 415 if k == '/':
417 416 k = 'root_path'
418 417
419 418 if k.find('.') != -1:
420 419 k = k.replace('.', '_')
421 420
422 421 if each.ui_section == 'hooks':
423 422 v = each.ui_active
424 423
425 424 settings[each.ui_section + '_' + k] = v
426 425
427 426 return settings
@@ -1,1667 +1,1665 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 from collections import defaultdict
32 32
33 33 from sqlalchemy import *
34 34 from sqlalchemy.ext.hybrid import hybrid_property
35 35 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
36 36 from sqlalchemy.exc import DatabaseError
37 37 from beaker.cache import cache_region, region_invalidate
38 38 from webob.exc import HTTPNotFound
39 39
40 40 from pylons.i18n.translation import lazy_ugettext as _
41 41
42 42 from rhodecode.lib.vcs import get_backend
43 43 from rhodecode.lib.vcs.utils.helpers import get_scm
44 44 from rhodecode.lib.vcs.exceptions import VCSError
45 45 from rhodecode.lib.vcs.utils.lazy import LazyProperty
46 46
47 47 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
48 48 safe_unicode
49 49 from rhodecode.lib.compat import json
50 50 from rhodecode.lib.caching_query import FromCache
51 51
52 52 from rhodecode.model.meta import Base, Session
53 53
54 54 URL_SEP = '/'
55 55 log = logging.getLogger(__name__)
56 56
57 57 #==============================================================================
58 58 # BASE CLASSES
59 59 #==============================================================================
60 60
61 61 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
62 62
63 63
64 64 class ModelSerializer(json.JSONEncoder):
65 65 """
66 66 Simple Serializer for JSON,
67 67
68 68 usage::
69 69
70 70 to make object customized for serialization implement a __json__
71 71 method that will return a dict for serialization into json
72 72
73 73 example::
74 74
75 75 class Task(object):
76 76
77 77 def __init__(self, name, value):
78 78 self.name = name
79 79 self.value = value
80 80
81 81 def __json__(self):
82 82 return dict(name=self.name,
83 83 value=self.value)
84 84
85 85 """
86 86
87 87 def default(self, obj):
88 88
89 89 if hasattr(obj, '__json__'):
90 90 return obj.__json__()
91 91 else:
92 92 return json.JSONEncoder.default(self, obj)
93 93
94 94
95 95 class BaseModel(object):
96 96 """
97 97 Base Model for all classess
98 98 """
99 99
100 100 @classmethod
101 101 def _get_keys(cls):
102 102 """return column names for this model """
103 103 return class_mapper(cls).c.keys()
104 104
105 105 def get_dict(self):
106 106 """
107 107 return dict with keys and values corresponding
108 108 to this model data """
109 109
110 110 d = {}
111 111 for k in self._get_keys():
112 112 d[k] = getattr(self, k)
113 113
114 114 # also use __json__() if present to get additional fields
115 115 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
116 116 d[k] = val
117 117 return d
118 118
119 119 def get_appstruct(self):
120 120 """return list with keys and values tupples corresponding
121 121 to this model data """
122 122
123 123 l = []
124 124 for k in self._get_keys():
125 125 l.append((k, getattr(self, k),))
126 126 return l
127 127
128 128 def populate_obj(self, populate_dict):
129 129 """populate model with data from given populate_dict"""
130 130
131 131 for k in self._get_keys():
132 132 if k in populate_dict:
133 133 setattr(self, k, populate_dict[k])
134 134
135 135 @classmethod
136 136 def query(cls):
137 137 return Session().query(cls)
138 138
139 139 @classmethod
140 140 def get(cls, id_):
141 141 if id_:
142 142 return cls.query().get(id_)
143 143
144 144 @classmethod
145 145 def get_or_404(cls, id_):
146 146 if id_:
147 147 res = cls.query().get(id_)
148 148 if not res:
149 149 raise HTTPNotFound
150 150 return res
151 151
152 152 @classmethod
153 153 def getAll(cls):
154 154 return cls.query().all()
155 155
156 156 @classmethod
157 157 def delete(cls, id_):
158 158 obj = cls.query().get(id_)
159 159 Session().delete(obj)
160 160
161 161 def __repr__(self):
162 162 if hasattr(self, '__unicode__'):
163 163 # python repr needs to return str
164 164 return safe_str(self.__unicode__())
165 165 return '<DB:%s>' % (self.__class__.__name__)
166 166
167 167
168 168 class RhodeCodeSetting(Base, BaseModel):
169 169 __tablename__ = 'rhodecode_settings'
170 170 __table_args__ = (
171 171 UniqueConstraint('app_settings_name'),
172 172 {'extend_existing': True, 'mysql_engine': 'InnoDB',
173 173 'mysql_charset': 'utf8'}
174 174 )
175 175 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
176 176 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
177 177 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
178 178
179 179 def __init__(self, k='', v=''):
180 180 self.app_settings_name = k
181 181 self.app_settings_value = v
182 182
183 183 @validates('_app_settings_value')
184 184 def validate_settings_value(self, key, val):
185 185 assert type(val) == unicode
186 186 return val
187 187
188 188 @hybrid_property
189 189 def app_settings_value(self):
190 190 v = self._app_settings_value
191 191 if self.app_settings_name == 'ldap_active':
192 192 v = str2bool(v)
193 193 return v
194 194
195 195 @app_settings_value.setter
196 196 def app_settings_value(self, val):
197 197 """
198 198 Setter that will always make sure we use unicode in app_settings_value
199 199
200 200 :param val:
201 201 """
202 202 self._app_settings_value = safe_unicode(val)
203 203
204 204 def __unicode__(self):
205 205 return u"<%s('%s:%s')>" % (
206 206 self.__class__.__name__,
207 207 self.app_settings_name, self.app_settings_value
208 208 )
209 209
210 210 @classmethod
211 211 def get_by_name(cls, ldap_key):
212 212 return cls.query()\
213 213 .filter(cls.app_settings_name == ldap_key).scalar()
214 214
215 215 @classmethod
216 216 def get_app_settings(cls, cache=False):
217 217
218 218 ret = cls.query()
219 219
220 220 if cache:
221 221 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
222 222
223 223 if not ret:
224 224 raise Exception('Could not get application settings !')
225 225 settings = {}
226 226 for each in ret:
227 227 settings['rhodecode_' + each.app_settings_name] = \
228 228 each.app_settings_value
229 229
230 230 return settings
231 231
232 232 @classmethod
233 233 def get_ldap_settings(cls, cache=False):
234 234 ret = cls.query()\
235 235 .filter(cls.app_settings_name.startswith('ldap_')).all()
236 236 fd = {}
237 237 for row in ret:
238 238 fd.update({row.app_settings_name: row.app_settings_value})
239 239
240 240 return fd
241 241
242 242
243 243 class RhodeCodeUi(Base, BaseModel):
244 244 __tablename__ = 'rhodecode_ui'
245 245 __table_args__ = (
246 246 UniqueConstraint('ui_key'),
247 247 {'extend_existing': True, 'mysql_engine': 'InnoDB',
248 248 'mysql_charset': 'utf8'}
249 249 )
250 250
251 251 HOOK_UPDATE = 'changegroup.update'
252 252 HOOK_REPO_SIZE = 'changegroup.repo_size'
253 253 HOOK_PUSH = 'changegroup.push_logger'
254 254 HOOK_PULL = 'preoutgoing.pull_logger'
255 255
256 256 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
257 257 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
258 258 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
259 259 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
260 260 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
261 261
262 262 @classmethod
263 263 def get_by_key(cls, key):
264 264 return cls.query().filter(cls.ui_key == key)
265 265
266 266 @classmethod
267 267 def get_builtin_hooks(cls):
268 268 q = cls.query()
269 269 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
270 270 cls.HOOK_REPO_SIZE,
271 271 cls.HOOK_PUSH, cls.HOOK_PULL]))
272 272 return q.all()
273 273
274 274 @classmethod
275 275 def get_custom_hooks(cls):
276 276 q = cls.query()
277 277 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
278 278 cls.HOOK_REPO_SIZE,
279 279 cls.HOOK_PUSH, cls.HOOK_PULL]))
280 280 q = q.filter(cls.ui_section == 'hooks')
281 281 return q.all()
282 282
283 283 @classmethod
284 284 def get_repos_location(cls):
285 285 return cls.get_by_key('/').one().ui_value
286 286
287 287 @classmethod
288 288 def create_or_update_hook(cls, key, val):
289 289 new_ui = cls.get_by_key(key).scalar() or cls()
290 290 new_ui.ui_section = 'hooks'
291 291 new_ui.ui_active = True
292 292 new_ui.ui_key = key
293 293 new_ui.ui_value = val
294 294
295 295 Session().add(new_ui)
296 296
297 297
298 298 class User(Base, BaseModel):
299 299 __tablename__ = 'users'
300 300 __table_args__ = (
301 301 UniqueConstraint('username'), UniqueConstraint('email'),
302 302 {'extend_existing': True, 'mysql_engine': 'InnoDB',
303 303 'mysql_charset': 'utf8'}
304 304 )
305 305 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
306 306 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
307 307 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
308 308 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
309 309 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
310 310 name = Column("firstname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
311 311 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
312 312 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
313 313 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
314 314 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
315 315 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
316 316
317 317 user_log = relationship('UserLog', cascade='all')
318 318 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
319 319
320 320 repositories = relationship('Repository')
321 321 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
322 322 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
323 323 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
324 324
325 325 group_member = relationship('UsersGroupMember', cascade='all')
326 326
327 327 notifications = relationship('UserNotification', cascade='all')
328 328 # notifications assigned to this user
329 329 user_created_notifications = relationship('Notification', cascade='all')
330 330 # comments created by this user
331 331 user_comments = relationship('ChangesetComment', cascade='all')
332 332
333 333 @hybrid_property
334 334 def email(self):
335 335 return self._email
336 336
337 337 @email.setter
338 338 def email(self, val):
339 339 self._email = val.lower() if val else None
340 340
341 341 @property
342 342 def emails(self):
343 343 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
344 344 return [self.email] + [x.email for x in other]
345 345
346 346 @property
347 347 def full_name(self):
348 348 return '%s %s' % (self.name, self.lastname)
349 349
350 350 @property
351 351 def full_name_or_username(self):
352 352 return ('%s %s' % (self.name, self.lastname)
353 353 if (self.name and self.lastname) else self.username)
354 354
355 355 @property
356 356 def full_contact(self):
357 357 return '%s %s <%s>' % (self.name, self.lastname, self.email)
358 358
359 359 @property
360 360 def short_contact(self):
361 361 return '%s %s' % (self.name, self.lastname)
362 362
363 363 @property
364 364 def is_admin(self):
365 365 return self.admin
366 366
367 367 def __unicode__(self):
368 368 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
369 369 self.user_id, self.username)
370 370
371 371 @classmethod
372 372 def get_by_username(cls, username, case_insensitive=False, cache=False):
373 373 if case_insensitive:
374 374 q = cls.query().filter(cls.username.ilike(username))
375 375 else:
376 376 q = cls.query().filter(cls.username == username)
377 377
378 378 if cache:
379 379 q = q.options(FromCache(
380 380 "sql_cache_short",
381 381 "get_user_%s" % _hash_key(username)
382 382 )
383 383 )
384 384 return q.scalar()
385 385
386 386 @classmethod
387 387 def get_by_api_key(cls, api_key, cache=False):
388 388 q = cls.query().filter(cls.api_key == api_key)
389 389
390 390 if cache:
391 391 q = q.options(FromCache("sql_cache_short",
392 392 "get_api_key_%s" % api_key))
393 393 return q.scalar()
394 394
395 395 @classmethod
396 396 def get_by_email(cls, email, case_insensitive=False, cache=False):
397 397 if case_insensitive:
398 398 q = cls.query().filter(cls.email.ilike(email))
399 399 else:
400 400 q = cls.query().filter(cls.email == email)
401 401
402 402 if cache:
403 403 q = q.options(FromCache("sql_cache_short",
404 404 "get_email_key_%s" % email))
405 405
406 406 ret = q.scalar()
407 407 if ret is None:
408 408 q = UserEmailMap.query()
409 409 # try fetching in alternate email map
410 410 if case_insensitive:
411 411 q = q.filter(UserEmailMap.email.ilike(email))
412 412 else:
413 413 q = q.filter(UserEmailMap.email == email)
414 414 q = q.options(joinedload(UserEmailMap.user))
415 415 if cache:
416 416 q = q.options(FromCache("sql_cache_short",
417 417 "get_email_map_key_%s" % email))
418 418 ret = getattr(q.scalar(), 'user', None)
419 419
420 420 return ret
421 421
422 422 def update_lastlogin(self):
423 423 """Update user lastlogin"""
424 424 self.last_login = datetime.datetime.now()
425 425 Session().add(self)
426 426 log.debug('updated user %s lastlogin' % self.username)
427 427
428 428 def get_api_data(self):
429 429 """
430 430 Common function for generating user related data for API
431 431 """
432 432 user = self
433 433 data = dict(
434 434 user_id=user.user_id,
435 435 username=user.username,
436 436 firstname=user.name,
437 437 lastname=user.lastname,
438 438 email=user.email,
439 439 emails=user.emails,
440 440 api_key=user.api_key,
441 441 active=user.active,
442 442 admin=user.admin,
443 443 ldap_dn=user.ldap_dn,
444 444 last_login=user.last_login,
445 445 )
446 446 return data
447 447
448 448 def __json__(self):
449 return dict(
450 user_id=self.user_id,
451 first_name=self.name,
452 last_name=self.lastname,
453 email=self.email,
449 data = dict(
454 450 full_name=self.full_name,
455 451 full_name_or_username=self.full_name_or_username,
456 452 short_contact=self.short_contact,
457 453 full_contact=self.full_contact
458 454 )
455 data.update(self.get_api_data())
456 return data
459 457
460 458
461 459 class UserEmailMap(Base, BaseModel):
462 460 __tablename__ = 'user_email_map'
463 461 __table_args__ = (
464 462 Index('uem_email_idx', 'email'),
465 463 UniqueConstraint('email'),
466 464 {'extend_existing': True, 'mysql_engine': 'InnoDB',
467 465 'mysql_charset': 'utf8'}
468 466 )
469 467 __mapper_args__ = {}
470 468
471 469 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
472 470 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
473 471 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
474 472
475 473 user = relationship('User', lazy='joined')
476 474
477 475 @validates('_email')
478 476 def validate_email(self, key, email):
479 477 # check if this email is not main one
480 478 main_email = Session().query(User).filter(User.email == email).scalar()
481 479 if main_email is not None:
482 480 raise AttributeError('email %s is present is user table' % email)
483 481 return email
484 482
485 483 @hybrid_property
486 484 def email(self):
487 485 return self._email
488 486
489 487 @email.setter
490 488 def email(self, val):
491 489 self._email = val.lower() if val else None
492 490
493 491
494 492 class UserLog(Base, BaseModel):
495 493 __tablename__ = 'user_logs'
496 494 __table_args__ = (
497 495 {'extend_existing': True, 'mysql_engine': 'InnoDB',
498 496 'mysql_charset': 'utf8'},
499 497 )
500 498 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
501 499 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
502 500 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
503 501 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
504 502 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
505 503 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
506 504 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
507 505
508 506 @property
509 507 def action_as_day(self):
510 508 return datetime.date(*self.action_date.timetuple()[:3])
511 509
512 510 user = relationship('User')
513 511 repository = relationship('Repository', cascade='')
514 512
515 513
516 514 class UsersGroup(Base, BaseModel):
517 515 __tablename__ = 'users_groups'
518 516 __table_args__ = (
519 517 {'extend_existing': True, 'mysql_engine': 'InnoDB',
520 518 'mysql_charset': 'utf8'},
521 519 )
522 520
523 521 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
524 522 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
525 523 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
526 524
527 525 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
528 526 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
529 527 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
530 528
531 529 def __unicode__(self):
532 530 return u'<userGroup(%s)>' % (self.users_group_name)
533 531
534 532 @classmethod
535 533 def get_by_group_name(cls, group_name, cache=False,
536 534 case_insensitive=False):
537 535 if case_insensitive:
538 536 q = cls.query().filter(cls.users_group_name.ilike(group_name))
539 537 else:
540 538 q = cls.query().filter(cls.users_group_name == group_name)
541 539 if cache:
542 540 q = q.options(FromCache(
543 541 "sql_cache_short",
544 542 "get_user_%s" % _hash_key(group_name)
545 543 )
546 544 )
547 545 return q.scalar()
548 546
549 547 @classmethod
550 548 def get(cls, users_group_id, cache=False):
551 549 users_group = cls.query()
552 550 if cache:
553 551 users_group = users_group.options(FromCache("sql_cache_short",
554 552 "get_users_group_%s" % users_group_id))
555 553 return users_group.get(users_group_id)
556 554
557 555 def get_api_data(self):
558 556 users_group = self
559 557
560 558 data = dict(
561 559 users_group_id=users_group.users_group_id,
562 560 group_name=users_group.users_group_name,
563 561 active=users_group.users_group_active,
564 562 )
565 563
566 564 return data
567 565
568 566
569 567 class UsersGroupMember(Base, BaseModel):
570 568 __tablename__ = 'users_groups_members'
571 569 __table_args__ = (
572 570 {'extend_existing': True, 'mysql_engine': 'InnoDB',
573 571 'mysql_charset': 'utf8'},
574 572 )
575 573
576 574 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
577 575 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
578 576 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
579 577
580 578 user = relationship('User', lazy='joined')
581 579 users_group = relationship('UsersGroup')
582 580
583 581 def __init__(self, gr_id='', u_id=''):
584 582 self.users_group_id = gr_id
585 583 self.user_id = u_id
586 584
587 585
588 586 class Repository(Base, BaseModel):
589 587 __tablename__ = 'repositories'
590 588 __table_args__ = (
591 589 UniqueConstraint('repo_name'),
592 590 {'extend_existing': True, 'mysql_engine': 'InnoDB',
593 591 'mysql_charset': 'utf8'},
594 592 )
595 593
596 594 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
597 595 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
598 596 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
599 597 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
600 598 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
601 599 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
602 600 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
603 601 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
604 602 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
605 603 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
606 604 landing_rev = Column("landing_revision", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
607 605
608 606 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
609 607 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
610 608
611 609 user = relationship('User')
612 610 fork = relationship('Repository', remote_side=repo_id)
613 611 group = relationship('RepoGroup')
614 612 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
615 613 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
616 614 stats = relationship('Statistics', cascade='all', uselist=False)
617 615
618 616 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
619 617
620 618 logs = relationship('UserLog')
621 619 comments = relationship('ChangesetComment')
622 620
623 621 def __unicode__(self):
624 622 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
625 623 self.repo_name)
626 624
627 625 @classmethod
628 626 def url_sep(cls):
629 627 return URL_SEP
630 628
631 629 @classmethod
632 630 def get_by_repo_name(cls, repo_name):
633 631 q = Session().query(cls).filter(cls.repo_name == repo_name)
634 632 q = q.options(joinedload(Repository.fork))\
635 633 .options(joinedload(Repository.user))\
636 634 .options(joinedload(Repository.group))
637 635 return q.scalar()
638 636
639 637 @classmethod
640 638 def get_by_full_path(cls, repo_full_path):
641 639 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
642 640 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
643 641
644 642 @classmethod
645 643 def get_repo_forks(cls, repo_id):
646 644 return cls.query().filter(Repository.fork_id == repo_id)
647 645
648 646 @classmethod
649 647 def base_path(cls):
650 648 """
651 649 Returns base path when all repos are stored
652 650
653 651 :param cls:
654 652 """
655 653 q = Session().query(RhodeCodeUi)\
656 654 .filter(RhodeCodeUi.ui_key == cls.url_sep())
657 655 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
658 656 return q.one().ui_value
659 657
660 658 @property
661 659 def forks(self):
662 660 """
663 661 Return forks of this repo
664 662 """
665 663 return Repository.get_repo_forks(self.repo_id)
666 664
667 665 @property
668 666 def parent(self):
669 667 """
670 668 Returns fork parent
671 669 """
672 670 return self.fork
673 671
674 672 @property
675 673 def just_name(self):
676 674 return self.repo_name.split(Repository.url_sep())[-1]
677 675
678 676 @property
679 677 def groups_with_parents(self):
680 678 groups = []
681 679 if self.group is None:
682 680 return groups
683 681
684 682 cur_gr = self.group
685 683 groups.insert(0, cur_gr)
686 684 while 1:
687 685 gr = getattr(cur_gr, 'parent_group', None)
688 686 cur_gr = cur_gr.parent_group
689 687 if gr is None:
690 688 break
691 689 groups.insert(0, gr)
692 690
693 691 return groups
694 692
695 693 @property
696 694 def groups_and_repo(self):
697 695 return self.groups_with_parents, self.just_name
698 696
699 697 @LazyProperty
700 698 def repo_path(self):
701 699 """
702 700 Returns base full path for that repository means where it actually
703 701 exists on a filesystem
704 702 """
705 703 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
706 704 Repository.url_sep())
707 705 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
708 706 return q.one().ui_value
709 707
710 708 @property
711 709 def repo_full_path(self):
712 710 p = [self.repo_path]
713 711 # we need to split the name by / since this is how we store the
714 712 # names in the database, but that eventually needs to be converted
715 713 # into a valid system path
716 714 p += self.repo_name.split(Repository.url_sep())
717 715 return os.path.join(*p)
718 716
719 717 def get_new_name(self, repo_name):
720 718 """
721 719 returns new full repository name based on assigned group and new new
722 720
723 721 :param group_name:
724 722 """
725 723 path_prefix = self.group.full_path_splitted if self.group else []
726 724 return Repository.url_sep().join(path_prefix + [repo_name])
727 725
728 726 @property
729 727 def _ui(self):
730 728 """
731 729 Creates an db based ui object for this repository
732 730 """
733 731 from mercurial import ui
734 732 from mercurial import config
735 733 baseui = ui.ui()
736 734
737 735 #clean the baseui object
738 736 baseui._ocfg = config.config()
739 737 baseui._ucfg = config.config()
740 738 baseui._tcfg = config.config()
741 739
742 740 ret = RhodeCodeUi.query()\
743 741 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
744 742
745 743 hg_ui = ret
746 744 for ui_ in hg_ui:
747 745 if ui_.ui_active:
748 746 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
749 747 ui_.ui_key, ui_.ui_value)
750 748 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
751 749
752 750 return baseui
753 751
754 752 @classmethod
755 753 def inject_ui(cls, repo, extras={}):
756 754 from rhodecode.lib.vcs.backends.hg import MercurialRepository
757 755 from rhodecode.lib.vcs.backends.git import GitRepository
758 756 required = (MercurialRepository, GitRepository)
759 757 if not isinstance(repo, required):
760 758 raise Exception('repo must be instance of %s' % required)
761 759
762 760 # inject ui extra param to log this action via push logger
763 761 for k, v in extras.items():
764 762 repo._repo.ui.setconfig('rhodecode_extras', k, v)
765 763
766 764 @classmethod
767 765 def is_valid(cls, repo_name):
768 766 """
769 767 returns True if given repo name is a valid filesystem repository
770 768
771 769 :param cls:
772 770 :param repo_name:
773 771 """
774 772 from rhodecode.lib.utils import is_valid_repo
775 773
776 774 return is_valid_repo(repo_name, cls.base_path())
777 775
778 776 def get_api_data(self):
779 777 """
780 778 Common function for generating repo api data
781 779
782 780 """
783 781 repo = self
784 782 data = dict(
785 783 repo_id=repo.repo_id,
786 784 repo_name=repo.repo_name,
787 785 repo_type=repo.repo_type,
788 786 clone_uri=repo.clone_uri,
789 787 private=repo.private,
790 788 created_on=repo.created_on,
791 789 description=repo.description,
792 790 landing_rev=repo.landing_rev,
793 791 owner=repo.user.username,
794 792 fork_of=repo.fork.repo_name if repo.fork else None
795 793 )
796 794
797 795 return data
798 796
799 797 #==========================================================================
800 798 # SCM PROPERTIES
801 799 #==========================================================================
802 800
803 801 def get_changeset(self, rev=None):
804 802 return get_changeset_safe(self.scm_instance, rev)
805 803
806 804 @property
807 805 def tip(self):
808 806 return self.get_changeset('tip')
809 807
810 808 @property
811 809 def author(self):
812 810 return self.tip.author
813 811
814 812 @property
815 813 def last_change(self):
816 814 return self.scm_instance.last_change
817 815
818 816 def get_comments(self, revisions=None):
819 817 """
820 818 Returns comments for this repository grouped by revisions
821 819
822 820 :param revisions: filter query by revisions only
823 821 """
824 822 cmts = ChangesetComment.query()\
825 823 .filter(ChangesetComment.repo == self)
826 824 if revisions:
827 825 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
828 826 grouped = defaultdict(list)
829 827 for cmt in cmts.all():
830 828 grouped[cmt.revision].append(cmt)
831 829 return grouped
832 830
833 831 def statuses(self, revisions=None):
834 832 """
835 833 Returns statuses for this repository
836 834
837 835 :param revisions: list of revisions to get statuses for
838 836 :type revisions: list
839 837 """
840 838
841 839 statuses = ChangesetStatus.query()\
842 840 .filter(ChangesetStatus.repo == self)\
843 841 .filter(ChangesetStatus.version == 0)
844 842 if revisions:
845 843 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
846 844 grouped = {}
847 845
848 846 #maybe we have open new pullrequest without a status ?
849 847 stat = ChangesetStatus.STATUS_UNDER_REVIEW
850 848 status_lbl = ChangesetStatus.get_status_lbl(stat)
851 849 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
852 850 for rev in pr.revisions:
853 851 pr_id = pr.pull_request_id
854 852 pr_repo = pr.other_repo.repo_name
855 853 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
856 854
857 855 for stat in statuses.all():
858 856 pr_id = pr_repo = None
859 857 if stat.pull_request:
860 858 pr_id = stat.pull_request.pull_request_id
861 859 pr_repo = stat.pull_request.other_repo.repo_name
862 860 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
863 861 pr_id, pr_repo]
864 862 return grouped
865 863
866 864 #==========================================================================
867 865 # SCM CACHE INSTANCE
868 866 #==========================================================================
869 867
870 868 @property
871 869 def invalidate(self):
872 870 return CacheInvalidation.invalidate(self.repo_name)
873 871
874 872 def set_invalidate(self):
875 873 """
876 874 set a cache for invalidation for this instance
877 875 """
878 876 CacheInvalidation.set_invalidate(self.repo_name)
879 877
880 878 @LazyProperty
881 879 def scm_instance(self):
882 880 return self.__get_instance()
883 881
884 882 def scm_instance_cached(self, cache_map=None):
885 883 @cache_region('long_term')
886 884 def _c(repo_name):
887 885 return self.__get_instance()
888 886 rn = self.repo_name
889 887 log.debug('Getting cached instance of repo')
890 888
891 889 if cache_map:
892 890 # get using prefilled cache_map
893 891 invalidate_repo = cache_map[self.repo_name]
894 892 if invalidate_repo:
895 893 invalidate_repo = (None if invalidate_repo.cache_active
896 894 else invalidate_repo)
897 895 else:
898 896 # get from invalidate
899 897 invalidate_repo = self.invalidate
900 898
901 899 if invalidate_repo is not None:
902 900 region_invalidate(_c, None, rn)
903 901 # update our cache
904 902 CacheInvalidation.set_valid(invalidate_repo.cache_key)
905 903 return _c(rn)
906 904
907 905 def __get_instance(self):
908 906 repo_full_path = self.repo_full_path
909 907 try:
910 908 alias = get_scm(repo_full_path)[0]
911 909 log.debug('Creating instance of %s repository' % alias)
912 910 backend = get_backend(alias)
913 911 except VCSError:
914 912 log.error(traceback.format_exc())
915 913 log.error('Perhaps this repository is in db and not in '
916 914 'filesystem run rescan repositories with '
917 915 '"destroy old data " option from admin panel')
918 916 return
919 917
920 918 if alias == 'hg':
921 919
922 920 repo = backend(safe_str(repo_full_path), create=False,
923 921 baseui=self._ui)
924 922 # skip hidden web repository
925 923 if repo._get_hidden():
926 924 return
927 925 else:
928 926 repo = backend(repo_full_path, create=False)
929 927
930 928 return repo
931 929
932 930
933 931 class RepoGroup(Base, BaseModel):
934 932 __tablename__ = 'groups'
935 933 __table_args__ = (
936 934 UniqueConstraint('group_name', 'group_parent_id'),
937 935 CheckConstraint('group_id != group_parent_id'),
938 936 {'extend_existing': True, 'mysql_engine': 'InnoDB',
939 937 'mysql_charset': 'utf8'},
940 938 )
941 939 __mapper_args__ = {'order_by': 'group_name'}
942 940
943 941 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
944 942 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
945 943 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
946 944 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
947 945
948 946 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
949 947 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
950 948
951 949 parent_group = relationship('RepoGroup', remote_side=group_id)
952 950
953 951 def __init__(self, group_name='', parent_group=None):
954 952 self.group_name = group_name
955 953 self.parent_group = parent_group
956 954
957 955 def __unicode__(self):
958 956 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
959 957 self.group_name)
960 958
961 959 @classmethod
962 960 def groups_choices(cls):
963 961 from webhelpers.html import literal as _literal
964 962 repo_groups = [('', '')]
965 963 sep = ' &raquo; '
966 964 _name = lambda k: _literal(sep.join(k))
967 965
968 966 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
969 967 for x in cls.query().all()])
970 968
971 969 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
972 970 return repo_groups
973 971
974 972 @classmethod
975 973 def url_sep(cls):
976 974 return URL_SEP
977 975
978 976 @classmethod
979 977 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
980 978 if case_insensitive:
981 979 gr = cls.query()\
982 980 .filter(cls.group_name.ilike(group_name))
983 981 else:
984 982 gr = cls.query()\
985 983 .filter(cls.group_name == group_name)
986 984 if cache:
987 985 gr = gr.options(FromCache(
988 986 "sql_cache_short",
989 987 "get_group_%s" % _hash_key(group_name)
990 988 )
991 989 )
992 990 return gr.scalar()
993 991
994 992 @property
995 993 def parents(self):
996 994 parents_recursion_limit = 5
997 995 groups = []
998 996 if self.parent_group is None:
999 997 return groups
1000 998 cur_gr = self.parent_group
1001 999 groups.insert(0, cur_gr)
1002 1000 cnt = 0
1003 1001 while 1:
1004 1002 cnt += 1
1005 1003 gr = getattr(cur_gr, 'parent_group', None)
1006 1004 cur_gr = cur_gr.parent_group
1007 1005 if gr is None:
1008 1006 break
1009 1007 if cnt == parents_recursion_limit:
1010 1008 # this will prevent accidental infinit loops
1011 1009 log.error('group nested more than %s' %
1012 1010 parents_recursion_limit)
1013 1011 break
1014 1012
1015 1013 groups.insert(0, gr)
1016 1014 return groups
1017 1015
1018 1016 @property
1019 1017 def children(self):
1020 1018 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1021 1019
1022 1020 @property
1023 1021 def name(self):
1024 1022 return self.group_name.split(RepoGroup.url_sep())[-1]
1025 1023
1026 1024 @property
1027 1025 def full_path(self):
1028 1026 return self.group_name
1029 1027
1030 1028 @property
1031 1029 def full_path_splitted(self):
1032 1030 return self.group_name.split(RepoGroup.url_sep())
1033 1031
1034 1032 @property
1035 1033 def repositories(self):
1036 1034 return Repository.query()\
1037 1035 .filter(Repository.group == self)\
1038 1036 .order_by(Repository.repo_name)
1039 1037
1040 1038 @property
1041 1039 def repositories_recursive_count(self):
1042 1040 cnt = self.repositories.count()
1043 1041
1044 1042 def children_count(group):
1045 1043 cnt = 0
1046 1044 for child in group.children:
1047 1045 cnt += child.repositories.count()
1048 1046 cnt += children_count(child)
1049 1047 return cnt
1050 1048
1051 1049 return cnt + children_count(self)
1052 1050
1053 1051 def get_new_name(self, group_name):
1054 1052 """
1055 1053 returns new full group name based on parent and new name
1056 1054
1057 1055 :param group_name:
1058 1056 """
1059 1057 path_prefix = (self.parent_group.full_path_splitted if
1060 1058 self.parent_group else [])
1061 1059 return RepoGroup.url_sep().join(path_prefix + [group_name])
1062 1060
1063 1061
1064 1062 class Permission(Base, BaseModel):
1065 1063 __tablename__ = 'permissions'
1066 1064 __table_args__ = (
1067 1065 Index('p_perm_name_idx', 'permission_name'),
1068 1066 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1069 1067 'mysql_charset': 'utf8'},
1070 1068 )
1071 1069 PERMS = [
1072 1070 ('repository.none', _('Repository no access')),
1073 1071 ('repository.read', _('Repository read access')),
1074 1072 ('repository.write', _('Repository write access')),
1075 1073 ('repository.admin', _('Repository admin access')),
1076 1074
1077 1075 ('group.none', _('Repositories Group no access')),
1078 1076 ('group.read', _('Repositories Group read access')),
1079 1077 ('group.write', _('Repositories Group write access')),
1080 1078 ('group.admin', _('Repositories Group admin access')),
1081 1079
1082 1080 ('hg.admin', _('RhodeCode Administrator')),
1083 1081 ('hg.create.none', _('Repository creation disabled')),
1084 1082 ('hg.create.repository', _('Repository creation enabled')),
1085 1083 ('hg.register.none', _('Register disabled')),
1086 1084 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1087 1085 'with manual activation')),
1088 1086
1089 1087 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1090 1088 'with auto activation')),
1091 1089 ]
1092 1090
1093 1091 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1094 1092 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1095 1093 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1096 1094
1097 1095 def __unicode__(self):
1098 1096 return u"<%s('%s:%s')>" % (
1099 1097 self.__class__.__name__, self.permission_id, self.permission_name
1100 1098 )
1101 1099
1102 1100 @classmethod
1103 1101 def get_by_key(cls, key):
1104 1102 return cls.query().filter(cls.permission_name == key).scalar()
1105 1103
1106 1104 @classmethod
1107 1105 def get_default_perms(cls, default_user_id):
1108 1106 q = Session().query(UserRepoToPerm, Repository, cls)\
1109 1107 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1110 1108 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1111 1109 .filter(UserRepoToPerm.user_id == default_user_id)
1112 1110
1113 1111 return q.all()
1114 1112
1115 1113 @classmethod
1116 1114 def get_default_group_perms(cls, default_user_id):
1117 1115 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1118 1116 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1119 1117 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1120 1118 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1121 1119
1122 1120 return q.all()
1123 1121
1124 1122
1125 1123 class UserRepoToPerm(Base, BaseModel):
1126 1124 __tablename__ = 'repo_to_perm'
1127 1125 __table_args__ = (
1128 1126 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1129 1127 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1130 1128 'mysql_charset': 'utf8'}
1131 1129 )
1132 1130 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1133 1131 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1134 1132 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1135 1133 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1136 1134
1137 1135 user = relationship('User')
1138 1136 repository = relationship('Repository')
1139 1137 permission = relationship('Permission')
1140 1138
1141 1139 @classmethod
1142 1140 def create(cls, user, repository, permission):
1143 1141 n = cls()
1144 1142 n.user = user
1145 1143 n.repository = repository
1146 1144 n.permission = permission
1147 1145 Session().add(n)
1148 1146 return n
1149 1147
1150 1148 def __unicode__(self):
1151 1149 return u'<user:%s => %s >' % (self.user, self.repository)
1152 1150
1153 1151
1154 1152 class UserToPerm(Base, BaseModel):
1155 1153 __tablename__ = 'user_to_perm'
1156 1154 __table_args__ = (
1157 1155 UniqueConstraint('user_id', 'permission_id'),
1158 1156 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1159 1157 'mysql_charset': 'utf8'}
1160 1158 )
1161 1159 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1162 1160 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1163 1161 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1164 1162
1165 1163 user = relationship('User')
1166 1164 permission = relationship('Permission', lazy='joined')
1167 1165
1168 1166
1169 1167 class UsersGroupRepoToPerm(Base, BaseModel):
1170 1168 __tablename__ = 'users_group_repo_to_perm'
1171 1169 __table_args__ = (
1172 1170 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1173 1171 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1174 1172 'mysql_charset': 'utf8'}
1175 1173 )
1176 1174 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1177 1175 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1178 1176 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1179 1177 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1180 1178
1181 1179 users_group = relationship('UsersGroup')
1182 1180 permission = relationship('Permission')
1183 1181 repository = relationship('Repository')
1184 1182
1185 1183 @classmethod
1186 1184 def create(cls, users_group, repository, permission):
1187 1185 n = cls()
1188 1186 n.users_group = users_group
1189 1187 n.repository = repository
1190 1188 n.permission = permission
1191 1189 Session().add(n)
1192 1190 return n
1193 1191
1194 1192 def __unicode__(self):
1195 1193 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1196 1194
1197 1195
1198 1196 class UsersGroupToPerm(Base, BaseModel):
1199 1197 __tablename__ = 'users_group_to_perm'
1200 1198 __table_args__ = (
1201 1199 UniqueConstraint('users_group_id', 'permission_id',),
1202 1200 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1203 1201 'mysql_charset': 'utf8'}
1204 1202 )
1205 1203 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1206 1204 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1207 1205 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1208 1206
1209 1207 users_group = relationship('UsersGroup')
1210 1208 permission = relationship('Permission')
1211 1209
1212 1210
1213 1211 class UserRepoGroupToPerm(Base, BaseModel):
1214 1212 __tablename__ = 'user_repo_group_to_perm'
1215 1213 __table_args__ = (
1216 1214 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1217 1215 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1218 1216 'mysql_charset': 'utf8'}
1219 1217 )
1220 1218
1221 1219 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1222 1220 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1223 1221 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1224 1222 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1225 1223
1226 1224 user = relationship('User')
1227 1225 group = relationship('RepoGroup')
1228 1226 permission = relationship('Permission')
1229 1227
1230 1228
1231 1229 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1232 1230 __tablename__ = 'users_group_repo_group_to_perm'
1233 1231 __table_args__ = (
1234 1232 UniqueConstraint('users_group_id', 'group_id'),
1235 1233 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1236 1234 'mysql_charset': 'utf8'}
1237 1235 )
1238 1236
1239 1237 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)
1240 1238 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1241 1239 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1242 1240 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1243 1241
1244 1242 users_group = relationship('UsersGroup')
1245 1243 permission = relationship('Permission')
1246 1244 group = relationship('RepoGroup')
1247 1245
1248 1246
1249 1247 class Statistics(Base, BaseModel):
1250 1248 __tablename__ = 'statistics'
1251 1249 __table_args__ = (
1252 1250 UniqueConstraint('repository_id'),
1253 1251 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1254 1252 'mysql_charset': 'utf8'}
1255 1253 )
1256 1254 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1257 1255 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1258 1256 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1259 1257 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1260 1258 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1261 1259 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1262 1260
1263 1261 repository = relationship('Repository', single_parent=True)
1264 1262
1265 1263
1266 1264 class UserFollowing(Base, BaseModel):
1267 1265 __tablename__ = 'user_followings'
1268 1266 __table_args__ = (
1269 1267 UniqueConstraint('user_id', 'follows_repository_id'),
1270 1268 UniqueConstraint('user_id', 'follows_user_id'),
1271 1269 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1272 1270 'mysql_charset': 'utf8'}
1273 1271 )
1274 1272
1275 1273 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1276 1274 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1277 1275 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1278 1276 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1279 1277 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1280 1278
1281 1279 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1282 1280
1283 1281 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1284 1282 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1285 1283
1286 1284 @classmethod
1287 1285 def get_repo_followers(cls, repo_id):
1288 1286 return cls.query().filter(cls.follows_repo_id == repo_id)
1289 1287
1290 1288
1291 1289 class CacheInvalidation(Base, BaseModel):
1292 1290 __tablename__ = 'cache_invalidation'
1293 1291 __table_args__ = (
1294 1292 UniqueConstraint('cache_key'),
1295 1293 Index('key_idx', 'cache_key'),
1296 1294 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1297 1295 'mysql_charset': 'utf8'},
1298 1296 )
1299 1297 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1300 1298 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1301 1299 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1302 1300 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1303 1301
1304 1302 def __init__(self, cache_key, cache_args=''):
1305 1303 self.cache_key = cache_key
1306 1304 self.cache_args = cache_args
1307 1305 self.cache_active = False
1308 1306
1309 1307 def __unicode__(self):
1310 1308 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1311 1309 self.cache_id, self.cache_key)
1312 1310
1313 1311 @classmethod
1314 1312 def clear_cache(cls):
1315 1313 cls.query().delete()
1316 1314
1317 1315 @classmethod
1318 1316 def _get_key(cls, key):
1319 1317 """
1320 1318 Wrapper for generating a key, together with a prefix
1321 1319
1322 1320 :param key:
1323 1321 """
1324 1322 import rhodecode
1325 1323 prefix = ''
1326 1324 iid = rhodecode.CONFIG.get('instance_id')
1327 1325 if iid:
1328 1326 prefix = iid
1329 1327 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1330 1328
1331 1329 @classmethod
1332 1330 def get_by_key(cls, key):
1333 1331 return cls.query().filter(cls.cache_key == key).scalar()
1334 1332
1335 1333 @classmethod
1336 1334 def _get_or_create_key(cls, key, prefix, org_key):
1337 1335 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1338 1336 if not inv_obj:
1339 1337 try:
1340 1338 inv_obj = CacheInvalidation(key, org_key)
1341 1339 Session().add(inv_obj)
1342 1340 Session().commit()
1343 1341 except Exception:
1344 1342 log.error(traceback.format_exc())
1345 1343 Session().rollback()
1346 1344 return inv_obj
1347 1345
1348 1346 @classmethod
1349 1347 def invalidate(cls, key):
1350 1348 """
1351 1349 Returns Invalidation object if this given key should be invalidated
1352 1350 None otherwise. `cache_active = False` means that this cache
1353 1351 state is not valid and needs to be invalidated
1354 1352
1355 1353 :param key:
1356 1354 """
1357 1355
1358 1356 key, _prefix, _org_key = cls._get_key(key)
1359 1357 inv = cls._get_or_create_key(key, _prefix, _org_key)
1360 1358
1361 1359 if inv and inv.cache_active is False:
1362 1360 return inv
1363 1361
1364 1362 @classmethod
1365 1363 def set_invalidate(cls, key):
1366 1364 """
1367 1365 Mark this Cache key for invalidation
1368 1366
1369 1367 :param key:
1370 1368 """
1371 1369
1372 1370 key, _prefix, _org_key = cls._get_key(key)
1373 1371 inv_objs = Session().query(cls).filter(cls.cache_args == _org_key).all()
1374 1372 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1375 1373 _org_key))
1376 1374 try:
1377 1375 for inv_obj in inv_objs:
1378 1376 if inv_obj:
1379 1377 inv_obj.cache_active = False
1380 1378
1381 1379 Session().add(inv_obj)
1382 1380 Session().commit()
1383 1381 except Exception:
1384 1382 log.error(traceback.format_exc())
1385 1383 Session().rollback()
1386 1384
1387 1385 @classmethod
1388 1386 def set_valid(cls, key):
1389 1387 """
1390 1388 Mark this cache key as active and currently cached
1391 1389
1392 1390 :param key:
1393 1391 """
1394 1392 inv_obj = cls.get_by_key(key)
1395 1393 inv_obj.cache_active = True
1396 1394 Session().add(inv_obj)
1397 1395 Session().commit()
1398 1396
1399 1397 @classmethod
1400 1398 def get_cache_map(cls):
1401 1399
1402 1400 class cachemapdict(dict):
1403 1401
1404 1402 def __init__(self, *args, **kwargs):
1405 1403 fixkey = kwargs.get('fixkey')
1406 1404 if fixkey:
1407 1405 del kwargs['fixkey']
1408 1406 self.fixkey = fixkey
1409 1407 super(cachemapdict, self).__init__(*args, **kwargs)
1410 1408
1411 1409 def __getattr__(self, name):
1412 1410 key = name
1413 1411 if self.fixkey:
1414 1412 key, _prefix, _org_key = cls._get_key(key)
1415 1413 if key in self.__dict__:
1416 1414 return self.__dict__[key]
1417 1415 else:
1418 1416 return self[key]
1419 1417
1420 1418 def __getitem__(self, key):
1421 1419 if self.fixkey:
1422 1420 key, _prefix, _org_key = cls._get_key(key)
1423 1421 try:
1424 1422 return super(cachemapdict, self).__getitem__(key)
1425 1423 except KeyError:
1426 1424 return
1427 1425
1428 1426 cache_map = cachemapdict(fixkey=True)
1429 1427 for obj in cls.query().all():
1430 1428 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1431 1429 return cache_map
1432 1430
1433 1431
1434 1432 class ChangesetComment(Base, BaseModel):
1435 1433 __tablename__ = 'changeset_comments'
1436 1434 __table_args__ = (
1437 1435 Index('cc_revision_idx', 'revision'),
1438 1436 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1439 1437 'mysql_charset': 'utf8'},
1440 1438 )
1441 1439 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1442 1440 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1443 1441 revision = Column('revision', String(40), nullable=True)
1444 1442 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1445 1443 line_no = Column('line_no', Unicode(10), nullable=True)
1446 1444 f_path = Column('f_path', Unicode(1000), nullable=True)
1447 1445 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1448 1446 text = Column('text', Unicode(25000), nullable=False)
1449 1447 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1450 1448
1451 1449 author = relationship('User', lazy='joined')
1452 1450 repo = relationship('Repository')
1453 1451 status_change = relationship('ChangesetStatus', uselist=False)
1454 1452 pull_request = relationship('PullRequest', lazy='joined')
1455 1453
1456 1454 @classmethod
1457 1455 def get_users(cls, revision=None, pull_request_id=None):
1458 1456 """
1459 1457 Returns user associated with this ChangesetComment. ie those
1460 1458 who actually commented
1461 1459
1462 1460 :param cls:
1463 1461 :param revision:
1464 1462 """
1465 1463 q = Session().query(User)\
1466 1464 .join(ChangesetComment.author)
1467 1465 if revision:
1468 1466 q = q.filter(cls.revision == revision)
1469 1467 elif pull_request_id:
1470 1468 q = q.filter(cls.pull_request_id == pull_request_id)
1471 1469 return q.all()
1472 1470
1473 1471
1474 1472 class ChangesetStatus(Base, BaseModel):
1475 1473 __tablename__ = 'changeset_statuses'
1476 1474 __table_args__ = (
1477 1475 Index('cs_revision_idx', 'revision'),
1478 1476 Index('cs_version_idx', 'version'),
1479 1477 UniqueConstraint('repo_id', 'revision', 'version'),
1480 1478 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1481 1479 'mysql_charset': 'utf8'}
1482 1480 )
1483 1481 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1484 1482 STATUS_APPROVED = 'approved'
1485 1483 STATUS_REJECTED = 'rejected'
1486 1484 STATUS_UNDER_REVIEW = 'under_review'
1487 1485
1488 1486 STATUSES = [
1489 1487 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1490 1488 (STATUS_APPROVED, _("Approved")),
1491 1489 (STATUS_REJECTED, _("Rejected")),
1492 1490 (STATUS_UNDER_REVIEW, _("Under Review")),
1493 1491 ]
1494 1492
1495 1493 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1496 1494 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1497 1495 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1498 1496 revision = Column('revision', String(40), nullable=False)
1499 1497 status = Column('status', String(128), nullable=False, default=DEFAULT)
1500 1498 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1501 1499 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1502 1500 version = Column('version', Integer(), nullable=False, default=0)
1503 1501 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1504 1502
1505 1503 author = relationship('User', lazy='joined')
1506 1504 repo = relationship('Repository')
1507 1505 comment = relationship('ChangesetComment', lazy='joined')
1508 1506 pull_request = relationship('PullRequest', lazy='joined')
1509 1507
1510 1508 def __unicode__(self):
1511 1509 return u"<%s('%s:%s')>" % (
1512 1510 self.__class__.__name__,
1513 1511 self.status, self.author
1514 1512 )
1515 1513
1516 1514 @classmethod
1517 1515 def get_status_lbl(cls, value):
1518 1516 return dict(cls.STATUSES).get(value)
1519 1517
1520 1518 @property
1521 1519 def status_lbl(self):
1522 1520 return ChangesetStatus.get_status_lbl(self.status)
1523 1521
1524 1522
1525 1523 class PullRequest(Base, BaseModel):
1526 1524 __tablename__ = 'pull_requests'
1527 1525 __table_args__ = (
1528 1526 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1529 1527 'mysql_charset': 'utf8'},
1530 1528 )
1531 1529
1532 1530 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1533 1531 title = Column('title', Unicode(256), nullable=True)
1534 1532 description = Column('description', Unicode(10240), nullable=True)
1535 1533 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1536 1534 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1537 1535 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1538 1536 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1539 1537 org_ref = Column('org_ref', Unicode(256), nullable=False)
1540 1538 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1541 1539 other_ref = Column('other_ref', Unicode(256), nullable=False)
1542 1540
1543 1541 @hybrid_property
1544 1542 def revisions(self):
1545 1543 return self._revisions.split(':')
1546 1544
1547 1545 @revisions.setter
1548 1546 def revisions(self, val):
1549 1547 self._revisions = ':'.join(val)
1550 1548
1551 1549 author = relationship('User', lazy='joined')
1552 1550 reviewers = relationship('PullRequestReviewers')
1553 1551 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1554 1552 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1555 1553
1556 1554 def __json__(self):
1557 1555 return dict(
1558 1556 revisions=self.revisions
1559 1557 )
1560 1558
1561 1559
1562 1560 class PullRequestReviewers(Base, BaseModel):
1563 1561 __tablename__ = 'pull_request_reviewers'
1564 1562 __table_args__ = (
1565 1563 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1566 1564 'mysql_charset': 'utf8'},
1567 1565 )
1568 1566
1569 1567 def __init__(self, user=None, pull_request=None):
1570 1568 self.user = user
1571 1569 self.pull_request = pull_request
1572 1570
1573 1571 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1574 1572 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1575 1573 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1576 1574
1577 1575 user = relationship('User')
1578 1576 pull_request = relationship('PullRequest')
1579 1577
1580 1578
1581 1579 class Notification(Base, BaseModel):
1582 1580 __tablename__ = 'notifications'
1583 1581 __table_args__ = (
1584 1582 Index('notification_type_idx', 'type'),
1585 1583 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1586 1584 'mysql_charset': 'utf8'},
1587 1585 )
1588 1586
1589 1587 TYPE_CHANGESET_COMMENT = u'cs_comment'
1590 1588 TYPE_MESSAGE = u'message'
1591 1589 TYPE_MENTION = u'mention'
1592 1590 TYPE_REGISTRATION = u'registration'
1593 1591 TYPE_PULL_REQUEST = u'pull_request'
1594 1592 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1595 1593
1596 1594 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1597 1595 subject = Column('subject', Unicode(512), nullable=True)
1598 1596 body = Column('body', Unicode(50000), nullable=True)
1599 1597 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1600 1598 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1601 1599 type_ = Column('type', Unicode(256))
1602 1600
1603 1601 created_by_user = relationship('User')
1604 1602 notifications_to_users = relationship('UserNotification', lazy='joined',
1605 1603 cascade="all, delete, delete-orphan")
1606 1604
1607 1605 @property
1608 1606 def recipients(self):
1609 1607 return [x.user for x in UserNotification.query()\
1610 1608 .filter(UserNotification.notification == self)\
1611 1609 .order_by(UserNotification.user).all()]
1612 1610
1613 1611 @classmethod
1614 1612 def create(cls, created_by, subject, body, recipients, type_=None):
1615 1613 if type_ is None:
1616 1614 type_ = Notification.TYPE_MESSAGE
1617 1615
1618 1616 notification = cls()
1619 1617 notification.created_by_user = created_by
1620 1618 notification.subject = subject
1621 1619 notification.body = body
1622 1620 notification.type_ = type_
1623 1621 notification.created_on = datetime.datetime.now()
1624 1622
1625 1623 for u in recipients:
1626 1624 assoc = UserNotification()
1627 1625 assoc.notification = notification
1628 1626 u.notifications.append(assoc)
1629 1627 Session().add(notification)
1630 1628 return notification
1631 1629
1632 1630 @property
1633 1631 def description(self):
1634 1632 from rhodecode.model.notification import NotificationModel
1635 1633 return NotificationModel().make_description(self)
1636 1634
1637 1635
1638 1636 class UserNotification(Base, BaseModel):
1639 1637 __tablename__ = 'user_to_notification'
1640 1638 __table_args__ = (
1641 1639 UniqueConstraint('user_id', 'notification_id'),
1642 1640 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1643 1641 'mysql_charset': 'utf8'}
1644 1642 )
1645 1643 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1646 1644 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1647 1645 read = Column('read', Boolean, default=False)
1648 1646 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1649 1647
1650 1648 user = relationship('User', lazy="joined")
1651 1649 notification = relationship('Notification', lazy="joined",
1652 1650 order_by=lambda: Notification.created_on.desc(),)
1653 1651
1654 1652 def mark_as_read(self):
1655 1653 self.read = True
1656 1654 Session().add(self)
1657 1655
1658 1656
1659 1657 class DbMigrateVersion(Base, BaseModel):
1660 1658 __tablename__ = 'db_migrate_version'
1661 1659 __table_args__ = (
1662 1660 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1663 1661 'mysql_charset': 'utf8'},
1664 1662 )
1665 1663 repository_id = Column('repository_id', String(250), primary_key=True)
1666 1664 repository_path = Column('repository_path', Text)
1667 1665 version = Column('version', Integer)
@@ -1,309 +1,310 b''
1 1 """ this is forms validation classes
2 2 http://formencode.org/module-formencode.validators.html
3 3 for list off all availible validators
4 4
5 5 we can create our own validators
6 6
7 7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 8 pre_validators [] These validators will be applied before the schema
9 9 chained_validators [] These validators will be applied after the schema
10 10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
13 13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
14 14
15 15
16 16 <name> = formencode.validators.<name of validator>
17 17 <name> must equal form name
18 18 list=[1,2,3,4,5]
19 19 for SELECT use formencode.All(OneOf(list), Int())
20 20
21 21 """
22 22 import logging
23 23
24 24 import formencode
25 25 from formencode import All
26 26
27 27 from pylons.i18n.translation import _
28 28
29 29 from rhodecode.model import validators as v
30 30 from rhodecode import BACKENDS
31 31
32 32 log = logging.getLogger(__name__)
33 33
34 34
35 35 class LoginForm(formencode.Schema):
36 36 allow_extra_fields = True
37 37 filter_extra_fields = True
38 38 username = v.UnicodeString(
39 39 strip=True,
40 40 min=1,
41 41 not_empty=True,
42 42 messages={
43 43 'empty': _(u'Please enter a login'),
44 44 'tooShort': _(u'Enter a value %(min)i characters long or more')}
45 45 )
46 46
47 47 password = v.UnicodeString(
48 48 strip=False,
49 49 min=3,
50 50 not_empty=True,
51 51 messages={
52 52 'empty': _(u'Please enter a password'),
53 53 'tooShort': _(u'Enter %(min)i characters or more')}
54 54 )
55 55
56 56 remember = v.StringBoolean(if_missing=False)
57 57
58 58 chained_validators = [v.ValidAuth()]
59 59
60 60
61 61 def UserForm(edit=False, old_data={}):
62 62 class _UserForm(formencode.Schema):
63 63 allow_extra_fields = True
64 64 filter_extra_fields = True
65 65 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
66 66 v.ValidUsername(edit, old_data))
67 67 if edit:
68 68 new_password = All(
69 v.ValidPassword(),
69 70 v.UnicodeString(strip=False, min=6, not_empty=False)
70 71 )
71 72 password_confirmation = All(
72 73 v.ValidPassword(),
73 74 v.UnicodeString(strip=False, min=6, not_empty=False),
74 75 )
75 76 admin = v.StringBoolean(if_missing=False)
76 77 else:
77 78 password = All(
78 79 v.ValidPassword(),
79 80 v.UnicodeString(strip=False, min=6, not_empty=True)
80 81 )
81 82 password_confirmation = All(
82 83 v.ValidPassword(),
83 84 v.UnicodeString(strip=False, min=6, not_empty=False)
84 85 )
85 86
86 87 active = v.StringBoolean(if_missing=False)
87 name = v.UnicodeString(strip=True, min=1, not_empty=False)
88 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
88 89 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
89 90 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
90 91
91 92 chained_validators = [v.ValidPasswordsMatch()]
92 93
93 94 return _UserForm
94 95
95 96
96 97 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
97 98 class _UsersGroupForm(formencode.Schema):
98 99 allow_extra_fields = True
99 100 filter_extra_fields = True
100 101
101 102 users_group_name = All(
102 103 v.UnicodeString(strip=True, min=1, not_empty=True),
103 104 v.ValidUsersGroup(edit, old_data)
104 105 )
105 106
106 107 users_group_active = v.StringBoolean(if_missing=False)
107 108
108 109 if edit:
109 110 users_group_members = v.OneOf(
110 111 available_members, hideList=False, testValueList=True,
111 112 if_missing=None, not_empty=False
112 113 )
113 114
114 115 return _UsersGroupForm
115 116
116 117
117 118 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
118 119 class _ReposGroupForm(formencode.Schema):
119 120 allow_extra_fields = True
120 121 filter_extra_fields = False
121 122
122 123 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
123 124 v.SlugifyName())
124 125 group_description = v.UnicodeString(strip=True, min=1,
125 126 not_empty=True)
126 127 group_parent_id = v.OneOf(available_groups, hideList=False,
127 128 testValueList=True,
128 129 if_missing=None, not_empty=False)
129 130
130 131 chained_validators = [v.ValidReposGroup(edit, old_data),
131 132 v.ValidPerms('group')]
132 133
133 134 return _ReposGroupForm
134 135
135 136
136 137 def RegisterForm(edit=False, old_data={}):
137 138 class _RegisterForm(formencode.Schema):
138 139 allow_extra_fields = True
139 140 filter_extra_fields = True
140 141 username = All(
141 142 v.ValidUsername(edit, old_data),
142 143 v.UnicodeString(strip=True, min=1, not_empty=True)
143 144 )
144 145 password = All(
145 146 v.ValidPassword(),
146 147 v.UnicodeString(strip=False, min=6, not_empty=True)
147 148 )
148 149 password_confirmation = All(
149 150 v.ValidPassword(),
150 151 v.UnicodeString(strip=False, min=6, not_empty=True)
151 152 )
152 153 active = v.StringBoolean(if_missing=False)
153 154 name = v.UnicodeString(strip=True, min=1, not_empty=False)
154 155 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
155 156 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
156 157
157 158 chained_validators = [v.ValidPasswordsMatch()]
158 159
159 160 return _RegisterForm
160 161
161 162
162 163 def PasswordResetForm():
163 164 class _PasswordResetForm(formencode.Schema):
164 165 allow_extra_fields = True
165 166 filter_extra_fields = True
166 167 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
167 168 return _PasswordResetForm
168 169
169 170
170 171 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
171 172 repo_groups=[], landing_revs=[]):
172 173 class _RepoForm(formencode.Schema):
173 174 allow_extra_fields = True
174 175 filter_extra_fields = False
175 176 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
176 177 v.SlugifyName())
177 178 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
178 179 repo_group = v.OneOf(repo_groups, hideList=True)
179 180 repo_type = v.OneOf(supported_backends)
180 181 description = v.UnicodeString(strip=True, min=1, not_empty=False)
181 182 private = v.StringBoolean(if_missing=False)
182 183 enable_statistics = v.StringBoolean(if_missing=False)
183 184 enable_downloads = v.StringBoolean(if_missing=False)
184 185 landing_rev = v.OneOf(landing_revs, hideList=True)
185 186
186 187 if edit:
187 188 #this is repo owner
188 189 user = All(v.UnicodeString(not_empty=True), v.ValidRepoUser())
189 190
190 191 chained_validators = [v.ValidCloneUri(),
191 192 v.ValidRepoName(edit, old_data),
192 193 v.ValidPerms()]
193 194 return _RepoForm
194 195
195 196
196 197 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
197 198 repo_groups=[], landing_revs=[]):
198 199 class _RepoForkForm(formencode.Schema):
199 200 allow_extra_fields = True
200 201 filter_extra_fields = False
201 202 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
202 203 v.SlugifyName())
203 204 repo_group = v.OneOf(repo_groups, hideList=True)
204 205 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
205 206 description = v.UnicodeString(strip=True, min=1, not_empty=True)
206 207 private = v.StringBoolean(if_missing=False)
207 208 copy_permissions = v.StringBoolean(if_missing=False)
208 209 update_after_clone = v.StringBoolean(if_missing=False)
209 210 fork_parent_id = v.UnicodeString()
210 211 chained_validators = [v.ValidForkName(edit, old_data)]
211 212 landing_rev = v.OneOf(landing_revs, hideList=True)
212 213
213 214 return _RepoForkForm
214 215
215 216
216 217 def RepoSettingsForm(edit=False, old_data={},
217 218 supported_backends=BACKENDS.keys(), repo_groups=[],
218 219 landing_revs=[]):
219 220 class _RepoForm(formencode.Schema):
220 221 allow_extra_fields = True
221 222 filter_extra_fields = False
222 223 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
223 224 v.SlugifyName())
224 225 description = v.UnicodeString(strip=True, min=1, not_empty=True)
225 226 repo_group = v.OneOf(repo_groups, hideList=True)
226 227 private = v.StringBoolean(if_missing=False)
227 228 landing_rev = v.OneOf(landing_revs, hideList=True)
228 229 chained_validators = [v.ValidRepoName(edit, old_data), v.ValidPerms(),
229 230 v.ValidSettings()]
230 231 return _RepoForm
231 232
232 233
233 234 def ApplicationSettingsForm():
234 235 class _ApplicationSettingsForm(formencode.Schema):
235 236 allow_extra_fields = True
236 237 filter_extra_fields = False
237 238 rhodecode_title = v.UnicodeString(strip=True, min=1, not_empty=True)
238 239 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
239 240 rhodecode_ga_code = v.UnicodeString(strip=True, min=1, not_empty=False)
240 241
241 242 return _ApplicationSettingsForm
242 243
243 244
244 245 def ApplicationUiSettingsForm():
245 246 class _ApplicationUiSettingsForm(formencode.Schema):
246 247 allow_extra_fields = True
247 248 filter_extra_fields = False
248 249 web_push_ssl = v.OneOf(['true', 'false'], if_missing='false')
249 250 paths_root_path = All(
250 251 v.ValidPath(),
251 252 v.UnicodeString(strip=True, min=1, not_empty=True)
252 253 )
253 254 hooks_changegroup_update = v.OneOf(['True', 'False'],
254 255 if_missing=False)
255 256 hooks_changegroup_repo_size = v.OneOf(['True', 'False'],
256 257 if_missing=False)
257 258 hooks_changegroup_push_logger = v.OneOf(['True', 'False'],
258 259 if_missing=False)
259 260 hooks_preoutgoing_pull_logger = v.OneOf(['True', 'False'],
260 261 if_missing=False)
261 262
262 263 return _ApplicationUiSettingsForm
263 264
264 265
265 266 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
266 267 class _DefaultPermissionsForm(formencode.Schema):
267 268 allow_extra_fields = True
268 269 filter_extra_fields = True
269 270 overwrite_default = v.StringBoolean(if_missing=False)
270 271 anonymous = v.OneOf(['True', 'False'], if_missing=False)
271 272 default_perm = v.OneOf(perms_choices)
272 273 default_register = v.OneOf(register_choices)
273 274 default_create = v.OneOf(create_choices)
274 275
275 276 return _DefaultPermissionsForm
276 277
277 278
278 279 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices,
279 280 tls_kind_choices):
280 281 class _LdapSettingsForm(formencode.Schema):
281 282 allow_extra_fields = True
282 283 filter_extra_fields = True
283 284 #pre_validators = [LdapLibValidator]
284 285 ldap_active = v.StringBoolean(if_missing=False)
285 286 ldap_host = v.UnicodeString(strip=True,)
286 287 ldap_port = v.Number(strip=True,)
287 288 ldap_tls_kind = v.OneOf(tls_kind_choices)
288 289 ldap_tls_reqcert = v.OneOf(tls_reqcert_choices)
289 290 ldap_dn_user = v.UnicodeString(strip=True,)
290 291 ldap_dn_pass = v.UnicodeString(strip=True,)
291 292 ldap_base_dn = v.UnicodeString(strip=True,)
292 293 ldap_filter = v.UnicodeString(strip=True,)
293 294 ldap_search_scope = v.OneOf(search_scope_choices)
294 295 ldap_attr_login = All(
295 296 v.AttrLoginValidator(),
296 297 v.UnicodeString(strip=True,)
297 298 )
298 299 ldap_attr_firstname = v.UnicodeString(strip=True,)
299 300 ldap_attr_lastname = v.UnicodeString(strip=True,)
300 301 ldap_attr_email = v.UnicodeString(strip=True,)
301 302
302 303 return _LdapSettingsForm
303 304
304 305
305 306 def UserExtraEmailForm():
306 307 class _UserExtraEmailForm(formencode.Schema):
307 308 email = All(v.UniqSystemEmail(), v.Email)
308 309
309 310 return _UserExtraEmailForm
@@ -1,625 +1,630 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
29 29 from pylons import url
30 30 from pylons.i18n.translation import _
31 31
32 32 from sqlalchemy.exc import DatabaseError
33 33 from sqlalchemy.orm import joinedload
34 34
35 35 from rhodecode.lib.utils2 import safe_unicode, generate_api_key
36 36 from rhodecode.lib.caching_query import FromCache
37 37 from rhodecode.model import BaseModel
38 38 from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
39 39 UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \
40 40 Notification, RepoGroup, UserRepoGroupToPerm, UsersGroupRepoGroupToPerm, \
41 41 UserEmailMap
42 42 from rhodecode.lib.exceptions import DefaultUserException, \
43 43 UserOwnsReposException
44 44
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48
49 49 PERM_WEIGHTS = {
50 50 'repository.none': 0,
51 51 'repository.read': 1,
52 52 'repository.write': 3,
53 53 'repository.admin': 4,
54 54 'group.none': 0,
55 55 'group.read': 1,
56 56 'group.write': 3,
57 57 'group.admin': 4,
58 58 }
59 59
60 60
61 61 class UserModel(BaseModel):
62 62 cls = User
63 63
64 64 def get(self, user_id, cache=False):
65 65 user = self.sa.query(User)
66 66 if cache:
67 67 user = user.options(FromCache("sql_cache_short",
68 68 "get_user_%s" % user_id))
69 69 return user.get(user_id)
70 70
71 71 def get_user(self, user):
72 72 return self._get_user(user)
73 73
74 74 def get_by_username(self, username, cache=False, case_insensitive=False):
75 75
76 76 if case_insensitive:
77 77 user = self.sa.query(User).filter(User.username.ilike(username))
78 78 else:
79 79 user = self.sa.query(User)\
80 80 .filter(User.username == username)
81 81 if cache:
82 82 user = user.options(FromCache("sql_cache_short",
83 83 "get_user_%s" % username))
84 84 return user.scalar()
85 85
86 86 def get_by_email(self, email, cache=False, case_insensitive=False):
87 87 return User.get_by_email(email, case_insensitive, cache)
88 88
89 89 def get_by_api_key(self, api_key, cache=False):
90 90 return User.get_by_api_key(api_key, cache)
91 91
92 92 def create(self, form_data):
93 93 from rhodecode.lib.auth import get_crypt_password
94 94 try:
95 95 new_user = User()
96 96 for k, v in form_data.items():
97 97 if k == 'password':
98 98 v = get_crypt_password(v)
99 if k == 'firstname':
100 k = 'name'
99 101 setattr(new_user, k, v)
100 102
101 103 new_user.api_key = generate_api_key(form_data['username'])
102 104 self.sa.add(new_user)
103 105 return new_user
104 106 except:
105 107 log.error(traceback.format_exc())
106 108 raise
107 109
108 110 def create_or_update(self, username, password, email, firstname='',
109 111 lastname='', active=True, admin=False, ldap_dn=None):
110 112 """
111 113 Creates a new instance if not found, or updates current one
112 114
113 115 :param username:
114 116 :param password:
115 117 :param email:
116 118 :param active:
117 119 :param firstname:
118 120 :param lastname:
119 121 :param active:
120 122 :param admin:
121 123 :param ldap_dn:
122 124 """
123 125
124 126 from rhodecode.lib.auth import get_crypt_password
125 127
126 128 log.debug('Checking for %s account in RhodeCode database' % username)
127 129 user = User.get_by_username(username, case_insensitive=True)
128 130 if user is None:
129 131 log.debug('creating new user %s' % username)
130 132 new_user = User()
131 133 edit = False
132 134 else:
133 135 log.debug('updating user %s' % username)
134 136 new_user = user
135 137 edit = True
136 138
137 139 try:
138 140 new_user.username = username
139 141 new_user.admin = admin
140 142 # set password only if creating an user or password is changed
141 143 if edit is False or user.password != password:
142 144 new_user.password = get_crypt_password(password)
143 145 new_user.api_key = generate_api_key(username)
144 146 new_user.email = email
145 147 new_user.active = active
146 148 new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None
147 149 new_user.name = firstname
148 150 new_user.lastname = lastname
149 151 self.sa.add(new_user)
150 152 return new_user
151 153 except (DatabaseError,):
152 154 log.error(traceback.format_exc())
153 155 raise
154 156
155 157 def create_for_container_auth(self, username, attrs):
156 158 """
157 159 Creates the given user if it's not already in the database
158 160
159 161 :param username:
160 162 :param attrs:
161 163 """
162 164 if self.get_by_username(username, case_insensitive=True) is None:
163 165
164 166 # autogenerate email for container account without one
165 167 generate_email = lambda usr: '%s@container_auth.account' % usr
166 168
167 169 try:
168 170 new_user = User()
169 171 new_user.username = username
170 172 new_user.password = None
171 173 new_user.api_key = generate_api_key(username)
172 174 new_user.email = attrs['email']
173 175 new_user.active = attrs.get('active', True)
174 176 new_user.name = attrs['name'] or generate_email(username)
175 177 new_user.lastname = attrs['lastname']
176 178
177 179 self.sa.add(new_user)
178 180 return new_user
179 181 except (DatabaseError,):
180 182 log.error(traceback.format_exc())
181 183 self.sa.rollback()
182 184 raise
183 185 log.debug('User %s already exists. Skipping creation of account'
184 186 ' for container auth.', username)
185 187 return None
186 188
187 189 def create_ldap(self, username, password, user_dn, attrs):
188 190 """
189 191 Checks if user is in database, if not creates this user marked
190 192 as ldap user
191 193
192 194 :param username:
193 195 :param password:
194 196 :param user_dn:
195 197 :param attrs:
196 198 """
197 199 from rhodecode.lib.auth import get_crypt_password
198 200 log.debug('Checking for such ldap account in RhodeCode database')
199 201 if self.get_by_username(username, case_insensitive=True) is None:
200 202
201 203 # autogenerate email for ldap account without one
202 204 generate_email = lambda usr: '%s@ldap.account' % usr
203 205
204 206 try:
205 207 new_user = User()
206 208 username = username.lower()
207 209 # add ldap account always lowercase
208 210 new_user.username = username
209 211 new_user.password = get_crypt_password(password)
210 212 new_user.api_key = generate_api_key(username)
211 213 new_user.email = attrs['email'] or generate_email(username)
212 214 new_user.active = attrs.get('active', True)
213 215 new_user.ldap_dn = safe_unicode(user_dn)
214 216 new_user.name = attrs['name']
215 217 new_user.lastname = attrs['lastname']
216 218
217 219 self.sa.add(new_user)
218 220 return new_user
219 221 except (DatabaseError,):
220 222 log.error(traceback.format_exc())
221 223 self.sa.rollback()
222 224 raise
223 225 log.debug('this %s user exists skipping creation of ldap account',
224 226 username)
225 227 return None
226 228
227 229 def create_registration(self, form_data):
228 230 from rhodecode.model.notification import NotificationModel
229 231
230 232 try:
231 233 form_data['admin'] = False
232 234 new_user = self.create(form_data)
233 235
234 236 self.sa.add(new_user)
235 237 self.sa.flush()
236 238
237 239 # notification to admins
238 240 subject = _('new user registration')
239 241 body = ('New user registration\n'
240 242 '---------------------\n'
241 243 '- Username: %s\n'
242 244 '- Full Name: %s\n'
243 245 '- Email: %s\n')
244 246 body = body % (new_user.username, new_user.full_name,
245 247 new_user.email)
246 248 edit_url = url('edit_user', id=new_user.user_id, qualified=True)
247 249 kw = {'registered_user_url': edit_url}
248 250 NotificationModel().create(created_by=new_user, subject=subject,
249 251 body=body, recipients=None,
250 252 type_=Notification.TYPE_REGISTRATION,
251 253 email_kwargs=kw)
252 254
253 255 except:
254 256 log.error(traceback.format_exc())
255 257 raise
256 258
257 259 def update(self, user_id, form_data):
258 260 from rhodecode.lib.auth import get_crypt_password
259 261 try:
260 262 user = self.get(user_id, cache=False)
261 263 if user.username == 'default':
262 264 raise DefaultUserException(
263 265 _("You can't Edit this user since it's"
264 266 " crucial for entire application"))
265 267
266 268 for k, v in form_data.items():
267 if k == 'new_password' and v != '':
269 if k == 'new_password' and v:
268 270 user.password = get_crypt_password(v)
269 271 user.api_key = generate_api_key(user.username)
270 272 else:
273 if k == 'firstname':
274 k = 'name'
271 275 setattr(user, k, v)
272
273 276 self.sa.add(user)
274 277 except:
275 278 log.error(traceback.format_exc())
276 279 raise
277 280
278 281 def update_my_account(self, user_id, form_data):
279 282 from rhodecode.lib.auth import get_crypt_password
280 283 try:
281 284 user = self.get(user_id, cache=False)
282 285 if user.username == 'default':
283 286 raise DefaultUserException(
284 287 _("You can't Edit this user since it's"
285 288 " crucial for entire application")
286 289 )
287 290 for k, v in form_data.items():
288 if k == 'new_password' and v != '':
291 if k == 'new_password' and v:
289 292 user.password = get_crypt_password(v)
290 293 user.api_key = generate_api_key(user.username)
291 294 else:
295 if k == 'firstname':
296 k = 'name'
292 297 if k not in ['admin', 'active']:
293 298 setattr(user, k, v)
294 299
295 300 self.sa.add(user)
296 301 except:
297 302 log.error(traceback.format_exc())
298 303 raise
299 304
300 305 def delete(self, user):
301 306 user = self._get_user(user)
302 307
303 308 try:
304 309 if user.username == 'default':
305 310 raise DefaultUserException(
306 311 _(u"You can't remove this user since it's"
307 312 " crucial for entire application")
308 313 )
309 314 if user.repositories:
310 315 repos = [x.repo_name for x in user.repositories]
311 316 raise UserOwnsReposException(
312 317 _(u'user "%s" still owns %s repositories and cannot be '
313 318 'removed. Switch owners or remove those repositories. %s')
314 319 % (user.username, len(repos), ', '.join(repos))
315 320 )
316 321 self.sa.delete(user)
317 322 except:
318 323 log.error(traceback.format_exc())
319 324 raise
320 325
321 326 def reset_password_link(self, data):
322 327 from rhodecode.lib.celerylib import tasks, run_task
323 328 run_task(tasks.send_password_link, data['email'])
324 329
325 330 def reset_password(self, data):
326 331 from rhodecode.lib.celerylib import tasks, run_task
327 332 run_task(tasks.reset_user_password, data['email'])
328 333
329 334 def fill_data(self, auth_user, user_id=None, api_key=None):
330 335 """
331 336 Fetches auth_user by user_id,or api_key if present.
332 337 Fills auth_user attributes with those taken from database.
333 338 Additionally set's is_authenitated if lookup fails
334 339 present in database
335 340
336 341 :param auth_user: instance of user to set attributes
337 342 :param user_id: user id to fetch by
338 343 :param api_key: api key to fetch by
339 344 """
340 345 if user_id is None and api_key is None:
341 346 raise Exception('You need to pass user_id or api_key')
342 347
343 348 try:
344 349 if api_key:
345 350 dbuser = self.get_by_api_key(api_key)
346 351 else:
347 352 dbuser = self.get(user_id)
348 353
349 354 if dbuser is not None and dbuser.active:
350 355 log.debug('filling %s data' % dbuser)
351 356 for k, v in dbuser.get_dict().items():
352 357 setattr(auth_user, k, v)
353 358 else:
354 359 return False
355 360
356 361 except:
357 362 log.error(traceback.format_exc())
358 363 auth_user.is_authenticated = False
359 364 return False
360 365
361 366 return True
362 367
363 368 def fill_perms(self, user):
364 369 """
365 370 Fills user permission attribute with permissions taken from database
366 371 works for permissions given for repositories, and for permissions that
367 372 are granted to groups
368 373
369 374 :param user: user instance to fill his perms
370 375 """
371 376 RK = 'repositories'
372 377 GK = 'repositories_groups'
373 378 GLOBAL = 'global'
374 379 user.permissions[RK] = {}
375 380 user.permissions[GK] = {}
376 381 user.permissions[GLOBAL] = set()
377 382
378 383 #======================================================================
379 384 # fetch default permissions
380 385 #======================================================================
381 386 default_user = User.get_by_username('default', cache=True)
382 387 default_user_id = default_user.user_id
383 388
384 389 default_repo_perms = Permission.get_default_perms(default_user_id)
385 390 default_repo_groups_perms = Permission.get_default_group_perms(default_user_id)
386 391
387 392 if user.is_admin:
388 393 #==================================================================
389 394 # admin user have all default rights for repositories
390 395 # and groups set to admin
391 396 #==================================================================
392 397 user.permissions[GLOBAL].add('hg.admin')
393 398
394 399 # repositories
395 400 for perm in default_repo_perms:
396 401 r_k = perm.UserRepoToPerm.repository.repo_name
397 402 p = 'repository.admin'
398 403 user.permissions[RK][r_k] = p
399 404
400 405 # repositories groups
401 406 for perm in default_repo_groups_perms:
402 407 rg_k = perm.UserRepoGroupToPerm.group.group_name
403 408 p = 'group.admin'
404 409 user.permissions[GK][rg_k] = p
405 410 return user
406 411
407 412 #==================================================================
408 413 # set default permissions first for repositories and groups
409 414 #==================================================================
410 415 uid = user.user_id
411 416
412 417 # default global permissions
413 418 default_global_perms = self.sa.query(UserToPerm)\
414 419 .filter(UserToPerm.user_id == default_user_id)
415 420
416 421 for perm in default_global_perms:
417 422 user.permissions[GLOBAL].add(perm.permission.permission_name)
418 423
419 424 # defaults for repositories, taken from default user
420 425 for perm in default_repo_perms:
421 426 r_k = perm.UserRepoToPerm.repository.repo_name
422 427 if perm.Repository.private and not (perm.Repository.user_id == uid):
423 428 # disable defaults for private repos,
424 429 p = 'repository.none'
425 430 elif perm.Repository.user_id == uid:
426 431 # set admin if owner
427 432 p = 'repository.admin'
428 433 else:
429 434 p = perm.Permission.permission_name
430 435
431 436 user.permissions[RK][r_k] = p
432 437
433 438 # defaults for repositories groups taken from default user permission
434 439 # on given group
435 440 for perm in default_repo_groups_perms:
436 441 rg_k = perm.UserRepoGroupToPerm.group.group_name
437 442 p = perm.Permission.permission_name
438 443 user.permissions[GK][rg_k] = p
439 444
440 445 #==================================================================
441 446 # overwrite defaults with user permissions if any found
442 447 #==================================================================
443 448
444 449 # user global permissions
445 450 user_perms = self.sa.query(UserToPerm)\
446 451 .options(joinedload(UserToPerm.permission))\
447 452 .filter(UserToPerm.user_id == uid).all()
448 453
449 454 for perm in user_perms:
450 455 user.permissions[GLOBAL].add(perm.permission.permission_name)
451 456
452 457 # user explicit permissions for repositories
453 458 user_repo_perms = \
454 459 self.sa.query(UserRepoToPerm, Permission, Repository)\
455 460 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
456 461 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
457 462 .filter(UserRepoToPerm.user_id == uid)\
458 463 .all()
459 464
460 465 for perm in user_repo_perms:
461 466 # set admin if owner
462 467 r_k = perm.UserRepoToPerm.repository.repo_name
463 468 if perm.Repository.user_id == uid:
464 469 p = 'repository.admin'
465 470 else:
466 471 p = perm.Permission.permission_name
467 472 user.permissions[RK][r_k] = p
468 473
469 474 # USER GROUP
470 475 #==================================================================
471 476 # check if user is part of user groups for this repository and
472 477 # fill in (or replace with higher) permissions
473 478 #==================================================================
474 479
475 480 # users group global
476 481 user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
477 482 .options(joinedload(UsersGroupToPerm.permission))\
478 483 .join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
479 484 UsersGroupMember.users_group_id))\
480 485 .filter(UsersGroupMember.user_id == uid).all()
481 486
482 487 for perm in user_perms_from_users_groups:
483 488 user.permissions[GLOBAL].add(perm.permission.permission_name)
484 489
485 490 # users group for repositories permissions
486 491 user_repo_perms_from_users_groups = \
487 492 self.sa.query(UsersGroupRepoToPerm, Permission, Repository,)\
488 493 .join((Repository, UsersGroupRepoToPerm.repository_id == Repository.repo_id))\
489 494 .join((Permission, UsersGroupRepoToPerm.permission_id == Permission.permission_id))\
490 495 .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id == UsersGroupMember.users_group_id))\
491 496 .filter(UsersGroupMember.user_id == uid)\
492 497 .all()
493 498
494 499 for perm in user_repo_perms_from_users_groups:
495 500 r_k = perm.UsersGroupRepoToPerm.repository.repo_name
496 501 p = perm.Permission.permission_name
497 502 cur_perm = user.permissions[RK][r_k]
498 503 # overwrite permission only if it's greater than permission
499 504 # given from other sources
500 505 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
501 506 user.permissions[RK][r_k] = p
502 507
503 508 # REPO GROUP
504 509 #==================================================================
505 510 # get access for this user for repos group and override defaults
506 511 #==================================================================
507 512
508 513 # user explicit permissions for repository
509 514 user_repo_groups_perms = \
510 515 self.sa.query(UserRepoGroupToPerm, Permission, RepoGroup)\
511 516 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
512 517 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
513 518 .filter(UserRepoGroupToPerm.user_id == uid)\
514 519 .all()
515 520
516 521 for perm in user_repo_groups_perms:
517 522 rg_k = perm.UserRepoGroupToPerm.group.group_name
518 523 p = perm.Permission.permission_name
519 524 cur_perm = user.permissions[GK][rg_k]
520 525 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
521 526 user.permissions[GK][rg_k] = p
522 527
523 528 # REPO GROUP + USER GROUP
524 529 #==================================================================
525 530 # check if user is part of user groups for this repo group and
526 531 # fill in (or replace with higher) permissions
527 532 #==================================================================
528 533
529 534 # users group for repositories permissions
530 535 user_repo_group_perms_from_users_groups = \
531 536 self.sa.query(UsersGroupRepoGroupToPerm, Permission, RepoGroup)\
532 537 .join((RepoGroup, UsersGroupRepoGroupToPerm.group_id == RepoGroup.group_id))\
533 538 .join((Permission, UsersGroupRepoGroupToPerm.permission_id == Permission.permission_id))\
534 539 .join((UsersGroupMember, UsersGroupRepoGroupToPerm.users_group_id == UsersGroupMember.users_group_id))\
535 540 .filter(UsersGroupMember.user_id == uid)\
536 541 .all()
537 542
538 543 for perm in user_repo_group_perms_from_users_groups:
539 544 g_k = perm.UsersGroupRepoGroupToPerm.group.group_name
540 545 p = perm.Permission.permission_name
541 546 cur_perm = user.permissions[GK][g_k]
542 547 # overwrite permission only if it's greater than permission
543 548 # given from other sources
544 549 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
545 550 user.permissions[GK][g_k] = p
546 551
547 552 return user
548 553
549 554 def has_perm(self, user, perm):
550 555 if not isinstance(perm, Permission):
551 556 raise Exception('perm needs to be an instance of Permission class '
552 557 'got %s instead' % type(perm))
553 558
554 559 user = self._get_user(user)
555 560
556 561 return UserToPerm.query().filter(UserToPerm.user == user)\
557 562 .filter(UserToPerm.permission == perm).scalar() is not None
558 563
559 564 def grant_perm(self, user, perm):
560 565 """
561 566 Grant user global permissions
562 567
563 568 :param user:
564 569 :param perm:
565 570 """
566 571 user = self._get_user(user)
567 572 perm = self._get_perm(perm)
568 573 # if this permission is already granted skip it
569 574 _perm = UserToPerm.query()\
570 575 .filter(UserToPerm.user == user)\
571 576 .filter(UserToPerm.permission == perm)\
572 577 .scalar()
573 578 if _perm:
574 579 return
575 580 new = UserToPerm()
576 581 new.user = user
577 582 new.permission = perm
578 583 self.sa.add(new)
579 584
580 585 def revoke_perm(self, user, perm):
581 586 """
582 587 Revoke users global permissions
583 588
584 589 :param user:
585 590 :param perm:
586 591 """
587 592 user = self._get_user(user)
588 593 perm = self._get_perm(perm)
589 594
590 595 obj = UserToPerm.query()\
591 596 .filter(UserToPerm.user == user)\
592 597 .filter(UserToPerm.permission == perm)\
593 598 .scalar()
594 599 if obj:
595 600 self.sa.delete(obj)
596 601
597 602 def add_extra_email(self, user, email):
598 603 """
599 604 Adds email address to UserEmailMap
600 605
601 606 :param user:
602 607 :param email:
603 608 """
604 609 from rhodecode.model import forms
605 610 form = forms.UserExtraEmailForm()()
606 611 data = form.to_python(dict(email=email))
607 612 user = self._get_user(user)
608 613
609 614 obj = UserEmailMap()
610 615 obj.user = user
611 616 obj.email = data['email']
612 617 self.sa.add(obj)
613 618 return obj
614 619
615 620 def delete_extra_email(self, user, email_id):
616 621 """
617 622 Removes email address from UserEmailMap
618 623
619 624 :param user:
620 625 :param email_id:
621 626 """
622 627 user = self._get_user(user)
623 628 obj = UserEmailMap.query().get(email_id)
624 629 if obj:
625 630 self.sa.delete(obj)
@@ -1,251 +1,251 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Edit user')} ${c.user.username} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 10 &raquo;
11 11 ${h.link_to(_('Users'),h.url('users'))}
12 12 &raquo;
13 13 ${_('edit')} "${c.user.username}"
14 14 </%def>
15 15
16 16 <%def name="page_nav()">
17 17 ${self.menu('admin')}
18 18 </%def>
19 19
20 20 <%def name="main()">
21 21 <div class="box box-left">
22 22 <!-- box / title -->
23 23 <div class="title">
24 24 ${self.breadcrumbs()}
25 25 </div>
26 26 <!-- end box / title -->
27 27 ${h.form(url('update_user', id=c.user.user_id),method='put')}
28 28 <div class="form">
29 29 <div class="field">
30 30 <div class="gravatar_box">
31 31 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/></div>
32 32 <p>
33 33 %if c.use_gravatar:
34 34 <strong>${_('Change your avatar at')} <a href="http://gravatar.com">gravatar.com</a></strong>
35 35 <br/>${_('Using')} ${c.user.email}
36 36 %else:
37 37 <br/>${c.user.email}
38 38 %endif
39 39 </div>
40 40 </div>
41 41 <div class="field">
42 42 <div class="label">
43 43 <label>${_('API key')}</label> ${c.user.api_key}
44 44 </div>
45 45 </div>
46 46
47 47 <div class="fields">
48 48 <div class="field">
49 49 <div class="label">
50 50 <label for="username">${_('Username')}:</label>
51 51 </div>
52 52 <div class="input">
53 53 ${h.text('username',class_='medium')}
54 54 </div>
55 55 </div>
56 56
57 57 <div class="field">
58 58 <div class="label">
59 59 <label for="ldap_dn">${_('LDAP DN')}:</label>
60 60 </div>
61 61 <div class="input">
62 62 ${h.text('ldap_dn',class_='medium disabled',readonly="readonly")}
63 63 </div>
64 64 </div>
65 65
66 66 <div class="field">
67 67 <div class="label">
68 68 <label for="new_password">${_('New password')}:</label>
69 69 </div>
70 70 <div class="input">
71 71 ${h.password('new_password',class_='medium',autocomplete="off")}
72 72 </div>
73 73 </div>
74 74
75 75 <div class="field">
76 76 <div class="label">
77 77 <label for="password_confirmation">${_('New password confirmation')}:</label>
78 78 </div>
79 79 <div class="input">
80 80 ${h.password('password_confirmation',class_="medium",autocomplete="off")}
81 81 </div>
82 82 </div>
83 83
84 84 <div class="field">
85 85 <div class="label">
86 <label for="name">${_('First Name')}:</label>
86 <label for="firstname">${_('First Name')}:</label>
87 87 </div>
88 88 <div class="input">
89 ${h.text('name',class_='medium')}
89 ${h.text('firstname',class_='medium')}
90 90 </div>
91 91 </div>
92 92
93 93 <div class="field">
94 94 <div class="label">
95 95 <label for="lastname">${_('Last Name')}:</label>
96 96 </div>
97 97 <div class="input">
98 98 ${h.text('lastname',class_='medium')}
99 99 </div>
100 100 </div>
101 101
102 102 <div class="field">
103 103 <div class="label">
104 104 <label for="email">${_('Email')}:</label>
105 105 </div>
106 106 <div class="input">
107 107 ${h.text('email',class_='medium')}
108 108 </div>
109 109 </div>
110 110
111 111 <div class="field">
112 112 <div class="label label-checkbox">
113 113 <label for="active">${_('Active')}:</label>
114 114 </div>
115 115 <div class="checkboxes">
116 116 ${h.checkbox('active',value=True)}
117 117 </div>
118 118 </div>
119 119
120 120 <div class="field">
121 121 <div class="label label-checkbox">
122 122 <label for="admin">${_('Admin')}:</label>
123 123 </div>
124 124 <div class="checkboxes">
125 125 ${h.checkbox('admin',value=True)}
126 126 </div>
127 127 </div>
128 128 <div class="buttons">
129 129 ${h.submit('save',_('Save'),class_="ui-button")}
130 130 ${h.reset('reset',_('Reset'),class_="ui-button")}
131 131 </div>
132 132 </div>
133 133 </div>
134 134 ${h.end_form()}
135 135 </div>
136 136 <div class="box box-right">
137 137 <!-- box / title -->
138 138 <div class="title">
139 139 <h5>${_('Permissions')}</h5>
140 140 </div>
141 141 ${h.form(url('user_perm', id=c.user.user_id),method='put')}
142 142 <div class="form">
143 143 <!-- fields -->
144 144 <div class="fields">
145 145 <div class="field">
146 146 <div class="label label-checkbox">
147 147 <label for="create_repo_perm">${_('Create repositories')}:</label>
148 148 </div>
149 149 <div class="checkboxes">
150 150 ${h.checkbox('create_repo_perm',value=True)}
151 151 </div>
152 152 </div>
153 153 <div class="buttons">
154 154 ${h.submit('save',_('Save'),class_="ui-button")}
155 155 ${h.reset('reset',_('Reset'),class_="ui-button")}
156 156 </div>
157 157 </div>
158 158 </div>
159 159 ${h.end_form()}
160 160
161 161 ## permissions overview
162 162 <div id="perms" class="table">
163 163 %for section in sorted(c.perm_user.permissions.keys()):
164 164 <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div>
165 165
166 166 <div id='tbl_list_wrap_${section}' class="yui-skin-sam">
167 167 <table id="tbl_list_${section}">
168 168 <thead>
169 169 <tr>
170 170 <th class="left">${_('Name')}</th>
171 171 <th class="left">${_('Permission')}</th>
172 172 </thead>
173 173 <tbody>
174 174 %for k in c.perm_user.permissions[section]:
175 175 <%
176 176 if section != 'global':
177 177 section_perm = c.perm_user.permissions[section].get(k)
178 178 _perm = section_perm.split('.')[-1]
179 179 else:
180 180 _perm = section_perm = None
181 181 %>
182 182 <tr>
183 183 <td>
184 184 %if section == 'repositories':
185 185 <a href="${h.url('summary_home',repo_name=k)}">${k}</a>
186 186 %elif section == 'repositories_groups':
187 187 <a href="${h.url('repos_group_home',group_name=k)}">${k}</a>
188 188 %else:
189 189 ${h.get_permission_name(k)}
190 190 %endif
191 191 </td>
192 192 <td>
193 193 %if section == 'global':
194 194 ${h.bool2icon(k.split('.')[-1] != 'none')}
195 195 %else:
196 196 <span class="perm_tag ${_perm}">${section_perm}</span>
197 197 %endif
198 198 </td>
199 199 </tr>
200 200 %endfor
201 201 </tbody>
202 202 </table>
203 203 </div>
204 204 %endfor
205 205 </div>
206 206 </div>
207 207 <div class="box box-left">
208 208 <!-- box / title -->
209 209 <div class="title">
210 210 <h5>${_('Email addresses')}</h5>
211 211 </div>
212 212
213 213 <div class="emails_wrap">
214 214 <table class="noborder">
215 215 %for em in c.user_email_map:
216 216 <tr>
217 217 <td><div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(em.user.email,16)}"/> </div></td>
218 218 <td><div class="email">${em.email}</div></td>
219 219 <td>
220 220 ${h.form(url('user_emails_delete', id=c.user.user_id),method='delete')}
221 221 ${h.hidden('del_email',em.email_id)}
222 222 ${h.submit('remove_',_('delete'),id="remove_email_%s" % em.email_id,
223 223 class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this email: %s') % em.email+"');")}
224 224 ${h.end_form()}
225 225 </td>
226 226 </tr>
227 227 %endfor
228 228 </table>
229 229 </div>
230 230
231 231 ${h.form(url('user_emails', id=c.user.user_id),method='put')}
232 232 <div class="form">
233 233 <!-- fields -->
234 234 <div class="fields">
235 235 <div class="field">
236 236 <div class="label">
237 237 <label for="email">${_('New email address')}:</label>
238 238 </div>
239 239 <div class="input">
240 240 ${h.text('new_email', class_='medium')}
241 241 </div>
242 242 </div>
243 243 <div class="buttons">
244 244 ${h.submit('save',_('Add'),class_="ui-button")}
245 245 ${h.reset('reset',_('Reset'),class_="ui-button")}
246 246 </div>
247 247 </div>
248 248 </div>
249 249 ${h.end_form()}
250 250 </div>
251 251 </%def>
@@ -1,85 +1,85 b''
1 1 <div>
2 2 ${h.form(url('admin_settings_my_account_update'),method='put')}
3 3 <div class="form">
4 4
5 5 <div class="field">
6 6 <div class="gravatar_box">
7 7 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/></div>
8 8 <p>
9 9 %if c.use_gravatar:
10 10 <strong>${_('Change your avatar at')} <a href="http://gravatar.com">gravatar.com</a></strong>
11 11 <br/>${_('Using')} ${c.user.email}
12 12 %else:
13 13 <br/>${c.user.email}
14 14 %endif
15 15 </p>
16 16 </div>
17 17 </div>
18 18 <div class="field">
19 19 <div class="label">
20 20 <label>${_('API key')}</label> ${c.user.api_key}
21 21 </div>
22 22 </div>
23 23 <div class="fields">
24 24 <div class="field">
25 25 <div class="label">
26 26 <label for="username">${_('Username')}:</label>
27 27 </div>
28 28 <div class="input">
29 29 ${h.text('username',class_="medium")}
30 30 </div>
31 31 </div>
32 32
33 33 <div class="field">
34 34 <div class="label">
35 35 <label for="new_password">${_('New password')}:</label>
36 36 </div>
37 37 <div class="input">
38 38 ${h.password('new_password',class_="medium",autocomplete="off")}
39 39 </div>
40 40 </div>
41 41
42 42 <div class="field">
43 43 <div class="label">
44 44 <label for="password_confirmation">${_('New password confirmation')}:</label>
45 45 </div>
46 46 <div class="input">
47 47 ${h.password('password_confirmation',class_="medium",autocomplete="off")}
48 48 </div>
49 49 </div>
50 50
51 51 <div class="field">
52 52 <div class="label">
53 53 <label for="name">${_('First Name')}:</label>
54 54 </div>
55 55 <div class="input">
56 ${h.text('name',class_="medium")}
56 ${h.text('firstname',class_="medium")}
57 57 </div>
58 58 </div>
59 59
60 60 <div class="field">
61 61 <div class="label">
62 62 <label for="lastname">${_('Last Name')}:</label>
63 63 </div>
64 64 <div class="input">
65 65 ${h.text('lastname',class_="medium")}
66 66 </div>
67 67 </div>
68 68
69 69 <div class="field">
70 70 <div class="label">
71 71 <label for="email">${_('Email')}:</label>
72 72 </div>
73 73 <div class="input">
74 74 ${h.text('email',class_="medium")}
75 75 </div>
76 76 </div>
77 77
78 78 <div class="buttons">
79 79 ${h.submit('save',_('Save'),class_="ui-button")}
80 80 ${h.reset('reset',_('Reset'),class_="ui-button")}
81 81 </div>
82 82 </div>
83 83 </div>
84 84 ${h.end_form()}
85 85 </div>
@@ -1,215 +1,213 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 from rhodecode.lib.auth import get_crypt_password, check_password
4 4 from rhodecode.model.db import User, RhodeCodeSetting
5 5 from rhodecode.tests import *
6 6 from rhodecode.lib import helpers as h
7 from rhodecode.model.user import UserModel
7 8
8 9
9 10 class TestAdminSettingsController(TestController):
10 11
11 12 def test_index(self):
12 13 response = self.app.get(url('admin_settings'))
13 14 # Test response...
14 15
15 16 def test_index_as_xml(self):
16 17 response = self.app.get(url('formatted_admin_settings', format='xml'))
17 18
18 19 def test_create(self):
19 20 response = self.app.post(url('admin_settings'))
20 21
21 22 def test_new(self):
22 23 response = self.app.get(url('admin_new_setting'))
23 24
24 25 def test_new_as_xml(self):
25 26 response = self.app.get(url('formatted_admin_new_setting', format='xml'))
26 27
27 28 def test_update(self):
28 29 response = self.app.put(url('admin_setting', setting_id=1))
29 30
30 31 def test_update_browser_fakeout(self):
31 32 response = self.app.post(url('admin_setting', setting_id=1), params=dict(_method='put'))
32 33
33 34 def test_delete(self):
34 35 response = self.app.delete(url('admin_setting', setting_id=1))
35 36
36 37 def test_delete_browser_fakeout(self):
37 38 response = self.app.post(url('admin_setting', setting_id=1), params=dict(_method='delete'))
38 39
39 40 def test_show(self):
40 41 response = self.app.get(url('admin_setting', setting_id=1))
41 42
42 43 def test_show_as_xml(self):
43 44 response = self.app.get(url('formatted_admin_setting', setting_id=1, format='xml'))
44 45
45 46 def test_edit(self):
46 47 response = self.app.get(url('admin_edit_setting', setting_id=1))
47 48
48 49 def test_edit_as_xml(self):
49 50 response = self.app.get(url('formatted_admin_edit_setting',
50 51 setting_id=1, format='xml'))
51 52
52 53 def test_ga_code_active(self):
53 54 self.log_user()
54 55 old_title = 'RhodeCode'
55 56 old_realm = 'RhodeCode authentication'
56 57 new_ga_code = 'ga-test-123456789'
57 58 response = self.app.post(url('admin_setting', setting_id='global'),
58 59 params=dict(
59 60 _method='put',
60 61 rhodecode_title=old_title,
61 62 rhodecode_realm=old_realm,
62 63 rhodecode_ga_code=new_ga_code
63 64 ))
64 65
65 66 self.checkSessionFlash(response, 'Updated application settings')
66 67
67 68 self.assertEqual(RhodeCodeSetting
68 69 .get_app_settings()['rhodecode_ga_code'], new_ga_code)
69 70
70 71 response = response.follow()
71 self.assertTrue("""_gaq.push(['_setAccount', '%s']);""" % new_ga_code
72 in response.body)
72 response.mustcontain("""_gaq.push(['_setAccount', '%s']);""" % new_ga_code)
73 73
74 74 def test_ga_code_inactive(self):
75 75 self.log_user()
76 76 old_title = 'RhodeCode'
77 77 old_realm = 'RhodeCode authentication'
78 78 new_ga_code = ''
79 79 response = self.app.post(url('admin_setting', setting_id='global'),
80 80 params=dict(
81 81 _method='put',
82 82 rhodecode_title=old_title,
83 83 rhodecode_realm=old_realm,
84 84 rhodecode_ga_code=new_ga_code
85 85 ))
86 86
87 87 self.assertTrue('Updated application settings' in
88 88 response.session['flash'][0][1])
89 89 self.assertEqual(RhodeCodeSetting
90 90 .get_app_settings()['rhodecode_ga_code'], new_ga_code)
91 91
92 92 response = response.follow()
93 self.assertTrue("""_gaq.push(['_setAccount', '%s']);""" % new_ga_code
94 not in response.body)
93 self.assertFalse("""_gaq.push(['_setAccount', '%s']);""" % new_ga_code
94 in response.body)
95 95
96 96 def test_title_change(self):
97 97 self.log_user()
98 98 old_title = 'RhodeCode'
99 99 new_title = old_title + '_changed'
100 100 old_realm = 'RhodeCode authentication'
101 101
102 102 for new_title in ['Changed', 'Ε»Γ³Ε‚wik', old_title]:
103 103 response = self.app.post(url('admin_setting', setting_id='global'),
104 104 params=dict(
105 105 _method='put',
106 106 rhodecode_title=new_title,
107 107 rhodecode_realm=old_realm,
108 108 rhodecode_ga_code=''
109 109 ))
110 110
111 111 self.checkSessionFlash(response, 'Updated application settings')
112 112 self.assertEqual(RhodeCodeSetting
113 113 .get_app_settings()['rhodecode_title'],
114 114 new_title.decode('utf-8'))
115 115
116 116 response = response.follow()
117 self.assertTrue("""<h1><a href="/">%s</a></h1>""" % new_title
118 in response.body)
117 response.mustcontain("""<h1><a href="/">%s</a></h1>""" % new_title)
119 118
120 119 def test_my_account(self):
121 120 self.log_user()
122 121 response = self.app.get(url('admin_settings_my_account'))
123 122
124 123 self.assertTrue('value="test_admin' in response.body)
125 124
126 def test_my_account_update(self):
127 self.log_user()
128
129 new_email = 'new@mail.pl'
130 new_name = 'NewName'
131 new_lastname = 'NewLastname'
132 new_password = 'test123'
133
134 response = self.app.post(url('admin_settings_my_account_update'),
135 params=dict(_method='put',
136 username='test_admin',
137 new_password=new_password,
138 password_confirmation=new_password,
139 password='',
140 name=new_name,
141 lastname=new_lastname,
142 email=new_email,))
143 response.follow()
125 @parameterized.expand([('firstname', 'new_username'),
126 ('lastname', 'new_username'),
127 ('admin', True),
128 ('admin', False),
129 ('ldap_dn', 'test'),
130 ('ldap_dn', None),
131 ('active', False),
132 ('active', True),
133 ('email', 'some@email.com'),
134 ])
135 def test_my_account_update(self, name, expected):
136 uname = 'testme'
137 usr = UserModel().create_or_update(username=uname, password='qweqwe',
138 email='testme@rhodecod.org')
139 self.Session().commit()
140 params = usr.get_api_data()
141 user_id = usr.user_id
142 self.log_user(username=uname, password='qweqwe')
143 params.update({name: expected})
144 params.update({'password_confirmation': ''})
145 params.update({'new_password': ''})
144 146
145 assert 'Your account was updated successfully' in response.session['flash'][0][1], 'no flash message about success of change'
146 user = self.Session.query(User).filter(User.username == 'test_admin').one()
147 assert user.email == new_email, 'incorrect user email after update got %s vs %s' % (user.email, new_email)
148 assert user.name == new_name, 'updated field mismatch %s vs %s' % (user.name, new_name)
149 assert user.lastname == new_lastname, 'updated field mismatch %s vs %s' % (user.lastname, new_lastname)
150 assert check_password(new_password, user.password) is True, 'password field mismatch %s vs %s' % (user.password, new_password)
147 try:
148 response = self.app.put(url('admin_settings_my_account_update',
149 id=user_id), params)
151 150
152 #bring back the admin settings
153 old_email = 'test_admin@mail.com'
154 old_name = 'RhodeCode'
155 old_lastname = 'Admin'
156 old_password = 'test12'
157
158 response = self.app.post(url('admin_settings_my_account_update'), params=dict(
159 _method='put',
160 username='test_admin',
161 new_password=old_password,
162 password_confirmation=old_password,
163 password='',
164 name=old_name,
165 lastname=old_lastname,
166 email=old_email,))
167
168 response.follow()
169 151 self.checkSessionFlash(response,
170 152 'Your account was updated successfully')
171 153
172 user = self.Session.query(User).filter(User.username == 'test_admin').one()
173 assert user.email == old_email, 'incorrect user email after update got %s vs %s' % (user.email, old_email)
154 updated_user = User.get_by_username(uname)
155 updated_params = updated_user.get_api_data()
156 updated_params.update({'password_confirmation': ''})
157 updated_params.update({'new_password': ''})
174 158
175 assert user.email == old_email, 'incorrect user email after update got %s vs %s' % (user.email, old_email)
176 assert user.name == old_name, 'updated field mismatch %s vs %s' % (user.name, old_name)
177 assert user.lastname == old_lastname, 'updated field mismatch %s vs %s' % (user.lastname, old_lastname)
178 assert check_password(old_password, user.password) is True, 'password updated field mismatch %s vs %s' % (user.password, old_password)
159 params['last_login'] = updated_params['last_login']
160 if name == 'email':
161 params['emails'] = [expected]
162 if name == 'ldap_dn':
163 #cannot update this via form
164 params['ldap_dn'] = None
165 if name == 'active':
166 #my account cannot deactivate account
167 params['active'] = True
168 if name == 'admin':
169 #my account cannot make you an admin !
170 params['admin'] = False
171
172 self.assertEqual(params, updated_params)
173
174 finally:
175 UserModel().delete('testme')
179 176
180 177 def test_my_account_update_err_email_exists(self):
181 178 self.log_user()
182 179
183 180 new_email = 'test_regular@mail.com' # already exisitn email
184 response = self.app.post(url('admin_settings_my_account_update'), params=dict(
185 _method='put',
181 response = self.app.put(url('admin_settings_my_account_update'),
182 params=dict(
186 183 username='test_admin',
187 184 new_password='test12',
188 185 password_confirmation='test122',
189 name='NewName',
186 firstname='NewName',
190 187 lastname='NewLastname',
191 email=new_email,))
188 email=new_email,)
189 )
192 190
193 assert 'This e-mail address is already taken' in response.body, 'Missing error message about existing email'
191 response.mustcontain('This e-mail address is already taken')
194 192
195 193 def test_my_account_update_err(self):
196 194 self.log_user('test_regular2', 'test12')
197 195
198 196 new_email = 'newmail.pl'
199 197 response = self.app.post(url('admin_settings_my_account_update'),
200 198 params=dict(
201 199 _method='put',
202 200 username='test_admin',
203 201 new_password='test12',
204 202 password_confirmation='test122',
205 name='NewName',
203 firstname='NewName',
206 204 lastname='NewLastname',
207 205 email=new_email,)
208 206 )
209 207
210 208 response.mustcontain('An email address must contain a single @')
211 209 from rhodecode.model import validators
212 210 msg = validators.ValidUsername(edit=False,
213 211 old_data={})._messages['username_exists']
214 212 msg = h.html_escape(msg % {'username': 'test_admin'})
215 213 response.mustcontain(u"%s" % msg)
@@ -1,180 +1,216 b''
1 1 from sqlalchemy.orm.exc import NoResultFound
2 2
3 3 from rhodecode.tests import *
4 4 from rhodecode.model.db import User, Permission
5 5 from rhodecode.lib.auth import check_password
6 6 from rhodecode.model.user import UserModel
7 7 from rhodecode.model import validators
8 8 from rhodecode.lib import helpers as h
9 9
10 10
11 11 class TestAdminUsersController(TestController):
12 12
13 13 def test_index(self):
14 14 self.log_user()
15 15 response = self.app.get(url('users'))
16 16 # Test response...
17 17
18 18 def test_index_as_xml(self):
19 19 response = self.app.get(url('formatted_users', format='xml'))
20 20
21 21 def test_create(self):
22 22 self.log_user()
23 23 username = 'newtestuser'
24 24 password = 'test12'
25 25 password_confirmation = password
26 26 name = 'name'
27 27 lastname = 'lastname'
28 28 email = 'mail@mail.com'
29 29
30 30 response = self.app.post(url('users'),
31 31 {'username': username,
32 32 'password': password,
33 33 'password_confirmation': password_confirmation,
34 'name': name,
34 'firstname': name,
35 35 'active': True,
36 36 'lastname': lastname,
37 37 'email': email})
38 38
39 39 self.checkSessionFlash(response, '''created user %s''' % (username))
40 40
41
42 41 new_user = self.Session.query(User).\
43 42 filter(User.username == username).one()
44 43
45 44 self.assertEqual(new_user.username, username)
46 45 self.assertEqual(check_password(password, new_user.password), True)
47 46 self.assertEqual(new_user.name, name)
48 47 self.assertEqual(new_user.lastname, lastname)
49 48 self.assertEqual(new_user.email, email)
50 49
51 50 response.follow()
52 51 response = response.follow()
53 self.assertTrue("""edit">newtestuser</a>""" in response.body)
52 response.mustcontain("""edit">newtestuser</a>""")
54 53
55 54 def test_create_err(self):
56 55 self.log_user()
57 56 username = 'new_user'
58 57 password = ''
59 58 name = 'name'
60 59 lastname = 'lastname'
61 60 email = 'errmail.com'
62 61
63 62 response = self.app.post(url('users'), {'username': username,
64 63 'password': password,
65 64 'name': name,
66 65 'active': False,
67 66 'lastname': lastname,
68 67 'email': email})
69 68
70 69 msg = validators.ValidUsername(False, {})._messages['system_invalid_username']
71 70 msg = h.html_escape(msg % {'username': 'new_user'})
72 71 response.mustcontain("""<span class="error-message">%s</span>""" % msg)
73 72 response.mustcontain("""<span class="error-message">Please enter a value</span>""")
74 73 response.mustcontain("""<span class="error-message">An email address must contain a single @</span>""")
75 74
76 75 def get_user():
77 76 self.Session.query(User).filter(User.username == username).one()
78 77
79 78 self.assertRaises(NoResultFound, get_user), 'found user in database'
80 79
81 80 def test_new(self):
82 81 self.log_user()
83 82 response = self.app.get(url('new_user'))
84 83
85 84 def test_new_as_xml(self):
86 85 response = self.app.get(url('formatted_new_user', format='xml'))
87 86
88 def test_update(self):
89 response = self.app.put(url('user', id=1))
87 @parameterized.expand([('firstname', 'new_username'),
88 ('lastname', 'new_username'),
89 ('admin', True),
90 ('admin', False),
91 ('ldap_dn', 'test'),
92 ('ldap_dn', None),
93 ('active', False),
94 ('active', True),
95 ('email', 'some@email.com'),
96 ])
97 def test_update(self, name, expected):
98 self.log_user()
99 uname = 'testme'
100 usr = UserModel().create_or_update(username=uname, password='qweqwe',
101 email='testme@rhodecod.org')
102 self.Session().commit()
103 params = usr.get_api_data()
104 params.update({name: expected})
105 params.update({'password_confirmation': ''})
106 params.update({'new_password': ''})
107 if name == 'email':
108 params['emails'] = [expected]
109 if name == 'ldap_dn':
110 #cannot update this via form
111 params['ldap_dn'] = None
112 try:
113 response = self.app.put(url('user', id=usr.user_id), params)
114
115 self.checkSessionFlash(response, '''User updated successfully''')
116
117 updated_user = User.get_by_username(uname)
118 updated_params = updated_user.get_api_data()
119 updated_params.update({'password_confirmation': ''})
120 updated_params.update({'new_password': ''})
121
122 self.assertEqual(params, updated_params)
123
124 finally:
125 UserModel().delete('testme')
90 126
91 127 def test_update_browser_fakeout(self):
92 128 response = self.app.post(url('user', id=1), params=dict(_method='put'))
93 129
94 130 def test_delete(self):
95 131 self.log_user()
96 132 username = 'newtestuserdeleteme'
97 133 password = 'test12'
98 134 name = 'name'
99 135 lastname = 'lastname'
100 136 email = 'todeletemail@mail.com'
101 137
102 138 response = self.app.post(url('users'), {'username': username,
103 139 'password': password,
104 140 'password_confirmation': password,
105 'name': name,
141 'firstname': name,
106 142 'active': True,
107 143 'lastname': lastname,
108 144 'email': email})
109 145
110 146 response = response.follow()
111 147
112 148 new_user = self.Session.query(User)\
113 149 .filter(User.username == username).one()
114 150 response = self.app.delete(url('user', id=new_user.user_id))
115 151
116 152 self.assertTrue("""successfully deleted user""" in
117 153 response.session['flash'][0])
118 154
119 155 def test_delete_browser_fakeout(self):
120 156 response = self.app.post(url('user', id=1),
121 157 params=dict(_method='delete'))
122 158
123 159 def test_show(self):
124 160 response = self.app.get(url('user', id=1))
125 161
126 162 def test_show_as_xml(self):
127 163 response = self.app.get(url('formatted_user', id=1, format='xml'))
128 164
129 165 def test_edit(self):
130 166 self.log_user()
131 167 user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
132 168 response = self.app.get(url('edit_user', id=user.user_id))
133 169
134 170 def test_add_perm_create_repo(self):
135 171 self.log_user()
136 172 perm_none = Permission.get_by_key('hg.create.none')
137 173 perm_create = Permission.get_by_key('hg.create.repository')
138 174
139 175 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
140 176
141 177 #User should have None permission on creation repository
142 178 self.assertEqual(UserModel().has_perm(user, perm_none), False)
143 179 self.assertEqual(UserModel().has_perm(user, perm_create), False)
144 180
145 181 response = self.app.post(url('user_perm', id=user.user_id),
146 182 params=dict(_method='put',
147 183 create_repo_perm=True))
148 184
149 185 perm_none = Permission.get_by_key('hg.create.none')
150 186 perm_create = Permission.get_by_key('hg.create.repository')
151 187
152 188 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
153 189 #User should have None permission on creation repository
154 190 self.assertEqual(UserModel().has_perm(user, perm_none), False)
155 191 self.assertEqual(UserModel().has_perm(user, perm_create), True)
156 192
157 193 def test_revoke_perm_create_repo(self):
158 194 self.log_user()
159 195 perm_none = Permission.get_by_key('hg.create.none')
160 196 perm_create = Permission.get_by_key('hg.create.repository')
161 197
162 198 user = User.get_by_username(TEST_USER_REGULAR2_LOGIN)
163 199
164 200 #User should have None permission on creation repository
165 201 self.assertEqual(UserModel().has_perm(user, perm_none), False)
166 202 self.assertEqual(UserModel().has_perm(user, perm_create), False)
167 203
168 204 response = self.app.post(url('user_perm', id=user.user_id),
169 205 params=dict(_method='put'))
170 206
171 207 perm_none = Permission.get_by_key('hg.create.none')
172 208 perm_create = Permission.get_by_key('hg.create.repository')
173 209
174 210 user = User.get_by_username(TEST_USER_REGULAR2_LOGIN)
175 211 #User should have None permission on creation repository
176 212 self.assertEqual(UserModel().has_perm(user, perm_none), True)
177 213 self.assertEqual(UserModel().has_perm(user, perm_create), False)
178 214
179 215 def test_edit_as_xml(self):
180 216 response = self.app.get(url('formatted_edit_user', id=1, format='xml'))
General Comments 0
You need to be logged in to leave comments. Login now