##// END OF EJS Templates
fixed repo_create permission by adding missing commit statements...
marcink -
r1758:a87aa385 beta
parent child Browse files
Show More
@@ -1,210 +1,210 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.users
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Users crud controller for pylons
7 7
8 8 :created_on: Apr 4, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29
30 30 from formencode import htmlfill
31 31 from pylons import request, session, tmpl_context as c, url, config
32 32 from pylons.controllers.util import abort, redirect
33 33 from pylons.i18n.translation import _
34 34
35 35 from rhodecode.lib.exceptions import DefaultUserException, \
36 36 UserOwnsReposException
37 37 from rhodecode.lib import helpers as h
38 38 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
39 39 from rhodecode.lib.base import BaseController, render
40 40
41 41 from rhodecode.model.db import User, UserRepoToPerm, UserToPerm, Permission
42 42 from rhodecode.model.forms import UserForm
43 43 from rhodecode.model.user import UserModel
44 44 from rhodecode.model.meta import Session
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48
49 49 class UsersController(BaseController):
50 50 """REST Controller styled on the Atom Publishing Protocol"""
51 51 # To properly map this controller, ensure your config/routing.py
52 52 # file has a resource setup:
53 53 # map.resource('user', 'users')
54 54
55 55 @LoginRequired()
56 56 @HasPermissionAllDecorator('hg.admin')
57 57 def __before__(self):
58 58 c.admin_user = session.get('admin_user')
59 59 c.admin_username = session.get('admin_username')
60 60 super(UsersController, self).__before__()
61 61 c.available_permissions = config['available_permissions']
62 62
63 63 def index(self, format='html'):
64 64 """GET /users: All items in the collection"""
65 65 # url('users')
66 66
67 67 c.users_list = self.sa.query(User).all()
68 68 return render('admin/users/users.html')
69 69
70 70 def create(self):
71 71 """POST /users: Create a new item"""
72 72 # url('users')
73 73
74 74 user_model = UserModel()
75 75 user_form = UserForm()()
76 76 try:
77 77 form_result = user_form.to_python(dict(request.POST))
78 78 user_model.create(form_result)
79 79 h.flash(_('created user %s') % form_result['username'],
80 80 category='success')
81 81 Session.commit()
82 82 #action_logger(self.rhodecode_user, 'new_user', '', '', self.sa)
83 83 except formencode.Invalid, errors:
84 84 return htmlfill.render(
85 85 render('admin/users/user_add.html'),
86 86 defaults=errors.value,
87 87 errors=errors.error_dict or {},
88 88 prefix_error=False,
89 89 encoding="UTF-8")
90 90 except Exception:
91 91 log.error(traceback.format_exc())
92 92 h.flash(_('error occurred during creation of user %s') \
93 93 % request.POST.get('username'), category='error')
94 94 return redirect(url('users'))
95 95
96 96 def new(self, format='html'):
97 97 """GET /users/new: Form to create a new item"""
98 98 # url('new_user')
99 99 return render('admin/users/user_add.html')
100 100
101 101 def update(self, id):
102 102 """PUT /users/id: Update an existing item"""
103 103 # Forms posted to this method should contain a hidden field:
104 104 # <input type="hidden" name="_method" value="PUT" />
105 105 # Or using helpers:
106 106 # h.form(url('update_user', id=ID),
107 107 # method='put')
108 108 # url('user', id=ID)
109 109 user_model = UserModel()
110 110 c.user = user_model.get(id)
111 111
112 112 _form = UserForm(edit=True, old_data={'user_id': id,
113 113 'email': c.user.email})()
114 114 form_result = {}
115 115 try:
116 116 form_result = _form.to_python(dict(request.POST))
117 117 user_model.update(id, form_result)
118 118 h.flash(_('User updated successfully'), category='success')
119 119 Session.commit()
120 120 except formencode.Invalid, errors:
121 121 e = errors.error_dict or {}
122 122 perm = Permission.get_by_key('hg.create.repository')
123 123 e.update({'create_repo_perm': user_model.has_perm(id, perm)})
124 124 return htmlfill.render(
125 125 render('admin/users/user_edit.html'),
126 126 defaults=errors.value,
127 127 errors=e,
128 128 prefix_error=False,
129 129 encoding="UTF-8")
130 130 except Exception:
131 131 log.error(traceback.format_exc())
132 132 h.flash(_('error occurred during update of user %s') \
133 133 % form_result.get('username'), category='error')
134 134
135 135 return redirect(url('users'))
136 136
137 137 def delete(self, id):
138 138 """DELETE /users/id: Delete an existing item"""
139 139 # Forms posted to this method should contain a hidden field:
140 140 # <input type="hidden" name="_method" value="DELETE" />
141 141 # Or using helpers:
142 142 # h.form(url('delete_user', id=ID),
143 143 # method='delete')
144 144 # url('user', id=ID)
145 145 user_model = UserModel()
146 146 try:
147 147 user_model.delete(id)
148 148 h.flash(_('successfully deleted user'), category='success')
149 149 Session.commit()
150 150 except (UserOwnsReposException, DefaultUserException), e:
151 151 h.flash(str(e), category='warning')
152 152 except Exception:
153 153 h.flash(_('An error occurred during deletion of user'),
154 154 category='error')
155 155 return redirect(url('users'))
156 156
157 157 def show(self, id, format='html'):
158 158 """GET /users/id: Show a specific item"""
159 159 # url('user', id=ID)
160 160
161 161 def edit(self, id, format='html'):
162 162 """GET /users/id/edit: Form to edit an existing item"""
163 163 # url('edit_user', id=ID)
164 164 c.user = User.get(id)
165 165 if not c.user:
166 166 return redirect(url('users'))
167 167 if c.user.username == 'default':
168 168 h.flash(_("You can't edit this user"), category='warning')
169 169 return redirect(url('users'))
170 170 c.user.permissions = {}
171 171 c.granted_permissions = UserModel().fill_perms(c.user)\
172 172 .permissions['global']
173 173
174 174 defaults = c.user.get_dict()
175 175 perm = Permission.get_by_key('hg.create.repository')
176 176 defaults.update({'create_repo_perm': UserModel().has_perm(id, perm)})
177 177
178 178 return htmlfill.render(
179 179 render('admin/users/user_edit.html'),
180 180 defaults=defaults,
181 181 encoding="UTF-8",
182 182 force_defaults=False
183 183 )
184 184
185 185 def update_perm(self, id):
186 186 """PUT /users_perm/id: Update an existing item"""
187 187 # url('user_perm', id=ID, method='put')
188 188
189 189 grant_perm = request.POST.get('create_repo_perm', False)
190 190 user_model = UserModel()
191 191
192 192 if grant_perm:
193 193 perm = Permission.get_by_key('hg.create.none')
194 194 user_model.revoke_perm(id, perm)
195 195
196 196 perm = Permission.get_by_key('hg.create.repository')
197 197 user_model.grant_perm(id, perm)
198 198 h.flash(_("Granted 'repository create' permission to user"),
199 199 category='success')
200
200 Session.commit()
201 201 else:
202 202 perm = Permission.get_by_key('hg.create.repository')
203 203 user_model.revoke_perm(id, perm)
204 204
205 205 perm = Permission.get_by_key('hg.create.none')
206 206 user_model.grant_perm(id, perm)
207 207 h.flash(_("Revoked 'repository create' permission to user"),
208 208 category='success')
209
209 Session.commit()
210 210 return redirect(url('edit_user', id=id))
@@ -1,468 +1,476 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.db_manage
4 4 ~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Database creation, and setup module for RhodeCode. Used for creation
7 7 of database as well as for migration operations
8 8
9 9 :created_on: Apr 10, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 27 import os
28 28 import sys
29 29 import uuid
30 30 import logging
31 31 from os.path import dirname as dn, join as jn
32 32
33 33 from rhodecode import __dbversion__
34 34 from rhodecode.model import meta
35 35
36 36 from rhodecode.model.user import UserModel
37 37 from rhodecode.lib.utils import ask_ok
38 38 from rhodecode.model import init_model
39 39 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
40 40 RhodeCodeSetting, UserToPerm, DbMigrateVersion
41 41
42 42 from sqlalchemy.engine import create_engine
43 43
44 44 log = logging.getLogger(__name__)
45 45
46 46
47 47 class DbManage(object):
48 48 def __init__(self, log_sql, dbconf, root, tests=False):
49 49 self.dbname = dbconf.split('/')[-1]
50 50 self.tests = tests
51 51 self.root = root
52 52 self.dburi = dbconf
53 53 self.log_sql = log_sql
54 54 self.db_exists = False
55 55 self.init_db()
56 56
57 57 def init_db(self):
58 58 engine = create_engine(self.dburi, echo=self.log_sql)
59 59 init_model(engine)
60 60 self.sa = meta.Session
61 61
62 62 def create_tables(self, override=False):
63 63 """Create a auth database
64 64 """
65 65
66 66 log.info("Any existing database is going to be destroyed")
67 67 if self.tests:
68 68 destroy = True
69 69 else:
70 70 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
71 71 if not destroy:
72 72 sys.exit()
73 73 if destroy:
74 74 meta.Base.metadata.drop_all()
75 75
76 76 checkfirst = not override
77 77 meta.Base.metadata.create_all(checkfirst=checkfirst)
78 78 log.info('Created tables for %s', self.dbname)
79 79
80 80 def set_db_version(self):
81 81 ver = DbMigrateVersion()
82 82 ver.version = __dbversion__
83 83 ver.repository_id = 'rhodecode_db_migrations'
84 84 ver.repository_path = 'versions'
85 85 self.sa.add(ver)
86 86 log.info('db version set to: %s', __dbversion__)
87 87
88 88 def upgrade(self):
89 89 """Upgrades given database schema to given revision following
90 90 all needed steps, to perform the upgrade
91 91
92 92 """
93 93
94 94 from rhodecode.lib.dbmigrate.migrate.versioning import api
95 95 from rhodecode.lib.dbmigrate.migrate.exceptions import \
96 96 DatabaseNotControlledError
97 97
98 98 upgrade = ask_ok('You are about to perform database upgrade, make '
99 99 'sure You backed up your database before. '
100 100 'Continue ? [y/n]')
101 101 if not upgrade:
102 102 sys.exit('Nothing done')
103 103
104 104 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
105 105 'rhodecode/lib/dbmigrate')
106 106 db_uri = self.dburi
107 107
108 108 try:
109 109 curr_version = api.db_version(db_uri, repository_path)
110 110 msg = ('Found current database under version'
111 111 ' control with version %s' % curr_version)
112 112
113 113 except (RuntimeError, DatabaseNotControlledError):
114 114 curr_version = 1
115 115 msg = ('Current database is not under version control. Setting'
116 116 ' as version %s' % curr_version)
117 117 api.version_control(db_uri, repository_path, curr_version)
118 118
119 119 print (msg)
120 120
121 121 if curr_version == __dbversion__:
122 122 sys.exit('This database is already at the newest version')
123 123
124 124 #======================================================================
125 125 # UPGRADE STEPS
126 126 #======================================================================
127 127 class UpgradeSteps(object):
128 128 """Those steps follow schema versions so for example schema
129 129 for example schema with seq 002 == step_2 and so on.
130 130 """
131 131
132 132 def __init__(self, klass):
133 133 self.klass = klass
134 134
135 135 def step_0(self):
136 136 #step 0 is the schema upgrade, and than follow proper upgrades
137 137 print ('attempting to do database upgrade to version %s' \
138 138 % __dbversion__)
139 139 api.upgrade(db_uri, repository_path, __dbversion__)
140 140 print ('Schema upgrade completed')
141 141
142 142 def step_1(self):
143 143 pass
144 144
145 145 def step_2(self):
146 146 print ('Patching repo paths for newer version of RhodeCode')
147 147 self.klass.fix_repo_paths()
148 148
149 149 print ('Patching default user of RhodeCode')
150 150 self.klass.fix_default_user()
151 151
152 152 log.info('Changing ui settings')
153 153 self.klass.create_ui_settings()
154 154
155 155 def step_3(self):
156 156 print ('Adding additional settings into RhodeCode db')
157 157 self.klass.fix_settings()
158 158 print ('Adding ldap defaults')
159 159 self.klass.create_ldap_options(skip_existing=True)
160 160
161 161 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
162 162
163 163 #CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
164 164 for step in upgrade_steps:
165 165 print ('performing upgrade step %s' % step)
166 166 getattr(UpgradeSteps(self), 'step_%s' % step)()
167 167
168 168 def fix_repo_paths(self):
169 169 """Fixes a old rhodecode version path into new one without a '*'
170 170 """
171 171
172 172 paths = self.sa.query(RhodeCodeUi)\
173 173 .filter(RhodeCodeUi.ui_key == '/')\
174 174 .scalar()
175 175
176 176 paths.ui_value = paths.ui_value.replace('*', '')
177 177
178 178 try:
179 179 self.sa.add(paths)
180 180 self.sa.commit()
181 181 except:
182 182 self.sa.rollback()
183 183 raise
184 184
185 185 def fix_default_user(self):
186 186 """Fixes a old default user with some 'nicer' default values,
187 187 used mostly for anonymous access
188 188 """
189 189 def_user = self.sa.query(User)\
190 190 .filter(User.username == 'default')\
191 191 .one()
192 192
193 193 def_user.name = 'Anonymous'
194 194 def_user.lastname = 'User'
195 195 def_user.email = 'anonymous@rhodecode.org'
196 196
197 197 try:
198 198 self.sa.add(def_user)
199 199 self.sa.commit()
200 200 except:
201 201 self.sa.rollback()
202 202 raise
203 203
204 204 def fix_settings(self):
205 205 """Fixes rhodecode settings adds ga_code key for google analytics
206 206 """
207 207
208 208 hgsettings3 = RhodeCodeSetting('ga_code', '')
209 209
210 210 try:
211 211 self.sa.add(hgsettings3)
212 212 self.sa.commit()
213 213 except:
214 214 self.sa.rollback()
215 215 raise
216 216
217 217 def admin_prompt(self, second=False):
218 218 if not self.tests:
219 219 import getpass
220 220
221 221 def get_password():
222 222 password = getpass.getpass('Specify admin password '
223 223 '(min 6 chars):')
224 224 confirm = getpass.getpass('Confirm password:')
225 225
226 226 if password != confirm:
227 227 log.error('passwords mismatch')
228 228 return False
229 229 if len(password) < 6:
230 230 log.error('password is to short use at least 6 characters')
231 231 return False
232 232
233 233 return password
234 234
235 235 username = raw_input('Specify admin username:')
236 236
237 237 password = get_password()
238 238 if not password:
239 239 #second try
240 240 password = get_password()
241 241 if not password:
242 242 sys.exit()
243 243
244 244 email = raw_input('Specify admin email:')
245 245 self.create_user(username, password, email, True)
246 246 else:
247 247 log.info('creating admin and regular test users')
248 self.create_user('test_admin', 'test12',
249 'test_admin@mail.com', True)
250 self.create_user('test_regular', 'test12',
251 'test_regular@mail.com', False)
252 self.create_user('test_regular2', 'test12',
253 'test_regular2@mail.com', False)
248 from rhodecode.tests import TEST_USER_ADMIN_LOGIN,\
249 TEST_USER_ADMIN_PASS ,TEST_USER_ADMIN_EMAIL,TEST_USER_REGULAR_LOGIN,\
250 TEST_USER_REGULAR_PASS,TEST_USER_REGULAR_EMAIL,\
251 TEST_USER_REGULAR2_LOGIN,TEST_USER_REGULAR2_PASS,\
252 TEST_USER_REGULAR2_EMAIL
253
254 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
255 TEST_USER_ADMIN_EMAIL, True)
256
257 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
258 TEST_USER_REGULAR_EMAIL, False)
259
260 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
261 TEST_USER_REGULAR2_EMAIL, False)
254 262
255 263 def create_ui_settings(self):
256 264 """Creates ui settings, fills out hooks
257 265 and disables dotencode
258 266
259 267 """
260 268 #HOOKS
261 269 hooks1_key = RhodeCodeUi.HOOK_UPDATE
262 270 hooks1_ = self.sa.query(RhodeCodeUi)\
263 271 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
264 272
265 273 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
266 274 hooks1.ui_section = 'hooks'
267 275 hooks1.ui_key = hooks1_key
268 276 hooks1.ui_value = 'hg update >&2'
269 277 hooks1.ui_active = False
270 278
271 279 hooks2_key = RhodeCodeUi.HOOK_REPO_SIZE
272 280 hooks2_ = self.sa.query(RhodeCodeUi)\
273 281 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
274 282
275 283 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
276 284 hooks2.ui_section = 'hooks'
277 285 hooks2.ui_key = hooks2_key
278 286 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
279 287
280 288 hooks3 = RhodeCodeUi()
281 289 hooks3.ui_section = 'hooks'
282 290 hooks3.ui_key = RhodeCodeUi.HOOK_PUSH
283 291 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
284 292
285 293 hooks4 = RhodeCodeUi()
286 294 hooks4.ui_section = 'hooks'
287 295 hooks4.ui_key = RhodeCodeUi.HOOK_PULL
288 296 hooks4.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
289 297
290 298 # For mercurial 1.7 set backward comapatibility with format
291 299 dotencode_disable = RhodeCodeUi()
292 300 dotencode_disable.ui_section = 'format'
293 301 dotencode_disable.ui_key = 'dotencode'
294 302 dotencode_disable.ui_value = 'false'
295 303
296 304 # enable largefiles
297 305 largefiles = RhodeCodeUi()
298 306 largefiles.ui_section = 'extensions'
299 307 largefiles.ui_key = 'largefiles'
300 308 largefiles.ui_value = '1'
301 309
302 310 self.sa.add(hooks1)
303 311 self.sa.add(hooks2)
304 312 self.sa.add(hooks3)
305 313 self.sa.add(hooks4)
306 314 self.sa.add(largefiles)
307 315
308 316 def create_ldap_options(self, skip_existing=False):
309 317 """Creates ldap settings"""
310 318
311 319 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
312 320 ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
313 321 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
314 322 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
315 323 ('ldap_filter', ''), ('ldap_search_scope', ''),
316 324 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
317 325 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
318 326
319 327 if skip_existing and RhodeCodeSetting.get_by_name(k) != None:
320 328 log.debug('Skipping option %s' % k)
321 329 continue
322 330 setting = RhodeCodeSetting(k, v)
323 331 self.sa.add(setting)
324 332
325 333 def config_prompt(self, test_repo_path='', retries=3):
326 334 if retries == 3:
327 335 log.info('Setting up repositories config')
328 336
329 337 if not self.tests and not test_repo_path:
330 338 path = raw_input('Specify valid full path to your repositories'
331 339 ' you can change this later in application settings:')
332 340 else:
333 341 path = test_repo_path
334 342 path_ok = True
335 343
336 344 #check proper dir
337 345 if not os.path.isdir(path):
338 346 path_ok = False
339 347 log.error('Given path %s is not a valid directory', path)
340 348
341 349 #check write access
342 350 if not os.access(path, os.W_OK) and path_ok:
343 351 path_ok = False
344 352 log.error('No write permission to given path %s', path)
345 353
346 354
347 355 if retries == 0:
348 356 sys.exit('max retries reached')
349 357 if path_ok is False:
350 358 retries -= 1
351 359 return self.config_prompt(test_repo_path, retries)
352 360
353 361 return path
354 362
355 363 def create_settings(self, path):
356 364
357 365 self.create_ui_settings()
358 366
359 367 #HG UI OPTIONS
360 368 web1 = RhodeCodeUi()
361 369 web1.ui_section = 'web'
362 370 web1.ui_key = 'push_ssl'
363 371 web1.ui_value = 'false'
364 372
365 373 web2 = RhodeCodeUi()
366 374 web2.ui_section = 'web'
367 375 web2.ui_key = 'allow_archive'
368 376 web2.ui_value = 'gz zip bz2'
369 377
370 378 web3 = RhodeCodeUi()
371 379 web3.ui_section = 'web'
372 380 web3.ui_key = 'allow_push'
373 381 web3.ui_value = '*'
374 382
375 383 web4 = RhodeCodeUi()
376 384 web4.ui_section = 'web'
377 385 web4.ui_key = 'baseurl'
378 386 web4.ui_value = '/'
379 387
380 388 paths = RhodeCodeUi()
381 389 paths.ui_section = 'paths'
382 390 paths.ui_key = '/'
383 391 paths.ui_value = path
384 392
385 393 hgsettings1 = RhodeCodeSetting('realm', 'RhodeCode authentication')
386 394 hgsettings2 = RhodeCodeSetting('title', 'RhodeCode')
387 395 hgsettings3 = RhodeCodeSetting('ga_code', '')
388 396
389 397 self.sa.add(web1)
390 398 self.sa.add(web2)
391 399 self.sa.add(web3)
392 400 self.sa.add(web4)
393 401 self.sa.add(paths)
394 402 self.sa.add(hgsettings1)
395 403 self.sa.add(hgsettings2)
396 404 self.sa.add(hgsettings3)
397 405
398 406 self.create_ldap_options()
399 407
400 408 log.info('created ui config')
401 409
402 410 def create_user(self, username, password, email='', admin=False):
403 411 log.info('creating user %s', username)
404 412 UserModel().create_or_update(username, password, email,
405 413 name='RhodeCode', lastname='Admin',
406 414 active=True, admin=admin)
407 415
408 416 def create_default_user(self):
409 417 log.info('creating default user')
410 418 # create default user for handling default permissions.
411 419 UserModel().create_or_update(username='default',
412 420 password=str(uuid.uuid1())[:8],
413 421 email='anonymous@rhodecode.org',
414 422 name='Anonymous', lastname='User')
415 423
416 424 def create_permissions(self):
417 425 #module.(access|create|change|delete)_[name]
418 426 #module.(read|write|owner)
419 427 perms = [('repository.none', 'Repository no access'),
420 428 ('repository.read', 'Repository read access'),
421 429 ('repository.write', 'Repository write access'),
422 430 ('repository.admin', 'Repository admin access'),
423 431 ('hg.admin', 'Hg Administrator'),
424 432 ('hg.create.repository', 'Repository create'),
425 433 ('hg.create.none', 'Repository creation disabled'),
426 434 ('hg.register.none', 'Register disabled'),
427 435 ('hg.register.manual_activate', 'Register new user with '
428 436 'RhodeCode without manual'
429 437 'activation'),
430 438
431 439 ('hg.register.auto_activate', 'Register new user with '
432 440 'RhodeCode without auto '
433 441 'activation'),
434 442 ]
435 443
436 444 for p in perms:
437 445 new_perm = Permission()
438 446 new_perm.permission_name = p[0]
439 447 new_perm.permission_longname = p[1]
440 448 self.sa.add(new_perm)
441 449
442 450 def populate_default_permissions(self):
443 451 log.info('creating default user permissions')
444 452
445 453 default_user = self.sa.query(User)\
446 454 .filter(User.username == 'default').scalar()
447 455
448 456 reg_perm = UserToPerm()
449 457 reg_perm.user = default_user
450 458 reg_perm.permission = self.sa.query(Permission)\
451 459 .filter(Permission.permission_name == 'hg.register.manual_activate')\
452 460 .scalar()
453 461
454 462 create_repo_perm = UserToPerm()
455 463 create_repo_perm.user = default_user
456 464 create_repo_perm.permission = self.sa.query(Permission)\
457 465 .filter(Permission.permission_name == 'hg.create.repository')\
458 466 .scalar()
459 467
460 468 default_repo_perm = UserToPerm()
461 469 default_repo_perm.user = default_user
462 470 default_repo_perm.permission = self.sa.query(Permission)\
463 471 .filter(Permission.permission_name == 'repository.read')\
464 472 .scalar()
465 473
466 474 self.sa.add(reg_perm)
467 475 self.sa.add(create_repo_perm)
468 476 self.sa.add(default_repo_perm)
@@ -1,1108 +1,1120 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) 2009-2011 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
31 31 from sqlalchemy import *
32 32 from sqlalchemy.ext.hybrid import hybrid_property
33 33 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
34 34 from beaker.cache import cache_region, region_invalidate
35 35
36 36 from vcs import get_backend
37 37 from vcs.utils.helpers import get_scm
38 38 from vcs.exceptions import VCSError
39 39 from vcs.utils.lazy import LazyProperty
40 40
41 41 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, safe_unicode
42 42 from rhodecode.lib.exceptions import UsersGroupsAssignedException
43 43 from rhodecode.lib.compat import json
44 44 from rhodecode.lib.caching_query import FromCache
45 45
46 46 from rhodecode.model.meta import Base, Session
47 47
48 48 log = logging.getLogger(__name__)
49 49
50 50 #==============================================================================
51 51 # BASE CLASSES
52 52 #==============================================================================
53 53
54 54 class ModelSerializer(json.JSONEncoder):
55 55 """
56 56 Simple Serializer for JSON,
57 57
58 58 usage::
59 59
60 60 to make object customized for serialization implement a __json__
61 61 method that will return a dict for serialization into json
62 62
63 63 example::
64 64
65 65 class Task(object):
66 66
67 67 def __init__(self, name, value):
68 68 self.name = name
69 69 self.value = value
70 70
71 71 def __json__(self):
72 72 return dict(name=self.name,
73 73 value=self.value)
74 74
75 75 """
76 76
77 77 def default(self, obj):
78 78
79 79 if hasattr(obj, '__json__'):
80 80 return obj.__json__()
81 81 else:
82 82 return json.JSONEncoder.default(self, obj)
83 83
84 84 class BaseModel(object):
85 """Base Model for all classess
86
85 """
86 Base Model for all classess
87 87 """
88 88
89 89 @classmethod
90 90 def _get_keys(cls):
91 91 """return column names for this model """
92 92 return class_mapper(cls).c.keys()
93 93
94 def get_dict(self):
95 """return dict with keys and values corresponding
96 to this model data """
94 def get_dict(self, serialized=False):
95 """
96 return dict with keys and values corresponding
97 to this model data
98 """
97 99
98 100 d = {}
99 101 for k in self._get_keys():
100 102 d[k] = getattr(self, k)
103
104 # also use __json__() if present to get additional fields
105 if hasattr(self, '__json__'):
106 for k,val in self.__json__().iteritems():
107 d[k] = val
101 108 return d
102 109
103 110 def get_appstruct(self):
104 111 """return list with keys and values tupples corresponding
105 112 to this model data """
106 113
107 114 l = []
108 115 for k in self._get_keys():
109 116 l.append((k, getattr(self, k),))
110 117 return l
111 118
112 119 def populate_obj(self, populate_dict):
113 120 """populate model with data from given populate_dict"""
114 121
115 122 for k in self._get_keys():
116 123 if k in populate_dict:
117 124 setattr(self, k, populate_dict[k])
118 125
119 126 @classmethod
120 127 def query(cls):
121 128 return Session.query(cls)
122 129
123 130 @classmethod
124 131 def get(cls, id_):
125 132 if id_:
126 133 return cls.query().get(id_)
127 134
128 135 @classmethod
129 136 def getAll(cls):
130 137 return cls.query().all()
131 138
132 139 @classmethod
133 140 def delete(cls, id_):
134 141 obj = cls.query().get(id_)
135 142 Session.delete(obj)
136 143
137 144
138 145 class RhodeCodeSetting(Base, BaseModel):
139 146 __tablename__ = 'rhodecode_settings'
140 147 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
141 148 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
142 149 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
143 150 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
144 151
145 152 def __init__(self, k='', v=''):
146 153 self.app_settings_name = k
147 154 self.app_settings_value = v
148 155
149 156
150 157 @validates('_app_settings_value')
151 158 def validate_settings_value(self, key, val):
152 159 assert type(val) == unicode
153 160 return val
154 161
155 162 @hybrid_property
156 163 def app_settings_value(self):
157 164 v = self._app_settings_value
158 165 if v == 'ldap_active':
159 166 v = str2bool(v)
160 167 return v
161 168
162 169 @app_settings_value.setter
163 170 def app_settings_value(self, val):
164 171 """
165 172 Setter that will always make sure we use unicode in app_settings_value
166 173
167 174 :param val:
168 175 """
169 176 self._app_settings_value = safe_unicode(val)
170 177
171 178 def __repr__(self):
172 179 return "<%s('%s:%s')>" % (self.__class__.__name__,
173 180 self.app_settings_name, self.app_settings_value)
174 181
175 182
176 183 @classmethod
177 184 def get_by_name(cls, ldap_key):
178 185 return cls.query()\
179 186 .filter(cls.app_settings_name == ldap_key).scalar()
180 187
181 188 @classmethod
182 189 def get_app_settings(cls, cache=False):
183 190
184 191 ret = cls.query()
185 192
186 193 if cache:
187 194 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
188 195
189 196 if not ret:
190 197 raise Exception('Could not get application settings !')
191 198 settings = {}
192 199 for each in ret:
193 200 settings['rhodecode_' + each.app_settings_name] = \
194 201 each.app_settings_value
195 202
196 203 return settings
197 204
198 205 @classmethod
199 206 def get_ldap_settings(cls, cache=False):
200 207 ret = cls.query()\
201 208 .filter(cls.app_settings_name.startswith('ldap_')).all()
202 209 fd = {}
203 210 for row in ret:
204 211 fd.update({row.app_settings_name:row.app_settings_value})
205 212
206 213 return fd
207 214
208 215
209 216 class RhodeCodeUi(Base, BaseModel):
210 217 __tablename__ = 'rhodecode_ui'
211 218 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
212 219
213 220 HOOK_UPDATE = 'changegroup.update'
214 221 HOOK_REPO_SIZE = 'changegroup.repo_size'
215 222 HOOK_PUSH = 'pretxnchangegroup.push_logger'
216 223 HOOK_PULL = 'preoutgoing.pull_logger'
217 224
218 225 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
219 226 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
220 227 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
221 228 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
222 229 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
223 230
224 231
225 232 @classmethod
226 233 def get_by_key(cls, key):
227 234 return cls.query().filter(cls.ui_key == key)
228 235
229 236
230 237 @classmethod
231 238 def get_builtin_hooks(cls):
232 239 q = cls.query()
233 240 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
234 241 cls.HOOK_REPO_SIZE,
235 242 cls.HOOK_PUSH, cls.HOOK_PULL]))
236 243 return q.all()
237 244
238 245 @classmethod
239 246 def get_custom_hooks(cls):
240 247 q = cls.query()
241 248 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
242 249 cls.HOOK_REPO_SIZE,
243 250 cls.HOOK_PUSH, cls.HOOK_PULL]))
244 251 q = q.filter(cls.ui_section == 'hooks')
245 252 return q.all()
246 253
247 254 @classmethod
248 255 def create_or_update_hook(cls, key, val):
249 256 new_ui = cls.get_by_key(key).scalar() or cls()
250 257 new_ui.ui_section = 'hooks'
251 258 new_ui.ui_active = True
252 259 new_ui.ui_key = key
253 260 new_ui.ui_value = val
254 261
255 262 Session.add(new_ui)
256 263
257 264
258 265 class User(Base, BaseModel):
259 266 __tablename__ = 'users'
260 267 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
261 268 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
262 269 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
263 270 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
264 271 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
265 272 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
266 273 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
267 274 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
268 275 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
269 276 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
270 277 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
271 278 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
272 279
273 280 user_log = relationship('UserLog', cascade='all')
274 281 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
275 282
276 283 repositories = relationship('Repository')
277 284 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
278 285 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
279 286
280 287 group_member = relationship('UsersGroupMember', cascade='all')
281 288
282 289 notifications = relationship('UserNotification',)
283 290
284 291 @hybrid_property
285 292 def email(self):
286 293 return self._email
287 294
288 295 @email.setter
289 296 def email(self, val):
290 297 self._email = val.lower() if val else None
291 298
292 299 @property
293 300 def full_name(self):
294 301 return '%s %s' % (self.name, self.lastname)
295 302
296 303 @property
297 304 def full_contact(self):
298 305 return '%s %s <%s>' % (self.name, self.lastname, self.email)
299 306
300 307 @property
301 308 def short_contact(self):
302 309 return '%s %s' % (self.name, self.lastname)
303 310
304 311 @property
305 312 def is_admin(self):
306 313 return self.admin
307 314
308 315 def __repr__(self):
309 316 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
310 317 self.user_id, self.username)
311 318
312 319
313 320 @classmethod
314 321 def get_by_username(cls, username, case_insensitive=False, cache=False):
315 322 if case_insensitive:
316 323 q = cls.query().filter(cls.username.ilike(username))
317 324 else:
318 325 q = cls.query().filter(cls.username == username)
319 326
320 327 if cache:
321 328 q = q.options(FromCache("sql_cache_short",
322 329 "get_user_%s" % username))
323 330 return q.scalar()
324 331
325 332 @classmethod
326 333 def get_by_api_key(cls, api_key, cache=False):
327 334 q = cls.query().filter(cls.api_key == api_key)
328 335
329 336 if cache:
330 337 q = q.options(FromCache("sql_cache_short",
331 338 "get_api_key_%s" % api_key))
332 339 return q.scalar()
333 340
334 341 @classmethod
335 342 def get_by_email(cls, email, case_insensitive=False, cache=False):
336 343 if case_insensitive:
337 344 q = cls.query().filter(cls.email.ilike(email))
338 345 else:
339 346 q = cls.query().filter(cls.email == email)
340 347
341 348 if cache:
342 349 q = q.options(FromCache("sql_cache_short",
343 350 "get_api_key_%s" % email))
344 351 return q.scalar()
345 352
346 353 def update_lastlogin(self):
347 354 """Update user lastlogin"""
348 355 self.last_login = datetime.datetime.now()
349 356 Session.add(self)
350 357 log.debug('updated user %s lastlogin', self.username)
351 358
352 359
360 def __json__(self):
361 return dict(email=self.email,
362 full_name=self.full_name)
363
364
353 365 class UserLog(Base, BaseModel):
354 366 __tablename__ = 'user_logs'
355 367 __table_args__ = {'extend_existing':True}
356 368 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
357 369 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
358 370 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
359 371 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
360 372 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
361 373 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
362 374 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
363 375
364 376 @property
365 377 def action_as_day(self):
366 378 return datetime.date(*self.action_date.timetuple()[:3])
367 379
368 380 user = relationship('User')
369 381 repository = relationship('Repository',cascade='')
370 382
371 383
372 384 class UsersGroup(Base, BaseModel):
373 385 __tablename__ = 'users_groups'
374 386 __table_args__ = {'extend_existing':True}
375 387
376 388 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
377 389 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
378 390 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
379 391
380 392 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
381 393
382 394 def __repr__(self):
383 395 return '<userGroup(%s)>' % (self.users_group_name)
384 396
385 397 @classmethod
386 398 def get_by_group_name(cls, group_name, cache=False,
387 399 case_insensitive=False):
388 400 if case_insensitive:
389 401 q = cls.query().filter(cls.users_group_name.ilike(group_name))
390 402 else:
391 403 q = cls.query().filter(cls.users_group_name == group_name)
392 404 if cache:
393 405 q = q.options(FromCache("sql_cache_short",
394 406 "get_user_%s" % group_name))
395 407 return q.scalar()
396 408
397 409 @classmethod
398 410 def get(cls, users_group_id, cache=False):
399 411 users_group = cls.query()
400 412 if cache:
401 413 users_group = users_group.options(FromCache("sql_cache_short",
402 414 "get_users_group_%s" % users_group_id))
403 415 return users_group.get(users_group_id)
404 416
405 417 class UsersGroupMember(Base, BaseModel):
406 418 __tablename__ = 'users_groups_members'
407 419 __table_args__ = {'extend_existing':True}
408 420
409 421 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
410 422 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
411 423 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
412 424
413 425 user = relationship('User', lazy='joined')
414 426 users_group = relationship('UsersGroup')
415 427
416 428 def __init__(self, gr_id='', u_id=''):
417 429 self.users_group_id = gr_id
418 430 self.user_id = u_id
419 431
420 432 @staticmethod
421 433 def add_user_to_group(group, user):
422 434 ugm = UsersGroupMember()
423 435 ugm.users_group = group
424 436 ugm.user = user
425 437 Session.add(ugm)
426 438 Session.commit()
427 439 return ugm
428 440
429 441 class Repository(Base, BaseModel):
430 442 __tablename__ = 'repositories'
431 443 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
432 444
433 445 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
434 446 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
435 447 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
436 448 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
437 449 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
438 450 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
439 451 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
440 452 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
441 453 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
442 454 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
443 455
444 456 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
445 457 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
446 458
447 459
448 460 user = relationship('User')
449 461 fork = relationship('Repository', remote_side=repo_id)
450 462 group = relationship('RepoGroup')
451 463 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
452 464 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
453 465 stats = relationship('Statistics', cascade='all', uselist=False)
454 466
455 467 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
456 468
457 469 logs = relationship('UserLog')
458 470
459 471 def __repr__(self):
460 472 return "<%s('%s:%s')>" % (self.__class__.__name__,
461 473 self.repo_id, self.repo_name)
462 474
463 475 @classmethod
464 476 def url_sep(cls):
465 477 return '/'
466 478
467 479 @classmethod
468 480 def get_by_repo_name(cls, repo_name):
469 481 q = Session.query(cls).filter(cls.repo_name == repo_name)
470 482 q = q.options(joinedload(Repository.fork))\
471 483 .options(joinedload(Repository.user))\
472 484 .options(joinedload(Repository.group))
473 485 return q.scalar()
474 486
475 487 @classmethod
476 488 def get_repo_forks(cls, repo_id):
477 489 return cls.query().filter(Repository.fork_id == repo_id)
478 490
479 491 @classmethod
480 492 def base_path(cls):
481 493 """
482 494 Returns base path when all repos are stored
483 495
484 496 :param cls:
485 497 """
486 498 q = Session.query(RhodeCodeUi)\
487 499 .filter(RhodeCodeUi.ui_key == cls.url_sep())
488 500 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
489 501 return q.one().ui_value
490 502
491 503 @property
492 504 def just_name(self):
493 505 return self.repo_name.split(Repository.url_sep())[-1]
494 506
495 507 @property
496 508 def groups_with_parents(self):
497 509 groups = []
498 510 if self.group is None:
499 511 return groups
500 512
501 513 cur_gr = self.group
502 514 groups.insert(0, cur_gr)
503 515 while 1:
504 516 gr = getattr(cur_gr, 'parent_group', None)
505 517 cur_gr = cur_gr.parent_group
506 518 if gr is None:
507 519 break
508 520 groups.insert(0, gr)
509 521
510 522 return groups
511 523
512 524 @property
513 525 def groups_and_repo(self):
514 526 return self.groups_with_parents, self.just_name
515 527
516 528 @LazyProperty
517 529 def repo_path(self):
518 530 """
519 531 Returns base full path for that repository means where it actually
520 532 exists on a filesystem
521 533 """
522 534 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
523 535 Repository.url_sep())
524 536 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
525 537 return q.one().ui_value
526 538
527 539 @property
528 540 def repo_full_path(self):
529 541 p = [self.repo_path]
530 542 # we need to split the name by / since this is how we store the
531 543 # names in the database, but that eventually needs to be converted
532 544 # into a valid system path
533 545 p += self.repo_name.split(Repository.url_sep())
534 546 return os.path.join(*p)
535 547
536 548 def get_new_name(self, repo_name):
537 549 """
538 550 returns new full repository name based on assigned group and new new
539 551
540 552 :param group_name:
541 553 """
542 554 path_prefix = self.group.full_path_splitted if self.group else []
543 555 return Repository.url_sep().join(path_prefix + [repo_name])
544 556
545 557 @property
546 558 def _ui(self):
547 559 """
548 560 Creates an db based ui object for this repository
549 561 """
550 562 from mercurial import ui
551 563 from mercurial import config
552 564 baseui = ui.ui()
553 565
554 566 #clean the baseui object
555 567 baseui._ocfg = config.config()
556 568 baseui._ucfg = config.config()
557 569 baseui._tcfg = config.config()
558 570
559 571
560 572 ret = RhodeCodeUi.query()\
561 573 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
562 574
563 575 hg_ui = ret
564 576 for ui_ in hg_ui:
565 577 if ui_.ui_active:
566 578 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
567 579 ui_.ui_key, ui_.ui_value)
568 580 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
569 581
570 582 return baseui
571 583
572 584 @classmethod
573 585 def is_valid(cls, repo_name):
574 586 """
575 587 returns True if given repo name is a valid filesystem repository
576 588
577 589 @param cls:
578 590 @param repo_name:
579 591 """
580 592 from rhodecode.lib.utils import is_valid_repo
581 593
582 594 return is_valid_repo(repo_name, cls.base_path())
583 595
584 596
585 597 #==========================================================================
586 598 # SCM PROPERTIES
587 599 #==========================================================================
588 600
589 601 def get_changeset(self, rev):
590 602 return get_changeset_safe(self.scm_instance, rev)
591 603
592 604 @property
593 605 def tip(self):
594 606 return self.get_changeset('tip')
595 607
596 608 @property
597 609 def author(self):
598 610 return self.tip.author
599 611
600 612 @property
601 613 def last_change(self):
602 614 return self.scm_instance.last_change
603 615
604 616 #==========================================================================
605 617 # SCM CACHE INSTANCE
606 618 #==========================================================================
607 619
608 620 @property
609 621 def invalidate(self):
610 622 return CacheInvalidation.invalidate(self.repo_name)
611 623
612 624 def set_invalidate(self):
613 625 """
614 626 set a cache for invalidation for this instance
615 627 """
616 628 CacheInvalidation.set_invalidate(self.repo_name)
617 629
618 630 @LazyProperty
619 631 def scm_instance(self):
620 632 return self.__get_instance()
621 633
622 634 @property
623 635 def scm_instance_cached(self):
624 636 @cache_region('long_term')
625 637 def _c(repo_name):
626 638 return self.__get_instance()
627 639 rn = self.repo_name
628 640 log.debug('Getting cached instance of repo')
629 641 inv = self.invalidate
630 642 if inv is not None:
631 643 region_invalidate(_c, None, rn)
632 644 # update our cache
633 645 CacheInvalidation.set_valid(inv.cache_key)
634 646 return _c(rn)
635 647
636 648 def __get_instance(self):
637 649 repo_full_path = self.repo_full_path
638 650 try:
639 651 alias = get_scm(repo_full_path)[0]
640 652 log.debug('Creating instance of %s repository', alias)
641 653 backend = get_backend(alias)
642 654 except VCSError:
643 655 log.error(traceback.format_exc())
644 656 log.error('Perhaps this repository is in db and not in '
645 657 'filesystem run rescan repositories with '
646 658 '"destroy old data " option from admin panel')
647 659 return
648 660
649 661 if alias == 'hg':
650 662 repo = backend(safe_str(repo_full_path), create=False,
651 663 baseui=self._ui)
652 664 # skip hidden web repository
653 665 if repo._get_hidden():
654 666 return
655 667 else:
656 668 repo = backend(repo_full_path, create=False)
657 669
658 670 return repo
659 671
660 672
661 673 class RepoGroup(Base, BaseModel):
662 674 __tablename__ = 'groups'
663 675 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
664 676 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
665 677 __mapper_args__ = {'order_by':'group_name'}
666 678
667 679 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
668 680 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
669 681 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
670 682 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
671 683
672 684 parent_group = relationship('RepoGroup', remote_side=group_id)
673 685
674 686
675 687 def __init__(self, group_name='', parent_group=None):
676 688 self.group_name = group_name
677 689 self.parent_group = parent_group
678 690
679 691 def __repr__(self):
680 692 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
681 693 self.group_name)
682 694
683 695 @classmethod
684 696 def groups_choices(cls):
685 697 from webhelpers.html import literal as _literal
686 698 repo_groups = [('', '')]
687 699 sep = ' &raquo; '
688 700 _name = lambda k: _literal(sep.join(k))
689 701
690 702 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
691 703 for x in cls.query().all()])
692 704
693 705 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
694 706 return repo_groups
695 707
696 708 @classmethod
697 709 def url_sep(cls):
698 710 return '/'
699 711
700 712 @classmethod
701 713 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
702 714 if case_insensitive:
703 715 gr = cls.query()\
704 716 .filter(cls.group_name.ilike(group_name))
705 717 else:
706 718 gr = cls.query()\
707 719 .filter(cls.group_name == group_name)
708 720 if cache:
709 721 gr = gr.options(FromCache("sql_cache_short",
710 722 "get_group_%s" % group_name))
711 723 return gr.scalar()
712 724
713 725 @property
714 726 def parents(self):
715 727 parents_recursion_limit = 5
716 728 groups = []
717 729 if self.parent_group is None:
718 730 return groups
719 731 cur_gr = self.parent_group
720 732 groups.insert(0, cur_gr)
721 733 cnt = 0
722 734 while 1:
723 735 cnt += 1
724 736 gr = getattr(cur_gr, 'parent_group', None)
725 737 cur_gr = cur_gr.parent_group
726 738 if gr is None:
727 739 break
728 740 if cnt == parents_recursion_limit:
729 741 # this will prevent accidental infinit loops
730 742 log.error('group nested more than %s' %
731 743 parents_recursion_limit)
732 744 break
733 745
734 746 groups.insert(0, gr)
735 747 return groups
736 748
737 749 @property
738 750 def children(self):
739 751 return RepoGroup.query().filter(RepoGroup.parent_group == self)
740 752
741 753 @property
742 754 def name(self):
743 755 return self.group_name.split(RepoGroup.url_sep())[-1]
744 756
745 757 @property
746 758 def full_path(self):
747 759 return self.group_name
748 760
749 761 @property
750 762 def full_path_splitted(self):
751 763 return self.group_name.split(RepoGroup.url_sep())
752 764
753 765 @property
754 766 def repositories(self):
755 767 return Repository.query().filter(Repository.group == self)
756 768
757 769 @property
758 770 def repositories_recursive_count(self):
759 771 cnt = self.repositories.count()
760 772
761 773 def children_count(group):
762 774 cnt = 0
763 775 for child in group.children:
764 776 cnt += child.repositories.count()
765 777 cnt += children_count(child)
766 778 return cnt
767 779
768 780 return cnt + children_count(self)
769 781
770 782
771 783 def get_new_name(self, group_name):
772 784 """
773 785 returns new full group name based on parent and new name
774 786
775 787 :param group_name:
776 788 """
777 789 path_prefix = (self.parent_group.full_path_splitted if
778 790 self.parent_group else [])
779 791 return RepoGroup.url_sep().join(path_prefix + [group_name])
780 792
781 793
782 794 class Permission(Base, BaseModel):
783 795 __tablename__ = 'permissions'
784 796 __table_args__ = {'extend_existing':True}
785 797 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
786 798 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
787 799 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
788 800
789 801 def __repr__(self):
790 802 return "<%s('%s:%s')>" % (self.__class__.__name__,
791 803 self.permission_id, self.permission_name)
792 804
793 805 @classmethod
794 806 def get_by_key(cls, key):
795 807 return cls.query().filter(cls.permission_name == key).scalar()
796 808
797 809 @classmethod
798 810 def get_default_perms(cls, default_user_id):
799 811 q = Session.query(UserRepoToPerm, Repository, cls)\
800 812 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
801 813 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
802 814 .filter(UserRepoToPerm.user_id == default_user_id)
803 815
804 816 return q.all()
805 817
806 818
807 819 class UserRepoToPerm(Base, BaseModel):
808 820 __tablename__ = 'repo_to_perm'
809 821 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
810 822 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
811 823 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
812 824 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
813 825 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
814 826
815 827 user = relationship('User')
816 828 permission = relationship('Permission')
817 829 repository = relationship('Repository')
818 830
819 831 @classmethod
820 832 def create(cls, user, repository, permission):
821 833 n = cls()
822 834 n.user = user
823 835 n.repository = repository
824 836 n.permission = permission
825 837 Session.add(n)
826 838 return n
827 839
828 840 def __repr__(self):
829 841 return '<user:%s => %s >' % (self.user, self.repository)
830 842
831 843 class UserToPerm(Base, BaseModel):
832 844 __tablename__ = 'user_to_perm'
833 845 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
834 846 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
835 847 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
836 848 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
837 849
838 850 user = relationship('User')
839 851 permission = relationship('Permission', lazy='joined')
840 852
841 853
842 854 class UsersGroupRepoToPerm(Base, BaseModel):
843 855 __tablename__ = 'users_group_repo_to_perm'
844 856 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
845 857 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
846 858 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
847 859 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
848 860 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
849 861
850 862 users_group = relationship('UsersGroup')
851 863 permission = relationship('Permission')
852 864 repository = relationship('Repository')
853 865
854 866 @classmethod
855 867 def create(cls, users_group, repository, permission):
856 868 n = cls()
857 869 n.users_group = users_group
858 870 n.repository = repository
859 871 n.permission = permission
860 872 Session.add(n)
861 873 return n
862 874
863 875 def __repr__(self):
864 876 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
865 877
866 878 class UsersGroupToPerm(Base, BaseModel):
867 879 __tablename__ = 'users_group_to_perm'
868 880 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
869 881 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
870 882 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
871 883
872 884 users_group = relationship('UsersGroup')
873 885 permission = relationship('Permission')
874 886
875 887
876 888 class UserRepoGroupToPerm(Base, BaseModel):
877 889 __tablename__ = 'group_to_perm'
878 890 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
879 891
880 892 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
881 893 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
882 894 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
883 895 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
884 896
885 897 user = relationship('User')
886 898 permission = relationship('Permission')
887 899 group = relationship('RepoGroup')
888 900
889 901 class UsersGroupRepoGroupToPerm(Base, BaseModel):
890 902 __tablename__ = 'users_group_repo_group_to_perm'
891 903 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
892 904
893 905 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)
894 906 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
895 907 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
896 908 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
897 909
898 910 users_group = relationship('UsersGroup')
899 911 permission = relationship('Permission')
900 912 group = relationship('RepoGroup')
901 913
902 914 class Statistics(Base, BaseModel):
903 915 __tablename__ = 'statistics'
904 916 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
905 917 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
906 918 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
907 919 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
908 920 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
909 921 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
910 922 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
911 923
912 924 repository = relationship('Repository', single_parent=True)
913 925
914 926 class UserFollowing(Base, BaseModel):
915 927 __tablename__ = 'user_followings'
916 928 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
917 929 UniqueConstraint('user_id', 'follows_user_id')
918 930 , {'extend_existing':True})
919 931
920 932 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
921 933 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
922 934 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
923 935 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
924 936 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
925 937
926 938 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
927 939
928 940 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
929 941 follows_repository = relationship('Repository', order_by='Repository.repo_name')
930 942
931 943
932 944 @classmethod
933 945 def get_repo_followers(cls, repo_id):
934 946 return cls.query().filter(cls.follows_repo_id == repo_id)
935 947
936 948 class CacheInvalidation(Base, BaseModel):
937 949 __tablename__ = 'cache_invalidation'
938 950 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
939 951 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
940 952 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
941 953 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
942 954 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
943 955
944 956
945 957 def __init__(self, cache_key, cache_args=''):
946 958 self.cache_key = cache_key
947 959 self.cache_args = cache_args
948 960 self.cache_active = False
949 961
950 962 def __repr__(self):
951 963 return "<%s('%s:%s')>" % (self.__class__.__name__,
952 964 self.cache_id, self.cache_key)
953 965
954 966 @classmethod
955 967 def invalidate(cls, key):
956 968 """
957 969 Returns Invalidation object if this given key should be invalidated
958 970 None otherwise. `cache_active = False` means that this cache
959 971 state is not valid and needs to be invalidated
960 972
961 973 :param key:
962 974 """
963 975 return cls.query()\
964 976 .filter(CacheInvalidation.cache_key == key)\
965 977 .filter(CacheInvalidation.cache_active == False)\
966 978 .scalar()
967 979
968 980 @classmethod
969 981 def set_invalidate(cls, key):
970 982 """
971 983 Mark this Cache key for invalidation
972 984
973 985 :param key:
974 986 """
975 987
976 988 log.debug('marking %s for invalidation' % key)
977 989 inv_obj = Session.query(cls)\
978 990 .filter(cls.cache_key == key).scalar()
979 991 if inv_obj:
980 992 inv_obj.cache_active = False
981 993 else:
982 994 log.debug('cache key not found in invalidation db -> creating one')
983 995 inv_obj = CacheInvalidation(key)
984 996
985 997 try:
986 998 Session.add(inv_obj)
987 999 Session.commit()
988 1000 except Exception:
989 1001 log.error(traceback.format_exc())
990 1002 Session.rollback()
991 1003
992 1004 @classmethod
993 1005 def set_valid(cls, key):
994 1006 """
995 1007 Mark this cache key as active and currently cached
996 1008
997 1009 :param key:
998 1010 """
999 1011 inv_obj = CacheInvalidation.query()\
1000 1012 .filter(CacheInvalidation.cache_key == key).scalar()
1001 1013 inv_obj.cache_active = True
1002 1014 Session.add(inv_obj)
1003 1015 Session.commit()
1004 1016
1005 1017
1006 1018 class ChangesetComment(Base, BaseModel):
1007 1019 __tablename__ = 'changeset_comments'
1008 1020 __table_args__ = ({'extend_existing':True},)
1009 1021 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1010 1022 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1011 1023 revision = Column('revision', String(40), nullable=False)
1012 1024 line_no = Column('line_no', Unicode(10), nullable=True)
1013 1025 f_path = Column('f_path', Unicode(1000), nullable=True)
1014 1026 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1015 1027 text = Column('text', Unicode(25000), nullable=False)
1016 1028 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1017 1029
1018 1030 author = relationship('User', lazy='joined')
1019 1031 repo = relationship('Repository')
1020 1032
1021 1033
1022 1034 @classmethod
1023 1035 def get_users(cls, revision):
1024 1036 """
1025 1037 Returns user associated with this changesetComment. ie those
1026 1038 who actually commented
1027 1039
1028 1040 :param cls:
1029 1041 :param revision:
1030 1042 """
1031 1043 return Session.query(User)\
1032 1044 .filter(cls.revision == revision)\
1033 1045 .join(ChangesetComment.author).all()
1034 1046
1035 1047
1036 1048 class Notification(Base, BaseModel):
1037 1049 __tablename__ = 'notifications'
1038 1050 __table_args__ = ({'extend_existing':True})
1039 1051
1040 1052 TYPE_CHANGESET_COMMENT = u'cs_comment'
1041 1053 TYPE_MESSAGE = u'message'
1042 1054 TYPE_MENTION = u'mention'
1043 1055 TYPE_REGISTRATION = u'registration'
1044 1056
1045 1057 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1046 1058 subject = Column('subject', Unicode(512), nullable=True)
1047 1059 body = Column('body', Unicode(50000), nullable=True)
1048 1060 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1049 1061 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1050 1062 type_ = Column('type', Unicode(256))
1051 1063
1052 1064 created_by_user = relationship('User')
1053 1065 notifications_to_users = relationship('UserNotification', lazy='joined',
1054 1066 cascade="all, delete, delete-orphan")
1055 1067
1056 1068 @property
1057 1069 def recipients(self):
1058 1070 return [x.user for x in UserNotification.query()\
1059 1071 .filter(UserNotification.notification == self).all()]
1060 1072
1061 1073 @classmethod
1062 1074 def create(cls, created_by, subject, body, recipients, type_=None):
1063 1075 if type_ is None:
1064 1076 type_ = Notification.TYPE_MESSAGE
1065 1077
1066 1078 notification = cls()
1067 1079 notification.created_by_user = created_by
1068 1080 notification.subject = subject
1069 1081 notification.body = body
1070 1082 notification.type_ = type_
1071 1083 notification.created_on = datetime.datetime.now()
1072 1084
1073 1085 for u in recipients:
1074 1086 assoc = UserNotification()
1075 1087 assoc.notification = notification
1076 1088 u.notifications.append(assoc)
1077 1089 Session.add(notification)
1078 1090 return notification
1079 1091
1080 1092 @property
1081 1093 def description(self):
1082 1094 from rhodecode.model.notification import NotificationModel
1083 1095 return NotificationModel().make_description(self)
1084 1096
1085 1097 class UserNotification(Base, BaseModel):
1086 1098 __tablename__ = 'user_to_notification'
1087 1099 __table_args__ = (UniqueConstraint('user_id', 'notification_id'),
1088 1100 {'extend_existing':True})
1089 1101 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), primary_key=True)
1090 1102 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1091 1103 read = Column('read', Boolean, default=False)
1092 1104 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1093 1105
1094 1106 user = relationship('User', lazy="joined")
1095 1107 notification = relationship('Notification', lazy="joined",
1096 1108 order_by=lambda:Notification.created_on.desc(),)
1097 1109
1098 1110 def mark_as_read(self):
1099 1111 self.read = True
1100 1112 Session.add(self)
1101 1113
1102 1114 class DbMigrateVersion(Base, BaseModel):
1103 1115 __tablename__ = 'db_migrate_version'
1104 1116 __table_args__ = {'extend_existing':True}
1105 1117 repository_id = Column('repository_id', String(250), primary_key=True)
1106 1118 repository_path = Column('repository_path', Text)
1107 1119 version = Column('version', Integer)
1108 1120
@@ -1,504 +1,507 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) 2009-2011 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 rhodecode.lib import safe_unicode
33 33 from rhodecode.lib.caching_query import FromCache
34 34
35 35 from rhodecode.model import BaseModel
36 36 from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
37 37 UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \
38 38 Notification
39 39 from rhodecode.lib.exceptions import DefaultUserException, \
40 40 UserOwnsReposException
41 41
42 42 from sqlalchemy.exc import DatabaseError
43 43 from rhodecode.lib import generate_api_key
44 44 from sqlalchemy.orm import joinedload
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48
49 49 PERM_WEIGHTS = {'repository.none': 0,
50 50 'repository.read': 1,
51 51 'repository.write': 3,
52 52 'repository.admin': 3}
53 53
54 54
55 55 class UserModel(BaseModel):
56 56
57 57 def __get_user(self, user):
58 58 return self._get_instance(User, user)
59 59
60 60 def get(self, user_id, cache=False):
61 61 user = self.sa.query(User)
62 62 if cache:
63 63 user = user.options(FromCache("sql_cache_short",
64 64 "get_user_%s" % user_id))
65 65 return user.get(user_id)
66 66
67 67 def get_by_username(self, username, cache=False, case_insensitive=False):
68 68
69 69 if case_insensitive:
70 70 user = self.sa.query(User).filter(User.username.ilike(username))
71 71 else:
72 72 user = self.sa.query(User)\
73 73 .filter(User.username == username)
74 74 if cache:
75 75 user = user.options(FromCache("sql_cache_short",
76 76 "get_user_%s" % username))
77 77 return user.scalar()
78 78
79 79 def get_by_api_key(self, api_key, cache=False):
80 80 return User.get_by_api_key(api_key, cache)
81 81
82 82 def create(self, form_data):
83 83 try:
84 84 new_user = User()
85 85 for k, v in form_data.items():
86 86 setattr(new_user, k, v)
87 87
88 88 new_user.api_key = generate_api_key(form_data['username'])
89 89 self.sa.add(new_user)
90 90 return new_user
91 91 except:
92 92 log.error(traceback.format_exc())
93 93 raise
94 94
95 95
96 96 def create_or_update(self, username, password, email, name, lastname,
97 97 active=True, admin=False, ldap_dn=None):
98 98 """
99 99 Creates a new instance if not found, or updates current one
100 100
101 101 :param username:
102 102 :param password:
103 103 :param email:
104 104 :param active:
105 105 :param name:
106 106 :param lastname:
107 107 :param active:
108 108 :param admin:
109 109 :param ldap_dn:
110 110 """
111 111
112 112 from rhodecode.lib.auth import get_crypt_password
113 113
114 114 log.debug('Checking for %s account in RhodeCode database', username)
115 115 user = User.get_by_username(username, case_insensitive=True)
116 116 if user is None:
117 117 log.debug('creating new user %s', username)
118 118 new_user = User()
119 119 else:
120 120 log.debug('updating user %s', username)
121 121 new_user = user
122 122
123 123 try:
124 124 new_user.username = username
125 125 new_user.admin = admin
126 126 new_user.password = get_crypt_password(password)
127 127 new_user.api_key = generate_api_key(username)
128 128 new_user.email = email
129 129 new_user.active = active
130 130 new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None
131 131 new_user.name = name
132 132 new_user.lastname = lastname
133 133 self.sa.add(new_user)
134 134 return new_user
135 135 except (DatabaseError,):
136 136 log.error(traceback.format_exc())
137 137 raise
138 138
139 139
140 140 def create_for_container_auth(self, username, attrs):
141 141 """
142 142 Creates the given user if it's not already in the database
143 143
144 144 :param username:
145 145 :param attrs:
146 146 """
147 147 if self.get_by_username(username, case_insensitive=True) is None:
148 148
149 149 # autogenerate email for container account without one
150 150 generate_email = lambda usr: '%s@container_auth.account' % usr
151 151
152 152 try:
153 153 new_user = User()
154 154 new_user.username = username
155 155 new_user.password = None
156 156 new_user.api_key = generate_api_key(username)
157 157 new_user.email = attrs['email']
158 158 new_user.active = attrs.get('active', True)
159 159 new_user.name = attrs['name'] or generate_email(username)
160 160 new_user.lastname = attrs['lastname']
161 161
162 162 self.sa.add(new_user)
163 163 return new_user
164 164 except (DatabaseError,):
165 165 log.error(traceback.format_exc())
166 166 self.sa.rollback()
167 167 raise
168 168 log.debug('User %s already exists. Skipping creation of account'
169 169 ' for container auth.', username)
170 170 return None
171 171
172 172 def create_ldap(self, username, password, user_dn, attrs):
173 173 """
174 174 Checks if user is in database, if not creates this user marked
175 175 as ldap user
176 176
177 177 :param username:
178 178 :param password:
179 179 :param user_dn:
180 180 :param attrs:
181 181 """
182 182 from rhodecode.lib.auth import get_crypt_password
183 183 log.debug('Checking for such ldap account in RhodeCode database')
184 184 if self.get_by_username(username, case_insensitive=True) is None:
185 185
186 186 # autogenerate email for ldap account without one
187 187 generate_email = lambda usr: '%s@ldap.account' % usr
188 188
189 189 try:
190 190 new_user = User()
191 191 username = username.lower()
192 192 # add ldap account always lowercase
193 193 new_user.username = username
194 194 new_user.password = get_crypt_password(password)
195 195 new_user.api_key = generate_api_key(username)
196 196 new_user.email = attrs['email'] or generate_email(username)
197 197 new_user.active = attrs.get('active', True)
198 198 new_user.ldap_dn = safe_unicode(user_dn)
199 199 new_user.name = attrs['name']
200 200 new_user.lastname = attrs['lastname']
201 201
202 202 self.sa.add(new_user)
203 203 return new_user
204 204 except (DatabaseError,):
205 205 log.error(traceback.format_exc())
206 206 self.sa.rollback()
207 207 raise
208 208 log.debug('this %s user exists skipping creation of ldap account',
209 209 username)
210 210 return None
211 211
212 212 def create_registration(self, form_data):
213 213 from rhodecode.model.notification import NotificationModel
214 214
215 215 try:
216 216 new_user = User()
217 217 for k, v in form_data.items():
218 218 if k != 'admin':
219 219 setattr(new_user, k, v)
220 220
221 221 self.sa.add(new_user)
222 222 self.sa.flush()
223 223
224 224 # notification to admins
225 225 subject = _('new user registration')
226 226 body = ('New user registration\n'
227 227 '---------------------\n'
228 228 '- Username: %s\n'
229 229 '- Full Name: %s\n'
230 230 '- Email: %s\n')
231 231 body = body % (new_user.username, new_user.full_name,
232 232 new_user.email)
233 233 edit_url = url('edit_user', id=new_user.user_id, qualified=True)
234 234 kw = {'registered_user_url':edit_url}
235 235 NotificationModel().create(created_by=new_user, subject=subject,
236 236 body=body, recipients=None,
237 237 type_=Notification.TYPE_REGISTRATION,
238 238 email_kwargs=kw)
239 239
240 240 except:
241 241 log.error(traceback.format_exc())
242 242 raise
243 243
244 244 def update(self, user_id, form_data):
245 245 try:
246 246 user = self.get(user_id, cache=False)
247 247 if user.username == 'default':
248 248 raise DefaultUserException(
249 249 _("You can't Edit this user since it's"
250 250 " crucial for entire application"))
251 251
252 252 for k, v in form_data.items():
253 253 if k == 'new_password' and v != '':
254 254 user.password = v
255 255 user.api_key = generate_api_key(user.username)
256 256 else:
257 257 setattr(user, k, v)
258 258
259 259 self.sa.add(user)
260 260 except:
261 261 log.error(traceback.format_exc())
262 262 raise
263 263
264 264 def update_my_account(self, user_id, form_data):
265 265 try:
266 266 user = self.get(user_id, cache=False)
267 267 if user.username == 'default':
268 268 raise DefaultUserException(
269 269 _("You can't Edit this user since it's"
270 270 " crucial for entire application"))
271 271 for k, v in form_data.items():
272 272 if k == 'new_password' and v != '':
273 273 user.password = v
274 274 user.api_key = generate_api_key(user.username)
275 275 else:
276 276 if k not in ['admin', 'active']:
277 277 setattr(user, k, v)
278 278
279 279 self.sa.add(user)
280 280 except:
281 281 log.error(traceback.format_exc())
282 282 raise
283 283
284 def delete(self, user_id):
284 def delete(self, user):
285 user = self.__get_user(user)
286
285 287 try:
286 user = self.get(user_id, cache=False)
287 288 if user.username == 'default':
288 289 raise DefaultUserException(
289 290 _("You can't remove this user since it's"
290 291 " crucial for entire application"))
291 292 if user.repositories:
292 293 raise UserOwnsReposException(_('This user still owns %s '
293 294 'repositories and cannot be '
294 295 'removed. Switch owners or '
295 296 'remove those repositories') \
296 297 % user.repositories)
297 298 self.sa.delete(user)
298 299 except:
299 300 log.error(traceback.format_exc())
300 301 raise
301 302
302 303 def reset_password_link(self, data):
303 304 from rhodecode.lib.celerylib import tasks, run_task
304 305 run_task(tasks.send_password_link, data['email'])
305 306
306 307 def reset_password(self, data):
307 308 from rhodecode.lib.celerylib import tasks, run_task
308 309 run_task(tasks.reset_user_password, data['email'])
309 310
310 311 def fill_data(self, auth_user, user_id=None, api_key=None):
311 312 """
312 313 Fetches auth_user by user_id,or api_key if present.
313 314 Fills auth_user attributes with those taken from database.
314 315 Additionally set's is_authenitated if lookup fails
315 316 present in database
316 317
317 318 :param auth_user: instance of user to set attributes
318 319 :param user_id: user id to fetch by
319 320 :param api_key: api key to fetch by
320 321 """
321 322 if user_id is None and api_key is None:
322 323 raise Exception('You need to pass user_id or api_key')
323 324
324 325 try:
325 326 if api_key:
326 327 dbuser = self.get_by_api_key(api_key)
327 328 else:
328 329 dbuser = self.get(user_id)
329 330
330 331 if dbuser is not None and dbuser.active:
331 332 log.debug('filling %s data', dbuser)
332 333 for k, v in dbuser.get_dict().items():
333 334 setattr(auth_user, k, v)
334 335 else:
335 336 return False
336 337
337 338 except:
338 339 log.error(traceback.format_exc())
339 340 auth_user.is_authenticated = False
340 341 return False
341 342
342 343 return True
343 344
344 345 def fill_perms(self, user):
345 346 """
346 347 Fills user permission attribute with permissions taken from database
347 348 works for permissions given for repositories, and for permissions that
348 349 are granted to groups
349 350
350 351 :param user: user instance to fill his perms
351 352 """
352 353
353 354 user.permissions['repositories'] = {}
354 355 user.permissions['global'] = set()
355 356
356 357 #======================================================================
357 358 # fetch default permissions
358 359 #======================================================================
359 360 default_user = User.get_by_username('default', cache=True)
360 361 default_user_id = default_user.user_id
361 362
362 363 default_perms = Permission.get_default_perms(default_user_id)
363 364
364 365 if user.is_admin:
365 366 #==================================================================
366 367 # #admin have all default rights set to admin
367 368 #==================================================================
368 369 user.permissions['global'].add('hg.admin')
369 370
370 371 for perm in default_perms:
371 372 p = 'repository.admin'
372 373 user.permissions['repositories'][perm.UserRepoToPerm.
373 374 repository.repo_name] = p
374 375
375 376 else:
376 377 #==================================================================
377 378 # set default permissions
378 379 #==================================================================
379 380 uid = user.user_id
380 381
381 382 # default global
382 383 default_global_perms = self.sa.query(UserToPerm)\
383 384 .filter(UserToPerm.user_id == default_user_id)
384 385
385 386 for perm in default_global_perms:
386 387 user.permissions['global'].add(perm.permission.permission_name)
387 388
388 389 # default for repositories
389 390 for perm in default_perms:
390 391 if perm.Repository.private and not (perm.Repository.user_id ==
391 392 uid):
392 393 # disable defaults for private repos,
393 394 p = 'repository.none'
394 395 elif perm.Repository.user_id == uid:
395 396 # set admin if owner
396 397 p = 'repository.admin'
397 398 else:
398 399 p = perm.Permission.permission_name
399 400
400 401 user.permissions['repositories'][perm.UserRepoToPerm.
401 402 repository.repo_name] = p
402 403
403 404 #==================================================================
404 405 # overwrite default with user permissions if any
405 406 #==================================================================
406 407
407 408 # user global
408 409 user_perms = self.sa.query(UserToPerm)\
409 410 .options(joinedload(UserToPerm.permission))\
410 411 .filter(UserToPerm.user_id == uid).all()
411 412
412 413 for perm in user_perms:
413 414 user.permissions['global'].add(perm.permission.permission_name)
414 415
415 416 # user repositories
416 417 user_repo_perms = self.sa.query(UserRepoToPerm, Permission,
417 418 Repository)\
418 419 .join((Repository, UserRepoToPerm.repository_id ==
419 420 Repository.repo_id))\
420 421 .join((Permission, UserRepoToPerm.permission_id ==
421 422 Permission.permission_id))\
422 423 .filter(UserRepoToPerm.user_id == uid).all()
423 424
424 425 for perm in user_repo_perms:
425 426 # set admin if owner
426 427 if perm.Repository.user_id == uid:
427 428 p = 'repository.admin'
428 429 else:
429 430 p = perm.Permission.permission_name
430 431 user.permissions['repositories'][perm.UserRepoToPerm.
431 432 repository.repo_name] = p
432 433
433 434 #==================================================================
434 435 # check if user is part of groups for this repository and fill in
435 436 # (or replace with higher) permissions
436 437 #==================================================================
437 438
438 439 # users group global
439 440 user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
440 441 .options(joinedload(UsersGroupToPerm.permission))\
441 442 .join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
442 443 UsersGroupMember.users_group_id))\
443 444 .filter(UsersGroupMember.user_id == uid).all()
444 445
445 446 for perm in user_perms_from_users_groups:
446 447 user.permissions['global'].add(perm.permission.permission_name)
447 448
448 449 # users group repositories
449 450 user_repo_perms_from_users_groups = self.sa.query(
450 451 UsersGroupRepoToPerm,
451 452 Permission, Repository,)\
452 453 .join((Repository, UsersGroupRepoToPerm.repository_id ==
453 454 Repository.repo_id))\
454 455 .join((Permission, UsersGroupRepoToPerm.permission_id ==
455 456 Permission.permission_id))\
456 457 .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id ==
457 458 UsersGroupMember.users_group_id))\
458 459 .filter(UsersGroupMember.user_id == uid).all()
459 460
460 461 for perm in user_repo_perms_from_users_groups:
461 462 p = perm.Permission.permission_name
462 463 cur_perm = user.permissions['repositories'][perm.
463 464 UsersGroupRepoToPerm.
464 465 repository.repo_name]
465 466 # overwrite permission only if it's greater than permission
466 467 # given from other sources
467 468 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
468 469 user.permissions['repositories'][perm.UsersGroupRepoToPerm.
469 470 repository.repo_name] = p
470 471
471 472 return user
472 473
473
474
475 474 def has_perm(self, user, perm):
476 475 if not isinstance(perm, Permission):
477 raise Exception('perm needs to be an instance of Permission class')
476 raise Exception('perm needs to be an instance of Permission class '
477 'got %s instead' % type(perm))
478 478
479 479 user = self.__get_user(user)
480 480
481 return UserToPerm.query().filter(UserToPerm.user == user.user)\
481 return UserToPerm.query().filter(UserToPerm.user == user)\
482 482 .filter(UserToPerm.permission == perm).scalar() is not None
483 483
484 484 def grant_perm(self, user, perm):
485 485 if not isinstance(perm, Permission):
486 raise Exception('perm needs to be an instance of Permission class')
486 raise Exception('perm needs to be an instance of Permission class '
487 'got %s instead' % type(perm))
487 488
488 489 user = self.__get_user(user)
489 490
490 491 new = UserToPerm()
491 new.user = user.user
492 new.user = user
492 493 new.permission = perm
493 494 self.sa.add(new)
494 495
495 496
496 497 def revoke_perm(self, user, perm):
497 498 if not isinstance(perm, Permission):
498 raise Exception('perm needs to be an instance of Permission class')
499 raise Exception('perm needs to be an instance of Permission class '
500 'got %s instead' % type(perm))
499 501
500 502 user = self.__get_user(user)
501 503
502 obj = UserToPerm.query().filter(UserToPerm.user == user.user)\
503 .filter(UserToPerm.permission == perm).one()
504 self.sa.delete(obj)
504 obj = UserToPerm.query().filter(UserToPerm.user == user)\
505 .filter(UserToPerm.permission == perm).scalar()
506 if obj:
507 self.sa.delete(obj)
@@ -1,96 +1,110 b''
1 1 """Pylons application test package
2 2
3 3 This package assumes the Pylons environment is already loaded, such as
4 4 when this script is imported from the `nosetests --with-pylons=test.ini`
5 5 command.
6 6
7 7 This module initializes the application via ``websetup`` (`paster
8 8 setup-app`) and provides the base testing objects.
9 9 """
10 10 import os
11 11 import time
12 12 import logging
13 13 from os.path import join as jn
14 14
15 15 from unittest import TestCase
16 16 from tempfile import _RandomNameSequence
17 17
18 18 from paste.deploy import loadapp
19 19 from paste.script.appinstall import SetupCommand
20 20 from pylons import config, url
21 21 from routes.util import URLGenerator
22 22 from webtest import TestApp
23 23
24 24 from rhodecode.model.meta import Session
25 25 from rhodecode.model.db import User
26 26
27 27 import pylons.test
28 28
29 29 os.environ['TZ'] = 'UTC'
30 30 time.tzset()
31 31
32 32 log = logging.getLogger(__name__)
33 33
34 __all__ = ['environ', 'url', 'TestController', 'TESTS_TMP_PATH', 'HG_REPO',
35 'GIT_REPO', 'NEW_HG_REPO', 'NEW_GIT_REPO', 'HG_FORK', 'GIT_FORK',
36 'TEST_USER_ADMIN_LOGIN', 'TEST_USER_ADMIN_PASS' ]
34 __all__ = [
35 'environ', 'url', 'TestController', 'TESTS_TMP_PATH', 'HG_REPO',
36 'GIT_REPO', 'NEW_HG_REPO', 'NEW_GIT_REPO', 'HG_FORK', 'GIT_FORK',
37 'TEST_USER_ADMIN_LOGIN', 'TEST_USER_REGULAR_LOGIN', 'TEST_USER_REGULAR_PASS',
38 'TEST_USER_REGULAR_EMAIL', 'TEST_USER_REGULAR2_LOGIN',
39 'TEST_USER_REGULAR2_PASS', 'TEST_USER_REGULAR2_EMAIL'
40 ]
37 41
38 42 # Invoke websetup with the current config file
39 43 # SetupCommand('setup-app').run([config_file])
40 44
41 45 ##RUNNING DESIRED TESTS
42 46 # nosetests -x rhodecode.tests.functional.test_admin_settings:TestSettingsController.test_my_account
43 47 # nosetests --pdb --pdb-failures
44 48 environ = {}
45 49
46 50 #SOME GLOBALS FOR TESTS
47 51
48 52 TESTS_TMP_PATH = jn('/', 'tmp', 'rc_test_%s' % _RandomNameSequence().next())
49 53 TEST_USER_ADMIN_LOGIN = 'test_admin'
50 54 TEST_USER_ADMIN_PASS = 'test12'
55 TEST_USER_ADMIN_EMAIL = 'test_admin@mail.com'
56
57 TEST_USER_REGULAR_LOGIN = 'test_regular'
58 TEST_USER_REGULAR_PASS = 'test12'
59 TEST_USER_REGULAR_EMAIL = 'test_regular@mail.com'
60
61 TEST_USER_REGULAR2_LOGIN = 'test_regular2'
62 TEST_USER_REGULAR2_PASS = 'test12'
63 TEST_USER_REGULAR2_EMAIL = 'test_regular2@mail.com'
64
51 65 HG_REPO = 'vcs_test_hg'
52 66 GIT_REPO = 'vcs_test_git'
53 67
54 68 NEW_HG_REPO = 'vcs_test_hg_new'
55 69 NEW_GIT_REPO = 'vcs_test_git_new'
56 70
57 71 HG_FORK = 'vcs_test_hg_fork'
58 72 GIT_FORK = 'vcs_test_git_fork'
59 73
60 74 class TestController(TestCase):
61 75
62 76 def __init__(self, *args, **kwargs):
63 77 wsgiapp = pylons.test.pylonsapp
64 78 config = wsgiapp.config
65 79
66 80 self.app = TestApp(wsgiapp)
67 81 url._push_object(URLGenerator(config['routes.map'], environ))
68 82 self.Session = Session
69 83 self.index_location = config['app_conf']['index_dir']
70 84 TestCase.__init__(self, *args, **kwargs)
71 85
72 86 def log_user(self, username=TEST_USER_ADMIN_LOGIN,
73 87 password=TEST_USER_ADMIN_PASS):
74 88 self._logged_username = username
75 89 response = self.app.post(url(controller='login', action='index'),
76 90 {'username':username,
77 91 'password':password})
78 92
79 93 if 'invalid user name' in response.body:
80 94 self.fail('could not login using %s %s' % (username, password))
81 95
82 96 self.assertEqual(response.status, '302 Found')
83 97 ses = response.session['rhodecode_user']
84 98 self.assertEqual(ses.get('username'), username)
85 99 response = response.follow()
86 100 self.assertEqual(ses.get('is_authenticated'), True)
87 101
88 102 return response.session['rhodecode_user']
89 103
90 104 def _get_logged_user(self):
91 105 return User.get_by_username(self._logged_username)
92 106
93 107 def checkSessionFlash(self, response, msg):
94 108 self.assertTrue('flash' in response.session)
95 109 self.assertTrue(msg in response.session['flash'][0][1])
96 110
@@ -1,122 +1,179 b''
1 1 from rhodecode.tests import *
2 from rhodecode.model.db import User
2 from rhodecode.model.db import User, Permission
3 3 from rhodecode.lib.auth import check_password
4 4 from sqlalchemy.orm.exc import NoResultFound
5 from rhodecode.model.user import UserModel
5 6
6 7 class TestAdminUsersController(TestController):
7 8
8 9 def test_index(self):
10 self.log_user()
9 11 response = self.app.get(url('users'))
10 12 # Test response...
11 13
12 14 def test_index_as_xml(self):
13 15 response = self.app.get(url('formatted_users', format='xml'))
14 16
15 17 def test_create(self):
16 18 self.log_user()
17 19 username = 'newtestuser'
18 20 password = 'test12'
19 21 password_confirmation = password
20 22 name = 'name'
21 23 lastname = 'lastname'
22 24 email = 'mail@mail.com'
23 25
24 response = self.app.post(url('users'), {'username':username,
25 'password':password,
26 'password_confirmation':password_confirmation,
27 'name':name,
28 'active':True,
29 'lastname':lastname,
30 'email':email})
26 response = self.app.post(url('users'),
27 {'username':username,
28 'password':password,
29 'password_confirmation':password_confirmation,
30 'name':name,
31 'active':True,
32 'lastname':lastname,
33 'email':email})
31 34
32 35
33 assert '''created user %s''' % (username) in response.session['flash'][0], 'No flash message about new user'
36 self.assertTrue('''created user %s''' % (username) in
37 response.session['flash'][0])
34 38
35 new_user = self.Session.query(User).filter(User.username == username).one()
36
39 new_user = self.Session.query(User).\
40 filter(User.username == username).one()
37 41
38 assert new_user.username == username, 'wrong info about username'
39 assert check_password(password, new_user.password) == True , 'wrong info about password'
40 assert new_user.name == name, 'wrong info about name'
41 assert new_user.lastname == lastname, 'wrong info about lastname'
42 assert new_user.email == email, 'wrong info about email'
43
42 self.assertEqual(new_user.username,username)
43 self.assertEqual(check_password(password, new_user.password),True)
44 self.assertEqual(new_user.name,name)
45 self.assertEqual(new_user.lastname,lastname)
46 self.assertEqual(new_user.email,email)
44 47
45 48 response.follow()
46 49 response = response.follow()
47 assert """edit">newtestuser</a>""" in response.body
50 self.assertTrue("""edit">newtestuser</a>""" in response.body)
48 51
49 52 def test_create_err(self):
50 53 self.log_user()
51 54 username = 'new_user'
52 55 password = ''
53 56 name = 'name'
54 57 lastname = 'lastname'
55 58 email = 'errmail.com'
56 59
57 60 response = self.app.post(url('users'), {'username':username,
58 61 'password':password,
59 62 'name':name,
60 63 'active':False,
61 64 'lastname':lastname,
62 65 'email':email})
63 66
64 assert """<span class="error-message">Invalid username</span>""" in response.body
65 assert """<span class="error-message">Please enter a value</span>""" in response.body
66 assert """<span class="error-message">An email address must contain a single @</span>""" in response.body
67 self.assertTrue("""<span class="error-message">Invalid username</span>""" in response.body)
68 self.assertTrue("""<span class="error-message">Please enter a value</span>""" in response.body)
69 self.assertTrue("""<span class="error-message">An email address must contain a single @</span>""" in response.body)
67 70
68 71 def get_user():
69 72 self.Session.query(User).filter(User.username == username).one()
70 73
71 74 self.assertRaises(NoResultFound, get_user), 'found user in database'
72 75
73 76 def test_new(self):
77 self.log_user()
74 78 response = self.app.get(url('new_user'))
75 79
76 80 def test_new_as_xml(self):
77 81 response = self.app.get(url('formatted_new_user', format='xml'))
78 82
79 83 def test_update(self):
80 84 response = self.app.put(url('user', id=1))
81 85
82 86 def test_update_browser_fakeout(self):
83 87 response = self.app.post(url('user', id=1), params=dict(_method='put'))
84 88
85 89 def test_delete(self):
86 90 self.log_user()
87 91 username = 'newtestuserdeleteme'
88 92 password = 'test12'
89 93 name = 'name'
90 94 lastname = 'lastname'
91 95 email = 'todeletemail@mail.com'
92 96
93 97 response = self.app.post(url('users'), {'username':username,
94 98 'password':password,
95 99 'password_confirmation':password,
96 100 'name':name,
97 101 'active':True,
98 102 'lastname':lastname,
99 103 'email':email})
100 104
101 105 response = response.follow()
102 106
103 new_user = self.Session.query(User).filter(User.username == username).one()
107 new_user = self.Session.query(User)\
108 .filter(User.username == username).one()
104 109 response = self.app.delete(url('user', id=new_user.user_id))
105 110
106 assert """successfully deleted user""" in response.session['flash'][0], 'No info about user deletion'
111 self.assertTrue("""successfully deleted user""" in
112 response.session['flash'][0])
107 113
108 114
109 115 def test_delete_browser_fakeout(self):
110 response = self.app.post(url('user', id=1), params=dict(_method='delete'))
116 response = self.app.post(url('user', id=1),
117 params=dict(_method='delete'))
111 118
112 119 def test_show(self):
113 120 response = self.app.get(url('user', id=1))
114 121
115 122 def test_show_as_xml(self):
116 123 response = self.app.get(url('formatted_user', id=1, format='xml'))
117 124
118 125 def test_edit(self):
119 response = self.app.get(url('edit_user', id=1))
126 self.log_user()
127 user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
128 response = self.app.get(url('edit_user', id=user.user_id))
129
130
131 def test_add_perm_create_repo(self):
132 self.log_user()
133 perm_none = Permission.get_by_key('hg.create.none')
134 perm_create = Permission.get_by_key('hg.create.repository')
135
136 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
137
138
139 #User should have None permission on creation repository
140 self.assertEqual(UserModel().has_perm(user, perm_none), False)
141 self.assertEqual(UserModel().has_perm(user, perm_create), False)
142
143 response = self.app.post(url('user_perm', id=user.user_id),
144 params=dict(_method='put',
145 create_repo_perm=True))
146
147 perm_none = Permission.get_by_key('hg.create.none')
148 perm_create = Permission.get_by_key('hg.create.repository')
149
150 user = User.get_by_username(TEST_USER_REGULAR_LOGIN)
151 #User should have None permission on creation repository
152 self.assertEqual(UserModel().has_perm(user, perm_none), False)
153 self.assertEqual(UserModel().has_perm(user, perm_create), True)
154
155 def test_revoke_perm_create_repo(self):
156 self.log_user()
157 perm_none = Permission.get_by_key('hg.create.none')
158 perm_create = Permission.get_by_key('hg.create.repository')
159
160 user = User.get_by_username(TEST_USER_REGULAR2_LOGIN)
161
162
163 #User should have None permission on creation repository
164 self.assertEqual(UserModel().has_perm(user, perm_none), False)
165 self.assertEqual(UserModel().has_perm(user, perm_create), False)
166
167 response = self.app.post(url('user_perm', id=user.user_id),
168 params=dict(_method='put'))
169
170 perm_none = Permission.get_by_key('hg.create.none')
171 perm_create = Permission.get_by_key('hg.create.repository')
172
173 user = User.get_by_username(TEST_USER_REGULAR2_LOGIN)
174 #User should have None permission on creation repository
175 self.assertEqual(UserModel().has_perm(user, perm_none), True)
176 self.assertEqual(UserModel().has_perm(user, perm_create), False)
120 177
121 178 def test_edit_as_xml(self):
122 179 response = self.app.get(url('formatted_edit_user', id=1, format='xml'))
@@ -1,362 +1,410 b''
1 1 import os
2 2 import unittest
3 3 from rhodecode.tests import *
4 4
5 5 from rhodecode.model.repos_group import ReposGroupModel
6 6 from rhodecode.model.repo import RepoModel
7 7 from rhodecode.model.db import RepoGroup, User, Notification, UserNotification, \
8 UsersGroup, UsersGroupMember
8 UsersGroup, UsersGroupMember, Permission
9 9 from sqlalchemy.exc import IntegrityError
10 10 from rhodecode.model.user import UserModel
11 11
12 12 from rhodecode.model.meta import Session
13 13 from rhodecode.model.notification import NotificationModel
14 14 from rhodecode.model.users_group import UsersGroupModel
15 15
16 16 class TestReposGroups(unittest.TestCase):
17 17
18 18 def setUp(self):
19 19 self.g1 = self.__make_group('test1', skip_if_exists=True)
20 20 self.g2 = self.__make_group('test2', skip_if_exists=True)
21 21 self.g3 = self.__make_group('test3', skip_if_exists=True)
22 22
23 23 def tearDown(self):
24 24 print 'out'
25 25
26 26 def __check_path(self, *path):
27 27 path = [TESTS_TMP_PATH] + list(path)
28 28 path = os.path.join(*path)
29 29 return os.path.isdir(path)
30 30
31 31 def _check_folders(self):
32 32 print os.listdir(TESTS_TMP_PATH)
33 33
34 34 def __make_group(self, path, desc='desc', parent_id=None,
35 35 skip_if_exists=False):
36 36
37 37 gr = RepoGroup.get_by_group_name(path)
38 38 if gr and skip_if_exists:
39 39 return gr
40 40
41 41 form_data = dict(group_name=path,
42 42 group_description=desc,
43 43 group_parent_id=parent_id)
44 44 gr = ReposGroupModel().create(form_data)
45 45 Session.commit()
46 46 return gr
47 47
48 48 def __delete_group(self, id_):
49 49 ReposGroupModel().delete(id_)
50 50
51 51
52 52 def __update_group(self, id_, path, desc='desc', parent_id=None):
53 53 form_data = dict(group_name=path,
54 54 group_description=desc,
55 55 group_parent_id=parent_id)
56 56
57 57 gr = ReposGroupModel().update(id_, form_data)
58 58 return gr
59 59
60 60 def test_create_group(self):
61 61 g = self.__make_group('newGroup')
62 62 self.assertEqual(g.full_path, 'newGroup')
63 63
64 64 self.assertTrue(self.__check_path('newGroup'))
65 65
66 66
67 67 def test_create_same_name_group(self):
68 68 self.assertRaises(IntegrityError, lambda:self.__make_group('newGroup'))
69 69 Session.rollback()
70 70
71 71 def test_same_subgroup(self):
72 72 sg1 = self.__make_group('sub1', parent_id=self.g1.group_id)
73 73 self.assertEqual(sg1.parent_group, self.g1)
74 74 self.assertEqual(sg1.full_path, 'test1/sub1')
75 75 self.assertTrue(self.__check_path('test1', 'sub1'))
76 76
77 77 ssg1 = self.__make_group('subsub1', parent_id=sg1.group_id)
78 78 self.assertEqual(ssg1.parent_group, sg1)
79 79 self.assertEqual(ssg1.full_path, 'test1/sub1/subsub1')
80 80 self.assertTrue(self.__check_path('test1', 'sub1', 'subsub1'))
81 81
82 82
83 83 def test_remove_group(self):
84 84 sg1 = self.__make_group('deleteme')
85 85 self.__delete_group(sg1.group_id)
86 86
87 87 self.assertEqual(RepoGroup.get(sg1.group_id), None)
88 88 self.assertFalse(self.__check_path('deteteme'))
89 89
90 90 sg1 = self.__make_group('deleteme', parent_id=self.g1.group_id)
91 91 self.__delete_group(sg1.group_id)
92 92
93 93 self.assertEqual(RepoGroup.get(sg1.group_id), None)
94 94 self.assertFalse(self.__check_path('test1', 'deteteme'))
95 95
96 96
97 97 def test_rename_single_group(self):
98 98 sg1 = self.__make_group('initial')
99 99
100 100 new_sg1 = self.__update_group(sg1.group_id, 'after')
101 101 self.assertTrue(self.__check_path('after'))
102 102 self.assertEqual(RepoGroup.get_by_group_name('initial'), None)
103 103
104 104
105 105 def test_update_group_parent(self):
106 106
107 107 sg1 = self.__make_group('initial', parent_id=self.g1.group_id)
108 108
109 109 new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g1.group_id)
110 110 self.assertTrue(self.__check_path('test1', 'after'))
111 111 self.assertEqual(RepoGroup.get_by_group_name('test1/initial'), None)
112 112
113 113
114 114 new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g3.group_id)
115 115 self.assertTrue(self.__check_path('test3', 'after'))
116 116 self.assertEqual(RepoGroup.get_by_group_name('test3/initial'), None)
117 117
118 118
119 119 new_sg1 = self.__update_group(sg1.group_id, 'hello')
120 120 self.assertTrue(self.__check_path('hello'))
121 121
122 122 self.assertEqual(RepoGroup.get_by_group_name('hello'), new_sg1)
123 123
124 124
125 125
126 126 def test_subgrouping_with_repo(self):
127 127
128 128 g1 = self.__make_group('g1')
129 129 g2 = self.__make_group('g2')
130 130
131 131 # create new repo
132 132 form_data = dict(repo_name='john',
133 133 repo_name_full='john',
134 134 fork_name=None,
135 135 description=None,
136 136 repo_group=None,
137 137 private=False,
138 138 repo_type='hg',
139 139 clone_uri=None)
140 140 cur_user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
141 141 r = RepoModel().create(form_data, cur_user)
142 142
143 143 self.assertEqual(r.repo_name, 'john')
144 144
145 145 # put repo into group
146 146 form_data = form_data
147 147 form_data['repo_group'] = g1.group_id
148 148 form_data['perms_new'] = []
149 149 form_data['perms_updates'] = []
150 150 RepoModel().update(r.repo_name, form_data)
151 151 self.assertEqual(r.repo_name, 'g1/john')
152 152
153 153
154 154 self.__update_group(g1.group_id, 'g1', parent_id=g2.group_id)
155 155 self.assertTrue(self.__check_path('g2', 'g1'))
156 156
157 157 # test repo
158 158 self.assertEqual(r.repo_name, os.path.join('g2', 'g1', r.just_name))
159 159
160 160 class TestUser(unittest.TestCase):
161
161 def __init__(self, methodName='runTest'):
162 Session.remove()
163 super(TestUser, self).__init__(methodName=methodName)
164
162 165 def test_create_and_remove(self):
163 166 usr = UserModel().create_or_update(username=u'test_user', password=u'qweqwe',
164 167 email=u'u232@rhodecode.org',
165 168 name=u'u1', lastname=u'u1')
166 169 Session.commit()
167 170 self.assertEqual(User.get_by_username(u'test_user'), usr)
168 171
169 172 # make users group
170 173 users_group = UsersGroupModel().create('some_example_group')
171 174 Session.commit()
172 175
173 176 UsersGroupModel().add_user_to_group(users_group, usr)
174 177 Session.commit()
175 178
176 179 self.assertEqual(UsersGroup.get(users_group.users_group_id), users_group)
177 180 self.assertEqual(UsersGroupMember.query().count(), 1)
178 181 UserModel().delete(usr.user_id)
179 182 Session.commit()
180 183
181 184 self.assertEqual(UsersGroupMember.query().all(), [])
182 185
183 186
184 187 class TestNotifications(unittest.TestCase):
185 188
186 189 def __init__(self, methodName='runTest'):
190 Session.remove()
187 191 self.u1 = UserModel().create_or_update(username=u'u1',
188 192 password=u'qweqwe',
189 193 email=u'u1@rhodecode.org',
190 194 name=u'u1', lastname=u'u1')
191 195 Session.commit()
192 196 self.u1 = self.u1.user_id
193 197
194 198 self.u2 = UserModel().create_or_update(username=u'u2',
195 199 password=u'qweqwe',
196 200 email=u'u2@rhodecode.org',
197 201 name=u'u2', lastname=u'u3')
198 202 Session.commit()
199 203 self.u2 = self.u2.user_id
200 204
201 205 self.u3 = UserModel().create_or_update(username=u'u3',
202 206 password=u'qweqwe',
203 207 email=u'u3@rhodecode.org',
204 208 name=u'u3', lastname=u'u3')
205 209 Session.commit()
206 210 self.u3 = self.u3.user_id
207 211
208 212 super(TestNotifications, self).__init__(methodName=methodName)
209 213
210 214 def _clean_notifications(self):
211 215 for n in Notification.query().all():
212 216 Session.delete(n)
213 217
214 218 Session.commit()
215 219 self.assertEqual(Notification.query().all(), [])
216 220
221 def tearDown(self):
222 self._clean_notifications()
217 223
218 224 def test_create_notification(self):
219 225 self.assertEqual([], Notification.query().all())
220 226 self.assertEqual([], UserNotification.query().all())
221 227
222 228 usrs = [self.u1, self.u2]
223 229 notification = NotificationModel().create(created_by=self.u1,
224 230 subject=u'subj', body=u'hi there',
225 231 recipients=usrs)
226 232 Session.commit()
227 233 u1 = User.get(self.u1)
228 234 u2 = User.get(self.u2)
229 235 u3 = User.get(self.u3)
230 236 notifications = Notification.query().all()
231 237 self.assertEqual(len(notifications), 1)
232 238
233 239 unotification = UserNotification.query()\
234 240 .filter(UserNotification.notification == notification).all()
235 241
236 242 self.assertEqual(notifications[0].recipients, [u1, u2])
237 243 self.assertEqual(notification.notification_id,
238 244 notifications[0].notification_id)
239 245 self.assertEqual(len(unotification), len(usrs))
240 246 self.assertEqual([x.user.user_id for x in unotification], usrs)
241 247
242 self._clean_notifications()
243 248
244 249 def test_user_notifications(self):
245 250 self.assertEqual([], Notification.query().all())
246 251 self.assertEqual([], UserNotification.query().all())
247 252
248 253 notification1 = NotificationModel().create(created_by=self.u1,
249 254 subject=u'subj', body=u'hi there1',
250 255 recipients=[self.u3])
251 256 Session.commit()
252 257 notification2 = NotificationModel().create(created_by=self.u1,
253 258 subject=u'subj', body=u'hi there2',
254 259 recipients=[self.u3])
255 260 Session.commit()
256 261 u3 = Session.query(User).get(self.u3)
257 262
258 263 self.assertEqual(sorted([x.notification for x in u3.notifications]),
259 264 sorted([notification2, notification1]))
260 self._clean_notifications()
261 265
262 266 def test_delete_notifications(self):
263 267 self.assertEqual([], Notification.query().all())
264 268 self.assertEqual([], UserNotification.query().all())
265 269
266 270 notification = NotificationModel().create(created_by=self.u1,
267 271 subject=u'title', body=u'hi there3',
268 272 recipients=[self.u3, self.u1, self.u2])
269 273 Session.commit()
270 274 notifications = Notification.query().all()
271 275 self.assertTrue(notification in notifications)
272 276
273 277 Notification.delete(notification.notification_id)
274 278 Session.commit()
275 279
276 280 notifications = Notification.query().all()
277 281 self.assertFalse(notification in notifications)
278 282
279 283 un = UserNotification.query().filter(UserNotification.notification
280 284 == notification).all()
281 285 self.assertEqual(un, [])
282 286
283 self._clean_notifications()
284 287
285 288 def test_delete_association(self):
286 289
287 290 self.assertEqual([], Notification.query().all())
288 291 self.assertEqual([], UserNotification.query().all())
289 292
290 293 notification = NotificationModel().create(created_by=self.u1,
291 294 subject=u'title', body=u'hi there3',
292 295 recipients=[self.u3, self.u1, self.u2])
293 296 Session.commit()
294 297
295 298 unotification = UserNotification.query()\
296 299 .filter(UserNotification.notification ==
297 300 notification)\
298 301 .filter(UserNotification.user_id == self.u3)\
299 302 .scalar()
300 303
301 304 self.assertEqual(unotification.user_id, self.u3)
302 305
303 306 NotificationModel().delete(self.u3,
304 307 notification.notification_id)
305 308 Session.commit()
306 309
307 310 u3notification = UserNotification.query()\
308 311 .filter(UserNotification.notification ==
309 312 notification)\
310 313 .filter(UserNotification.user_id == self.u3)\
311 314 .scalar()
312 315
313 316 self.assertEqual(u3notification, None)
314 317
315 318 # notification object is still there
316 319 self.assertEqual(Notification.query().all(), [notification])
317 320
318 321 #u1 and u2 still have assignments
319 322 u1notification = UserNotification.query()\
320 323 .filter(UserNotification.notification ==
321 324 notification)\
322 325 .filter(UserNotification.user_id == self.u1)\
323 326 .scalar()
324 327 self.assertNotEqual(u1notification, None)
325 328 u2notification = UserNotification.query()\
326 329 .filter(UserNotification.notification ==
327 330 notification)\
328 331 .filter(UserNotification.user_id == self.u2)\
329 332 .scalar()
330 333 self.assertNotEqual(u2notification, None)
331 334
332 self._clean_notifications()
333
334 335 def test_notification_counter(self):
335 336 self._clean_notifications()
336 337 self.assertEqual([], Notification.query().all())
337 338 self.assertEqual([], UserNotification.query().all())
338 339
339 340 NotificationModel().create(created_by=self.u1,
340 341 subject=u'title', body=u'hi there_delete',
341 342 recipients=[self.u3, self.u1])
342 343 Session.commit()
343 344
344 345 self.assertEqual(NotificationModel()
345 346 .get_unread_cnt_for_user(self.u1), 1)
346 347 self.assertEqual(NotificationModel()
347 348 .get_unread_cnt_for_user(self.u2), 0)
348 349 self.assertEqual(NotificationModel()
349 350 .get_unread_cnt_for_user(self.u3), 1)
350 351
351 352 notification = NotificationModel().create(created_by=self.u1,
352 353 subject=u'title', body=u'hi there3',
353 354 recipients=[self.u3, self.u1, self.u2])
354 355 Session.commit()
355 356
356 357 self.assertEqual(NotificationModel()
357 358 .get_unread_cnt_for_user(self.u1), 2)
358 359 self.assertEqual(NotificationModel()
359 360 .get_unread_cnt_for_user(self.u2), 1)
360 361 self.assertEqual(NotificationModel()
361 362 .get_unread_cnt_for_user(self.u3), 2)
362 self._clean_notifications()
363
364 class TestUsers(unittest.TestCase):
365
366 def __init__(self, methodName='runTest'):
367 super(TestUsers, self).__init__(methodName=methodName)
368
369 def setUp(self):
370 self.u1 = UserModel().create_or_update(username=u'u1',
371 password=u'qweqwe',
372 email=u'u1@rhodecode.org',
373 name=u'u1', lastname=u'u1')
374
375 def tearDown(self):
376 perm = Permission.query().all()
377 for p in perm:
378 UserModel().revoke_perm(self.u1, p)
379
380 UserModel().delete(self.u1)
381 Session.commit()
382
383 def test_add_perm(self):
384 perm = Permission.query().all()[0]
385 UserModel().grant_perm(self.u1, perm)
386 Session.commit()
387 self.assertEqual(UserModel().has_perm(self.u1, perm), True)
388
389 def test_has_perm(self):
390 perm = Permission.query().all()
391 for p in perm:
392 has_p = UserModel().has_perm(self.u1, p)
393 self.assertEqual(False, has_p)
394
395 def test_revoke_perm(self):
396 perm = Permission.query().all()[0]
397 UserModel().grant_perm(self.u1, perm)
398 Session.commit()
399 self.assertEqual(UserModel().has_perm(self.u1, perm), True)
400
401 #revoke
402 UserModel().revoke_perm(self.u1, perm)
403 Session.commit()
404 self.assertEqual(UserModel().has_perm(self.u1, perm),False)
405
406
407
408
409
410
General Comments 0
You need to be logged in to leave comments. Login now