##// END OF EJS Templates
commit less models...
marcink -
r1749:8ecc6b82 beta
parent child Browse files
Show More
@@ -1,169 +1,169 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.permissions
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 permissions controller for Rhodecode
7 7
8 8 :created_on: Apr 27, 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 from formencode import htmlfill
30 30
31 31 from pylons import request, session, tmpl_context as c, url
32 32 from pylons.controllers.util import abort, redirect
33 33 from pylons.i18n.translation import _
34 34
35 35 from rhodecode.lib import helpers as h
36 36 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
37 37 from rhodecode.lib.base import BaseController, render
38 38 from rhodecode.model.forms import DefaultPermissionsForm
39 39 from rhodecode.model.permission import PermissionModel
40 40 from rhodecode.model.db import User
41 41 from rhodecode.model.meta import Session
42 42
43 43 log = logging.getLogger(__name__)
44 44
45 45
46 46 class PermissionsController(BaseController):
47 47 """REST Controller styled on the Atom Publishing Protocol"""
48 48 # To properly map this controller, ensure your config/routing.py
49 49 # file has a resource setup:
50 50 # map.resource('permission', 'permissions')
51 51
52 52 @LoginRequired()
53 53 @HasPermissionAllDecorator('hg.admin')
54 54 def __before__(self):
55 55 c.admin_user = session.get('admin_user')
56 56 c.admin_username = session.get('admin_username')
57 57 super(PermissionsController, self).__before__()
58 58
59 59 self.perms_choices = [('repository.none', _('None'),),
60 60 ('repository.read', _('Read'),),
61 61 ('repository.write', _('Write'),),
62 62 ('repository.admin', _('Admin'),)]
63 63 self.register_choices = [
64 64 ('hg.register.none',
65 65 _('disabled')),
66 66 ('hg.register.manual_activate',
67 67 _('allowed with manual account activation')),
68 68 ('hg.register.auto_activate',
69 69 _('allowed with automatic account activation')), ]
70 70
71 71 self.create_choices = [('hg.create.none', _('Disabled')),
72 72 ('hg.create.repository', _('Enabled'))]
73 73
74 74 def index(self, format='html'):
75 75 """GET /permissions: All items in the collection"""
76 76 # url('permissions')
77 77
78 78 def create(self):
79 79 """POST /permissions: Create a new item"""
80 80 # url('permissions')
81 81
82 82 def new(self, format='html'):
83 83 """GET /permissions/new: Form to create a new item"""
84 84 # url('new_permission')
85 85
86 86 def update(self, id):
87 87 """PUT /permissions/id: Update an existing item"""
88 88 # Forms posted to this method should contain a hidden field:
89 89 # <input type="hidden" name="_method" value="PUT" />
90 90 # Or using helpers:
91 91 # h.form(url('permission', id=ID),
92 92 # method='put')
93 93 # url('permission', id=ID)
94 94
95 95 permission_model = PermissionModel()
96 96
97 97 _form = DefaultPermissionsForm([x[0] for x in self.perms_choices],
98 98 [x[0] for x in self.register_choices],
99 99 [x[0] for x in self.create_choices])()
100 100
101 101 try:
102 102 form_result = _form.to_python(dict(request.POST))
103 103 form_result.update({'perm_user_name': id})
104 104 permission_model.update(form_result)
105 Session().commit()
105 Session.commit()
106 106 h.flash(_('Default permissions updated successfully'),
107 107 category='success')
108 108
109 109 except formencode.Invalid, errors:
110 110 c.perms_choices = self.perms_choices
111 111 c.register_choices = self.register_choices
112 112 c.create_choices = self.create_choices
113 113 defaults = errors.value
114 114
115 115 return htmlfill.render(
116 116 render('admin/permissions/permissions.html'),
117 117 defaults=defaults,
118 118 errors=errors.error_dict or {},
119 119 prefix_error=False,
120 120 encoding="UTF-8")
121 121 except Exception:
122 122 log.error(traceback.format_exc())
123 123 h.flash(_('error occurred during update of permissions'),
124 124 category='error')
125 125
126 126 return redirect(url('edit_permission', id=id))
127 127
128 128 def delete(self, id):
129 129 """DELETE /permissions/id: Delete an existing item"""
130 130 # Forms posted to this method should contain a hidden field:
131 131 # <input type="hidden" name="_method" value="DELETE" />
132 132 # Or using helpers:
133 133 # h.form(url('permission', id=ID),
134 134 # method='delete')
135 135 # url('permission', id=ID)
136 136
137 137 def show(self, id, format='html'):
138 138 """GET /permissions/id: Show a specific item"""
139 139 # url('permission', id=ID)
140 140
141 141 def edit(self, id, format='html'):
142 142 """GET /permissions/id/edit: Form to edit an existing item"""
143 143 #url('edit_permission', id=ID)
144 144 c.perms_choices = self.perms_choices
145 145 c.register_choices = self.register_choices
146 146 c.create_choices = self.create_choices
147 147
148 148 if id == 'default':
149 149 default_user = User.get_by_username('default')
150 150 defaults = {'_method': 'put',
151 151 'anonymous': default_user.active}
152 152
153 153 for p in default_user.user_perms:
154 154 if p.permission.permission_name.startswith('repository.'):
155 155 defaults['default_perm'] = p.permission.permission_name
156 156
157 157 if p.permission.permission_name.startswith('hg.register.'):
158 158 defaults['default_register'] = p.permission.permission_name
159 159
160 160 if p.permission.permission_name.startswith('hg.create.'):
161 161 defaults['default_create'] = p.permission.permission_name
162 162
163 163 return htmlfill.render(
164 164 render('admin/permissions/permissions.html'),
165 165 defaults=defaults,
166 166 encoding="UTF-8",
167 167 force_defaults=True,)
168 168 else:
169 169 return redirect(url('admin_home'))
@@ -1,398 +1,398 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.repos
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Admin controller for RhodeCode
7 7
8 8 :created_on: Apr 7, 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 from formencode import htmlfill
30 30
31 31 from paste.httpexceptions import HTTPInternalServerError
32 32 from pylons import request, session, tmpl_context as c, url
33 33 from pylons.controllers.util import redirect
34 34 from pylons.i18n.translation import _
35 35 from sqlalchemy.exc import IntegrityError
36 36
37 37 from rhodecode.lib import helpers as h
38 38 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
39 39 HasPermissionAnyDecorator
40 40 from rhodecode.lib.base import BaseController, render
41 41 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
42 42 from rhodecode.lib.helpers import get_token
43 43 from rhodecode.model.meta import Session
44 44 from rhodecode.model.db import User, Repository, UserFollowing, RepoGroup
45 45 from rhodecode.model.forms import RepoForm
46 46 from rhodecode.model.scm import ScmModel
47 47 from rhodecode.model.repo import RepoModel
48 48
49 49 log = logging.getLogger(__name__)
50 50
51 51
52 52 class ReposController(BaseController):
53 53 """
54 54 REST Controller styled on the Atom Publishing Protocol"""
55 55 # To properly map this controller, ensure your config/routing.py
56 56 # file has a resource setup:
57 57 # map.resource('repo', 'repos')
58 58
59 59 @LoginRequired()
60 60 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
61 61 def __before__(self):
62 62 c.admin_user = session.get('admin_user')
63 63 c.admin_username = session.get('admin_username')
64 64 super(ReposController, self).__before__()
65 65
66 66 def __load_defaults(self):
67 67 c.repo_groups = RepoGroup.groups_choices()
68 68 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
69 69
70 70 repo_model = RepoModel()
71 71 c.users_array = repo_model.get_users_js()
72 72 c.users_groups_array = repo_model.get_users_groups_js()
73 73
74 74 def __load_data(self, repo_name=None):
75 75 """
76 76 Load defaults settings for edit, and update
77 77
78 78 :param repo_name:
79 79 """
80 80 self.__load_defaults()
81 81
82 82 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
83 83 repo = db_repo.scm_instance
84 84
85 85 if c.repo_info is None:
86 86 h.flash(_('%s repository is not mapped to db perhaps'
87 87 ' it was created or renamed from the filesystem'
88 88 ' please run the application again'
89 89 ' in order to rescan repositories') % repo_name,
90 90 category='error')
91 91
92 92 return redirect(url('repos'))
93 93
94 94 c.default_user_id = User.get_by_username('default').user_id
95 95 c.in_public_journal = UserFollowing.query()\
96 96 .filter(UserFollowing.user_id == c.default_user_id)\
97 97 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
98 98
99 99 if c.repo_info.stats:
100 100 last_rev = c.repo_info.stats.stat_on_revision
101 101 else:
102 102 last_rev = 0
103 103 c.stats_revision = last_rev
104 104
105 105 c.repo_last_rev = repo.count() - 1 if repo.revisions else 0
106 106
107 107 if last_rev == 0 or c.repo_last_rev == 0:
108 108 c.stats_percentage = 0
109 109 else:
110 110 c.stats_percentage = '%.2f' % ((float((last_rev)) /
111 111 c.repo_last_rev) * 100)
112 112
113 113 defaults = RepoModel()._get_defaults(repo_name)
114 114 return defaults
115 115
116 116 @HasPermissionAllDecorator('hg.admin')
117 117 def index(self, format='html'):
118 118 """GET /repos: All items in the collection"""
119 119 # url('repos')
120 120
121 121 c.repos_list = ScmModel().get_repos(Repository.query()
122 122 .order_by(Repository.repo_name)
123 123 .all(), sort_key='name_sort')
124 124 return render('admin/repos/repos.html')
125 125
126 126 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
127 127 def create(self):
128 128 """
129 129 POST /repos: Create a new item"""
130 130 # url('repos')
131 131
132 132 self.__load_defaults()
133 133 form_result = {}
134 134 try:
135 135 form_result = RepoForm(repo_groups=c.repo_groups_choices)()\
136 136 .to_python(dict(request.POST))
137 137 RepoModel().create(form_result, self.rhodecode_user)
138 138 if form_result['clone_uri']:
139 139 h.flash(_('created repository %s from %s') \
140 140 % (form_result['repo_name'], form_result['clone_uri']),
141 141 category='success')
142 142 else:
143 143 h.flash(_('created repository %s') % form_result['repo_name'],
144 144 category='success')
145 145
146 146 if request.POST.get('user_created'):
147 147 # created by regular non admin user
148 148 action_logger(self.rhodecode_user, 'user_created_repo',
149 149 form_result['repo_name_full'], '', self.sa)
150 150 else:
151 151 action_logger(self.rhodecode_user, 'admin_created_repo',
152 152 form_result['repo_name_full'], '', self.sa)
153 Session().commit()
153 Session.commit()
154 154 except formencode.Invalid, errors:
155 155
156 156 c.new_repo = errors.value['repo_name']
157 157
158 158 if request.POST.get('user_created'):
159 159 r = render('admin/repos/repo_add_create_repository.html')
160 160 else:
161 161 r = render('admin/repos/repo_add.html')
162 162
163 163 return htmlfill.render(
164 164 r,
165 165 defaults=errors.value,
166 166 errors=errors.error_dict or {},
167 167 prefix_error=False,
168 168 encoding="UTF-8")
169 169
170 170 except Exception:
171 171 log.error(traceback.format_exc())
172 172 msg = _('error occurred during creation of repository %s') \
173 173 % form_result.get('repo_name')
174 174 h.flash(msg, category='error')
175 175 if request.POST.get('user_created'):
176 176 return redirect(url('home'))
177 177 return redirect(url('repos'))
178 178
179 179 @HasPermissionAllDecorator('hg.admin')
180 180 def new(self, format='html'):
181 181 """GET /repos/new: Form to create a new item"""
182 182 new_repo = request.GET.get('repo', '')
183 183 c.new_repo = repo_name_slug(new_repo)
184 184 self.__load_defaults()
185 185 return render('admin/repos/repo_add.html')
186 186
187 187 @HasPermissionAllDecorator('hg.admin')
188 188 def update(self, repo_name):
189 189 """
190 190 PUT /repos/repo_name: Update an existing item"""
191 191 # Forms posted to this method should contain a hidden field:
192 192 # <input type="hidden" name="_method" value="PUT" />
193 193 # Or using helpers:
194 194 # h.form(url('repo', repo_name=ID),
195 195 # method='put')
196 196 # url('repo', repo_name=ID)
197 197 self.__load_defaults()
198 198 repo_model = RepoModel()
199 199 changed_name = repo_name
200 200 _form = RepoForm(edit=True, old_data={'repo_name': repo_name},
201 201 repo_groups=c.repo_groups_choices)()
202 202 try:
203 203 form_result = _form.to_python(dict(request.POST))
204 204 repo = repo_model.update(repo_name, form_result)
205 205 invalidate_cache('get_repo_cached_%s' % repo_name)
206 206 h.flash(_('Repository %s updated successfully' % repo_name),
207 207 category='success')
208 208 changed_name = repo.repo_name
209 209 action_logger(self.rhodecode_user, 'admin_updated_repo',
210 210 changed_name, '', self.sa)
211 Session().commit()
211 Session.commit()
212 212 except formencode.Invalid, errors:
213 213 defaults = self.__load_data(repo_name)
214 214 defaults.update(errors.value)
215 215 return htmlfill.render(
216 216 render('admin/repos/repo_edit.html'),
217 217 defaults=defaults,
218 218 errors=errors.error_dict or {},
219 219 prefix_error=False,
220 220 encoding="UTF-8")
221 221
222 222 except Exception:
223 223 log.error(traceback.format_exc())
224 224 h.flash(_('error occurred during update of repository %s') \
225 225 % repo_name, category='error')
226 226 return redirect(url('edit_repo', repo_name=changed_name))
227 227
228 228 @HasPermissionAllDecorator('hg.admin')
229 229 def delete(self, repo_name):
230 230 """
231 231 DELETE /repos/repo_name: Delete an existing item"""
232 232 # Forms posted to this method should contain a hidden field:
233 233 # <input type="hidden" name="_method" value="DELETE" />
234 234 # Or using helpers:
235 235 # h.form(url('repo', repo_name=ID),
236 236 # method='delete')
237 237 # url('repo', repo_name=ID)
238 238
239 239 repo_model = RepoModel()
240 240 repo = repo_model.get_by_repo_name(repo_name)
241 241 if not repo:
242 242 h.flash(_('%s repository is not mapped to db perhaps'
243 243 ' it was moved or renamed from the filesystem'
244 244 ' please run the application again'
245 245 ' in order to rescan repositories') % repo_name,
246 246 category='error')
247 247
248 248 return redirect(url('repos'))
249 249 try:
250 250 action_logger(self.rhodecode_user, 'admin_deleted_repo',
251 251 repo_name, '', self.sa)
252 252 repo_model.delete(repo)
253 253 invalidate_cache('get_repo_cached_%s' % repo_name)
254 254 h.flash(_('deleted repository %s') % repo_name, category='success')
255 Session().commit()
255 Session.commit()
256 256 except IntegrityError, e:
257 257 if e.message.find('repositories_fork_id_fkey'):
258 258 log.error(traceback.format_exc())
259 259 h.flash(_('Cannot delete %s it still contains attached '
260 260 'forks') % repo_name,
261 261 category='warning')
262 262 else:
263 263 log.error(traceback.format_exc())
264 264 h.flash(_('An error occurred during '
265 265 'deletion of %s') % repo_name,
266 266 category='error')
267 267
268 268 except Exception, e:
269 269 log.error(traceback.format_exc())
270 270 h.flash(_('An error occurred during deletion of %s') % repo_name,
271 271 category='error')
272 272
273 273 return redirect(url('repos'))
274 274
275 275 @HasPermissionAllDecorator('hg.admin')
276 276 def delete_perm_user(self, repo_name):
277 277 """
278 278 DELETE an existing repository permission user
279 279
280 280 :param repo_name:
281 281 """
282 282
283 283 try:
284 284 repo_model = RepoModel()
285 285 repo_model.delete_perm_user(request.POST, repo_name)
286 286 except Exception, e:
287 287 h.flash(_('An error occurred during deletion of repository user'),
288 288 category='error')
289 289 raise HTTPInternalServerError()
290 290
291 291 @HasPermissionAllDecorator('hg.admin')
292 292 def delete_perm_users_group(self, repo_name):
293 293 """
294 294 DELETE an existing repository permission users group
295 295
296 296 :param repo_name:
297 297 """
298 298 try:
299 299 repo_model = RepoModel()
300 300 repo_model.delete_perm_users_group(request.POST, repo_name)
301 301 except Exception, e:
302 302 h.flash(_('An error occurred during deletion of repository'
303 303 ' users groups'),
304 304 category='error')
305 305 raise HTTPInternalServerError()
306 306
307 307 @HasPermissionAllDecorator('hg.admin')
308 308 def repo_stats(self, repo_name):
309 309 """
310 310 DELETE an existing repository statistics
311 311
312 312 :param repo_name:
313 313 """
314 314
315 315 try:
316 316 repo_model = RepoModel()
317 317 repo_model.delete_stats(repo_name)
318 318 except Exception, e:
319 319 h.flash(_('An error occurred during deletion of repository stats'),
320 320 category='error')
321 321 return redirect(url('edit_repo', repo_name=repo_name))
322 322
323 323 @HasPermissionAllDecorator('hg.admin')
324 324 def repo_cache(self, repo_name):
325 325 """
326 326 INVALIDATE existing repository cache
327 327
328 328 :param repo_name:
329 329 """
330 330
331 331 try:
332 332 ScmModel().mark_for_invalidation(repo_name)
333 333 except Exception, e:
334 334 h.flash(_('An error occurred during cache invalidation'),
335 335 category='error')
336 336 return redirect(url('edit_repo', repo_name=repo_name))
337 337
338 338 @HasPermissionAllDecorator('hg.admin')
339 339 def repo_public_journal(self, repo_name):
340 340 """
341 341 Set's this repository to be visible in public journal,
342 342 in other words assing default user to follow this repo
343 343
344 344 :param repo_name:
345 345 """
346 346
347 347 cur_token = request.POST.get('auth_token')
348 348 token = get_token()
349 349 if cur_token == token:
350 350 try:
351 351 repo_id = Repository.get_by_repo_name(repo_name).repo_id
352 352 user_id = User.get_by_username('default').user_id
353 353 self.scm_model.toggle_following_repo(repo_id, user_id)
354 354 h.flash(_('Updated repository visibility in public journal'),
355 355 category='success')
356 356 except:
357 357 h.flash(_('An error occurred during setting this'
358 358 ' repository in public journal'),
359 359 category='error')
360 360
361 361 else:
362 362 h.flash(_('Token mismatch'), category='error')
363 363 return redirect(url('edit_repo', repo_name=repo_name))
364 364
365 365 @HasPermissionAllDecorator('hg.admin')
366 366 def repo_pull(self, repo_name):
367 367 """
368 368 Runs task to update given repository with remote changes,
369 369 ie. make pull on remote location
370 370
371 371 :param repo_name:
372 372 """
373 373 try:
374 374 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
375 375 h.flash(_('Pulled from remote location'), category='success')
376 376 except Exception, e:
377 377 h.flash(_('An error occurred during pull from remote location'),
378 378 category='error')
379 379
380 380 return redirect(url('edit_repo', repo_name=repo_name))
381 381
382 382 @HasPermissionAllDecorator('hg.admin')
383 383 def show(self, repo_name, format='html'):
384 384 """GET /repos/repo_name: Show a specific item"""
385 385 # url('repo', repo_name=ID)
386 386
387 387 @HasPermissionAllDecorator('hg.admin')
388 388 def edit(self, repo_name, format='html'):
389 389 """GET /repos/repo_name/edit: Form to edit an existing item"""
390 390 # url('edit_repo', repo_name=ID)
391 391 defaults = self.__load_data(repo_name)
392 392
393 393 return htmlfill.render(
394 394 render('admin/repos/repo_edit.html'),
395 395 defaults=defaults,
396 396 encoding="UTF-8",
397 397 force_defaults=False
398 398 )
@@ -1,230 +1,230 b''
1 1 import logging
2 2 import traceback
3 3 import formencode
4 4
5 5 from formencode import htmlfill
6 6 from operator import itemgetter
7 7
8 8 from pylons import request, response, session, tmpl_context as c, url
9 9 from pylons.controllers.util import abort, redirect
10 10 from pylons.i18n.translation import _
11 11
12 12 from sqlalchemy.exc import IntegrityError
13 13
14 14 from rhodecode.lib import helpers as h
15 15 from rhodecode.lib.auth import LoginRequired, HasPermissionAnyDecorator
16 16 from rhodecode.lib.base import BaseController, render
17 17 from rhodecode.model.db import RepoGroup
18 18 from rhodecode.model.repos_group import ReposGroupModel
19 19 from rhodecode.model.forms import ReposGroupForm
20 20 from rhodecode.model.meta import Session
21 21
22 22 log = logging.getLogger(__name__)
23 23
24 24
25 25 class ReposGroupsController(BaseController):
26 26 """REST Controller styled on the Atom Publishing Protocol"""
27 27 # To properly map this controller, ensure your config/routing.py
28 28 # file has a resource setup:
29 29 # map.resource('repos_group', 'repos_groups')
30 30
31 31 @LoginRequired()
32 32 def __before__(self):
33 33 super(ReposGroupsController, self).__before__()
34 34
35 35 def __load_defaults(self):
36 36 c.repo_groups = RepoGroup.groups_choices()
37 37 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
38 38
39 39 def __load_data(self, group_id):
40 40 """
41 41 Load defaults settings for edit, and update
42 42
43 43 :param group_id:
44 44 """
45 45 self.__load_defaults()
46 46
47 47 repo_group = RepoGroup.get(group_id)
48 48
49 49 data = repo_group.get_dict()
50 50
51 51 data['group_name'] = repo_group.name
52 52
53 53 return data
54 54
55 55 @HasPermissionAnyDecorator('hg.admin')
56 56 def index(self, format='html'):
57 57 """GET /repos_groups: All items in the collection"""
58 58 # url('repos_groups')
59 59
60 60 sk = lambda g:g.parents[0].group_name if g.parents else g.group_name
61 61 c.groups = sorted(RepoGroup.query().all(), key=sk)
62 62 return render('admin/repos_groups/repos_groups_show.html')
63 63
64 64 @HasPermissionAnyDecorator('hg.admin')
65 65 def create(self):
66 66 """POST /repos_groups: Create a new item"""
67 67 # url('repos_groups')
68 68 self.__load_defaults()
69 69 repos_group_form = ReposGroupForm(available_groups=
70 70 c.repo_groups_choices)()
71 71 try:
72 72 form_result = repos_group_form.to_python(dict(request.POST))
73 73 ReposGroupModel().create(form_result)
74 Session().commit()
74 Session.commit()
75 75 h.flash(_('created repos group %s') \
76 76 % form_result['group_name'], category='success')
77 77 #TODO: in futureaction_logger(, '', '', '', self.sa)
78 78 except formencode.Invalid, errors:
79 79
80 80 return htmlfill.render(
81 81 render('admin/repos_groups/repos_groups_add.html'),
82 82 defaults=errors.value,
83 83 errors=errors.error_dict or {},
84 84 prefix_error=False,
85 85 encoding="UTF-8")
86 86 except Exception:
87 87 log.error(traceback.format_exc())
88 88 h.flash(_('error occurred during creation of repos group %s') \
89 89 % request.POST.get('group_name'), category='error')
90 90
91 91 return redirect(url('repos_groups'))
92 92
93 93
94 94 @HasPermissionAnyDecorator('hg.admin')
95 95 def new(self, format='html'):
96 96 """GET /repos_groups/new: Form to create a new item"""
97 97 # url('new_repos_group')
98 98 self.__load_defaults()
99 99 return render('admin/repos_groups/repos_groups_add.html')
100 100
101 101 @HasPermissionAnyDecorator('hg.admin')
102 102 def update(self, id):
103 103 """PUT /repos_groups/id: Update an existing item"""
104 104 # Forms posted to this method should contain a hidden field:
105 105 # <input type="hidden" name="_method" value="PUT" />
106 106 # Or using helpers:
107 107 # h.form(url('repos_group', id=ID),
108 108 # method='put')
109 109 # url('repos_group', id=ID)
110 110
111 111 self.__load_defaults()
112 112 c.repos_group = RepoGroup.get(id)
113 113
114 114 repos_group_form = ReposGroupForm(edit=True,
115 115 old_data=c.repos_group.get_dict(),
116 116 available_groups=
117 117 c.repo_groups_choices)()
118 118 try:
119 119 form_result = repos_group_form.to_python(dict(request.POST))
120 120 ReposGroupModel().update(id, form_result)
121 Session().commit()
121 Session.commit()
122 122 h.flash(_('updated repos group %s') \
123 123 % form_result['group_name'], category='success')
124 124 #TODO: in futureaction_logger(, '', '', '', self.sa)
125 125 except formencode.Invalid, errors:
126 126
127 127 return htmlfill.render(
128 128 render('admin/repos_groups/repos_groups_edit.html'),
129 129 defaults=errors.value,
130 130 errors=errors.error_dict or {},
131 131 prefix_error=False,
132 132 encoding="UTF-8")
133 133 except Exception:
134 134 log.error(traceback.format_exc())
135 135 h.flash(_('error occurred during update of repos group %s') \
136 136 % request.POST.get('group_name'), category='error')
137 137
138 138 return redirect(url('repos_groups'))
139 139
140 140
141 141 @HasPermissionAnyDecorator('hg.admin')
142 142 def delete(self, id):
143 143 """DELETE /repos_groups/id: Delete an existing item"""
144 144 # Forms posted to this method should contain a hidden field:
145 145 # <input type="hidden" name="_method" value="DELETE" />
146 146 # Or using helpers:
147 147 # h.form(url('repos_group', id=ID),
148 148 # method='delete')
149 149 # url('repos_group', id=ID)
150 150
151 151 gr = RepoGroup.get(id)
152 152 repos = gr.repositories.all()
153 153 if repos:
154 154 h.flash(_('This group contains %s repositores and cannot be '
155 155 'deleted' % len(repos)),
156 156 category='error')
157 157 return redirect(url('repos_groups'))
158 158
159 159 try:
160 160 ReposGroupModel().delete(id)
161 Session().commit()
161 Session.commit()
162 162 h.flash(_('removed repos group %s' % gr.group_name), category='success')
163 163 #TODO: in future action_logger(, '', '', '', self.sa)
164 164 except IntegrityError, e:
165 165 if e.message.find('groups_group_parent_id_fkey'):
166 166 log.error(traceback.format_exc())
167 167 h.flash(_('Cannot delete this group it still contains '
168 168 'subgroups'),
169 169 category='warning')
170 170 else:
171 171 log.error(traceback.format_exc())
172 172 h.flash(_('error occurred during deletion of repos '
173 173 'group %s' % gr.group_name), category='error')
174 174
175 175 except Exception:
176 176 log.error(traceback.format_exc())
177 177 h.flash(_('error occurred during deletion of repos '
178 178 'group %s' % gr.group_name), category='error')
179 179
180 180 return redirect(url('repos_groups'))
181 181
182 182 def show_by_name(self, group_name):
183 183 id_ = RepoGroup.get_by_group_name(group_name).group_id
184 184 return self.show(id_)
185 185
186 186 def show(self, id, format='html'):
187 187 """GET /repos_groups/id: Show a specific item"""
188 188 # url('repos_group', id=ID)
189 189
190 190 c.group = RepoGroup.get(id)
191 191
192 192 if c.group:
193 193 c.group_repos = c.group.repositories.all()
194 194 else:
195 195 return redirect(url('home'))
196 196
197 197 #overwrite our cached list with current filter
198 198 gr_filter = c.group_repos
199 199 c.cached_repo_list = self.scm_model.get_repos(all_repos=gr_filter)
200 200
201 201 c.repos_list = c.cached_repo_list
202 202
203 203 c.repo_cnt = 0
204 204
205 205 c.groups = self.sa.query(RepoGroup).order_by(RepoGroup.group_name)\
206 206 .filter(RepoGroup.group_parent_id == id).all()
207 207
208 208 return render('admin/repos_groups/repos_groups.html')
209 209
210 210 @HasPermissionAnyDecorator('hg.admin')
211 211 def edit(self, id, format='html'):
212 212 """GET /repos_groups/id/edit: Form to edit an existing item"""
213 213 # url('edit_repos_group', id=ID)
214 214
215 215 id_ = int(id)
216 216
217 217 c.repos_group = RepoGroup.get(id_)
218 218 defaults = self.__load_data(id_)
219 219
220 220 # we need to exclude this group from the group list for editing
221 221 c.repo_groups = filter(lambda x:x[0] != id_, c.repo_groups)
222 222
223 223 return htmlfill.render(
224 224 render('admin/repos_groups/repos_groups_edit.html'),
225 225 defaults=defaults,
226 226 encoding="UTF-8",
227 227 force_defaults=False
228 228 )
229 229
230 230
@@ -1,411 +1,412 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.settings
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 settings controller for rhodecode admin
7 7
8 8 :created_on: Jul 14, 2010
9 9 :author: marcink
10 10 :copyright: (C) 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 sqlalchemy import func
31 31 from formencode import htmlfill
32 32 from pylons import request, session, tmpl_context as c, url, config
33 33 from pylons.controllers.util import abort, redirect
34 34 from pylons.i18n.translation import _
35 35
36 36 from rhodecode.lib import helpers as h
37 37 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
38 38 HasPermissionAnyDecorator, NotAnonymous
39 39 from rhodecode.lib.base import BaseController, render
40 40 from rhodecode.lib.celerylib import tasks, run_task
41 41 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
42 42 set_rhodecode_config, repo_name_slug
43 43 from rhodecode.model.db import RhodeCodeUi, Repository, RepoGroup, \
44 44 RhodeCodeSetting
45 45 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
46 46 ApplicationUiSettingsForm
47 47 from rhodecode.model.scm import ScmModel
48 48 from rhodecode.model.user import UserModel
49 49 from rhodecode.model.db import User
50 50 from rhodecode.model.notification import EmailNotificationModel
51 from rhodecode.model.meta import Session
51 52
52 53 log = logging.getLogger(__name__)
53 54
54 55
55 56 class SettingsController(BaseController):
56 57 """REST Controller styled on the Atom Publishing Protocol"""
57 58 # To properly map this controller, ensure your config/routing.py
58 59 # file has a resource setup:
59 60 # map.resource('setting', 'settings', controller='admin/settings',
60 61 # path_prefix='/admin', name_prefix='admin_')
61 62
62 63 @LoginRequired()
63 64 def __before__(self):
64 65 c.admin_user = session.get('admin_user')
65 66 c.admin_username = session.get('admin_username')
66 67 super(SettingsController, self).__before__()
67 68
68 69 @HasPermissionAllDecorator('hg.admin')
69 70 def index(self, format='html'):
70 71 """GET /admin/settings: All items in the collection"""
71 72 # url('admin_settings')
72 73
73 74 defaults = RhodeCodeSetting.get_app_settings()
74 75 defaults.update(self.get_hg_ui_settings())
75 76 return htmlfill.render(
76 77 render('admin/settings/settings.html'),
77 78 defaults=defaults,
78 79 encoding="UTF-8",
79 80 force_defaults=False
80 81 )
81 82
82 83 @HasPermissionAllDecorator('hg.admin')
83 84 def create(self):
84 85 """POST /admin/settings: Create a new item"""
85 86 # url('admin_settings')
86 87
87 88 @HasPermissionAllDecorator('hg.admin')
88 89 def new(self, format='html'):
89 90 """GET /admin/settings/new: Form to create a new item"""
90 91 # url('admin_new_setting')
91 92
92 93 @HasPermissionAllDecorator('hg.admin')
93 94 def update(self, setting_id):
94 95 """PUT /admin/settings/setting_id: Update an existing item"""
95 96 # Forms posted to this method should contain a hidden field:
96 97 # <input type="hidden" name="_method" value="PUT" />
97 98 # Or using helpers:
98 99 # h.form(url('admin_setting', setting_id=ID),
99 100 # method='put')
100 101 # url('admin_setting', setting_id=ID)
101 102 if setting_id == 'mapping':
102 103 rm_obsolete = request.POST.get('destroy', False)
103 104 log.debug('Rescanning directories with destroy=%s', rm_obsolete)
104 105 initial = ScmModel().repo_scan()
105 106 log.debug('invalidating all repositories')
106 107 for repo_name in initial.keys():
107 108 invalidate_cache('get_repo_cached_%s' % repo_name)
108 109
109 110 added, removed = repo2db_mapper(initial, rm_obsolete)
110 111
111 112 h.flash(_('Repositories successfully'
112 113 ' rescanned added: %s,removed: %s') % (added, removed),
113 114 category='success')
114 115
115 116 if setting_id == 'whoosh':
116 117 repo_location = self.get_hg_ui_settings()['paths_root_path']
117 118 full_index = request.POST.get('full_index', False)
118 119 run_task(tasks.whoosh_index, repo_location, full_index)
119 120
120 121 h.flash(_('Whoosh reindex task scheduled'), category='success')
121 122 if setting_id == 'global':
122 123
123 124 application_form = ApplicationSettingsForm()()
124 125 try:
125 126 form_result = application_form.to_python(dict(request.POST))
126 127
127 128 try:
128 129 hgsettings1 = RhodeCodeSetting.get_by_name('title')
129 130 hgsettings1.app_settings_value = \
130 131 form_result['rhodecode_title']
131 132
132 133 hgsettings2 = RhodeCodeSetting.get_by_name('realm')
133 134 hgsettings2.app_settings_value = \
134 135 form_result['rhodecode_realm']
135 136
136 137 hgsettings3 = RhodeCodeSetting.get_by_name('ga_code')
137 138 hgsettings3.app_settings_value = \
138 139 form_result['rhodecode_ga_code']
139 140
140 141 self.sa.add(hgsettings1)
141 142 self.sa.add(hgsettings2)
142 143 self.sa.add(hgsettings3)
143 144 self.sa.commit()
144 145 set_rhodecode_config(config)
145 146 h.flash(_('Updated application settings'),
146 147 category='success')
147 148
148 149 except Exception:
149 150 log.error(traceback.format_exc())
150 151 h.flash(_('error occurred during updating '
151 152 'application settings'),
152 153 category='error')
153 154
154 155 self.sa.rollback()
155 156
156 157 except formencode.Invalid, errors:
157 158 return htmlfill.render(
158 159 render('admin/settings/settings.html'),
159 160 defaults=errors.value,
160 161 errors=errors.error_dict or {},
161 162 prefix_error=False,
162 163 encoding="UTF-8")
163 164
164 165 if setting_id == 'mercurial':
165 166 application_form = ApplicationUiSettingsForm()()
166 167 try:
167 168 form_result = application_form.to_python(dict(request.POST))
168 169
169 170 try:
170 171
171 172 hgsettings1 = self.sa.query(RhodeCodeUi)\
172 173 .filter(RhodeCodeUi.ui_key == 'push_ssl').one()
173 174 hgsettings1.ui_value = form_result['web_push_ssl']
174 175
175 176 hgsettings2 = self.sa.query(RhodeCodeUi)\
176 177 .filter(RhodeCodeUi.ui_key == '/').one()
177 178 hgsettings2.ui_value = form_result['paths_root_path']
178 179
179 180 #HOOKS
180 181 hgsettings3 = self.sa.query(RhodeCodeUi)\
181 182 .filter(RhodeCodeUi.ui_key == 'changegroup.update').one()
182 183 hgsettings3.ui_active = \
183 184 bool(form_result['hooks_changegroup_update'])
184 185
185 186 hgsettings4 = self.sa.query(RhodeCodeUi)\
186 187 .filter(RhodeCodeUi.ui_key ==
187 188 'changegroup.repo_size').one()
188 189 hgsettings4.ui_active = \
189 190 bool(form_result['hooks_changegroup_repo_size'])
190 191
191 192 hgsettings5 = self.sa.query(RhodeCodeUi)\
192 193 .filter(RhodeCodeUi.ui_key ==
193 194 'pretxnchangegroup.push_logger').one()
194 195 hgsettings5.ui_active = \
195 196 bool(form_result['hooks_pretxnchangegroup'
196 197 '_push_logger'])
197 198
198 199 hgsettings6 = self.sa.query(RhodeCodeUi)\
199 200 .filter(RhodeCodeUi.ui_key ==
200 201 'preoutgoing.pull_logger').one()
201 202 hgsettings6.ui_active = \
202 203 bool(form_result['hooks_preoutgoing_pull_logger'])
203 204
204 205 self.sa.add(hgsettings1)
205 206 self.sa.add(hgsettings2)
206 207 self.sa.add(hgsettings3)
207 208 self.sa.add(hgsettings4)
208 209 self.sa.add(hgsettings5)
209 210 self.sa.add(hgsettings6)
210 211 self.sa.commit()
211 212
212 213 h.flash(_('Updated mercurial settings'),
213 214 category='success')
214 215
215 216 except:
216 217 log.error(traceback.format_exc())
217 218 h.flash(_('error occurred during updating '
218 219 'application settings'), category='error')
219 220
220 221 self.sa.rollback()
221 222
222 223 except formencode.Invalid, errors:
223 224 return htmlfill.render(
224 225 render('admin/settings/settings.html'),
225 226 defaults=errors.value,
226 227 errors=errors.error_dict or {},
227 228 prefix_error=False,
228 229 encoding="UTF-8")
229 230
230 231
231 232 if setting_id == 'hooks':
232 233 ui_key = request.POST.get('new_hook_ui_key')
233 234 ui_value = request.POST.get('new_hook_ui_value')
234 235 try:
235 236
236 237 if ui_value and ui_key:
237 238 RhodeCodeUi.create_or_update_hook(ui_key, ui_value)
238 239 h.flash(_('Added new hook'),
239 240 category='success')
240 241
241 242 # check for edits
242 243 update = False
243 244 _d = request.POST.dict_of_lists()
244 245 for k, v in zip(_d.get('hook_ui_key', []), _d.get('hook_ui_value_new', [])):
245 246 RhodeCodeUi.create_or_update_hook(k, v)
246 247 update = True
247 248
248 249 if update:
249 250 h.flash(_('Updated hooks'), category='success')
250
251 Session.commit()
251 252 except:
252 253 log.error(traceback.format_exc())
253 254 h.flash(_('error occurred during hook creation'),
254 255 category='error')
255 256
256 257 return redirect(url('admin_edit_setting', setting_id='hooks'))
257 258
258 259
259 260
260 261 if setting_id == 'email':
261 262 test_email = request.POST.get('test_email')
262 263 test_email_subj = 'RhodeCode TestEmail'
263 264 test_email_body = 'RhodeCode Email test'
264 265 test_email_html_body = EmailNotificationModel()\
265 266 .get_email_tmpl(EmailNotificationModel.TYPE_DEFAULT)
266 267
267 268 run_task(tasks.send_email, [test_email], test_email_subj,
268 269 test_email_body, test_email_html_body)
269 270
270 271 h.flash(_('Email task created'), category='success')
271 272 return redirect(url('admin_settings'))
272 273
273 274 @HasPermissionAllDecorator('hg.admin')
274 275 def delete(self, setting_id):
275 276 """DELETE /admin/settings/setting_id: Delete an existing item"""
276 277 # Forms posted to this method should contain a hidden field:
277 278 # <input type="hidden" name="_method" value="DELETE" />
278 279 # Or using helpers:
279 280 # h.form(url('admin_setting', setting_id=ID),
280 281 # method='delete')
281 282 # url('admin_setting', setting_id=ID)
282 283 if setting_id == 'hooks':
283 284 hook_id = request.POST.get('hook_id')
284 285 RhodeCodeUi.delete(hook_id)
285 286
286 287
287 288 @HasPermissionAllDecorator('hg.admin')
288 289 def show(self, setting_id, format='html'):
289 290 """
290 291 GET /admin/settings/setting_id: Show a specific item"""
291 292 # url('admin_setting', setting_id=ID)
292 293
293 294 @HasPermissionAllDecorator('hg.admin')
294 295 def edit(self, setting_id, format='html'):
295 296 """
296 297 GET /admin/settings/setting_id/edit: Form to
297 298 edit an existing item"""
298 299 # url('admin_edit_setting', setting_id=ID)
299 300
300 301 c.hooks = RhodeCodeUi.get_builtin_hooks()
301 302 c.custom_hooks = RhodeCodeUi.get_custom_hooks()
302 303
303 304 return htmlfill.render(
304 305 render('admin/settings/hooks.html'),
305 306 defaults={},
306 307 encoding="UTF-8",
307 308 force_defaults=False
308 309 )
309 310
310 311 @NotAnonymous()
311 312 def my_account(self):
312 313 """
313 314 GET /_admin/my_account Displays info about my account
314 315 """
315 316 # url('admin_settings_my_account')
316 317
317 318 c.user = User.get(self.rhodecode_user.user_id)
318 319 all_repos = self.sa.query(Repository)\
319 320 .filter(Repository.user_id == c.user.user_id)\
320 321 .order_by(func.lower(Repository.repo_name)).all()
321 322
322 323 c.user_repos = ScmModel().get_repos(all_repos)
323 324
324 325 if c.user.username == 'default':
325 326 h.flash(_("You can't edit this user since it's"
326 327 " crucial for entire application"), category='warning')
327 328 return redirect(url('users'))
328 329
329 330 defaults = c.user.get_dict()
330 331 return htmlfill.render(
331 332 render('admin/users/user_edit_my_account.html'),
332 333 defaults=defaults,
333 334 encoding="UTF-8",
334 335 force_defaults=False
335 336 )
336 337
337 338 def my_account_update(self):
338 339 """PUT /_admin/my_account_update: Update an existing item"""
339 340 # Forms posted to this method should contain a hidden field:
340 341 # <input type="hidden" name="_method" value="PUT" />
341 342 # Or using helpers:
342 343 # h.form(url('admin_settings_my_account_update'),
343 344 # method='put')
344 345 # url('admin_settings_my_account_update', id=ID)
345 346 user_model = UserModel()
346 347 uid = self.rhodecode_user.user_id
347 348 _form = UserForm(edit=True,
348 349 old_data={'user_id': uid,
349 350 'email': self.rhodecode_user.email})()
350 351 form_result = {}
351 352 try:
352 353 form_result = _form.to_python(dict(request.POST))
353 354 user_model.update_my_account(uid, form_result)
354 355 h.flash(_('Your account was updated successfully'),
355 356 category='success')
356
357 Session.commit()
357 358 except formencode.Invalid, errors:
358 359 c.user = User.get(self.rhodecode_user.user_id)
359 360 all_repos = self.sa.query(Repository)\
360 361 .filter(Repository.user_id == c.user.user_id)\
361 362 .order_by(func.lower(Repository.repo_name))\
362 363 .all()
363 364 c.user_repos = ScmModel().get_repos(all_repos)
364 365
365 366 return htmlfill.render(
366 367 render('admin/users/user_edit_my_account.html'),
367 368 defaults=errors.value,
368 369 errors=errors.error_dict or {},
369 370 prefix_error=False,
370 371 encoding="UTF-8")
371 372 except Exception:
372 373 log.error(traceback.format_exc())
373 374 h.flash(_('error occurred during update of user %s') \
374 375 % form_result.get('username'), category='error')
375 376
376 377 return redirect(url('my_account'))
377 378
378 379 @NotAnonymous()
379 380 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
380 381 def create_repository(self):
381 382 """GET /_admin/create_repository: Form to create a new item"""
382 383
383 384 c.repo_groups = RepoGroup.groups_choices()
384 385 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
385 386
386 387 new_repo = request.GET.get('repo', '')
387 388 c.new_repo = repo_name_slug(new_repo)
388 389
389 390 return render('admin/repos/repo_add_create_repository.html')
390 391
391 392 def get_hg_ui_settings(self):
392 393 ret = self.sa.query(RhodeCodeUi).all()
393 394
394 395 if not ret:
395 396 raise Exception('Could not get application ui settings !')
396 397 settings = {}
397 398 for each in ret:
398 399 k = each.ui_key
399 400 v = each.ui_value
400 401 if k == '/':
401 402 k = 'root_path'
402 403
403 404 if k.find('.') != -1:
404 405 k = k.replace('.', '_')
405 406
406 407 if each.ui_section == 'hooks':
407 408 v = each.ui_active
408 409
409 410 settings[each.ui_section + '_' + k] = v
410 411
411 412 return settings
@@ -1,207 +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 from rhodecode.model.meta import Session
44 45
45 46 log = logging.getLogger(__name__)
46 47
47 48
48 49 class UsersController(BaseController):
49 50 """REST Controller styled on the Atom Publishing Protocol"""
50 51 # To properly map this controller, ensure your config/routing.py
51 52 # file has a resource setup:
52 53 # map.resource('user', 'users')
53 54
54 55 @LoginRequired()
55 56 @HasPermissionAllDecorator('hg.admin')
56 57 def __before__(self):
57 58 c.admin_user = session.get('admin_user')
58 59 c.admin_username = session.get('admin_username')
59 60 super(UsersController, self).__before__()
60 61 c.available_permissions = config['available_permissions']
61 62
62 63 def index(self, format='html'):
63 64 """GET /users: All items in the collection"""
64 65 # url('users')
65 66
66 67 c.users_list = self.sa.query(User).all()
67 68 return render('admin/users/users.html')
68 69
69 70 def create(self):
70 71 """POST /users: Create a new item"""
71 72 # url('users')
72 73
73 74 user_model = UserModel()
74 75 user_form = UserForm()()
75 76 try:
76 77 form_result = user_form.to_python(dict(request.POST))
77 78 user_model.create(form_result)
78 79 h.flash(_('created user %s') % form_result['username'],
79 80 category='success')
81 Session.commit()
80 82 #action_logger(self.rhodecode_user, 'new_user', '', '', self.sa)
81 83 except formencode.Invalid, errors:
82 84 return htmlfill.render(
83 85 render('admin/users/user_add.html'),
84 86 defaults=errors.value,
85 87 errors=errors.error_dict or {},
86 88 prefix_error=False,
87 89 encoding="UTF-8")
88 90 except Exception:
89 91 log.error(traceback.format_exc())
90 92 h.flash(_('error occurred during creation of user %s') \
91 93 % request.POST.get('username'), category='error')
92 94 return redirect(url('users'))
93 95
94 96 def new(self, format='html'):
95 97 """GET /users/new: Form to create a new item"""
96 98 # url('new_user')
97 99 return render('admin/users/user_add.html')
98 100
99 101 def update(self, id):
100 102 """PUT /users/id: Update an existing item"""
101 103 # Forms posted to this method should contain a hidden field:
102 104 # <input type="hidden" name="_method" value="PUT" />
103 105 # Or using helpers:
104 106 # h.form(url('update_user', id=ID),
105 107 # method='put')
106 108 # url('user', id=ID)
107 109 user_model = UserModel()
108 110 c.user = user_model.get(id)
109 111
110 112 _form = UserForm(edit=True, old_data={'user_id': id,
111 113 'email': c.user.email})()
112 114 form_result = {}
113 115 try:
114 116 form_result = _form.to_python(dict(request.POST))
115 117 user_model.update(id, form_result)
116 118 h.flash(_('User updated successfully'), category='success')
117
119 Session.commit()
118 120 except formencode.Invalid, errors:
119 121 e = errors.error_dict or {}
120 122 perm = Permission.get_by_key('hg.create.repository')
121 e.update({'create_repo_perm': UserToPerm.has_perm(id, perm)})
123 e.update({'create_repo_perm': user_model.has_perm(id, perm)})
122 124 return htmlfill.render(
123 125 render('admin/users/user_edit.html'),
124 126 defaults=errors.value,
125 127 errors=e,
126 128 prefix_error=False,
127 129 encoding="UTF-8")
128 130 except Exception:
129 131 log.error(traceback.format_exc())
130 132 h.flash(_('error occurred during update of user %s') \
131 133 % form_result.get('username'), category='error')
132 134
133 135 return redirect(url('users'))
134 136
135 137 def delete(self, id):
136 138 """DELETE /users/id: Delete an existing item"""
137 139 # Forms posted to this method should contain a hidden field:
138 140 # <input type="hidden" name="_method" value="DELETE" />
139 141 # Or using helpers:
140 142 # h.form(url('delete_user', id=ID),
141 143 # method='delete')
142 144 # url('user', id=ID)
143 145 user_model = UserModel()
144 146 try:
145 147 user_model.delete(id)
146 148 h.flash(_('successfully deleted user'), category='success')
149 Session.commit()
147 150 except (UserOwnsReposException, DefaultUserException), e:
148 151 h.flash(str(e), category='warning')
149 152 except Exception:
150 153 h.flash(_('An error occurred during deletion of user'),
151 154 category='error')
152 155 return redirect(url('users'))
153 156
154 157 def show(self, id, format='html'):
155 158 """GET /users/id: Show a specific item"""
156 159 # url('user', id=ID)
157 160
158 161 def edit(self, id, format='html'):
159 162 """GET /users/id/edit: Form to edit an existing item"""
160 163 # url('edit_user', id=ID)
161 user_model = UserModel()
162 c.user = user_model.get(id)
164 c.user = User.get(id)
163 165 if not c.user:
164 166 return redirect(url('users'))
165 167 if c.user.username == 'default':
166 168 h.flash(_("You can't edit this user"), category='warning')
167 169 return redirect(url('users'))
168 170 c.user.permissions = {}
169 c.granted_permissions = user_model.fill_perms(c.user)\
171 c.granted_permissions = UserModel().fill_perms(c.user)\
170 172 .permissions['global']
171 173
172 174 defaults = c.user.get_dict()
173 175 perm = Permission.get_by_key('hg.create.repository')
174 defaults.update({'create_repo_perm': UserToPerm.has_perm(id, perm)})
176 defaults.update({'create_repo_perm': UserModel().has_perm(id, perm)})
175 177
176 178 return htmlfill.render(
177 179 render('admin/users/user_edit.html'),
178 180 defaults=defaults,
179 181 encoding="UTF-8",
180 182 force_defaults=False
181 183 )
182 184
183 185 def update_perm(self, id):
184 186 """PUT /users_perm/id: Update an existing item"""
185 187 # url('user_perm', id=ID, method='put')
186 188
187 189 grant_perm = request.POST.get('create_repo_perm', False)
188
190 user_model = UserModel()
191
189 192 if grant_perm:
190 193 perm = Permission.get_by_key('hg.create.none')
191 UserToPerm.revoke_perm(id, perm)
194 user_model.revoke_perm(id, perm)
192 195
193 196 perm = Permission.get_by_key('hg.create.repository')
194 UserToPerm.grant_perm(id, perm)
197 user_model.grant_perm(id, perm)
195 198 h.flash(_("Granted 'repository create' permission to user"),
196 199 category='success')
197 200
198 201 else:
199 202 perm = Permission.get_by_key('hg.create.repository')
200 UserToPerm.revoke_perm(id, perm)
203 user_model.revoke_perm(id, perm)
201 204
202 205 perm = Permission.get_by_key('hg.create.none')
203 UserToPerm.grant_perm(id, perm)
206 user_model.grant_perm(id, perm)
204 207 h.flash(_("Revoked 'repository create' permission to user"),
205 208 category='success')
206 209
207 210 return redirect(url('edit_user', id=id))
@@ -1,214 +1,224 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.users_groups
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Users Groups crud controller for pylons
7 7
8 8 :created_on: Jan 25, 2011
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 UsersGroupsAssignedException
36 from rhodecode.lib import helpers as h
36 from rhodecode.lib import helpers as h, safe_unicode
37 37 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
38 38 from rhodecode.lib.base import BaseController, render
39 39
40 from rhodecode.model.users_group import UsersGroupModel
41
40 42 from rhodecode.model.db import User, UsersGroup, Permission, UsersGroupToPerm
41 from rhodecode.model.forms import UserForm, UsersGroupForm
43 from rhodecode.model.forms import UsersGroupForm
44 from rhodecode.model.meta import Session
42 45
43 46 log = logging.getLogger(__name__)
44 47
45 48
46 49 class UsersGroupsController(BaseController):
47 50 """REST Controller styled on the Atom Publishing Protocol"""
48 51 # To properly map this controller, ensure your config/routing.py
49 52 # file has a resource setup:
50 53 # map.resource('users_group', 'users_groups')
51 54
52 55 @LoginRequired()
53 56 @HasPermissionAllDecorator('hg.admin')
54 57 def __before__(self):
55 58 c.admin_user = session.get('admin_user')
56 59 c.admin_username = session.get('admin_username')
57 60 super(UsersGroupsController, self).__before__()
58 61 c.available_permissions = config['available_permissions']
59 62
60 63 def index(self, format='html'):
61 64 """GET /users_groups: All items in the collection"""
62 65 # url('users_groups')
63 66 c.users_groups_list = self.sa.query(UsersGroup).all()
64 67 return render('admin/users_groups/users_groups.html')
65 68
66 69 def create(self):
67 70 """POST /users_groups: Create a new item"""
68 71 # url('users_groups')
69 72
70 73 users_group_form = UsersGroupForm()()
71 74 try:
72 75 form_result = users_group_form.to_python(dict(request.POST))
73 UsersGroup.create(form_result)
76 UsersGroupModel().create(name=form_result['users_group_name'],
77 active=form_result['users_group_active'])
74 78 h.flash(_('created users group %s') \
75 79 % form_result['users_group_name'], category='success')
76 80 #action_logger(self.rhodecode_user, 'new_user', '', '', self.sa)
81 Session.commit()
77 82 except formencode.Invalid, errors:
78 83 return htmlfill.render(
79 84 render('admin/users_groups/users_group_add.html'),
80 85 defaults=errors.value,
81 86 errors=errors.error_dict or {},
82 87 prefix_error=False,
83 88 encoding="UTF-8")
84 89 except Exception:
85 90 log.error(traceback.format_exc())
86 91 h.flash(_('error occurred during creation of users group %s') \
87 92 % request.POST.get('users_group_name'), category='error')
88 93
89 94 return redirect(url('users_groups'))
90 95
91 96 def new(self, format='html'):
92 97 """GET /users_groups/new: Form to create a new item"""
93 98 # url('new_users_group')
94 99 return render('admin/users_groups/users_group_add.html')
95 100
96 101 def update(self, id):
97 102 """PUT /users_groups/id: Update an existing item"""
98 103 # Forms posted to this method should contain a hidden field:
99 104 # <input type="hidden" name="_method" value="PUT" />
100 105 # Or using helpers:
101 106 # h.form(url('users_group', id=ID),
102 107 # method='put')
103 108 # url('users_group', id=ID)
104 109
105 110 c.users_group = UsersGroup.get(id)
106 111 c.group_members = [(x.user_id, x.user.username) for x in
107 112 c.users_group.members]
108 113
109 114 c.available_members = [(x.user_id, x.username) for x in
110 115 self.sa.query(User).all()]
116
117 available_members = [safe_unicode(x[0]) for x in c.available_members]
118
111 119 users_group_form = UsersGroupForm(edit=True,
112 120 old_data=c.users_group.get_dict(),
113 available_members=[str(x[0]) for x
114 in c.available_members])()
121 available_members=available_members)()
115 122
116 123 try:
117 124 form_result = users_group_form.to_python(request.POST)
118 UsersGroup.update(id, form_result)
125 UsersGroupModel().update(c.users_group, form_result)
119 126 h.flash(_('updated users group %s') \
120 127 % form_result['users_group_name'],
121 128 category='success')
122 129 #action_logger(self.rhodecode_user, 'new_user', '', '', self.sa)
130 Session.commit()
123 131 except formencode.Invalid, errors:
124 132 e = errors.error_dict or {}
125 133
126 134 perm = Permission.get_by_key('hg.create.repository')
127 135 e.update({'create_repo_perm':
128 UsersGroupToPerm.has_perm(id, perm)})
136 UsersGroupModel().has_perm(id, perm)})
129 137
130 138 return htmlfill.render(
131 139 render('admin/users_groups/users_group_edit.html'),
132 140 defaults=errors.value,
133 141 errors=e,
134 142 prefix_error=False,
135 143 encoding="UTF-8")
136 144 except Exception:
137 145 log.error(traceback.format_exc())
138 146 h.flash(_('error occurred during update of users group %s') \
139 147 % request.POST.get('users_group_name'), category='error')
140 148
141 149 return redirect(url('users_groups'))
142 150
143 151 def delete(self, id):
144 152 """DELETE /users_groups/id: Delete an existing item"""
145 153 # Forms posted to this method should contain a hidden field:
146 154 # <input type="hidden" name="_method" value="DELETE" />
147 155 # Or using helpers:
148 156 # h.form(url('users_group', id=ID),
149 157 # method='delete')
150 158 # url('users_group', id=ID)
151 159
152 160 try:
153 UsersGroup.delete(id)
161 UsersGroupModel().delete(id)
154 162 h.flash(_('successfully deleted users group'), category='success')
163 Session.commit()
155 164 except UsersGroupsAssignedException, e:
156 165 h.flash(e, category='error')
157 166 except Exception:
158 167 h.flash(_('An error occurred during deletion of users group'),
159 168 category='error')
160 169 return redirect(url('users_groups'))
161 170
162 171 def show(self, id, format='html'):
163 172 """GET /users_groups/id: Show a specific item"""
164 173 # url('users_group', id=ID)
165 174
166 175 def edit(self, id, format='html'):
167 176 """GET /users_groups/id/edit: Form to edit an existing item"""
168 177 # url('edit_users_group', id=ID)
169 178
170 179 c.users_group = self.sa.query(UsersGroup).get(id)
171 180 if not c.users_group:
172 181 return redirect(url('users_groups'))
173 182
174 183 c.users_group.permissions = {}
175 184 c.group_members = [(x.user_id, x.user.username) for x in
176 185 c.users_group.members]
177 186 c.available_members = [(x.user_id, x.username) for x in
178 187 self.sa.query(User).all()]
179 188 defaults = c.users_group.get_dict()
180 189 perm = Permission.get_by_key('hg.create.repository')
181 190 defaults.update({'create_repo_perm':
182 UsersGroupToPerm.has_perm(id, perm)})
191 UsersGroupModel().has_perm(c.users_group, perm)})
183 192 return htmlfill.render(
184 193 render('admin/users_groups/users_group_edit.html'),
185 194 defaults=defaults,
186 195 encoding="UTF-8",
187 196 force_defaults=False
188 197 )
189 198
190 199 def update_perm(self, id):
191 200 """PUT /users_perm/id: Update an existing item"""
192 201 # url('users_group_perm', id=ID, method='put')
193 202
194 203 grant_perm = request.POST.get('create_repo_perm', False)
195 204
196 205 if grant_perm:
197 206 perm = Permission.get_by_key('hg.create.none')
198 UsersGroupToPerm.revoke_perm(id, perm)
207 UsersGroupModel().revoke_perm(id, perm)
199 208
200 209 perm = Permission.get_by_key('hg.create.repository')
201 UsersGroupToPerm.grant_perm(id, perm)
210 UsersGroupModel().grant_perm(id, perm)
202 211 h.flash(_("Granted 'repository create' permission to user"),
203 212 category='success')
204
213
214 Session.commit()
205 215 else:
206 216 perm = Permission.get_by_key('hg.create.repository')
207 UsersGroupToPerm.revoke_perm(id, perm)
217 UsersGroupModel().revoke_perm(id, perm)
208 218
209 219 perm = Permission.get_by_key('hg.create.none')
210 UsersGroupToPerm.grant_perm(id, perm)
220 UsersGroupModel().grant_perm(id, perm)
211 221 h.flash(_("Revoked 'repository create' permission to user"),
212 222 category='success')
213
223 Session.commit()
214 224 return redirect(url('edit_users_group', id=id))
@@ -1,371 +1,369 b''
1 1 import traceback
2 2 import logging
3 3
4 4 from sqlalchemy.orm.exc import NoResultFound
5 5
6 6 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
7 7 from rhodecode.lib.auth import HasPermissionAllDecorator, \
8 8 HasPermissionAnyDecorator
9 9
10 10 from rhodecode.model.meta import Session
11 11 from rhodecode.model.scm import ScmModel
12 12 from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository
13 13 from rhodecode.model.repo import RepoModel
14 14 from rhodecode.model.user import UserModel
15 15 from rhodecode.model.repo_permission import RepositoryPermissionModel
16 16 from rhodecode.model.users_group import UsersGroupModel
17 17 from rhodecode.model.repos_group import ReposGroupModel
18 18
19 19
20 20 log = logging.getLogger(__name__)
21 21
22 22
23 23 class ApiController(JSONRPCController):
24 24 """
25 25 API Controller
26 26
27 27
28 28 Each method needs to have USER as argument this is then based on given
29 29 API_KEY propagated as instance of user object
30 30
31 31 Preferably this should be first argument also
32 32
33 33
34 34 Each function should also **raise** JSONRPCError for any
35 35 errors that happens
36 36
37 37 """
38 38
39 39 @HasPermissionAllDecorator('hg.admin')
40 40 def pull(self, apiuser, repo):
41 41 """
42 42 Dispatch pull action on given repo
43 43
44 44
45 45 :param user:
46 46 :param repo:
47 47 """
48 48
49 49 if Repository.is_valid(repo) is False:
50 50 raise JSONRPCError('Unknown repo "%s"' % repo)
51 51
52 52 try:
53 53 ScmModel().pull_changes(repo, self.rhodecode_user.username)
54 54 return 'Pulled from %s' % repo
55 55 except Exception:
56 56 raise JSONRPCError('Unable to pull changes from "%s"' % repo)
57 57
58 58 @HasPermissionAllDecorator('hg.admin')
59 59 def get_user(self, apiuser, username):
60 60 """"
61 61 Get a user by username
62 62
63 63 :param apiuser
64 64 :param username
65 65 """
66 66
67 67 user = User.get_by_username(username)
68 68 if not user:
69 69 return None
70 70
71 71 return dict(id=user.user_id,
72 72 username=user.username,
73 73 firstname=user.name,
74 74 lastname=user.lastname,
75 75 email=user.email,
76 76 active=user.active,
77 77 admin=user.admin,
78 78 ldap=user.ldap_dn)
79 79
80 80 @HasPermissionAllDecorator('hg.admin')
81 81 def get_users(self, apiuser):
82 82 """"
83 83 Get all users
84 84
85 85 :param apiuser
86 86 """
87 87
88 88 result = []
89 89 for user in User.getAll():
90 90 result.append(dict(id=user.user_id,
91 91 username=user.username,
92 92 firstname=user.name,
93 93 lastname=user.lastname,
94 94 email=user.email,
95 95 active=user.active,
96 96 admin=user.admin,
97 97 ldap=user.ldap_dn))
98 98 return result
99 99
100 100 @HasPermissionAllDecorator('hg.admin')
101 101 def create_user(self, apiuser, username, password, firstname,
102 102 lastname, email, active=True, admin=False, ldap_dn=None):
103 103 """
104 104 Create new user
105 105
106 106 :param apiuser:
107 107 :param username:
108 108 :param password:
109 109 :param name:
110 110 :param lastname:
111 111 :param email:
112 112 :param active:
113 113 :param admin:
114 114 :param ldap_dn:
115 115 """
116 116
117 117 if User.get_by_username(username):
118 118 raise JSONRPCError("user %s already exist" % username)
119 119
120 120 try:
121 121 UserModel().create_or_update(username, password, email, firstname,
122 122 lastname, active, admin, ldap_dn)
123 Session().commit()
123 Session.commit()
124 124 return dict(msg='created new user %s' % username)
125 125 except Exception:
126 126 log.error(traceback.format_exc())
127 127 raise JSONRPCError('failed to create user %s' % username)
128 128
129 129 @HasPermissionAllDecorator('hg.admin')
130 130 def get_users_group(self, apiuser, group_name):
131 131 """"
132 132 Get users group by name
133 133
134 134 :param apiuser
135 135 :param group_name
136 136 """
137 137
138 138 users_group = UsersGroup.get_by_group_name(group_name)
139 139 if not users_group:
140 140 return None
141 141
142 142 members = []
143 143 for user in users_group.members:
144 144 user = user.user
145 145 members.append(dict(id=user.user_id,
146 146 username=user.username,
147 147 firstname=user.name,
148 148 lastname=user.lastname,
149 149 email=user.email,
150 150 active=user.active,
151 151 admin=user.admin,
152 152 ldap=user.ldap_dn))
153 153
154 154 return dict(id=users_group.users_group_id,
155 155 name=users_group.users_group_name,
156 156 active=users_group.users_group_active,
157 157 members=members)
158 158
159 159 @HasPermissionAllDecorator('hg.admin')
160 160 def get_users_groups(self, apiuser):
161 161 """"
162 162 Get all users groups
163 163
164 164 :param apiuser
165 165 """
166 166
167 167 result = []
168 168 for users_group in UsersGroup.getAll():
169 169 members = []
170 170 for user in users_group.members:
171 171 user = user.user
172 172 members.append(dict(id=user.user_id,
173 173 username=user.username,
174 174 firstname=user.name,
175 175 lastname=user.lastname,
176 176 email=user.email,
177 177 active=user.active,
178 178 admin=user.admin,
179 179 ldap=user.ldap_dn))
180 180
181 181 result.append(dict(id=users_group.users_group_id,
182 182 name=users_group.users_group_name,
183 183 active=users_group.users_group_active,
184 184 members=members))
185 185 return result
186 186
187 187 @HasPermissionAllDecorator('hg.admin')
188 188 def create_users_group(self, apiuser, name, active=True):
189 189 """
190 190 Creates an new usergroup
191 191
192 192 :param name:
193 193 :param active:
194 194 """
195 195
196 196 if self.get_users_group(apiuser, name):
197 197 raise JSONRPCError("users group %s already exist" % name)
198 198
199 199 try:
200 200 ug = UsersGroupModel().create(name=name, active=active)
201 Session().commit()
201 Session.commit()
202 202 return dict(id=ug.users_group_id,
203 203 msg='created new users group %s' % name)
204 204 except Exception:
205 205 log.error(traceback.format_exc())
206 206 raise JSONRPCError('failed to create group %s' % name)
207 207
208 208 @HasPermissionAllDecorator('hg.admin')
209 209 def add_user_to_users_group(self, apiuser, group_name, user_name):
210 210 """"
211 211 Add a user to a group
212 212
213 213 :param apiuser
214 214 :param group_name
215 215 :param user_name
216 216 """
217 217
218 218 try:
219 219 users_group = UsersGroup.get_by_group_name(group_name)
220 220 if not users_group:
221 221 raise JSONRPCError('unknown users group %s' % group_name)
222 222
223 223 try:
224 224 user = User.get_by_username(user_name)
225 225 except NoResultFound:
226 226 raise JSONRPCError('unknown user %s' % user_name)
227 227
228 228 ugm = UsersGroupModel().add_user_to_group(users_group, user)
229 Session().commit()
229 Session.commit()
230 230 return dict(id=ugm.users_group_member_id,
231 231 msg='created new users group member')
232 232 except Exception:
233 233 log.error(traceback.format_exc())
234 234 raise JSONRPCError('failed to create users group member')
235 235
236 236 @HasPermissionAnyDecorator('hg.admin')
237 237 def get_repo(self, apiuser, repo_name):
238 238 """"
239 239 Get repository by name
240 240
241 241 :param apiuser
242 242 :param repo_name
243 243 """
244 244
245 try:
246 repo = Repository.get_by_repo_name(repo_name)
247 except NoResultFound:
248 return None
245 repo = Repository.get_by_repo_name(repo_name)
246 if repo is None:
247 raise JSONRPCError('unknown repository %s' % repo)
249 248
250 249 members = []
251 250 for user in repo.repo_to_perm:
252 251 perm = user.permission.permission_name
253 252 user = user.user
254 253 members.append(dict(type_="user",
255 254 id=user.user_id,
256 255 username=user.username,
257 256 firstname=user.name,
258 257 lastname=user.lastname,
259 258 email=user.email,
260 259 active=user.active,
261 260 admin=user.admin,
262 261 ldap=user.ldap_dn,
263 262 permission=perm))
264 263 for users_group in repo.users_group_to_perm:
265 264 perm = users_group.permission.permission_name
266 265 users_group = users_group.users_group
267 266 members.append(dict(type_="users_group",
268 267 id=users_group.users_group_id,
269 268 name=users_group.users_group_name,
270 269 active=users_group.users_group_active,
271 270 permission=perm))
272 271
273 272 return dict(id=repo.repo_id,
274 273 name=repo.repo_name,
275 274 type=repo.repo_type,
276 275 description=repo.description,
277 276 members=members)
278 277
279 278 @HasPermissionAnyDecorator('hg.admin')
280 279 def get_repos(self, apiuser):
281 280 """"
282 281 Get all repositories
283 282
284 283 :param apiuser
285 284 """
286 285
287 286 result = []
288 287 for repository in Repository.getAll():
289 288 result.append(dict(id=repository.repo_id,
290 289 name=repository.repo_name,
291 290 type=repository.repo_type,
292 291 description=repository.description))
293 292 return result
294 293
295 294 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
296 295 def create_repo(self, apiuser, name, owner_name, description='',
297 296 repo_type='hg', private=False):
298 297 """
299 298 Create a repository
300 299
301 300 :param apiuser
302 301 :param name
303 302 :param description
304 303 :param type
305 304 :param private
306 305 :param owner_name
307 306 """
308 307
309 308 try:
310 309 try:
311 310 owner = User.get_by_username(owner_name)
312 311 except NoResultFound:
313 312 raise JSONRPCError('unknown user %s' % owner)
314 313
315 314 if self.get_repo(apiuser, name):
316 315 raise JSONRPCError("repo %s already exist" % name)
317 316
318 317 groups = name.split('/')
319 318 real_name = groups[-1]
320 319 groups = groups[:-1]
321 320 parent_id = None
322 321 for g in groups:
323 322 group = RepoGroup.get_by_group_name(g)
324 323 if not group:
325 324 group = ReposGroupModel().create(dict(group_name=g,
326 325 group_description='',
327 326 group_parent_id=parent_id))
328 327 parent_id = group.group_id
329 328
330 329 RepoModel().create(dict(repo_name=real_name,
331 330 repo_name_full=name,
332 331 description=description,
333 332 private=private,
334 333 repo_type=repo_type,
335 334 repo_group=parent_id,
336 335 clone_uri=None), owner)
337 Session().commit()
336 Session.commit()
338 337 except Exception:
339 338 log.error(traceback.format_exc())
340 339 raise JSONRPCError('failed to create repository %s' % name)
341 340
342 341 @HasPermissionAnyDecorator('hg.admin')
343 342 def add_user_to_repo(self, apiuser, repo_name, user_name, perm):
344 343 """
345 344 Add permission for a user to a repository
346 345
347 346 :param apiuser
348 347 :param repo_name
349 348 :param user_name
350 349 :param perm
351 350 """
352 351
353 352 try:
354 try:
355 repo = Repository.get_by_repo_name(repo_name)
356 except NoResultFound:
357 raise JSONRPCError('unknown repository %s' % repo)
353 repo = Repository.get_by_repo_name(repo_name)
354 if repo is None:
355 raise JSONRPCError('unknown repository %s' % repo)
358 356
359 357 try:
360 358 user = User.get_by_username(user_name)
361 359 except NoResultFound:
362 360 raise JSONRPCError('unknown user %s' % user)
363 361
364 362 RepositoryPermissionModel()\
365 363 .update_or_delete_user_permission(repo, user, perm)
366 Session().commit()
364 Session.commit()
367 365 except Exception:
368 366 log.error(traceback.format_exc())
369 367 raise JSONRPCError('failed to edit permission %(repo)s for %(user)s'
370 368 % dict(user=user_name, repo=repo_name))
371 369
@@ -1,297 +1,297 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.changeset
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 changeset controller for pylons showoing changes beetween
7 7 revisions
8 8
9 9 :created_on: Apr 25, 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 import logging
27 27 import traceback
28 28
29 29 from pylons import tmpl_context as c, url, request, response
30 30 from pylons.i18n.translation import _
31 31 from pylons.controllers.util import redirect
32 32 from pylons.decorators import jsonify
33 33
34 34 import rhodecode.lib.helpers as h
35 35 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
36 36 from rhodecode.lib.base import BaseRepoController, render
37 37 from rhodecode.lib.utils import EmptyChangeset
38 38 from rhodecode.lib.compat import OrderedDict
39 39 from rhodecode.model.db import ChangesetComment
40 40 from rhodecode.model.comment import ChangesetCommentsModel
41 41
42 42 from vcs.exceptions import RepositoryError, ChangesetError, \
43 43 ChangesetDoesNotExistError
44 44 from vcs.nodes import FileNode
45 45 from vcs.utils import diffs as differ
46 46 from webob.exc import HTTPForbidden
47 47 from rhodecode.model.meta import Session
48 48
49 49 log = logging.getLogger(__name__)
50 50
51 51
52 52 class ChangesetController(BaseRepoController):
53 53
54 54 @LoginRequired()
55 55 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
56 56 'repository.admin')
57 57 def __before__(self):
58 58 super(ChangesetController, self).__before__()
59 59 c.affected_files_cut_off = 60
60 60
61 61 def index(self, revision):
62 62
63 63 def wrap_to_table(str):
64 64
65 65 return '''<table class="code-difftable">
66 66 <tr class="line">
67 67 <td class="lineno new"></td>
68 68 <td class="code"><pre>%s</pre></td>
69 69 </tr>
70 70 </table>''' % str
71 71
72 72 #get ranges of revisions if preset
73 73 rev_range = revision.split('...')[:2]
74 74
75 75 try:
76 76 if len(rev_range) == 2:
77 77 rev_start = rev_range[0]
78 78 rev_end = rev_range[1]
79 79 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
80 80 end=rev_end)
81 81 else:
82 82 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
83 83
84 84 c.cs_ranges = list(rev_ranges)
85 85 if not c.cs_ranges:
86 86 raise RepositoryError('Changeset range returned empty result')
87 87
88 88 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
89 89 log.error(traceback.format_exc())
90 90 h.flash(str(e), category='warning')
91 91 return redirect(url('home'))
92 92
93 93 c.changes = OrderedDict()
94 94 c.sum_added = 0
95 95 c.sum_removed = 0
96 96 c.lines_added = 0
97 97 c.lines_deleted = 0
98 98 c.cut_off = False # defines if cut off limit is reached
99 99
100 100 c.comments = []
101 101 c.inline_comments = []
102 102 c.inline_cnt = 0
103 103 # Iterate over ranges (default changeset view is always one changeset)
104 104 for changeset in c.cs_ranges:
105 105 c.comments.extend(ChangesetCommentsModel()\
106 106 .get_comments(c.rhodecode_db_repo.repo_id,
107 107 changeset.raw_id))
108 108 inlines = ChangesetCommentsModel()\
109 109 .get_inline_comments(c.rhodecode_db_repo.repo_id,
110 110 changeset.raw_id)
111 111 c.inline_comments.extend(inlines)
112 112 c.changes[changeset.raw_id] = []
113 113 try:
114 114 changeset_parent = changeset.parents[0]
115 115 except IndexError:
116 116 changeset_parent = None
117 117
118 118 #==================================================================
119 119 # ADDED FILES
120 120 #==================================================================
121 121 for node in changeset.added:
122 122
123 123 filenode_old = FileNode(node.path, '', EmptyChangeset())
124 124 if filenode_old.is_binary or node.is_binary:
125 125 diff = wrap_to_table(_('binary file'))
126 126 st = (0, 0)
127 127 else:
128 128 # in this case node.size is good parameter since those are
129 129 # added nodes and their size defines how many changes were
130 130 # made
131 131 c.sum_added += node.size
132 132 if c.sum_added < self.cut_off_limit:
133 133 f_gitdiff = differ.get_gitdiff(filenode_old, node)
134 134 d = differ.DiffProcessor(f_gitdiff, format='gitdiff')
135 135
136 136 st = d.stat()
137 137 diff = d.as_html()
138 138
139 139 else:
140 140 diff = wrap_to_table(_('Changeset is to big and '
141 141 'was cut off, see raw '
142 142 'changeset instead'))
143 143 c.cut_off = True
144 144 break
145 145
146 146 cs1 = None
147 147 cs2 = node.last_changeset.raw_id
148 148 c.lines_added += st[0]
149 149 c.lines_deleted += st[1]
150 150 c.changes[changeset.raw_id].append(('added', node, diff,
151 151 cs1, cs2, st))
152 152
153 153 #==================================================================
154 154 # CHANGED FILES
155 155 #==================================================================
156 156 if not c.cut_off:
157 157 for node in changeset.changed:
158 158 try:
159 159 filenode_old = changeset_parent.get_node(node.path)
160 160 except ChangesetError:
161 161 log.warning('Unable to fetch parent node for diff')
162 162 filenode_old = FileNode(node.path, '',
163 163 EmptyChangeset())
164 164
165 165 if filenode_old.is_binary or node.is_binary:
166 166 diff = wrap_to_table(_('binary file'))
167 167 st = (0, 0)
168 168 else:
169 169
170 170 if c.sum_removed < self.cut_off_limit:
171 171 f_gitdiff = differ.get_gitdiff(filenode_old, node)
172 172 d = differ.DiffProcessor(f_gitdiff,
173 173 format='gitdiff')
174 174 st = d.stat()
175 175 if (st[0] + st[1]) * 256 > self.cut_off_limit:
176 176 diff = wrap_to_table(_('Diff is to big '
177 177 'and was cut off, see '
178 178 'raw diff instead'))
179 179 else:
180 180 diff = d.as_html()
181 181
182 182 if diff:
183 183 c.sum_removed += len(diff)
184 184 else:
185 185 diff = wrap_to_table(_('Changeset is to big and '
186 186 'was cut off, see raw '
187 187 'changeset instead'))
188 188 c.cut_off = True
189 189 break
190 190
191 191 cs1 = filenode_old.last_changeset.raw_id
192 192 cs2 = node.last_changeset.raw_id
193 193 c.lines_added += st[0]
194 194 c.lines_deleted += st[1]
195 195 c.changes[changeset.raw_id].append(('changed', node, diff,
196 196 cs1, cs2, st))
197 197
198 198 #==================================================================
199 199 # REMOVED FILES
200 200 #==================================================================
201 201 if not c.cut_off:
202 202 for node in changeset.removed:
203 203 c.changes[changeset.raw_id].append(('removed', node, None,
204 204 None, None, (0, 0)))
205 205
206 206 # count inline comments
207 207 for path, lines in c.inline_comments:
208 208 for comments in lines.values():
209 209 c.inline_cnt += len(comments)
210 210
211 211 if len(c.cs_ranges) == 1:
212 212 c.changeset = c.cs_ranges[0]
213 213 c.changes = c.changes[c.changeset.raw_id]
214 214
215 215 return render('changeset/changeset.html')
216 216 else:
217 217 return render('changeset/changeset_range.html')
218 218
219 219 def raw_changeset(self, revision):
220 220
221 221 method = request.GET.get('diff', 'show')
222 222 try:
223 223 c.scm_type = c.rhodecode_repo.alias
224 224 c.changeset = c.rhodecode_repo.get_changeset(revision)
225 225 except RepositoryError:
226 226 log.error(traceback.format_exc())
227 227 return redirect(url('home'))
228 228 else:
229 229 try:
230 230 c.changeset_parent = c.changeset.parents[0]
231 231 except IndexError:
232 232 c.changeset_parent = None
233 233 c.changes = []
234 234
235 235 for node in c.changeset.added:
236 236 filenode_old = FileNode(node.path, '')
237 237 if filenode_old.is_binary or node.is_binary:
238 238 diff = _('binary file') + '\n'
239 239 else:
240 240 f_gitdiff = differ.get_gitdiff(filenode_old, node)
241 241 diff = differ.DiffProcessor(f_gitdiff,
242 242 format='gitdiff').raw_diff()
243 243
244 244 cs1 = None
245 245 cs2 = node.last_changeset.raw_id
246 246 c.changes.append(('added', node, diff, cs1, cs2))
247 247
248 248 for node in c.changeset.changed:
249 249 filenode_old = c.changeset_parent.get_node(node.path)
250 250 if filenode_old.is_binary or node.is_binary:
251 251 diff = _('binary file')
252 252 else:
253 253 f_gitdiff = differ.get_gitdiff(filenode_old, node)
254 254 diff = differ.DiffProcessor(f_gitdiff,
255 255 format='gitdiff').raw_diff()
256 256
257 257 cs1 = filenode_old.last_changeset.raw_id
258 258 cs2 = node.last_changeset.raw_id
259 259 c.changes.append(('changed', node, diff, cs1, cs2))
260 260
261 261 response.content_type = 'text/plain'
262 262
263 263 if method == 'download':
264 264 response.content_disposition = 'attachment; filename=%s.patch' \
265 265 % revision
266 266
267 267 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id for x in
268 268 c.changeset.parents])
269 269
270 270 c.diffs = ''
271 271 for x in c.changes:
272 272 c.diffs += x[2]
273 273
274 274 return render('changeset/raw_changeset.html')
275 275
276 276 def comment(self, repo_name, revision):
277 277 ChangesetCommentsModel().create(text=request.POST.get('text'),
278 278 repo_id=c.rhodecode_db_repo.repo_id,
279 279 user_id=c.rhodecode_user.user_id,
280 280 revision=revision,
281 281 f_path=request.POST.get('f_path'),
282 282 line_no=request.POST.get('line'))
283 Session().commit()
283 Session.commit()
284 284 return redirect(h.url('changeset_home', repo_name=repo_name,
285 285 revision=revision))
286 286
287 287 @jsonify
288 288 def delete_comment(self, repo_name, comment_id):
289 289 co = ChangesetComment.get(comment_id)
290 290 owner = lambda : co.author.user_id == c.rhodecode_user.user_id
291 291 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
292 292 ChangesetCommentsModel().delete(comment=co)
293 Session().commit()
293 Session.commit()
294 294 return True
295 295 else:
296 296 raise HTTPForbidden()
297 297
@@ -1,242 +1,242 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.journal
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Journal controller for pylons
7 7
8 8 :created_on: Nov 21, 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 import logging
26 26 from itertools import groupby
27 27
28 28 from sqlalchemy import or_
29 29 from sqlalchemy.orm import joinedload
30 30 from webhelpers.paginate import Page
31 31 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
32 32
33 33 from paste.httpexceptions import HTTPBadRequest
34 34 from pylons import request, tmpl_context as c, response, url
35 35 from pylons.i18n.translation import _
36 36
37 37 import rhodecode.lib.helpers as h
38 38 from rhodecode.lib.auth import LoginRequired, NotAnonymous
39 39 from rhodecode.lib.base import BaseController, render
40 40 from rhodecode.model.db import UserLog, UserFollowing, Repository, User
41 41 from rhodecode.model.meta import Session
42 42 from sqlalchemy.sql.expression import func
43 43 from rhodecode.model.scm import ScmModel
44 44
45 45 log = logging.getLogger(__name__)
46 46
47 47
48 48 class JournalController(BaseController):
49 49
50 50 def __before__(self):
51 51 super(JournalController, self).__before__()
52 52 self.rhodecode_user = self.rhodecode_user
53 53 self.title = _('%s public journal %s feed') % (c.rhodecode_name, '%s')
54 54 self.language = 'en-us'
55 55 self.ttl = "5"
56 56 self.feed_nr = 20
57 57
58 58 @LoginRequired()
59 59 @NotAnonymous()
60 60 def index(self):
61 61 # Return a rendered template
62 62 p = int(request.params.get('page', 1))
63 63
64 64 c.user = User.get(self.rhodecode_user.user_id)
65 65 all_repos = self.sa.query(Repository)\
66 66 .filter(Repository.user_id == c.user.user_id)\
67 67 .order_by(func.lower(Repository.repo_name)).all()
68 68
69 69 c.user_repos = ScmModel().get_repos(all_repos)
70 70
71 71 c.following = self.sa.query(UserFollowing)\
72 72 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
73 73 .options(joinedload(UserFollowing.follows_repository))\
74 74 .all()
75 75
76 76 journal = self._get_journal_data(c.following)
77 77
78 78 c.journal_pager = Page(journal, page=p, items_per_page=20)
79 79
80 80 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
81 81
82 82 c.journal_data = render('journal/journal_data.html')
83 83 if request.environ.get('HTTP_X_PARTIAL_XHR'):
84 84 return c.journal_data
85 85 return render('journal/journal.html')
86 86
87 87 def _get_daily_aggregate(self, journal):
88 88 groups = []
89 89 for k, g in groupby(journal, lambda x: x.action_as_day):
90 90 user_group = []
91 91 for k2, g2 in groupby(list(g), lambda x: x.user.email):
92 92 l = list(g2)
93 93 user_group.append((l[0].user, l))
94 94
95 95 groups.append((k, user_group,))
96 96
97 97 return groups
98 98
99 99 def _get_journal_data(self, following_repos):
100 100 repo_ids = [x.follows_repository.repo_id for x in following_repos
101 101 if x.follows_repository is not None]
102 102 user_ids = [x.follows_user.user_id for x in following_repos
103 103 if x.follows_user is not None]
104 104
105 105 filtering_criterion = None
106 106
107 107 if repo_ids and user_ids:
108 108 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
109 109 UserLog.user_id.in_(user_ids))
110 110 if repo_ids and not user_ids:
111 111 filtering_criterion = UserLog.repository_id.in_(repo_ids)
112 112 if not repo_ids and user_ids:
113 113 filtering_criterion = UserLog.user_id.in_(user_ids)
114 114 if filtering_criterion is not None:
115 115 journal = self.sa.query(UserLog)\
116 116 .options(joinedload(UserLog.user))\
117 117 .options(joinedload(UserLog.repository))\
118 118 .filter(filtering_criterion)\
119 119 .order_by(UserLog.action_date.desc())
120 120 else:
121 121 journal = []
122 122
123 123 return journal
124 124
125 125 @LoginRequired()
126 126 @NotAnonymous()
127 127 def toggle_following(self):
128 128 cur_token = request.POST.get('auth_token')
129 129 token = h.get_token()
130 130 if cur_token == token:
131 131
132 132 user_id = request.POST.get('follows_user_id')
133 133 if user_id:
134 134 try:
135 135 self.scm_model.toggle_following_user(user_id,
136 136 self.rhodecode_user.user_id)
137 Session().commit()
137 Session.commit()
138 138 return 'ok'
139 139 except:
140 140 raise HTTPBadRequest()
141 141
142 142 repo_id = request.POST.get('follows_repo_id')
143 143 if repo_id:
144 144 try:
145 145 self.scm_model.toggle_following_repo(repo_id,
146 146 self.rhodecode_user.user_id)
147 Session().commit()
147 Session.commit()
148 148 return 'ok'
149 149 except:
150 150 raise HTTPBadRequest()
151 151
152 152 log.debug('token mismatch %s vs %s', cur_token, token)
153 153 raise HTTPBadRequest()
154 154
155 155 @LoginRequired()
156 156 def public_journal(self):
157 157 # Return a rendered template
158 158 p = int(request.params.get('page', 1))
159 159
160 160 c.following = self.sa.query(UserFollowing)\
161 161 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
162 162 .options(joinedload(UserFollowing.follows_repository))\
163 163 .all()
164 164
165 165 journal = self._get_journal_data(c.following)
166 166
167 167 c.journal_pager = Page(journal, page=p, items_per_page=20)
168 168
169 169 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
170 170
171 171 c.journal_data = render('journal/journal_data.html')
172 172 if request.environ.get('HTTP_X_PARTIAL_XHR'):
173 173 return c.journal_data
174 174 return render('journal/public_journal.html')
175 175
176 176 @LoginRequired(api_access=True)
177 177 def public_journal_atom(self):
178 178 """
179 179 Produce an atom-1.0 feed via feedgenerator module
180 180 """
181 181 c.following = self.sa.query(UserFollowing)\
182 182 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
183 183 .options(joinedload(UserFollowing.follows_repository))\
184 184 .all()
185 185
186 186 journal = self._get_journal_data(c.following)
187 187
188 188 feed = Atom1Feed(title=self.title % 'atom',
189 189 link=url('public_journal_atom', qualified=True),
190 190 description=_('Public journal'),
191 191 language=self.language,
192 192 ttl=self.ttl)
193 193
194 194 for entry in journal[:self.feed_nr]:
195 195 #tmpl = h.action_parser(entry)[0]
196 196 action, action_extra = h.action_parser(entry, feed=True)
197 197 title = "%s - %s %s" % (entry.user.short_contact, action,
198 198 entry.repository.repo_name)
199 199 desc = action_extra()
200 200 feed.add_item(title=title,
201 201 pubdate=entry.action_date,
202 202 link=url('', qualified=True),
203 203 author_email=entry.user.email,
204 204 author_name=entry.user.full_contact,
205 205 description=desc)
206 206
207 207 response.content_type = feed.mime_type
208 208 return feed.writeString('utf-8')
209 209
210 210 @LoginRequired(api_access=True)
211 211 def public_journal_rss(self):
212 212 """
213 213 Produce an rss2 feed via feedgenerator module
214 214 """
215 215 c.following = self.sa.query(UserFollowing)\
216 216 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
217 217 .options(joinedload(UserFollowing.follows_repository))\
218 218 .all()
219 219
220 220 journal = self._get_journal_data(c.following)
221 221
222 222 feed = Rss201rev2Feed(title=self.title % 'rss',
223 223 link=url('public_journal_rss', qualified=True),
224 224 description=_('Public journal'),
225 225 language=self.language,
226 226 ttl=self.ttl)
227 227
228 228 for entry in journal[:self.feed_nr]:
229 229 #tmpl = h.action_parser(entry)[0]
230 230 action, action_extra = h.action_parser(entry, feed=True)
231 231 title = "%s - %s %s" % (entry.user.short_contact, action,
232 232 entry.repository.repo_name)
233 233 desc = action_extra()
234 234 feed.add_item(title=title,
235 235 pubdate=entry.action_date,
236 236 link=url('', qualified=True),
237 237 author_email=entry.user.email,
238 238 author_name=entry.user.full_contact,
239 239 description=desc)
240 240
241 241 response.content_type = feed.mime_type
242 242 return feed.writeString('utf-8')
@@ -1,169 +1,165 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.login
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Login controller for rhodeocode
7 7
8 8 :created_on: Apr 22, 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 formencode
28 28
29 29 from formencode import htmlfill
30 30
31 31 from pylons.i18n.translation import _
32 32 from pylons.controllers.util import abort, redirect
33 33 from pylons import request, response, session, tmpl_context as c, url
34 34
35 35 import rhodecode.lib.helpers as h
36 36 from rhodecode.lib.auth import AuthUser, HasPermissionAnyDecorator
37 37 from rhodecode.lib.base import BaseController, render
38 38 from rhodecode.model.db import User
39 39 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
40 40 from rhodecode.model.user import UserModel
41 41 from rhodecode.model.meta import Session
42 42
43 43
44 44 log = logging.getLogger(__name__)
45 45
46 46
47 47 class LoginController(BaseController):
48 48
49 49 def __before__(self):
50 50 super(LoginController, self).__before__()
51 51
52 52 def index(self):
53 53 # redirect if already logged in
54 54 c.came_from = request.GET.get('came_from', None)
55 55
56 56 if self.rhodecode_user.is_authenticated \
57 57 and self.rhodecode_user.username != 'default':
58 58
59 59 return redirect(url('home'))
60 60
61 61 if request.POST:
62 62 # import Login Form validator class
63 63 login_form = LoginForm()
64 64 try:
65 65 c.form_result = login_form.to_python(dict(request.POST))
66 66 # form checks for username/password, now we're authenticated
67 67 username = c.form_result['username']
68 68 user = User.get_by_username(username, case_insensitive=True)
69 69 auth_user = AuthUser(user.user_id)
70 70 auth_user.set_authenticated()
71 71 cs = auth_user.get_cookie_store()
72 72 session['rhodecode_user'] = cs
73 73 session.save()
74 74
75 75 log.info('user %s is now authenticated and stored in '
76 76 'session, session attrs %s' % (username, cs))
77 77 user.update_lastlogin()
78
78 Session.commit()
79
79 80 if c.came_from:
80 81 return redirect(c.came_from)
81 82 else:
82 83 return redirect(url('home'))
83 84
84 85 except formencode.Invalid, errors:
85 86 return htmlfill.render(
86 87 render('/login.html'),
87 88 defaults=errors.value,
88 89 errors=errors.error_dict or {},
89 90 prefix_error=False,
90 91 encoding="UTF-8")
91 92
92 93 return render('/login.html')
93 94
94 95 @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
95 96 'hg.register.manual_activate')
96 97 def register(self):
97 user_model = UserModel()
98 98 c.auto_active = False
99 99 for perm in User.get_by_username('default').user_perms:
100 100 if perm.permission.permission_name == 'hg.register.auto_activate':
101 101 c.auto_active = True
102 102 break
103 103
104 104 if request.POST:
105 105
106 106 register_form = RegisterForm()()
107 107 try:
108 108 form_result = register_form.to_python(dict(request.POST))
109 109 form_result['active'] = c.auto_active
110 user_model.create_registration(form_result)
110 UserModel().create_registration(form_result)
111 111 h.flash(_('You have successfully registered into rhodecode'),
112 112 category='success')
113 Session().commit()
113 Session.commit()
114 114 return redirect(url('login_home'))
115 115
116 116 except formencode.Invalid, errors:
117 117 return htmlfill.render(
118 118 render('/register.html'),
119 119 defaults=errors.value,
120 120 errors=errors.error_dict or {},
121 121 prefix_error=False,
122 122 encoding="UTF-8")
123 123
124 124 return render('/register.html')
125 125
126 126 def password_reset(self):
127 user_model = UserModel()
128 127 if request.POST:
129
130 128 password_reset_form = PasswordResetForm()()
131 129 try:
132 130 form_result = password_reset_form.to_python(dict(request.POST))
133 user_model.reset_password_link(form_result)
131 UserModel().reset_password_link(form_result)
134 132 h.flash(_('Your password reset link was sent'),
135 133 category='success')
136 134 return redirect(url('login_home'))
137 135
138 136 except formencode.Invalid, errors:
139 137 return htmlfill.render(
140 138 render('/password_reset.html'),
141 139 defaults=errors.value,
142 140 errors=errors.error_dict or {},
143 141 prefix_error=False,
144 142 encoding="UTF-8")
145 143
146 144 return render('/password_reset.html')
147 145
148 146 def password_reset_confirmation(self):
149
150 147 if request.GET and request.GET.get('key'):
151 148 try:
152 user_model = UserModel()
153 149 user = User.get_by_api_key(request.GET.get('key'))
154 150 data = dict(email=user.email)
155 user_model.reset_password(data)
151 UserModel().reset_password(data)
156 152 h.flash(_('Your password reset was successful, '
157 153 'new password has been sent to your email'),
158 154 category='success')
159 155 except Exception, e:
160 156 log.error(e)
161 157 return redirect(url('reset_password'))
162 158
163 159 return redirect(url('login_home'))
164 160
165 161 def logout(self):
166 162 del session['rhodecode_user']
167 163 session.save()
168 164 log.info('Logging out and setting user as Empty')
169 165 redirect(url('home'))
@@ -1,158 +1,158 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.settings
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Settings controller for rhodecode
7 7
8 8 :created_on: Jun 30, 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
32 32 from pylons import tmpl_context as c, request, url
33 33 from pylons.controllers.util import redirect
34 34 from pylons.i18n.translation import _
35 35
36 36 import rhodecode.lib.helpers as h
37 37
38 38 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAllDecorator
39 39 from rhodecode.lib.base import BaseRepoController, render
40 40 from rhodecode.lib.utils import invalidate_cache, action_logger
41 41
42 42 from rhodecode.model.forms import RepoSettingsForm
43 43 from rhodecode.model.repo import RepoModel
44 44 from rhodecode.model.db import RepoGroup
45 45 from rhodecode.model.meta import Session
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49
50 50 class SettingsController(BaseRepoController):
51 51
52 52 @LoginRequired()
53 53 def __before__(self):
54 54 super(SettingsController, self).__before__()
55 55
56 56 def __load_defaults(self):
57 57 c.repo_groups = RepoGroup.groups_choices()
58 58 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
59 59
60 60 repo_model = RepoModel()
61 61 c.users_array = repo_model.get_users_js()
62 62 c.users_groups_array = repo_model.get_users_groups_js()
63 63
64 64 @HasRepoPermissionAllDecorator('repository.admin')
65 65 def index(self, repo_name):
66 66 repo_model = RepoModel()
67 67 c.repo_info = repo = repo_model.get_by_repo_name(repo_name)
68 68 if not repo:
69 69 h.flash(_('%s repository is not mapped to db perhaps'
70 70 ' it was created or renamed from the file system'
71 71 ' please run the application again'
72 72 ' in order to rescan repositories') % repo_name,
73 73 category='error')
74 74
75 75 return redirect(url('home'))
76 76
77 77 self.__load_defaults()
78 78
79 79 defaults = RepoModel()._get_defaults(repo_name)
80 80
81 81 return htmlfill.render(
82 82 render('settings/repo_settings.html'),
83 83 defaults=defaults,
84 84 encoding="UTF-8",
85 85 force_defaults=False
86 86 )
87 87
88 88 @HasRepoPermissionAllDecorator('repository.admin')
89 89 def update(self, repo_name):
90 90 repo_model = RepoModel()
91 91 changed_name = repo_name
92 92
93 93 self.__load_defaults()
94 94
95 95 _form = RepoSettingsForm(edit=True,
96 96 old_data={'repo_name': repo_name},
97 97 repo_groups=c.repo_groups_choices)()
98 98 try:
99 99 form_result = _form.to_python(dict(request.POST))
100 100
101 101 repo_model.update(repo_name, form_result)
102 102 invalidate_cache('get_repo_cached_%s' % repo_name)
103 103 h.flash(_('Repository %s updated successfully' % repo_name),
104 104 category='success')
105 105 changed_name = form_result['repo_name_full']
106 106 action_logger(self.rhodecode_user, 'user_updated_repo',
107 107 changed_name, '', self.sa)
108 Session().commit()
108 Session.commit()
109 109 except formencode.Invalid, errors:
110 110 c.repo_info = repo_model.get_by_repo_name(repo_name)
111 111 c.users_array = repo_model.get_users_js()
112 112 errors.value.update({'user': c.repo_info.user.username})
113 113 return htmlfill.render(
114 114 render('settings/repo_settings.html'),
115 115 defaults=errors.value,
116 116 errors=errors.error_dict or {},
117 117 prefix_error=False,
118 118 encoding="UTF-8")
119 119 except Exception:
120 120 log.error(traceback.format_exc())
121 121 h.flash(_('error occurred during update of repository %s') \
122 122 % repo_name, category='error')
123 123
124 124 return redirect(url('repo_settings_home', repo_name=changed_name))
125 125
126 126 @HasRepoPermissionAllDecorator('repository.admin')
127 127 def delete(self, repo_name):
128 128 """DELETE /repos/repo_name: Delete an existing item"""
129 129 # Forms posted to this method should contain a hidden field:
130 130 # <input type="hidden" name="_method" value="DELETE" />
131 131 # Or using helpers:
132 132 # h.form(url('repo_settings_delete', repo_name=ID),
133 133 # method='delete')
134 134 # url('repo_settings_delete', repo_name=ID)
135 135
136 136 repo_model = RepoModel()
137 137 repo = repo_model.get_by_repo_name(repo_name)
138 138 if not repo:
139 139 h.flash(_('%s repository is not mapped to db perhaps'
140 140 ' it was moved or renamed from the filesystem'
141 141 ' please run the application again'
142 142 ' in order to rescan repositories') % repo_name,
143 143 category='error')
144 144
145 145 return redirect(url('home'))
146 146 try:
147 147 action_logger(self.rhodecode_user, 'user_deleted_repo',
148 148 repo_name, '', self.sa)
149 149 repo_model.delete(repo)
150 150 invalidate_cache('get_repo_cached_%s' % repo_name)
151 151 h.flash(_('deleted repository %s') % repo_name, category='success')
152 Session().commit()
152 Session.commit()
153 153 except Exception:
154 154 log.error(traceback.format_exc())
155 155 h.flash(_('An error occurred during deletion of %s') % repo_name,
156 156 category='error')
157 157
158 158 return redirect(url('home'))
@@ -1,694 +1,697 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.auth
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 authentication and permission libraries
7 7
8 8 :created_on: Apr 4, 2010
9 9 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 10 :license: GPLv3, see COPYING for more details.
11 11 """
12 12 # This program is free software: you can redistribute it and/or modify
13 13 # it under the terms of the GNU General Public License as published by
14 14 # the Free Software Foundation, either version 3 of the License, or
15 15 # (at your option) any later version.
16 16 #
17 17 # This program is distributed in the hope that it will be useful,
18 18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 20 # GNU General Public License for more details.
21 21 #
22 22 # You should have received a copy of the GNU General Public License
23 23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 24
25 25 import random
26 26 import logging
27 27 import traceback
28 28 import hashlib
29 29
30 30 from tempfile import _RandomNameSequence
31 31 from decorator import decorator
32 32
33 33 from pylons import config, session, url, request
34 34 from pylons.controllers.util import abort, redirect
35 35 from pylons.i18n.translation import _
36 36
37 37 from rhodecode import __platform__, PLATFORM_WIN, PLATFORM_OTHERS
38 from rhodecode.model.meta import Session
38 39
39 40 if __platform__ in PLATFORM_WIN:
40 41 from hashlib import sha256
41 42 if __platform__ in PLATFORM_OTHERS:
42 43 import bcrypt
43 44
44 45 from rhodecode.lib import str2bool, safe_unicode
45 46 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
46 47 from rhodecode.lib.utils import get_repo_slug
47 48 from rhodecode.lib.auth_ldap import AuthLdap
48 49
49 50 from rhodecode.model import meta
50 51 from rhodecode.model.user import UserModel
51 52 from rhodecode.model.db import Permission, RhodeCodeSetting, User
52 53
53 54 log = logging.getLogger(__name__)
54 55
55 56
56 57 class PasswordGenerator(object):
57 58 """
58 59 This is a simple class for generating password from different sets of
59 60 characters
60 61 usage::
61 62
62 63 passwd_gen = PasswordGenerator()
63 64 #print 8-letter password containing only big and small letters
64 65 of alphabet
65 66 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
66 67 """
67 68 ALPHABETS_NUM = r'''1234567890'''
68 69 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
69 70 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
70 71 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
71 72 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
72 73 + ALPHABETS_NUM + ALPHABETS_SPECIAL
73 74 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
74 75 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
75 76 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
76 77 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
77 78
78 79 def __init__(self, passwd=''):
79 80 self.passwd = passwd
80 81
81 82 def gen_password(self, len, type):
82 83 self.passwd = ''.join([random.choice(type) for _ in xrange(len)])
83 84 return self.passwd
84 85
85 86
86 87 class RhodeCodeCrypto(object):
87 88
88 89 @classmethod
89 90 def hash_string(cls, str_):
90 91 """
91 92 Cryptographic function used for password hashing based on pybcrypt
92 93 or pycrypto in windows
93 94
94 95 :param password: password to hash
95 96 """
96 97 if __platform__ in PLATFORM_WIN:
97 98 return sha256(str_).hexdigest()
98 99 elif __platform__ in PLATFORM_OTHERS:
99 100 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
100 101 else:
101 102 raise Exception('Unknown or unsupported platform %s' \
102 103 % __platform__)
103 104
104 105 @classmethod
105 106 def hash_check(cls, password, hashed):
106 107 """
107 108 Checks matching password with it's hashed value, runs different
108 109 implementation based on platform it runs on
109 110
110 111 :param password: password
111 112 :param hashed: password in hashed form
112 113 """
113 114
114 115 if __platform__ in PLATFORM_WIN:
115 116 return sha256(password).hexdigest() == hashed
116 117 elif __platform__ in PLATFORM_OTHERS:
117 118 return bcrypt.hashpw(password, hashed) == hashed
118 119 else:
119 120 raise Exception('Unknown or unsupported platform %s' \
120 121 % __platform__)
121 122
122 123
123 124 def get_crypt_password(password):
124 125 return RhodeCodeCrypto.hash_string(password)
125 126
126 127
127 128 def check_password(password, hashed):
128 129 return RhodeCodeCrypto.hash_check(password, hashed)
129 130
130 131 def generate_api_key(str_, salt=None):
131 132 """
132 133 Generates API KEY from given string
133 134
134 135 :param str_:
135 136 :param salt:
136 137 """
137 138
138 139 if salt is None:
139 140 salt = _RandomNameSequence().next()
140 141
141 142 return hashlib.sha1(str_ + salt).hexdigest()
142 143
143 144
144 145 def authfunc(environ, username, password):
145 146 """
146 147 Dummy authentication wrapper function used in Mercurial and Git for
147 148 access control.
148 149
149 150 :param environ: needed only for using in Basic auth
150 151 """
151 152 return authenticate(username, password)
152 153
153 154
154 155 def authenticate(username, password):
155 156 """
156 157 Authentication function used for access control,
157 158 firstly checks for db authentication then if ldap is enabled for ldap
158 159 authentication, also creates ldap user if not in database
159 160
160 161 :param username: username
161 162 :param password: password
162 163 """
163 164
164 165 user_model = UserModel()
165 166 user = User.get_by_username(username)
166 167
167 168 log.debug('Authenticating user using RhodeCode account')
168 169 if user is not None and not user.ldap_dn:
169 170 if user.active:
170 171 if user.username == 'default' and user.active:
171 172 log.info('user %s authenticated correctly as anonymous user',
172 173 username)
173 174 return True
174 175
175 176 elif user.username == username and check_password(password,
176 177 user.password):
177 178 log.info('user %s authenticated correctly', username)
178 179 return True
179 180 else:
180 181 log.warning('user %s is disabled', username)
181 182
182 183 else:
183 184 log.debug('Regular authentication failed')
184 185 user_obj = User.get_by_username(username, case_insensitive=True)
185 186
186 187 if user_obj is not None and not user_obj.ldap_dn:
187 188 log.debug('this user already exists as non ldap')
188 189 return False
189 190
190 191 ldap_settings = RhodeCodeSetting.get_ldap_settings()
191 192 #======================================================================
192 193 # FALLBACK TO LDAP AUTH IF ENABLE
193 194 #======================================================================
194 195 if str2bool(ldap_settings.get('ldap_active')):
195 196 log.debug("Authenticating user using ldap")
196 197 kwargs = {
197 198 'server': ldap_settings.get('ldap_host', ''),
198 199 'base_dn': ldap_settings.get('ldap_base_dn', ''),
199 200 'port': ldap_settings.get('ldap_port'),
200 201 'bind_dn': ldap_settings.get('ldap_dn_user'),
201 202 'bind_pass': ldap_settings.get('ldap_dn_pass'),
202 203 'tls_kind': ldap_settings.get('ldap_tls_kind'),
203 204 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
204 205 'ldap_filter': ldap_settings.get('ldap_filter'),
205 206 'search_scope': ldap_settings.get('ldap_search_scope'),
206 207 'attr_login': ldap_settings.get('ldap_attr_login'),
207 208 'ldap_version': 3,
208 209 }
209 210 log.debug('Checking for ldap authentication')
210 211 try:
211 212 aldap = AuthLdap(**kwargs)
212 213 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
213 214 password)
214 215 log.debug('Got ldap DN response %s', user_dn)
215 216
216 217 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
217 218 .get(k), [''])[0]
218 219
219 220 user_attrs = {
220 221 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
221 222 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
222 223 'email': get_ldap_attr('ldap_attr_email'),
223 224 }
224 225
225 226 if user_model.create_ldap(username, password, user_dn,
226 227 user_attrs):
227 228 log.info('created new ldap user %s', username)
228
229
230 Session.commit()
229 231 return True
230 232 except (LdapUsernameError, LdapPasswordError,):
231 233 pass
232 234 except (Exception,):
233 235 log.error(traceback.format_exc())
234 236 pass
235 237 return False
236 238
237 239 def login_container_auth(username):
238 240 user = User.get_by_username(username)
239 241 if user is None:
240 user_model = UserModel()
241 242 user_attrs = {
242 243 'name': username,
243 244 'lastname': None,
244 245 'email': None,
245 246 }
246 user = user_model.create_for_container_auth(username, user_attrs)
247 user = UserModel().create_for_container_auth(username, user_attrs)
247 248 if not user:
248 249 return None
249 250 log.info('User %s was created by container authentication', username)
250 251
251 252 if not user.active:
252 253 return None
253 254
254 255 user.update_lastlogin()
256 Session.commit()
257
255 258 log.debug('User %s is now logged in by container authentication',
256 259 user.username)
257 260 return user
258 261
259 262 def get_container_username(environ, config):
260 263 username = None
261 264
262 265 if str2bool(config.get('container_auth_enabled', False)):
263 266 from paste.httpheaders import REMOTE_USER
264 267 username = REMOTE_USER(environ)
265 268
266 269 if not username and str2bool(config.get('proxypass_auth_enabled', False)):
267 270 username = environ.get('HTTP_X_FORWARDED_USER')
268 271
269 272 if username:
270 273 # Removing realm and domain from username
271 274 username = username.partition('@')[0]
272 275 username = username.rpartition('\\')[2]
273 276 log.debug('Received username %s from container', username)
274 277
275 278 return username
276 279
277 280 class AuthUser(object):
278 281 """
279 282 A simple object that handles all attributes of user in RhodeCode
280 283
281 284 It does lookup based on API key,given user, or user present in session
282 285 Then it fills all required information for such user. It also checks if
283 286 anonymous access is enabled and if so, it returns default user as logged
284 287 in
285 288 """
286 289
287 290 def __init__(self, user_id=None, api_key=None, username=None):
288 291
289 292 self.user_id = user_id
290 293 self.api_key = None
291 294 self.username = username
292 295
293 296 self.name = ''
294 297 self.lastname = ''
295 298 self.email = ''
296 299 self.is_authenticated = False
297 300 self.admin = False
298 301 self.permissions = {}
299 302 self._api_key = api_key
300 303 self.propagate_data()
301 304
302 305 def propagate_data(self):
303 306 user_model = UserModel()
304 307 self.anonymous_user = User.get_by_username('default', cache=True)
305 308 is_user_loaded = False
306 309
307 310 # try go get user by api key
308 311 if self._api_key and self._api_key != self.anonymous_user.api_key:
309 312 log.debug('Auth User lookup by API KEY %s', self._api_key)
310 313 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
311 314 # lookup by userid
312 315 elif (self.user_id is not None and
313 316 self.user_id != self.anonymous_user.user_id):
314 317 log.debug('Auth User lookup by USER ID %s', self.user_id)
315 318 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
316 319 # lookup by username
317 320 elif self.username:
318 321 log.debug('Auth User lookup by USER NAME %s', self.username)
319 322 dbuser = login_container_auth(self.username)
320 323 if dbuser is not None:
321 324 for k, v in dbuser.get_dict().items():
322 325 setattr(self, k, v)
323 326 self.set_authenticated()
324 327 is_user_loaded = True
325 328
326 329 if not is_user_loaded:
327 330 # if we cannot authenticate user try anonymous
328 331 if self.anonymous_user.active is True:
329 332 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
330 333 # then we set this user is logged in
331 334 self.is_authenticated = True
332 335 else:
333 336 self.user_id = None
334 337 self.username = None
335 338 self.is_authenticated = False
336 339
337 340 if not self.username:
338 341 self.username = 'None'
339 342
340 343 log.debug('Auth User is now %s', self)
341 344 user_model.fill_perms(self)
342 345
343 346 @property
344 347 def is_admin(self):
345 348 return self.admin
346 349
347 350 @property
348 351 def full_contact(self):
349 352 return '%s %s <%s>' % (self.name, self.lastname, self.email)
350 353
351 354 def __repr__(self):
352 355 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
353 356 self.is_authenticated)
354 357
355 358 def set_authenticated(self, authenticated=True):
356 359 if self.user_id != self.anonymous_user.user_id:
357 360 self.is_authenticated = authenticated
358 361
359 362 def get_cookie_store(self):
360 363 return {'username':self.username,
361 364 'user_id': self.user_id,
362 365 'is_authenticated':self.is_authenticated}
363 366
364 367 @classmethod
365 368 def from_cookie_store(cls, cookie_store):
366 369 user_id = cookie_store.get('user_id')
367 370 username = cookie_store.get('username')
368 371 api_key = cookie_store.get('api_key')
369 372 return AuthUser(user_id, api_key, username)
370 373
371 374 def set_available_permissions(config):
372 375 """
373 376 This function will propagate pylons globals with all available defined
374 377 permission given in db. We don't want to check each time from db for new
375 378 permissions since adding a new permission also requires application restart
376 379 ie. to decorate new views with the newly created permission
377 380
378 381 :param config: current pylons config instance
379 382
380 383 """
381 384 log.info('getting information about all available permissions')
382 385 try:
383 sa = meta.Session()
386 sa = meta.Session
384 387 all_perms = sa.query(Permission).all()
385 388 except:
386 389 pass
387 390 finally:
388 391 meta.Session.remove()
389 392
390 393 config['available_permissions'] = [x.permission_name for x in all_perms]
391 394
392 395
393 396 #==============================================================================
394 397 # CHECK DECORATORS
395 398 #==============================================================================
396 399 class LoginRequired(object):
397 400 """
398 401 Must be logged in to execute this function else
399 402 redirect to login page
400 403
401 404 :param api_access: if enabled this checks only for valid auth token
402 405 and grants access based on valid token
403 406 """
404 407
405 408 def __init__(self, api_access=False):
406 409 self.api_access = api_access
407 410
408 411 def __call__(self, func):
409 412 return decorator(self.__wrapper, func)
410 413
411 414 def __wrapper(self, func, *fargs, **fkwargs):
412 415 cls = fargs[0]
413 416 user = cls.rhodecode_user
414 417
415 418 api_access_ok = False
416 419 if self.api_access:
417 420 log.debug('Checking API KEY access for %s', cls)
418 421 if user.api_key == request.GET.get('api_key'):
419 422 api_access_ok = True
420 423 else:
421 424 log.debug("API KEY token not valid")
422 425
423 426 log.debug('Checking if %s is authenticated @ %s', user.username, cls)
424 427 if user.is_authenticated or api_access_ok:
425 428 log.debug('user %s is authenticated', user.username)
426 429 return func(*fargs, **fkwargs)
427 430 else:
428 431 log.warn('user %s NOT authenticated', user.username)
429 432 p = url.current()
430 433
431 434 log.debug('redirecting to login page with %s', p)
432 435 return redirect(url('login_home', came_from=p))
433 436
434 437
435 438 class NotAnonymous(object):
436 439 """
437 440 Must be logged in to execute this function else
438 441 redirect to login page"""
439 442
440 443 def __call__(self, func):
441 444 return decorator(self.__wrapper, func)
442 445
443 446 def __wrapper(self, func, *fargs, **fkwargs):
444 447 cls = fargs[0]
445 448 self.user = cls.rhodecode_user
446 449
447 450 log.debug('Checking if user is not anonymous @%s', cls)
448 451
449 452 anonymous = self.user.username == 'default'
450 453
451 454 if anonymous:
452 455 p = url.current()
453 456
454 457 import rhodecode.lib.helpers as h
455 458 h.flash(_('You need to be a registered user to '
456 459 'perform this action'),
457 460 category='warning')
458 461 return redirect(url('login_home', came_from=p))
459 462 else:
460 463 return func(*fargs, **fkwargs)
461 464
462 465
463 466 class PermsDecorator(object):
464 467 """Base class for controller decorators"""
465 468
466 469 def __init__(self, *required_perms):
467 470 available_perms = config['available_permissions']
468 471 for perm in required_perms:
469 472 if perm not in available_perms:
470 473 raise Exception("'%s' permission is not defined" % perm)
471 474 self.required_perms = set(required_perms)
472 475 self.user_perms = None
473 476
474 477 def __call__(self, func):
475 478 return decorator(self.__wrapper, func)
476 479
477 480 def __wrapper(self, func, *fargs, **fkwargs):
478 481 cls = fargs[0]
479 482 self.user = cls.rhodecode_user
480 483 self.user_perms = self.user.permissions
481 484 log.debug('checking %s permissions %s for %s %s',
482 485 self.__class__.__name__, self.required_perms, cls,
483 486 self.user)
484 487
485 488 if self.check_permissions():
486 489 log.debug('Permission granted for %s %s', cls, self.user)
487 490 return func(*fargs, **fkwargs)
488 491
489 492 else:
490 493 log.warning('Permission denied for %s %s', cls, self.user)
491 494 anonymous = self.user.username == 'default'
492 495
493 496 if anonymous:
494 497 p = url.current()
495 498
496 499 import rhodecode.lib.helpers as h
497 500 h.flash(_('You need to be a signed in to '
498 501 'view this page'),
499 502 category='warning')
500 503 return redirect(url('login_home', came_from=p))
501 504
502 505 else:
503 506 # redirect with forbidden ret code
504 507 return abort(403)
505 508
506 509 def check_permissions(self):
507 510 """Dummy function for overriding"""
508 511 raise Exception('You have to write this function in child class')
509 512
510 513
511 514 class HasPermissionAllDecorator(PermsDecorator):
512 515 """
513 516 Checks for access permission for all given predicates. All of them
514 517 have to be meet in order to fulfill the request
515 518 """
516 519
517 520 def check_permissions(self):
518 521 if self.required_perms.issubset(self.user_perms.get('global')):
519 522 return True
520 523 return False
521 524
522 525
523 526 class HasPermissionAnyDecorator(PermsDecorator):
524 527 """
525 528 Checks for access permission for any of given predicates. In order to
526 529 fulfill the request any of predicates must be meet
527 530 """
528 531
529 532 def check_permissions(self):
530 533 if self.required_perms.intersection(self.user_perms.get('global')):
531 534 return True
532 535 return False
533 536
534 537
535 538 class HasRepoPermissionAllDecorator(PermsDecorator):
536 539 """
537 540 Checks for access permission for all given predicates for specific
538 541 repository. All of them have to be meet in order to fulfill the request
539 542 """
540 543
541 544 def check_permissions(self):
542 545 repo_name = get_repo_slug(request)
543 546 try:
544 547 user_perms = set([self.user_perms['repositories'][repo_name]])
545 548 except KeyError:
546 549 return False
547 550 if self.required_perms.issubset(user_perms):
548 551 return True
549 552 return False
550 553
551 554
552 555 class HasRepoPermissionAnyDecorator(PermsDecorator):
553 556 """
554 557 Checks for access permission for any of given predicates for specific
555 558 repository. In order to fulfill the request any of predicates must be meet
556 559 """
557 560
558 561 def check_permissions(self):
559 562 repo_name = get_repo_slug(request)
560 563
561 564 try:
562 565 user_perms = set([self.user_perms['repositories'][repo_name]])
563 566 except KeyError:
564 567 return False
565 568 if self.required_perms.intersection(user_perms):
566 569 return True
567 570 return False
568 571
569 572
570 573 #==============================================================================
571 574 # CHECK FUNCTIONS
572 575 #==============================================================================
573 576 class PermsFunction(object):
574 577 """Base function for other check functions"""
575 578
576 579 def __init__(self, *perms):
577 580 available_perms = config['available_permissions']
578 581
579 582 for perm in perms:
580 583 if perm not in available_perms:
581 584 raise Exception("'%s' permission in not defined" % perm)
582 585 self.required_perms = set(perms)
583 586 self.user_perms = None
584 587 self.granted_for = ''
585 588 self.repo_name = None
586 589
587 590 def __call__(self, check_Location=''):
588 591 user = request.user
589 592 if not user:
590 593 return False
591 594 self.user_perms = user.permissions
592 595 self.granted_for = user
593 596 log.debug('checking %s %s %s', self.__class__.__name__,
594 597 self.required_perms, user)
595 598
596 599 if self.check_permissions():
597 600 log.debug('Permission granted %s @ %s', self.granted_for,
598 601 check_Location or 'unspecified location')
599 602 return True
600 603
601 604 else:
602 605 log.warning('Permission denied for %s @ %s', self.granted_for,
603 606 check_Location or 'unspecified location')
604 607 return False
605 608
606 609 def check_permissions(self):
607 610 """Dummy function for overriding"""
608 611 raise Exception('You have to write this function in child class')
609 612
610 613
611 614 class HasPermissionAll(PermsFunction):
612 615 def check_permissions(self):
613 616 if self.required_perms.issubset(self.user_perms.get('global')):
614 617 return True
615 618 return False
616 619
617 620
618 621 class HasPermissionAny(PermsFunction):
619 622 def check_permissions(self):
620 623 if self.required_perms.intersection(self.user_perms.get('global')):
621 624 return True
622 625 return False
623 626
624 627
625 628 class HasRepoPermissionAll(PermsFunction):
626 629
627 630 def __call__(self, repo_name=None, check_Location=''):
628 631 self.repo_name = repo_name
629 632 return super(HasRepoPermissionAll, self).__call__(check_Location)
630 633
631 634 def check_permissions(self):
632 635 if not self.repo_name:
633 636 self.repo_name = get_repo_slug(request)
634 637
635 638 try:
636 639 self.user_perms = set([self.user_perms['reposit'
637 640 'ories'][self.repo_name]])
638 641 except KeyError:
639 642 return False
640 643 self.granted_for = self.repo_name
641 644 if self.required_perms.issubset(self.user_perms):
642 645 return True
643 646 return False
644 647
645 648
646 649 class HasRepoPermissionAny(PermsFunction):
647 650
648 651 def __call__(self, repo_name=None, check_Location=''):
649 652 self.repo_name = repo_name
650 653 return super(HasRepoPermissionAny, self).__call__(check_Location)
651 654
652 655 def check_permissions(self):
653 656 if not self.repo_name:
654 657 self.repo_name = get_repo_slug(request)
655 658
656 659 try:
657 660 self.user_perms = set([self.user_perms['reposi'
658 661 'tories'][self.repo_name]])
659 662 except KeyError:
660 663 return False
661 664 self.granted_for = self.repo_name
662 665 if self.required_perms.intersection(self.user_perms):
663 666 return True
664 667 return False
665 668
666 669
667 670 #==============================================================================
668 671 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
669 672 #==============================================================================
670 673 class HasPermissionAnyMiddleware(object):
671 674 def __init__(self, *perms):
672 675 self.required_perms = set(perms)
673 676
674 677 def __call__(self, user, repo_name):
675 678 usr = AuthUser(user.user_id)
676 679 try:
677 680 self.user_perms = set([usr.permissions['repositories'][repo_name]])
678 681 except:
679 682 self.user_perms = set()
680 683 self.granted_for = ''
681 684 self.username = user.username
682 685 self.repo_name = repo_name
683 686 return self.check_permissions()
684 687
685 688 def check_permissions(self):
686 689 log.debug('checking mercurial protocol '
687 690 'permissions %s for user:%s repository:%s', self.user_perms,
688 691 self.username, self.repo_name)
689 692 if self.required_perms.intersection(self.user_perms):
690 693 log.debug('permission granted')
691 694 return True
692 695 log.debug('permission denied')
693 696 return False
694 697
@@ -1,96 +1,96 b''
1 1 """The base Controller API
2 2
3 3 Provides the BaseController class for subclassing.
4 4 """
5 5 import logging
6 6 import time
7 7 from pylons import config, tmpl_context as c, request, session, url
8 8 from pylons.controllers import WSGIController
9 9 from pylons.controllers.util import redirect
10 10 from pylons.templating import render_mako as render
11 11
12 12 from rhodecode import __version__, BACKENDS
13 13
14 14 from rhodecode.lib import str2bool
15 15 from rhodecode.lib.auth import AuthUser, get_container_username
16 16 from rhodecode.lib.utils import get_repo_slug
17 17 from rhodecode.model import meta
18 18
19 19 from rhodecode.model.db import Repository
20 20 from rhodecode.model.notification import NotificationModel
21 21 from rhodecode.model.scm import ScmModel
22 22
23 23 log = logging.getLogger(__name__)
24 24
25 25 class BaseController(WSGIController):
26 26
27 27 def __before__(self):
28 28 c.rhodecode_version = __version__
29 29 c.rhodecode_name = config.get('rhodecode_title')
30 30 c.use_gravatar = str2bool(config.get('use_gravatar'))
31 31 c.ga_code = config.get('rhodecode_ga_code')
32 32 c.repo_name = get_repo_slug(request)
33 33 c.backends = BACKENDS.keys()
34 34 c.unread_notifications = NotificationModel()\
35 35 .get_unread_cnt_for_user(c.rhodecode_user.user_id)
36 36 self.cut_off_limit = int(config.get('cut_off_limit'))
37 37
38 self.sa = meta.Session()
38 self.sa = meta.Session
39 39 self.scm_model = ScmModel(self.sa)
40 40
41 41 def __call__(self, environ, start_response):
42 42 """Invoke the Controller"""
43 43 # WSGIController.__call__ dispatches to the Controller method
44 44 # the request is routed to. This routing information is
45 45 # available in environ['pylons.routes_dict']
46 46 start = time.time()
47 47 try:
48 48 # make sure that we update permissions each time we call controller
49 49 api_key = request.GET.get('api_key')
50 50 cookie_store = session.get('rhodecode_user') or {}
51 51 user_id = cookie_store.get('user_id', None)
52 52 username = get_container_username(environ, config)
53 53
54 54 auth_user = AuthUser(user_id, api_key, username)
55 55 request.user = auth_user
56 56 self.rhodecode_user = c.rhodecode_user = auth_user
57 57 if not self.rhodecode_user.is_authenticated and \
58 58 self.rhodecode_user.user_id is not None:
59 59 self.rhodecode_user\
60 60 .set_authenticated(cookie_store.get('is_authenticated'))
61 61
62 62 session['rhodecode_user'] = self.rhodecode_user.get_cookie_store()
63 63 session.save()
64 64 return WSGIController.__call__(self, environ, start_response)
65 65 finally:
66 66 log.debug('Request time: %.3fs' % (time.time() - start))
67 67 meta.Session.remove()
68 68
69 69
70 70 class BaseRepoController(BaseController):
71 71 """
72 72 Base class for controllers responsible for loading all needed data for
73 73 repository loaded items are
74 74
75 75 c.rhodecode_repo: instance of scm repository
76 76 c.rhodecode_db_repo: instance of db
77 77 c.repository_followers: number of followers
78 78 c.repository_forks: number of forks
79 79 """
80 80
81 81 def __before__(self):
82 82 super(BaseRepoController, self).__before__()
83 83 if c.repo_name:
84 84
85 85 c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
86 86 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
87 87
88 88 if c.rhodecode_repo is None:
89 89 log.error('%s this repository is present in database but it '
90 90 'cannot be created as an scm instance', c.repo_name)
91 91
92 92 redirect(url('home'))
93 93
94 94 c.repository_followers = self.scm_model.get_followers(c.repo_name)
95 95 c.repository_forks = self.scm_model.get_forks(c.repo_name)
96 96
@@ -1,410 +1,414 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.celerylib.tasks
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 RhodeCode task modules, containing all task that suppose to be run
7 7 by celery daemon
8 8
9 9 :created_on: Oct 6, 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 from celery.decorators import task
27 27
28 28 import os
29 29 import traceback
30 30 import logging
31 31 from os.path import join as jn
32 32
33 33 from time import mktime
34 34 from operator import itemgetter
35 35 from string import lower
36 36
37 37 from pylons import config, url
38 38 from pylons.i18n.translation import _
39 39
40 40 from vcs import get_backend
41 41
42 42 from rhodecode import CELERY_ON
43 43 from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, safe_str
44 44 from rhodecode.lib.celerylib import run_task, locked_task, str2bool, \
45 45 __get_lockkey, LockHeld, DaemonLock
46 46 from rhodecode.lib.helpers import person
47 47 from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
48 48 from rhodecode.lib.utils import add_cache, action_logger
49 49 from rhodecode.lib.compat import json, OrderedDict
50 50
51 51 from rhodecode.model import init_model
52 52 from rhodecode.model import meta
53 53 from rhodecode.model.db import Statistics, Repository, User
54 54
55 55 from sqlalchemy import engine_from_config
56 56
57 57 add_cache(config)
58 58
59 59 __all__ = ['whoosh_index', 'get_commits_stats',
60 60 'reset_user_password', 'send_email']
61 61
62 62
63 63 def get_session():
64 64 if CELERY_ON:
65 65 engine = engine_from_config(config, 'sqlalchemy.db1.')
66 66 init_model(engine)
67 sa = meta.Session()
67 sa = meta.Session
68 68 return sa
69 69
70 70 def get_logger(cls):
71 71 if CELERY_ON:
72 72 try:
73 73 log = cls.get_logger()
74 74 except:
75 75 log = logging.getLogger(__name__)
76 76 else:
77 77 log = logging.getLogger(__name__)
78 78
79 79 return log
80 80
81 81 @task(ignore_result=True)
82 82 @locked_task
83 83 def whoosh_index(repo_location, full_index):
84 84 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
85 85
86 86 #log = whoosh_index.get_logger()
87 87
88 88 index_location = config['index_dir']
89 89 WhooshIndexingDaemon(index_location=index_location,
90 90 repo_location=repo_location, sa=get_session())\
91 91 .run(full_index=full_index)
92 92
93 93
94 94 @task(ignore_result=True)
95 95 def get_commits_stats(repo_name, ts_min_y, ts_max_y):
96 96 log = get_logger(get_commits_stats)
97 97
98 98 lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
99 99 ts_max_y)
100 100 lockkey_path = config['here']
101 101
102 102 log.info('running task with lockkey %s', lockkey)
103 103 try:
104 104 sa = get_session()
105 105 lock = l = DaemonLock(file_=jn(lockkey_path, lockkey))
106 106
107 107 # for js data compatibilty cleans the key for person from '
108 108 akc = lambda k: person(k).replace('"', "")
109 109
110 110 co_day_auth_aggr = {}
111 111 commits_by_day_aggregate = {}
112 repo = Repository.get_by_repo_name(repo_name).scm_instance
112 repo = Repository.get_by_repo_name(repo_name)
113 if repo is None:
114 return True
115
116 repo = repo.scm_instance
113 117 repo_size = len(repo.revisions)
114 118 #return if repo have no revisions
115 119 if repo_size < 1:
116 120 lock.release()
117 121 return True
118 122
119 123 skip_date_limit = True
120 124 parse_limit = int(config['app_conf'].get('commit_parse_limit'))
121 125 last_rev = 0
122 126 last_cs = None
123 127 timegetter = itemgetter('time')
124 128
125 129 dbrepo = sa.query(Repository)\
126 130 .filter(Repository.repo_name == repo_name).scalar()
127 131 cur_stats = sa.query(Statistics)\
128 132 .filter(Statistics.repository == dbrepo).scalar()
129 133
130 134 if cur_stats is not None:
131 135 last_rev = cur_stats.stat_on_revision
132 136
133 137 if last_rev == repo.get_changeset().revision and repo_size > 1:
134 138 # pass silently without any work if we're not on first revision or
135 139 # current state of parsing revision(from db marker) is the
136 140 # last revision
137 141 lock.release()
138 142 return True
139 143
140 144 if cur_stats:
141 145 commits_by_day_aggregate = OrderedDict(json.loads(
142 146 cur_stats.commit_activity_combined))
143 147 co_day_auth_aggr = json.loads(cur_stats.commit_activity)
144 148
145 149 log.debug('starting parsing %s', parse_limit)
146 150 lmktime = mktime
147 151
148 152 last_rev = last_rev + 1 if last_rev > 0 else last_rev
149 153
150 154 for cs in repo[last_rev:last_rev + parse_limit]:
151 155 last_cs = cs # remember last parsed changeset
152 156 k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1],
153 157 cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0])
154 158
155 159 if akc(cs.author) in co_day_auth_aggr:
156 160 try:
157 161 l = [timegetter(x) for x in
158 162 co_day_auth_aggr[akc(cs.author)]['data']]
159 163 time_pos = l.index(k)
160 164 except ValueError:
161 165 time_pos = False
162 166
163 167 if time_pos >= 0 and time_pos is not False:
164 168
165 169 datadict = \
166 170 co_day_auth_aggr[akc(cs.author)]['data'][time_pos]
167 171
168 172 datadict["commits"] += 1
169 173 datadict["added"] += len(cs.added)
170 174 datadict["changed"] += len(cs.changed)
171 175 datadict["removed"] += len(cs.removed)
172 176
173 177 else:
174 178 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
175 179
176 180 datadict = {"time": k,
177 181 "commits": 1,
178 182 "added": len(cs.added),
179 183 "changed": len(cs.changed),
180 184 "removed": len(cs.removed),
181 185 }
182 186 co_day_auth_aggr[akc(cs.author)]['data']\
183 187 .append(datadict)
184 188
185 189 else:
186 190 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
187 191 co_day_auth_aggr[akc(cs.author)] = {
188 192 "label": akc(cs.author),
189 193 "data": [{"time":k,
190 194 "commits":1,
191 195 "added":len(cs.added),
192 196 "changed":len(cs.changed),
193 197 "removed":len(cs.removed),
194 198 }],
195 199 "schema": ["commits"],
196 200 }
197 201
198 202 #gather all data by day
199 203 if k in commits_by_day_aggregate:
200 204 commits_by_day_aggregate[k] += 1
201 205 else:
202 206 commits_by_day_aggregate[k] = 1
203 207
204 208 overview_data = sorted(commits_by_day_aggregate.items(),
205 209 key=itemgetter(0))
206 210
207 211 if not co_day_auth_aggr:
208 212 co_day_auth_aggr[akc(repo.contact)] = {
209 213 "label": akc(repo.contact),
210 214 "data": [0, 1],
211 215 "schema": ["commits"],
212 216 }
213 217
214 218 stats = cur_stats if cur_stats else Statistics()
215 219 stats.commit_activity = json.dumps(co_day_auth_aggr)
216 220 stats.commit_activity_combined = json.dumps(overview_data)
217 221
218 222 log.debug('last revison %s', last_rev)
219 223 leftovers = len(repo.revisions[last_rev:])
220 224 log.debug('revisions to parse %s', leftovers)
221 225
222 226 if last_rev == 0 or leftovers < parse_limit:
223 227 log.debug('getting code trending stats')
224 228 stats.languages = json.dumps(__get_codes_stats(repo_name))
225 229
226 230 try:
227 231 stats.repository = dbrepo
228 232 stats.stat_on_revision = last_cs.revision if last_cs else 0
229 233 sa.add(stats)
230 234 sa.commit()
231 235 except:
232 236 log.error(traceback.format_exc())
233 237 sa.rollback()
234 238 lock.release()
235 239 return False
236 240
237 241 #final release
238 242 lock.release()
239 243
240 244 #execute another task if celery is enabled
241 245 if len(repo.revisions) > 1 and CELERY_ON:
242 246 run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
243 247 return True
244 248 except LockHeld:
245 249 log.info('LockHeld')
246 250 return 'Task with key %s already running' % lockkey
247 251
248 252 @task(ignore_result=True)
249 253 def send_password_link(user_email):
250 254 from rhodecode.model.notification import EmailNotificationModel
251 255
252 256 log = get_logger(send_password_link)
253 257
254 258 try:
255 259 sa = get_session()
256 260 user = User.get_by_email(user_email)
257 261 if user:
258 262 log.debug('password reset user found %s' % user)
259 263 link = url('reset_password_confirmation', key=user.api_key,
260 264 qualified=True)
261 265 reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
262 266 body = EmailNotificationModel().get_email_tmpl(reg_type,
263 267 **{'user':user.short_contact,
264 268 'reset_url':link})
265 269 log.debug('sending email')
266 270 run_task(send_email, user_email,
267 271 _("password reset link"), body)
268 272 log.info('send new password mail to %s', user_email)
269 273 else:
270 274 log.debug("password reset email %s not found" % user_email)
271 275 except:
272 276 log.error(traceback.format_exc())
273 277 return False
274 278
275 279 return True
276 280
277 281 @task(ignore_result=True)
278 282 def reset_user_password(user_email):
279 283 from rhodecode.lib import auth
280 284
281 285 log = get_logger(reset_user_password)
282 286
283 287 try:
284 288 try:
285 289 sa = get_session()
286 290 user = User.get_by_email(user_email)
287 291 new_passwd = auth.PasswordGenerator().gen_password(8,
288 292 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
289 293 if user:
290 294 user.password = auth.get_crypt_password(new_passwd)
291 295 user.api_key = auth.generate_api_key(user.username)
292 296 sa.add(user)
293 297 sa.commit()
294 298 log.info('change password for %s', user_email)
295 299 if new_passwd is None:
296 300 raise Exception('unable to generate new password')
297 301 except:
298 302 log.error(traceback.format_exc())
299 303 sa.rollback()
300 304
301 305 run_task(send_email, user_email,
302 306 'Your new password',
303 307 'Your new RhodeCode password:%s' % (new_passwd))
304 308 log.info('send new password mail to %s', user_email)
305 309
306 310 except:
307 311 log.error('Failed to update user password')
308 312 log.error(traceback.format_exc())
309 313
310 314 return True
311 315
312 316
313 317 @task(ignore_result=True)
314 318 def send_email(recipients, subject, body, html_body=''):
315 319 """
316 320 Sends an email with defined parameters from the .ini files.
317 321
318 322 :param recipients: list of recipients, it this is empty the defined email
319 323 address from field 'email_to' is used instead
320 324 :param subject: subject of the mail
321 325 :param body: body of the mail
322 326 :param html_body: html version of body
323 327 """
324 328 log = get_logger(send_email)
325 329 sa = get_session()
326 330 email_config = config
327 331 subject = "%s %s" % (email_config.get('email_prefix'), subject)
328 332 if not recipients:
329 333 # if recipients are not defined we send to email_config + all admins
330 334 admins = [u.email for u in User.query()
331 335 .filter(User.admin == True).all()]
332 336 recipients = [email_config.get('email_to')] + admins
333 337
334 338 mail_from = email_config.get('app_email_from', 'RhodeCode')
335 339 user = email_config.get('smtp_username')
336 340 passwd = email_config.get('smtp_password')
337 341 mail_server = email_config.get('smtp_server')
338 342 mail_port = email_config.get('smtp_port')
339 343 tls = str2bool(email_config.get('smtp_use_tls'))
340 344 ssl = str2bool(email_config.get('smtp_use_ssl'))
341 345 debug = str2bool(config.get('debug'))
342 346 smtp_auth = email_config.get('smtp_auth')
343 347
344 348 try:
345 349 m = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth,
346 350 mail_port, ssl, tls, debug=debug)
347 351 m.send(recipients, subject, body, html_body)
348 352 except:
349 353 log.error('Mail sending failed')
350 354 log.error(traceback.format_exc())
351 355 return False
352 356 return True
353 357
354 358
355 359 @task(ignore_result=True)
356 360 def create_repo_fork(form_data, cur_user):
357 361 """
358 362 Creates a fork of repository using interval VCS methods
359 363
360 364 :param form_data:
361 365 :param cur_user:
362 366 """
363 367 from rhodecode.model.repo import RepoModel
364 368
365 369 log = get_logger(create_repo_fork)
366 370
367 371 Session = get_session()
368 372 base_path = Repository.base_path()
369 373
370 374 RepoModel(Session).create(form_data, cur_user, just_db=True, fork=True)
371 375
372 376 alias = form_data['repo_type']
373 377 org_repo_name = form_data['org_path']
374 378 fork_name = form_data['repo_name_full']
375 379 update_after_clone = form_data['update_after_clone']
376 380 source_repo_path = os.path.join(base_path, org_repo_name)
377 381 destination_fork_path = os.path.join(base_path, fork_name)
378 382
379 383 log.info('creating fork of %s as %s', source_repo_path,
380 384 destination_fork_path)
381 385 backend = get_backend(alias)
382 386 backend(safe_str(destination_fork_path), create=True,
383 387 src_url=safe_str(source_repo_path),
384 388 update_after_clone=update_after_clone)
385 389 action_logger(cur_user, 'user_forked_repo:%s' % fork_name,
386 390 org_repo_name, '', Session)
387 391
388 392 action_logger(cur_user, 'user_created_fork:%s' % fork_name,
389 393 fork_name, '', Session)
390 394 # finally commit at latest possible stage
391 395 Session.commit()
392 396
393 397 def __get_codes_stats(repo_name):
394 398 repo = Repository.get_by_repo_name(repo_name).scm_instance
395 399
396 400 tip = repo.get_changeset()
397 401 code_stats = {}
398 402
399 403 def aggregate(cs):
400 404 for f in cs[2]:
401 405 ext = lower(f.extension)
402 406 if ext in LANGUAGES_EXTENSIONS_MAP.keys() and not f.is_binary:
403 407 if ext in code_stats:
404 408 code_stats[ext] += 1
405 409 else:
406 410 code_stats[ext] = 1
407 411
408 412 map(aggregate, tip.walk('/'))
409 413
410 414 return code_stats or {}
@@ -1,468 +1,468 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 self.sa = meta.Session()
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 248 self.create_user('test_admin', 'test12',
249 249 'test_admin@mail.com', True)
250 250 self.create_user('test_regular', 'test12',
251 251 'test_regular@mail.com', False)
252 252 self.create_user('test_regular2', 'test12',
253 253 'test_regular2@mail.com', False)
254 254
255 255 def create_ui_settings(self):
256 256 """Creates ui settings, fills out hooks
257 257 and disables dotencode
258 258
259 259 """
260 260 #HOOKS
261 261 hooks1_key = RhodeCodeUi.HOOK_UPDATE
262 262 hooks1_ = self.sa.query(RhodeCodeUi)\
263 263 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
264 264
265 265 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
266 266 hooks1.ui_section = 'hooks'
267 267 hooks1.ui_key = hooks1_key
268 268 hooks1.ui_value = 'hg update >&2'
269 269 hooks1.ui_active = False
270 270
271 271 hooks2_key = RhodeCodeUi.HOOK_REPO_SIZE
272 272 hooks2_ = self.sa.query(RhodeCodeUi)\
273 273 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
274 274
275 275 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
276 276 hooks2.ui_section = 'hooks'
277 277 hooks2.ui_key = hooks2_key
278 278 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
279 279
280 280 hooks3 = RhodeCodeUi()
281 281 hooks3.ui_section = 'hooks'
282 282 hooks3.ui_key = RhodeCodeUi.HOOK_PUSH
283 283 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
284 284
285 285 hooks4 = RhodeCodeUi()
286 286 hooks4.ui_section = 'hooks'
287 287 hooks4.ui_key = RhodeCodeUi.HOOK_PULL
288 288 hooks4.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
289 289
290 290 # For mercurial 1.7 set backward comapatibility with format
291 291 dotencode_disable = RhodeCodeUi()
292 292 dotencode_disable.ui_section = 'format'
293 293 dotencode_disable.ui_key = 'dotencode'
294 294 dotencode_disable.ui_value = 'false'
295 295
296 296 # enable largefiles
297 297 largefiles = RhodeCodeUi()
298 298 largefiles.ui_section = 'extensions'
299 299 largefiles.ui_key = 'largefiles'
300 300 largefiles.ui_value = '1'
301 301
302 302 self.sa.add(hooks1)
303 303 self.sa.add(hooks2)
304 304 self.sa.add(hooks3)
305 305 self.sa.add(hooks4)
306 306 self.sa.add(largefiles)
307 307
308 308 def create_ldap_options(self, skip_existing=False):
309 309 """Creates ldap settings"""
310 310
311 311 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
312 312 ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
313 313 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
314 314 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
315 315 ('ldap_filter', ''), ('ldap_search_scope', ''),
316 316 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
317 317 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
318 318
319 319 if skip_existing and RhodeCodeSetting.get_by_name(k) != None:
320 320 log.debug('Skipping option %s' % k)
321 321 continue
322 322 setting = RhodeCodeSetting(k, v)
323 323 self.sa.add(setting)
324 324
325 325 def config_prompt(self, test_repo_path='', retries=3):
326 326 if retries == 3:
327 327 log.info('Setting up repositories config')
328 328
329 329 if not self.tests and not test_repo_path:
330 330 path = raw_input('Specify valid full path to your repositories'
331 331 ' you can change this later in application settings:')
332 332 else:
333 333 path = test_repo_path
334 334 path_ok = True
335 335
336 336 #check proper dir
337 337 if not os.path.isdir(path):
338 338 path_ok = False
339 339 log.error('Given path %s is not a valid directory', path)
340 340
341 341 #check write access
342 342 if not os.access(path, os.W_OK) and path_ok:
343 343 path_ok = False
344 344 log.error('No write permission to given path %s', path)
345 345
346 346
347 347 if retries == 0:
348 348 sys.exit('max retries reached')
349 349 if path_ok is False:
350 350 retries -= 1
351 351 return self.config_prompt(test_repo_path, retries)
352 352
353 353 return path
354 354
355 355 def create_settings(self, path):
356 356
357 357 self.create_ui_settings()
358 358
359 359 #HG UI OPTIONS
360 360 web1 = RhodeCodeUi()
361 361 web1.ui_section = 'web'
362 362 web1.ui_key = 'push_ssl'
363 363 web1.ui_value = 'false'
364 364
365 365 web2 = RhodeCodeUi()
366 366 web2.ui_section = 'web'
367 367 web2.ui_key = 'allow_archive'
368 368 web2.ui_value = 'gz zip bz2'
369 369
370 370 web3 = RhodeCodeUi()
371 371 web3.ui_section = 'web'
372 372 web3.ui_key = 'allow_push'
373 373 web3.ui_value = '*'
374 374
375 375 web4 = RhodeCodeUi()
376 376 web4.ui_section = 'web'
377 377 web4.ui_key = 'baseurl'
378 378 web4.ui_value = '/'
379 379
380 380 paths = RhodeCodeUi()
381 381 paths.ui_section = 'paths'
382 382 paths.ui_key = '/'
383 383 paths.ui_value = path
384 384
385 385 hgsettings1 = RhodeCodeSetting('realm', 'RhodeCode authentication')
386 386 hgsettings2 = RhodeCodeSetting('title', 'RhodeCode')
387 387 hgsettings3 = RhodeCodeSetting('ga_code', '')
388 388
389 389 self.sa.add(web1)
390 390 self.sa.add(web2)
391 391 self.sa.add(web3)
392 392 self.sa.add(web4)
393 393 self.sa.add(paths)
394 394 self.sa.add(hgsettings1)
395 395 self.sa.add(hgsettings2)
396 396 self.sa.add(hgsettings3)
397 397
398 398 self.create_ldap_options()
399 399
400 400 log.info('created ui config')
401 401
402 402 def create_user(self, username, password, email='', admin=False):
403 403 log.info('creating user %s', username)
404 404 UserModel().create_or_update(username, password, email,
405 405 name='RhodeCode', lastname='Admin',
406 406 active=True, admin=admin)
407 407
408 408 def create_default_user(self):
409 409 log.info('creating default user')
410 410 # create default user for handling default permissions.
411 411 UserModel().create_or_update(username='default',
412 412 password=str(uuid.uuid1())[:8],
413 413 email='anonymous@rhodecode.org',
414 414 name='Anonymous', lastname='User')
415 415
416 416 def create_permissions(self):
417 417 #module.(access|create|change|delete)_[name]
418 418 #module.(read|write|owner)
419 419 perms = [('repository.none', 'Repository no access'),
420 420 ('repository.read', 'Repository read access'),
421 421 ('repository.write', 'Repository write access'),
422 422 ('repository.admin', 'Repository admin access'),
423 423 ('hg.admin', 'Hg Administrator'),
424 424 ('hg.create.repository', 'Repository create'),
425 425 ('hg.create.none', 'Repository creation disabled'),
426 426 ('hg.register.none', 'Register disabled'),
427 427 ('hg.register.manual_activate', 'Register new user with '
428 428 'RhodeCode without manual'
429 429 'activation'),
430 430
431 431 ('hg.register.auto_activate', 'Register new user with '
432 432 'RhodeCode without auto '
433 433 'activation'),
434 434 ]
435 435
436 436 for p in perms:
437 437 new_perm = Permission()
438 438 new_perm.permission_name = p[0]
439 439 new_perm.permission_longname = p[1]
440 440 self.sa.add(new_perm)
441 441
442 442 def populate_default_permissions(self):
443 443 log.info('creating default user permissions')
444 444
445 445 default_user = self.sa.query(User)\
446 446 .filter(User.username == 'default').scalar()
447 447
448 448 reg_perm = UserToPerm()
449 449 reg_perm.user = default_user
450 450 reg_perm.permission = self.sa.query(Permission)\
451 451 .filter(Permission.permission_name == 'hg.register.manual_activate')\
452 452 .scalar()
453 453
454 454 create_repo_perm = UserToPerm()
455 455 create_repo_perm.user = default_user
456 456 create_repo_perm.permission = self.sa.query(Permission)\
457 457 .filter(Permission.permission_name == 'hg.create.repository')\
458 458 .scalar()
459 459
460 460 default_repo_perm = UserToPerm()
461 461 default_repo_perm.user = default_user
462 462 default_repo_perm.permission = self.sa.query(Permission)\
463 463 .filter(Permission.permission_name == 'repository.read')\
464 464 .scalar()
465 465
466 466 self.sa.add(reg_perm)
467 467 self.sa.add(create_repo_perm)
468 468 self.sa.add(default_repo_perm)
@@ -1,1098 +1,1098 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 from datetime import date
31 31
32 32 from sqlalchemy import *
33 33 from sqlalchemy.ext.hybrid import hybrid_property
34 34 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
35 35 from beaker.cache import cache_region, region_invalidate
36 36
37 37 from vcs import get_backend
38 38 from vcs.utils.helpers import get_scm
39 39 from vcs.exceptions import VCSError
40 40 from vcs.utils.lazy import LazyProperty
41 41
42 42 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, \
43 43 generate_api_key, safe_unicode
44 44 from rhodecode.lib.exceptions import UsersGroupsAssignedException
45 45 from rhodecode.lib.compat import json
46 46
47 47 from rhodecode.model.meta import Base, Session
48 48 from rhodecode.lib.caching_query import FromCache
49 49
50 50
51 51 log = logging.getLogger(__name__)
52 52
53 53 #==============================================================================
54 54 # BASE CLASSES
55 55 #==============================================================================
56 56
57 57 class ModelSerializer(json.JSONEncoder):
58 58 """
59 59 Simple Serializer for JSON,
60 60
61 61 usage::
62 62
63 63 to make object customized for serialization implement a __json__
64 64 method that will return a dict for serialization into json
65 65
66 66 example::
67 67
68 68 class Task(object):
69 69
70 70 def __init__(self, name, value):
71 71 self.name = name
72 72 self.value = value
73 73
74 74 def __json__(self):
75 75 return dict(name=self.name,
76 76 value=self.value)
77 77
78 78 """
79 79
80 80 def default(self, obj):
81 81
82 82 if hasattr(obj, '__json__'):
83 83 return obj.__json__()
84 84 else:
85 85 return json.JSONEncoder.default(self, obj)
86 86
87 87 class BaseModel(object):
88 88 """Base Model for all classess
89 89
90 90 """
91 91
92 92 @classmethod
93 93 def _get_keys(cls):
94 94 """return column names for this model """
95 95 return class_mapper(cls).c.keys()
96 96
97 97 def get_dict(self):
98 98 """return dict with keys and values corresponding
99 99 to this model data """
100 100
101 101 d = {}
102 102 for k in self._get_keys():
103 103 d[k] = getattr(self, k)
104 104 return d
105 105
106 106 def get_appstruct(self):
107 107 """return list with keys and values tupples corresponding
108 108 to this model data """
109 109
110 110 l = []
111 111 for k in self._get_keys():
112 112 l.append((k, getattr(self, k),))
113 113 return l
114 114
115 115 def populate_obj(self, populate_dict):
116 116 """populate model with data from given populate_dict"""
117 117
118 118 for k in self._get_keys():
119 119 if k in populate_dict:
120 120 setattr(self, k, populate_dict[k])
121 121
122 122 @classmethod
123 123 def query(cls):
124 124 return Session.query(cls)
125 125
126 126 @classmethod
127 127 def get(cls, id_):
128 128 if id_:
129 129 return cls.query().get(id_)
130 130
131 131 @classmethod
132 132 def getAll(cls):
133 133 return cls.query().all()
134 134
135 135 @classmethod
136 136 def delete(cls, id_):
137 137 obj = cls.query().get(id_)
138 138 Session.delete(obj)
139 139 Session.commit()
140 140
141 141
142 142 class RhodeCodeSetting(Base, BaseModel):
143 143 __tablename__ = 'rhodecode_settings'
144 144 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
145 145 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
146 146 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
147 147 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
148 148
149 149 def __init__(self, k='', v=''):
150 150 self.app_settings_name = k
151 151 self.app_settings_value = v
152 152
153 153
154 154 @validates('_app_settings_value')
155 155 def validate_settings_value(self, key, val):
156 156 assert type(val) == unicode
157 157 return val
158 158
159 159 @hybrid_property
160 160 def app_settings_value(self):
161 161 v = self._app_settings_value
162 162 if v == 'ldap_active':
163 163 v = str2bool(v)
164 164 return v
165 165
166 166 @app_settings_value.setter
167 167 def app_settings_value(self, val):
168 168 """
169 169 Setter that will always make sure we use unicode in app_settings_value
170 170
171 171 :param val:
172 172 """
173 173 self._app_settings_value = safe_unicode(val)
174 174
175 175 def __repr__(self):
176 176 return "<%s('%s:%s')>" % (self.__class__.__name__,
177 177 self.app_settings_name, self.app_settings_value)
178 178
179 179
180 180 @classmethod
181 181 def get_by_name(cls, ldap_key):
182 182 return cls.query()\
183 183 .filter(cls.app_settings_name == ldap_key).scalar()
184 184
185 185 @classmethod
186 186 def get_app_settings(cls, cache=False):
187 187
188 188 ret = cls.query()
189 189
190 190 if cache:
191 191 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
192 192
193 193 if not ret:
194 194 raise Exception('Could not get application settings !')
195 195 settings = {}
196 196 for each in ret:
197 197 settings['rhodecode_' + each.app_settings_name] = \
198 198 each.app_settings_value
199 199
200 200 return settings
201 201
202 202 @classmethod
203 203 def get_ldap_settings(cls, cache=False):
204 204 ret = cls.query()\
205 205 .filter(cls.app_settings_name.startswith('ldap_')).all()
206 206 fd = {}
207 207 for row in ret:
208 208 fd.update({row.app_settings_name:row.app_settings_value})
209 209
210 210 return fd
211 211
212 212
213 213 class RhodeCodeUi(Base, BaseModel):
214 214 __tablename__ = 'rhodecode_ui'
215 215 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
216 216
217 217 HOOK_UPDATE = 'changegroup.update'
218 218 HOOK_REPO_SIZE = 'changegroup.repo_size'
219 219 HOOK_PUSH = 'pretxnchangegroup.push_logger'
220 220 HOOK_PULL = 'preoutgoing.pull_logger'
221 221
222 222 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
223 223 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
224 224 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
225 225 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
226 226 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
227 227
228 228
229 229 @classmethod
230 230 def get_by_key(cls, key):
231 231 return cls.query().filter(cls.ui_key == key)
232 232
233 233
234 234 @classmethod
235 235 def get_builtin_hooks(cls):
236 236 q = cls.query()
237 237 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
238 238 cls.HOOK_REPO_SIZE,
239 239 cls.HOOK_PUSH, cls.HOOK_PULL]))
240 240 return q.all()
241 241
242 242 @classmethod
243 243 def get_custom_hooks(cls):
244 244 q = cls.query()
245 245 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
246 246 cls.HOOK_REPO_SIZE,
247 247 cls.HOOK_PUSH, cls.HOOK_PULL]))
248 248 q = q.filter(cls.ui_section == 'hooks')
249 249 return q.all()
250 250
251 251 @classmethod
252 252 def create_or_update_hook(cls, key, val):
253 253 new_ui = cls.get_by_key(key).scalar() or cls()
254 254 new_ui.ui_section = 'hooks'
255 255 new_ui.ui_active = True
256 256 new_ui.ui_key = key
257 257 new_ui.ui_value = val
258 258
259 259 Session.add(new_ui)
260 260 Session.commit()
261 261
262 262
263 263 class User(Base, BaseModel):
264 264 __tablename__ = 'users'
265 265 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
266 266 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
267 267 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
268 268 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
269 269 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
270 270 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
271 271 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
272 272 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
273 273 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
274 274 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
275 275 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
276 276 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
277 277
278 278 user_log = relationship('UserLog', cascade='all')
279 279 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
280 280
281 281 repositories = relationship('Repository')
282 282 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
283 283 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
284 284
285 285 group_member = relationship('UsersGroupMember', cascade='all')
286 286
287 287 @property
288 288 def full_contact(self):
289 289 return '%s %s <%s>' % (self.name, self.lastname, self.email)
290 290
291 291 @property
292 292 def short_contact(self):
293 293 return '%s %s' % (self.name, self.lastname)
294 294
295 295 @property
296 296 def is_admin(self):
297 297 return self.admin
298 298
299 299 def __repr__(self):
300 300 try:
301 301 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
302 302 self.user_id, self.username)
303 303 except:
304 304 return self.__class__.__name__
305 305
306 306 @classmethod
307 307 def get_by_username(cls, username, case_insensitive=False):
308 308 if case_insensitive:
309 309 return Session.query(cls).filter(cls.username.ilike(username)).scalar()
310 310 else:
311 311 return Session.query(cls).filter(cls.username == username).scalar()
312 312
313 313 @classmethod
314 314 def get_by_api_key(cls, api_key):
315 315 return cls.query().filter(cls.api_key == api_key).one()
316 316
317 317 def update_lastlogin(self):
318 318 """Update user lastlogin"""
319 319
320 320 self.last_login = datetime.datetime.now()
321 321 Session.add(self)
322 322 Session.commit()
323 323 log.debug('updated user %s lastlogin', self.username)
324 324
325 325 @classmethod
326 326 def create(cls, form_data):
327 327 from rhodecode.lib.auth import get_crypt_password
328 328
329 329 try:
330 330 new_user = cls()
331 331 for k, v in form_data.items():
332 332 if k == 'password':
333 333 v = get_crypt_password(v)
334 334 setattr(new_user, k, v)
335 335
336 336 new_user.api_key = generate_api_key(form_data['username'])
337 337 Session.add(new_user)
338 338 Session.commit()
339 339 return new_user
340 340 except:
341 341 log.error(traceback.format_exc())
342 342 Session.rollback()
343 343 raise
344 344
345 345 class UserLog(Base, BaseModel):
346 346 __tablename__ = 'user_logs'
347 347 __table_args__ = {'extend_existing':True}
348 348 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
349 349 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
350 350 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
351 351 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
352 352 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
353 353 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
354 354 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
355 355
356 356 @property
357 357 def action_as_day(self):
358 358 return date(*self.action_date.timetuple()[:3])
359 359
360 360 user = relationship('User')
361 361 repository = relationship('Repository')
362 362
363 363
364 364 class UsersGroup(Base, BaseModel):
365 365 __tablename__ = 'users_groups'
366 366 __table_args__ = {'extend_existing':True}
367 367
368 368 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
369 369 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
370 370 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
371 371
372 372 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
373 373
374 374 def __repr__(self):
375 375 return '<userGroup(%s)>' % (self.users_group_name)
376 376
377 377 @classmethod
378 378 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
379 379 if case_insensitive:
380 380 gr = cls.query()\
381 381 .filter(cls.users_group_name.ilike(group_name))
382 382 else:
383 383 gr = cls.query()\
384 384 .filter(cls.users_group_name == group_name)
385 385 if cache:
386 386 gr = gr.options(FromCache("sql_cache_short",
387 387 "get_user_%s" % group_name))
388 388 return gr.scalar()
389 389
390 390
391 391 @classmethod
392 392 def get(cls, users_group_id, cache=False):
393 393 users_group = cls.query()
394 394 if cache:
395 395 users_group = users_group.options(FromCache("sql_cache_short",
396 396 "get_users_group_%s" % users_group_id))
397 397 return users_group.get(users_group_id)
398 398
399 399 @classmethod
400 400 def create(cls, form_data):
401 401 try:
402 402 new_users_group = cls()
403 403 for k, v in form_data.items():
404 404 setattr(new_users_group, k, v)
405 405
406 406 Session.add(new_users_group)
407 407 Session.commit()
408 408 return new_users_group
409 409 except:
410 410 log.error(traceback.format_exc())
411 411 Session.rollback()
412 412 raise
413 413
414 414 @classmethod
415 415 def update(cls, users_group_id, form_data):
416 416
417 417 try:
418 418 users_group = cls.get(users_group_id, cache=False)
419 419
420 420 for k, v in form_data.items():
421 421 if k == 'users_group_members':
422 422 users_group.members = []
423 423 Session.flush()
424 424 members_list = []
425 425 if v:
426 426 v = [v] if isinstance(v, basestring) else v
427 427 for u_id in set(v):
428 428 member = UsersGroupMember(users_group_id, u_id)
429 429 members_list.append(member)
430 430 setattr(users_group, 'members', members_list)
431 431 setattr(users_group, k, v)
432 432
433 433 Session.add(users_group)
434 434 Session.commit()
435 435 except:
436 436 log.error(traceback.format_exc())
437 437 Session.rollback()
438 438 raise
439 439
440 440 @classmethod
441 441 def delete(cls, users_group_id):
442 442 try:
443 443
444 444 # check if this group is not assigned to repo
445 445 assigned_groups = UsersGroupRepoToPerm.query()\
446 446 .filter(UsersGroupRepoToPerm.users_group_id ==
447 447 users_group_id).all()
448 448
449 449 if assigned_groups:
450 450 raise UsersGroupsAssignedException('RepoGroup assigned to %s' %
451 451 assigned_groups)
452 452
453 453 users_group = cls.get(users_group_id, cache=False)
454 454 Session.delete(users_group)
455 455 Session.commit()
456 456 except:
457 457 log.error(traceback.format_exc())
458 458 Session.rollback()
459 459 raise
460 460
461 461 class UsersGroupMember(Base, BaseModel):
462 462 __tablename__ = 'users_groups_members'
463 463 __table_args__ = {'extend_existing':True}
464 464
465 465 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
466 466 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
467 467 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
468 468
469 469 user = relationship('User', lazy='joined')
470 470 users_group = relationship('UsersGroup')
471 471
472 472 def __init__(self, gr_id='', u_id=''):
473 473 self.users_group_id = gr_id
474 474 self.user_id = u_id
475 475
476 476 @staticmethod
477 477 def add_user_to_group(group, user):
478 478 ugm = UsersGroupMember()
479 479 ugm.users_group = group
480 480 ugm.user = user
481 481 Session.add(ugm)
482 482 Session.commit()
483 483 return ugm
484 484
485 485 class Repository(Base, BaseModel):
486 486 __tablename__ = 'repositories'
487 487 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
488 488
489 489 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
490 490 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
491 491 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
492 492 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
493 493 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
494 494 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
495 495 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
496 496 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
497 497 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
498 498 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
499 499
500 500 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
501 501 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
502 502
503 503
504 504 user = relationship('User')
505 505 fork = relationship('Repository', remote_side=repo_id)
506 506 group = relationship('RepoGroup')
507 507 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
508 508 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
509 509 stats = relationship('Statistics', cascade='all', uselist=False)
510 510
511 511 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
512 512
513 513 logs = relationship('UserLog', cascade='all')
514 514
515 515 def __repr__(self):
516 516 return "<%s('%s:%s')>" % (self.__class__.__name__,
517 517 self.repo_id, self.repo_name)
518 518
519 519 @classmethod
520 520 def url_sep(cls):
521 521 return '/'
522 522
523 523 @classmethod
524 524 def get_by_repo_name(cls, repo_name):
525 525 q = Session.query(cls).filter(cls.repo_name == repo_name)
526 526 q = q.options(joinedload(Repository.fork))\
527 527 .options(joinedload(Repository.user))\
528 528 .options(joinedload(Repository.group))
529 529 return q.one()
530 530
531 531 @classmethod
532 532 def get_repo_forks(cls, repo_id):
533 533 return cls.query().filter(Repository.fork_id == repo_id)
534 534
535 535 @classmethod
536 536 def base_path(cls):
537 537 """
538 538 Returns base path when all repos are stored
539 539
540 540 :param cls:
541 541 """
542 542 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
543 543 cls.url_sep())
544 544 q.options(FromCache("sql_cache_short", "repository_repo_path"))
545 545 return q.one().ui_value
546 546
547 547 @property
548 548 def just_name(self):
549 549 return self.repo_name.split(Repository.url_sep())[-1]
550 550
551 551 @property
552 552 def groups_with_parents(self):
553 553 groups = []
554 554 if self.group is None:
555 555 return groups
556 556
557 557 cur_gr = self.group
558 558 groups.insert(0, cur_gr)
559 559 while 1:
560 560 gr = getattr(cur_gr, 'parent_group', None)
561 561 cur_gr = cur_gr.parent_group
562 562 if gr is None:
563 563 break
564 564 groups.insert(0, gr)
565 565
566 566 return groups
567 567
568 568 @property
569 569 def groups_and_repo(self):
570 570 return self.groups_with_parents, self.just_name
571 571
572 572 @LazyProperty
573 573 def repo_path(self):
574 574 """
575 575 Returns base full path for that repository means where it actually
576 576 exists on a filesystem
577 577 """
578 578 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
579 579 Repository.url_sep())
580 580 q.options(FromCache("sql_cache_short", "repository_repo_path"))
581 581 return q.one().ui_value
582 582
583 583 @property
584 584 def repo_full_path(self):
585 585 p = [self.repo_path]
586 586 # we need to split the name by / since this is how we store the
587 587 # names in the database, but that eventually needs to be converted
588 588 # into a valid system path
589 589 p += self.repo_name.split(Repository.url_sep())
590 590 return os.path.join(*p)
591 591
592 592 def get_new_name(self, repo_name):
593 593 """
594 594 returns new full repository name based on assigned group and new new
595 595
596 596 :param group_name:
597 597 """
598 598 path_prefix = self.group.full_path_splitted if self.group else []
599 599 return Repository.url_sep().join(path_prefix + [repo_name])
600 600
601 601 @property
602 602 def _ui(self):
603 603 """
604 604 Creates an db based ui object for this repository
605 605 """
606 606 from mercurial import ui
607 607 from mercurial import config
608 608 baseui = ui.ui()
609 609
610 610 #clean the baseui object
611 611 baseui._ocfg = config.config()
612 612 baseui._ucfg = config.config()
613 613 baseui._tcfg = config.config()
614 614
615 615
616 616 ret = RhodeCodeUi.query()\
617 617 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
618 618
619 619 hg_ui = ret
620 620 for ui_ in hg_ui:
621 621 if ui_.ui_active:
622 622 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
623 623 ui_.ui_key, ui_.ui_value)
624 624 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
625 625
626 626 return baseui
627 627
628 628 @classmethod
629 629 def is_valid(cls, repo_name):
630 630 """
631 631 returns True if given repo name is a valid filesystem repository
632 632
633 633 @param cls:
634 634 @param repo_name:
635 635 """
636 636 from rhodecode.lib.utils import is_valid_repo
637 637
638 638 return is_valid_repo(repo_name, cls.base_path())
639 639
640 640
641 641 #==========================================================================
642 642 # SCM PROPERTIES
643 643 #==========================================================================
644 644
645 645 def get_changeset(self, rev):
646 646 return get_changeset_safe(self.scm_instance, rev)
647 647
648 648 @property
649 649 def tip(self):
650 650 return self.get_changeset('tip')
651 651
652 652 @property
653 653 def author(self):
654 654 return self.tip.author
655 655
656 656 @property
657 657 def last_change(self):
658 658 return self.scm_instance.last_change
659 659
660 660 #==========================================================================
661 661 # SCM CACHE INSTANCE
662 662 #==========================================================================
663 663
664 664 @property
665 665 def invalidate(self):
666 666 return CacheInvalidation.invalidate(self.repo_name)
667 667
668 668 def set_invalidate(self):
669 669 """
670 670 set a cache for invalidation for this instance
671 671 """
672 672 CacheInvalidation.set_invalidate(self.repo_name)
673 673
674 674 @LazyProperty
675 675 def scm_instance(self):
676 676 return self.__get_instance()
677 677
678 678 @property
679 679 def scm_instance_cached(self):
680 680 @cache_region('long_term')
681 681 def _c(repo_name):
682 682 return self.__get_instance()
683 683 rn = self.repo_name
684 684
685 685 inv = self.invalidate
686 686 if inv is not None:
687 687 region_invalidate(_c, None, rn)
688 688 # update our cache
689 689 CacheInvalidation.set_valid(inv.cache_key)
690 690 return _c(rn)
691 691
692 692 def __get_instance(self):
693 693
694 694 repo_full_path = self.repo_full_path
695 695
696 696 try:
697 697 alias = get_scm(repo_full_path)[0]
698 698 log.debug('Creating instance of %s repository', alias)
699 699 backend = get_backend(alias)
700 700 except VCSError:
701 701 log.error(traceback.format_exc())
702 702 log.error('Perhaps this repository is in db and not in '
703 703 'filesystem run rescan repositories with '
704 704 '"destroy old data " option from admin panel')
705 705 return
706 706
707 707 if alias == 'hg':
708 708
709 709 repo = backend(safe_str(repo_full_path), create=False,
710 710 baseui=self._ui)
711 711 # skip hidden web repository
712 712 if repo._get_hidden():
713 713 return
714 714 else:
715 715 repo = backend(repo_full_path, create=False)
716 716
717 717 return repo
718 718
719 719
720 720 class RepoGroup(Base, BaseModel):
721 721 __tablename__ = 'groups'
722 722 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
723 723 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
724 724 __mapper_args__ = {'order_by':'group_name'}
725 725
726 726 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
727 727 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
728 728 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
729 729 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
730 730
731 731 parent_group = relationship('RepoGroup', remote_side=group_id)
732 732
733 733
734 734 def __init__(self, group_name='', parent_group=None):
735 735 self.group_name = group_name
736 736 self.parent_group = parent_group
737 737
738 738 def __repr__(self):
739 739 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
740 740 self.group_name)
741 741
742 742 @classmethod
743 743 def groups_choices(cls):
744 744 from webhelpers.html import literal as _literal
745 745 repo_groups = [('', '')]
746 746 sep = ' &raquo; '
747 747 _name = lambda k: _literal(sep.join(k))
748 748
749 749 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
750 750 for x in cls.query().all()])
751 751
752 752 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
753 753 return repo_groups
754 754
755 755 @classmethod
756 756 def url_sep(cls):
757 757 return '/'
758 758
759 759 @classmethod
760 760 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
761 761 if case_insensitive:
762 762 gr = cls.query()\
763 763 .filter(cls.group_name.ilike(group_name))
764 764 else:
765 765 gr = cls.query()\
766 766 .filter(cls.group_name == group_name)
767 767 if cache:
768 768 gr = gr.options(FromCache("sql_cache_short",
769 769 "get_group_%s" % group_name))
770 770 return gr.scalar()
771 771
772 772 @property
773 773 def parents(self):
774 774 parents_recursion_limit = 5
775 775 groups = []
776 776 if self.parent_group is None:
777 777 return groups
778 778 cur_gr = self.parent_group
779 779 groups.insert(0, cur_gr)
780 780 cnt = 0
781 781 while 1:
782 782 cnt += 1
783 783 gr = getattr(cur_gr, 'parent_group', None)
784 784 cur_gr = cur_gr.parent_group
785 785 if gr is None:
786 786 break
787 787 if cnt == parents_recursion_limit:
788 788 # this will prevent accidental infinit loops
789 789 log.error('group nested more than %s' %
790 790 parents_recursion_limit)
791 791 break
792 792
793 793 groups.insert(0, gr)
794 794 return groups
795 795
796 796 @property
797 797 def children(self):
798 798 return Group.query().filter(Group.parent_group == self)
799 799
800 800 @property
801 801 def name(self):
802 802 return self.group_name.split(Group.url_sep())[-1]
803 803
804 804 @property
805 805 def full_path(self):
806 806 return self.group_name
807 807
808 808 @property
809 809 def full_path_splitted(self):
810 810 return self.group_name.split(Group.url_sep())
811 811
812 812 @property
813 813 def repositories(self):
814 814 return Repository.query().filter(Repository.group == self)
815 815
816 816 @property
817 817 def repositories_recursive_count(self):
818 818 cnt = self.repositories.count()
819 819
820 820 def children_count(group):
821 821 cnt = 0
822 822 for child in group.children:
823 823 cnt += child.repositories.count()
824 824 cnt += children_count(child)
825 825 return cnt
826 826
827 827 return cnt + children_count(self)
828 828
829 829
830 830 def get_new_name(self, group_name):
831 831 """
832 832 returns new full group name based on parent and new name
833 833
834 834 :param group_name:
835 835 """
836 836 path_prefix = (self.parent_group.full_path_splitted if
837 837 self.parent_group else [])
838 838 return Group.url_sep().join(path_prefix + [group_name])
839 839
840 840
841 841 class Permission(Base, BaseModel):
842 842 __tablename__ = 'permissions'
843 843 __table_args__ = {'extend_existing':True}
844 844 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
845 845 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
846 846 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
847 847
848 848 def __repr__(self):
849 849 return "<%s('%s:%s')>" % (self.__class__.__name__,
850 850 self.permission_id, self.permission_name)
851 851
852 852 @classmethod
853 853 def get_by_key(cls, key):
854 854 return cls.query().filter(cls.permission_name == key).scalar()
855 855
856 856 class UserRepoToPerm(Base, BaseModel):
857 857 __tablename__ = 'repo_to_perm'
858 858 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
859 859 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
860 860 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
861 861 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
862 862 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
863 863
864 864 user = relationship('User')
865 865 permission = relationship('Permission')
866 866 repository = relationship('Repository')
867 867
868 868 class UserToPerm(Base, BaseModel):
869 869 __tablename__ = 'user_to_perm'
870 870 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
871 871 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
872 872 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
873 873 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
874 874
875 875 user = relationship('User')
876 876 permission = relationship('Permission')
877 877
878 878 @classmethod
879 879 def has_perm(cls, user_id, perm):
880 880 if not isinstance(perm, Permission):
881 881 raise Exception('perm needs to be an instance of Permission class')
882 882
883 883 return cls.query().filter(cls.user_id == user_id)\
884 884 .filter(cls.permission == perm).scalar() is not None
885 885
886 886 @classmethod
887 887 def grant_perm(cls, user_id, perm):
888 888 if not isinstance(perm, Permission):
889 889 raise Exception('perm needs to be an instance of Permission class')
890 890
891 891 new = cls()
892 892 new.user_id = user_id
893 893 new.permission = perm
894 894 try:
895 895 Session.add(new)
896 896 Session.commit()
897 897 except:
898 898 Session.rollback()
899 899
900 900
901 901 @classmethod
902 902 def revoke_perm(cls, user_id, perm):
903 903 if not isinstance(perm, Permission):
904 904 raise Exception('perm needs to be an instance of Permission class')
905 905
906 906 try:
907 907 cls.query().filter(cls.user_id == user_id)\
908 908 .filter(cls.permission == perm).delete()
909 909 Session.commit()
910 910 except:
911 911 Session.rollback()
912 912
913 913 class UsersGroupRepoToPerm(Base, BaseModel):
914 914 __tablename__ = 'users_group_repo_to_perm'
915 915 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
916 916 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
917 917 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
918 918 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
919 919 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
920 920
921 921 users_group = relationship('UsersGroup')
922 922 permission = relationship('Permission')
923 923 repository = relationship('Repository')
924 924
925 925 def __repr__(self):
926 926 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
927 927
928 928 class UsersGroupToPerm(Base, BaseModel):
929 929 __tablename__ = 'users_group_to_perm'
930 930 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
931 931 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
932 932 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
933 933
934 934 users_group = relationship('UsersGroup')
935 935 permission = relationship('Permission')
936 936
937 937
938 938 @classmethod
939 939 def has_perm(cls, users_group_id, perm):
940 940 if not isinstance(perm, Permission):
941 941 raise Exception('perm needs to be an instance of Permission class')
942 942
943 943 return cls.query().filter(cls.users_group_id ==
944 944 users_group_id)\
945 945 .filter(cls.permission == perm)\
946 946 .scalar() is not None
947 947
948 948 @classmethod
949 949 def grant_perm(cls, users_group_id, perm):
950 950 if not isinstance(perm, Permission):
951 951 raise Exception('perm needs to be an instance of Permission class')
952 952
953 953 new = cls()
954 954 new.users_group_id = users_group_id
955 955 new.permission = perm
956 956 try:
957 957 Session.add(new)
958 958 Session.commit()
959 959 except:
960 960 Session.rollback()
961 961
962 962
963 963 @classmethod
964 964 def revoke_perm(cls, users_group_id, perm):
965 965 if not isinstance(perm, Permission):
966 966 raise Exception('perm needs to be an instance of Permission class')
967 967
968 968 try:
969 969 cls.query().filter(cls.users_group_id == users_group_id)\
970 970 .filter(cls.permission == perm).delete()
971 971 Session.commit()
972 972 except:
973 973 Session.rollback()
974 974
975 975
976 976 class UserRepoGroupToPerm(Base, BaseModel):
977 977 __tablename__ = 'group_to_perm'
978 978 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
979 979
980 980 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
981 981 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
982 982 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
983 983 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
984 984
985 985 user = relationship('User')
986 986 permission = relationship('Permission')
987 987 group = relationship('RepoGroup')
988 988
989 989 class Statistics(Base, BaseModel):
990 990 __tablename__ = 'statistics'
991 991 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
992 992 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
993 993 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
994 994 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
995 995 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
996 996 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
997 997 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
998 998
999 999 repository = relationship('Repository', single_parent=True)
1000 1000
1001 1001 class UserFollowing(Base, BaseModel):
1002 1002 __tablename__ = 'user_followings'
1003 1003 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
1004 1004 UniqueConstraint('user_id', 'follows_user_id')
1005 1005 , {'extend_existing':True})
1006 1006
1007 1007 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1008 1008 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1009 1009 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1010 1010 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1011 1011 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1012 1012
1013 1013 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1014 1014
1015 1015 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1016 1016 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1017 1017
1018 1018
1019 1019 @classmethod
1020 1020 def get_repo_followers(cls, repo_id):
1021 1021 return cls.query().filter(cls.follows_repo_id == repo_id)
1022 1022
1023 1023 class CacheInvalidation(Base, BaseModel):
1024 1024 __tablename__ = 'cache_invalidation'
1025 1025 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
1026 1026 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1027 1027 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1028 1028 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1029 1029 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1030 1030
1031 1031
1032 1032 def __init__(self, cache_key, cache_args=''):
1033 1033 self.cache_key = cache_key
1034 1034 self.cache_args = cache_args
1035 1035 self.cache_active = False
1036 1036
1037 1037 def __repr__(self):
1038 1038 return "<%s('%s:%s')>" % (self.__class__.__name__,
1039 1039 self.cache_id, self.cache_key)
1040 1040
1041 1041 @classmethod
1042 1042 def invalidate(cls, key):
1043 1043 """
1044 1044 Returns Invalidation object if this given key should be invalidated
1045 1045 None otherwise. `cache_active = False` means that this cache
1046 1046 state is not valid and needs to be invalidated
1047 1047
1048 1048 :param key:
1049 1049 """
1050 1050 return cls.query()\
1051 1051 .filter(CacheInvalidation.cache_key == key)\
1052 1052 .filter(CacheInvalidation.cache_active == False)\
1053 1053 .scalar()
1054 1054
1055 1055 @classmethod
1056 1056 def set_invalidate(cls, key):
1057 1057 """
1058 1058 Mark this Cache key for invalidation
1059 1059
1060 1060 :param key:
1061 1061 """
1062 1062
1063 1063 log.debug('marking %s for invalidation' % key)
1064 inv_obj = Session().query(cls)\
1064 inv_obj = Session.query(cls)\
1065 1065 .filter(cls.cache_key == key).scalar()
1066 1066 if inv_obj:
1067 1067 inv_obj.cache_active = False
1068 1068 else:
1069 1069 log.debug('cache key not found in invalidation db -> creating one')
1070 1070 inv_obj = CacheInvalidation(key)
1071 1071
1072 1072 try:
1073 1073 Session.add(inv_obj)
1074 1074 Session.commit()
1075 1075 except Exception:
1076 1076 log.error(traceback.format_exc())
1077 1077 Session.rollback()
1078 1078
1079 1079 @classmethod
1080 1080 def set_valid(cls, key):
1081 1081 """
1082 1082 Mark this cache key as active and currently cached
1083 1083
1084 1084 :param key:
1085 1085 """
1086 inv_obj = Session().query(CacheInvalidation)\
1086 inv_obj = Session.query(CacheInvalidation)\
1087 1087 .filter(CacheInvalidation.cache_key == key).scalar()
1088 1088 inv_obj.cache_active = True
1089 1089 Session.add(inv_obj)
1090 1090 Session.commit()
1091 1091
1092 1092 class DbMigrateVersion(Base, BaseModel):
1093 1093 __tablename__ = 'db_migrate_version'
1094 1094 __table_args__ = {'extend_existing':True}
1095 1095 repository_id = Column('repository_id', String(250), primary_key=True)
1096 1096 repository_path = Column('repository_path', Text)
1097 1097 version = Column('version', Integer)
1098 1098
@@ -1,601 +1,601 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.utils
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Utilities library for RhodeCode
7 7
8 8 :created_on: Apr 18, 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 import paste
31 31 import beaker
32 32 import tarfile
33 33 import shutil
34 34 from os.path import abspath
35 35 from os.path import dirname as dn, join as jn
36 36
37 37 from paste.script.command import Command, BadCommand
38 38
39 39 from mercurial import ui, config
40 40
41 41 from webhelpers.text import collapse, remove_formatting, strip_tags
42 42
43 43 from vcs import get_backend
44 44 from vcs.backends.base import BaseChangeset
45 45 from vcs.utils.lazy import LazyProperty
46 46 from vcs.utils.helpers import get_scm
47 47 from vcs.exceptions import VCSError
48 48
49 49 from rhodecode.lib.caching_query import FromCache
50 50
51 51 from rhodecode.model import meta
52 52 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
53 53 UserLog, RepoGroup, RhodeCodeSetting
54 54 from rhodecode.model.meta import Session
55 55
56 56 log = logging.getLogger(__name__)
57 57
58 58
59 59 def recursive_replace(str_, replace=' '):
60 60 """Recursive replace of given sign to just one instance
61 61
62 62 :param str_: given string
63 63 :param replace: char to find and replace multiple instances
64 64
65 65 Examples::
66 66 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
67 67 'Mighty-Mighty-Bo-sstones'
68 68 """
69 69
70 70 if str_.find(replace * 2) == -1:
71 71 return str_
72 72 else:
73 73 str_ = str_.replace(replace * 2, replace)
74 74 return recursive_replace(str_, replace)
75 75
76 76
77 77 def repo_name_slug(value):
78 78 """Return slug of name of repository
79 79 This function is called on each creation/modification
80 80 of repository to prevent bad names in repo
81 81 """
82 82
83 83 slug = remove_formatting(value)
84 84 slug = strip_tags(slug)
85 85
86 86 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
87 87 slug = slug.replace(c, '-')
88 88 slug = recursive_replace(slug, '-')
89 89 slug = collapse(slug, '-')
90 90 return slug
91 91
92 92
93 93 def get_repo_slug(request):
94 94 return request.environ['pylons.routes_dict'].get('repo_name')
95 95
96 96
97 97 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
98 98 """
99 99 Action logger for various actions made by users
100 100
101 101 :param user: user that made this action, can be a unique username string or
102 102 object containing user_id attribute
103 103 :param action: action to log, should be on of predefined unique actions for
104 104 easy translations
105 105 :param repo: string name of repository or object containing repo_id,
106 106 that action was made on
107 107 :param ipaddr: optional ip address from what the action was made
108 108 :param sa: optional sqlalchemy session
109 109
110 110 """
111 111
112 112 if not sa:
113 sa = meta.Session()
113 sa = meta.Session
114 114
115 115 try:
116 116 if hasattr(user, 'user_id'):
117 117 user_obj = user
118 118 elif isinstance(user, basestring):
119 119 user_obj = User.get_by_username(user)
120 120 else:
121 121 raise Exception('You have to provide user object or username')
122 122
123 123 if hasattr(repo, 'repo_id'):
124 124 repo_obj = Repository.get(repo.repo_id)
125 125 repo_name = repo_obj.repo_name
126 126 elif isinstance(repo, basestring):
127 127 repo_name = repo.lstrip('/')
128 128 repo_obj = Repository.get_by_repo_name(repo_name)
129 129 else:
130 130 raise Exception('You have to provide repository to action logger')
131 131
132 132 user_log = UserLog()
133 133 user_log.user_id = user_obj.user_id
134 134 user_log.action = action
135 135
136 136 user_log.repository_id = repo_obj.repo_id
137 137 user_log.repository_name = repo_name
138 138
139 139 user_log.action_date = datetime.datetime.now()
140 140 user_log.user_ip = ipaddr
141 141 sa.add(user_log)
142 142
143 143 log.info('Adding user %s, action %s on %s', user_obj, action, repo)
144 144 if commit:
145 145 sa.commit()
146 146 except:
147 147 log.error(traceback.format_exc())
148 148 raise
149 149
150 150
151 151 def get_repos(path, recursive=False):
152 152 """
153 153 Scans given path for repos and return (name,(type,path)) tuple
154 154
155 155 :param path: path to scann for repositories
156 156 :param recursive: recursive search and return names with subdirs in front
157 157 """
158 158
159 159 if path.endswith(os.sep):
160 160 #remove ending slash for better results
161 161 path = path[:-1]
162 162
163 163 def _get_repos(p):
164 164 if not os.access(p, os.W_OK):
165 165 return
166 166 for dirpath in os.listdir(p):
167 167 if os.path.isfile(os.path.join(p, dirpath)):
168 168 continue
169 169 cur_path = os.path.join(p, dirpath)
170 170 try:
171 171 scm_info = get_scm(cur_path)
172 172 yield scm_info[1].split(path)[-1].lstrip(os.sep), scm_info
173 173 except VCSError:
174 174 if not recursive:
175 175 continue
176 176 #check if this dir containts other repos for recursive scan
177 177 rec_path = os.path.join(p, dirpath)
178 178 if os.path.isdir(rec_path):
179 179 for inner_scm in _get_repos(rec_path):
180 180 yield inner_scm
181 181
182 182 return _get_repos(path)
183 183
184 184
185 185 def is_valid_repo(repo_name, base_path):
186 186 """
187 187 Returns True if given path is a valid repository False otherwise
188 188 :param repo_name:
189 189 :param base_path:
190 190
191 191 :return True: if given path is a valid repository
192 192 """
193 193 full_path = os.path.join(base_path, repo_name)
194 194
195 195 try:
196 196 get_scm(full_path)
197 197 return True
198 198 except VCSError:
199 199 return False
200 200
201 201 def is_valid_repos_group(repos_group_name, base_path):
202 202 """
203 203 Returns True if given path is a repos group False otherwise
204 204
205 205 :param repo_name:
206 206 :param base_path:
207 207 """
208 208 full_path = os.path.join(base_path, repos_group_name)
209 209
210 210 # check if it's not a repo
211 211 if is_valid_repo(repos_group_name, base_path):
212 212 return False
213 213
214 214 # check if it's a valid path
215 215 if os.path.isdir(full_path):
216 216 return True
217 217
218 218 return False
219 219
220 220 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
221 221 while True:
222 222 ok = raw_input(prompt)
223 223 if ok in ('y', 'ye', 'yes'):
224 224 return True
225 225 if ok in ('n', 'no', 'nop', 'nope'):
226 226 return False
227 227 retries = retries - 1
228 228 if retries < 0:
229 229 raise IOError
230 230 print complaint
231 231
232 232 #propagated from mercurial documentation
233 233 ui_sections = ['alias', 'auth',
234 234 'decode/encode', 'defaults',
235 235 'diff', 'email',
236 236 'extensions', 'format',
237 237 'merge-patterns', 'merge-tools',
238 238 'hooks', 'http_proxy',
239 239 'smtp', 'patch',
240 240 'paths', 'profiling',
241 241 'server', 'trusted',
242 242 'ui', 'web', ]
243 243
244 244
245 245 def make_ui(read_from='file', path=None, checkpaths=True):
246 246 """A function that will read python rc files or database
247 247 and make an mercurial ui object from read options
248 248
249 249 :param path: path to mercurial config file
250 250 :param checkpaths: check the path
251 251 :param read_from: read from 'file' or 'db'
252 252 """
253 253
254 254 baseui = ui.ui()
255 255
256 256 #clean the baseui object
257 257 baseui._ocfg = config.config()
258 258 baseui._ucfg = config.config()
259 259 baseui._tcfg = config.config()
260 260
261 261 if read_from == 'file':
262 262 if not os.path.isfile(path):
263 263 log.warning('Unable to read config file %s' % path)
264 264 return False
265 265 log.debug('reading hgrc from %s', path)
266 266 cfg = config.config()
267 267 cfg.read(path)
268 268 for section in ui_sections:
269 269 for k, v in cfg.items(section):
270 270 log.debug('settings ui from file[%s]%s:%s', section, k, v)
271 271 baseui.setconfig(section, k, v)
272 272
273 273 elif read_from == 'db':
274 sa = meta.Session()
274 sa = meta.Session
275 275 ret = sa.query(RhodeCodeUi)\
276 276 .options(FromCache("sql_cache_short",
277 277 "get_hg_ui_settings")).all()
278 278
279 279 hg_ui = ret
280 280 for ui_ in hg_ui:
281 281 if ui_.ui_active:
282 282 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
283 283 ui_.ui_key, ui_.ui_value)
284 284 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
285 285
286 286 meta.Session.remove()
287 287 return baseui
288 288
289 289
290 290 def set_rhodecode_config(config):
291 291 """
292 292 Updates pylons config with new settings from database
293 293
294 294 :param config:
295 295 """
296 296 hgsettings = RhodeCodeSetting.get_app_settings()
297 297
298 298 for k, v in hgsettings.items():
299 299 config[k] = v
300 300
301 301
302 302 def invalidate_cache(cache_key, *args):
303 303 """
304 304 Puts cache invalidation task into db for
305 305 further global cache invalidation
306 306 """
307 307
308 308 from rhodecode.model.scm import ScmModel
309 309
310 310 if cache_key.startswith('get_repo_cached_'):
311 311 name = cache_key.split('get_repo_cached_')[-1]
312 312 ScmModel().mark_for_invalidation(name)
313 313
314 314
315 315 class EmptyChangeset(BaseChangeset):
316 316 """
317 317 An dummy empty changeset. It's possible to pass hash when creating
318 318 an EmptyChangeset
319 319 """
320 320
321 321 def __init__(self, cs='0' * 40, repo=None, requested_revision=None, alias=None):
322 322 self._empty_cs = cs
323 323 self.revision = -1
324 324 self.message = ''
325 325 self.author = ''
326 326 self.date = ''
327 327 self.repository = repo
328 328 self.requested_revision = requested_revision
329 329 self.alias = alias
330 330
331 331 @LazyProperty
332 332 def raw_id(self):
333 333 """
334 334 Returns raw string identifying this changeset, useful for web
335 335 representation.
336 336 """
337 337
338 338 return self._empty_cs
339 339
340 340 @LazyProperty
341 341 def branch(self):
342 342 return get_backend(self.alias).DEFAULT_BRANCH_NAME
343 343
344 344 @LazyProperty
345 345 def short_id(self):
346 346 return self.raw_id[:12]
347 347
348 348 def get_file_changeset(self, path):
349 349 return self
350 350
351 351 def get_file_content(self, path):
352 352 return u''
353 353
354 354 def get_file_size(self, path):
355 355 return 0
356 356
357 357
358 358 def map_groups(groups):
359 359 """
360 360 Checks for groups existence, and creates groups structures.
361 361 It returns last group in structure
362 362
363 363 :param groups: list of groups structure
364 364 """
365 sa = meta.Session()
365 sa = meta.Session
366 366
367 367 parent = None
368 368 group = None
369 369
370 370 # last element is repo in nested groups structure
371 371 groups = groups[:-1]
372 372
373 373 for lvl, group_name in enumerate(groups):
374 374 group_name = '/'.join(groups[:lvl] + [group_name])
375 375 group = sa.query(RepoGroup).filter(RepoGroup.group_name == group_name).scalar()
376 376
377 377 if group is None:
378 378 group = RepoGroup(group_name, parent)
379 379 sa.add(group)
380 380 sa.commit()
381 381 parent = group
382 382 return group
383 383
384 384
385 385 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
386 386 """
387 387 maps all repos given in initial_repo_list, non existing repositories
388 388 are created, if remove_obsolete is True it also check for db entries
389 389 that are not in initial_repo_list and removes them.
390 390
391 391 :param initial_repo_list: list of repositories found by scanning methods
392 392 :param remove_obsolete: check for obsolete entries in database
393 393 """
394 394 from rhodecode.model.repo import RepoModel
395 sa = meta.Session()
395 sa = meta.Session
396 396 rm = RepoModel()
397 397 user = sa.query(User).filter(User.admin == True).first()
398 398 if user is None:
399 399 raise Exception('Missing administrative account !')
400 400 added = []
401 401
402 402 for name, repo in initial_repo_list.items():
403 403 group = map_groups(name.split(Repository.url_sep()))
404 404 if not rm.get_by_repo_name(name, cache=False):
405 405 log.info('repository %s not found creating default', name)
406 406 added.append(name)
407 407 form_data = {
408 408 'repo_name': name,
409 409 'repo_name_full': name,
410 410 'repo_type': repo.alias,
411 411 'description': repo.description \
412 412 if repo.description != 'unknown' else \
413 413 '%s repository' % name,
414 414 'private': False,
415 415 'group_id': getattr(group, 'group_id', None)
416 416 }
417 417 rm.create(form_data, user, just_db=True)
418 418 sa.commit()
419 419 removed = []
420 420 if remove_obsolete:
421 421 #remove from database those repositories that are not in the filesystem
422 422 for repo in sa.query(Repository).all():
423 423 if repo.repo_name not in initial_repo_list.keys():
424 424 removed.append(repo.repo_name)
425 425 sa.delete(repo)
426 426 sa.commit()
427 427
428 428 return added, removed
429 429
430 430 # set cache regions for beaker so celery can utilise it
431 431 def add_cache(settings):
432 432 cache_settings = {'regions': None}
433 433 for key in settings.keys():
434 434 for prefix in ['beaker.cache.', 'cache.']:
435 435 if key.startswith(prefix):
436 436 name = key.split(prefix)[1].strip()
437 437 cache_settings[name] = settings[key].strip()
438 438 if cache_settings['regions']:
439 439 for region in cache_settings['regions'].split(','):
440 440 region = region.strip()
441 441 region_settings = {}
442 442 for key, value in cache_settings.items():
443 443 if key.startswith(region):
444 444 region_settings[key.split('.')[1]] = value
445 445 region_settings['expire'] = int(region_settings.get('expire',
446 446 60))
447 447 region_settings.setdefault('lock_dir',
448 448 cache_settings.get('lock_dir'))
449 449 region_settings.setdefault('data_dir',
450 450 cache_settings.get('data_dir'))
451 451
452 452 if 'type' not in region_settings:
453 453 region_settings['type'] = cache_settings.get('type',
454 454 'memory')
455 455 beaker.cache.cache_regions[region] = region_settings
456 456
457 457
458 458 #==============================================================================
459 459 # TEST FUNCTIONS AND CREATORS
460 460 #==============================================================================
461 461 def create_test_index(repo_location, config, full_index):
462 462 """
463 463 Makes default test index
464 464
465 465 :param config: test config
466 466 :param full_index:
467 467 """
468 468
469 469 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
470 470 from rhodecode.lib.pidlock import DaemonLock, LockHeld
471 471
472 472 repo_location = repo_location
473 473
474 474 index_location = os.path.join(config['app_conf']['index_dir'])
475 475 if not os.path.exists(index_location):
476 476 os.makedirs(index_location)
477 477
478 478 try:
479 479 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
480 480 WhooshIndexingDaemon(index_location=index_location,
481 481 repo_location=repo_location)\
482 482 .run(full_index=full_index)
483 483 l.release()
484 484 except LockHeld:
485 485 pass
486 486
487 487
488 488 def create_test_env(repos_test_path, config):
489 489 """
490 490 Makes a fresh database and
491 491 install test repository into tmp dir
492 492 """
493 493 from rhodecode.lib.db_manage import DbManage
494 494 from rhodecode.tests import HG_REPO, TESTS_TMP_PATH
495 495
496 496 # PART ONE create db
497 497 dbconf = config['sqlalchemy.db1.url']
498 498 log.debug('making test db %s', dbconf)
499 499
500 500 # create test dir if it doesn't exist
501 501 if not os.path.isdir(repos_test_path):
502 502 log.debug('Creating testdir %s' % repos_test_path)
503 503 os.makedirs(repos_test_path)
504 504
505 505 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
506 506 tests=True)
507 507 dbmanage.create_tables(override=True)
508 508 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
509 509 dbmanage.create_default_user()
510 510 dbmanage.admin_prompt()
511 511 dbmanage.create_permissions()
512 512 dbmanage.populate_default_permissions()
513 Session().commit()
513 Session.commit()
514 514 # PART TWO make test repo
515 515 log.debug('making test vcs repositories')
516 516
517 517 idx_path = config['app_conf']['index_dir']
518 518 data_path = config['app_conf']['cache_dir']
519 519
520 520 #clean index and data
521 521 if idx_path and os.path.exists(idx_path):
522 522 log.debug('remove %s' % idx_path)
523 523 shutil.rmtree(idx_path)
524 524
525 525 if data_path and os.path.exists(data_path):
526 526 log.debug('remove %s' % data_path)
527 527 shutil.rmtree(data_path)
528 528
529 529 #CREATE DEFAULT HG REPOSITORY
530 530 cur_dir = dn(dn(abspath(__file__)))
531 531 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
532 532 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
533 533 tar.close()
534 534
535 535
536 536 #==============================================================================
537 537 # PASTER COMMANDS
538 538 #==============================================================================
539 539 class BasePasterCommand(Command):
540 540 """
541 541 Abstract Base Class for paster commands.
542 542
543 543 The celery commands are somewhat aggressive about loading
544 544 celery.conf, and since our module sets the `CELERY_LOADER`
545 545 environment variable to our loader, we have to bootstrap a bit and
546 546 make sure we've had a chance to load the pylons config off of the
547 547 command line, otherwise everything fails.
548 548 """
549 549 min_args = 1
550 550 min_args_error = "Please provide a paster config file as an argument."
551 551 takes_config_file = 1
552 552 requires_config_file = True
553 553
554 554 def notify_msg(self, msg, log=False):
555 555 """Make a notification to user, additionally if logger is passed
556 556 it logs this action using given logger
557 557
558 558 :param msg: message that will be printed to user
559 559 :param log: logging instance, to use to additionally log this message
560 560
561 561 """
562 562 if log and isinstance(log, logging):
563 563 log(msg)
564 564
565 565 def run(self, args):
566 566 """
567 567 Overrides Command.run
568 568
569 569 Checks for a config file argument and loads it.
570 570 """
571 571 if len(args) < self.min_args:
572 572 raise BadCommand(
573 573 self.min_args_error % {'min_args': self.min_args,
574 574 'actual_args': len(args)})
575 575
576 576 # Decrement because we're going to lob off the first argument.
577 577 # @@ This is hacky
578 578 self.min_args -= 1
579 579 self.bootstrap_config(args[0])
580 580 self.update_parser()
581 581 return super(BasePasterCommand, self).run(args[1:])
582 582
583 583 def update_parser(self):
584 584 """
585 585 Abstract method. Allows for the class's parser to be updated
586 586 before the superclass's `run` method is called. Necessary to
587 587 allow options/arguments to be passed through to the underlying
588 588 celery command.
589 589 """
590 590 raise NotImplementedError("Abstract Method.")
591 591
592 592 def bootstrap_config(self, conf):
593 593 """
594 594 Loads the pylons configuration.
595 595 """
596 596 from pylons import config as pylonsconfig
597 597
598 598 path_to_ini_file = os.path.realpath(conf)
599 599 conf = paste.deploy.appconfig('config:' + path_to_ini_file)
600 600 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
601 601
@@ -1,92 +1,92 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.__init__
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 The application's model objects
7 7
8 8 :created_on: Nov 25, 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
14 14 :example:
15 15
16 16 .. code-block:: python
17 17
18 18 from paste.deploy import appconfig
19 19 from pylons import config
20 20 from sqlalchemy import engine_from_config
21 21 from rhodecode.config.environment import load_environment
22 22
23 23 conf = appconfig('config:development.ini', relative_to = './../../')
24 24 load_environment(conf.global_conf, conf.local_conf)
25 25
26 26 engine = engine_from_config(config, 'sqlalchemy.')
27 27 init_model(engine)
28 28 # RUN YOUR CODE HERE
29 29
30 30 """
31 31 # This program is free software: you can redistribute it and/or modify
32 32 # it under the terms of the GNU General Public License as published by
33 33 # the Free Software Foundation, either version 3 of the License, or
34 34 # (at your option) any later version.
35 35 #
36 36 # This program is distributed in the hope that it will be useful,
37 37 # but WITHOUT ANY WARRANTY; without even the implied warranty of
38 38 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
39 39 # GNU General Public License for more details.
40 40 #
41 41 # You should have received a copy of the GNU General Public License
42 42 # along with this program. If not, see <http://www.gnu.org/licenses/>.
43 43
44 44 import logging
45 45
46 46 from rhodecode.model import meta
47 47
48 48 log = logging.getLogger(__name__)
49 49
50 50
51 51 def init_model(engine):
52 52 """
53 53 Initializes db session, bind the engine with the metadata,
54 54 Call this before using any of the tables or classes in the model,
55 55 preferably once in application start
56 56
57 57 :param engine: engine to bind to
58 58 """
59 59 log.info("initializing db for %s", engine)
60 60 meta.Base.metadata.bind = engine
61 61
62 62
63 63 class BaseModel(object):
64 64 """Base Model for all RhodeCode models, it adds sql alchemy session
65 65 into instance of model
66 66
67 67 :param sa: If passed it reuses this session instead of creating a new one
68 68 """
69 69
70 70 def __init__(self, sa=None):
71 71 if sa is not None:
72 72 self.sa = sa
73 73 else:
74 self.sa = meta.Session()
74 self.sa = meta.Session
75 75
76 76 def _get_instance(self, cls, instance):
77 77 """
78 78 Get's instance of given cls using some simple lookup mechanism
79 79
80 80 :param cls: class to fetch
81 81 :param instance: int or Instance
82 82 """
83 83
84 84 if isinstance(instance, cls):
85 85 return instance
86 86 elif isinstance(instance, int) or str(instance).isdigit():
87 87 return cls.get(instance)
88 88 else:
89 89 if instance:
90 90 raise Exception('given object must be int or Instance'
91 91 ' of %s got %s' % (type(cls),
92 92 type(instance)))
@@ -1,1237 +1,1097 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 85 """Base Model for all classess
86 86
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 94 def get_dict(self):
95 95 """return dict with keys and values corresponding
96 96 to this model data """
97 97
98 98 d = {}
99 99 for k in self._get_keys():
100 100 d[k] = getattr(self, k)
101 101 return d
102 102
103 103 def get_appstruct(self):
104 104 """return list with keys and values tupples corresponding
105 105 to this model data """
106 106
107 107 l = []
108 108 for k in self._get_keys():
109 109 l.append((k, getattr(self, k),))
110 110 return l
111 111
112 112 def populate_obj(self, populate_dict):
113 113 """populate model with data from given populate_dict"""
114 114
115 115 for k in self._get_keys():
116 116 if k in populate_dict:
117 117 setattr(self, k, populate_dict[k])
118 118
119 119 @classmethod
120 120 def query(cls):
121 return Session().query(cls)
121 return Session.query(cls)
122 122
123 123 @classmethod
124 124 def get(cls, id_):
125 125 if id_:
126 126 return cls.query().get(id_)
127 127
128 128 @classmethod
129 129 def getAll(cls):
130 130 return cls.query().all()
131 131
132 132 @classmethod
133 133 def delete(cls, id_):
134 134 obj = cls.query().get(id_)
135 Session().delete(obj)
135 Session.delete(obj)
136 136
137 137
138 138 class RhodeCodeSetting(Base, BaseModel):
139 139 __tablename__ = 'rhodecode_settings'
140 140 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
141 141 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
142 142 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
143 143 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
144 144
145 145 def __init__(self, k='', v=''):
146 146 self.app_settings_name = k
147 147 self.app_settings_value = v
148 148
149 149
150 150 @validates('_app_settings_value')
151 151 def validate_settings_value(self, key, val):
152 152 assert type(val) == unicode
153 153 return val
154 154
155 155 @hybrid_property
156 156 def app_settings_value(self):
157 157 v = self._app_settings_value
158 158 if v == 'ldap_active':
159 159 v = str2bool(v)
160 160 return v
161 161
162 162 @app_settings_value.setter
163 163 def app_settings_value(self, val):
164 164 """
165 165 Setter that will always make sure we use unicode in app_settings_value
166 166
167 167 :param val:
168 168 """
169 169 self._app_settings_value = safe_unicode(val)
170 170
171 171 def __repr__(self):
172 172 return "<%s('%s:%s')>" % (self.__class__.__name__,
173 173 self.app_settings_name, self.app_settings_value)
174 174
175 175
176 176 @classmethod
177 177 def get_by_name(cls, ldap_key):
178 178 return cls.query()\
179 179 .filter(cls.app_settings_name == ldap_key).scalar()
180 180
181 181 @classmethod
182 182 def get_app_settings(cls, cache=False):
183 183
184 184 ret = cls.query()
185 185
186 186 if cache:
187 187 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
188 188
189 189 if not ret:
190 190 raise Exception('Could not get application settings !')
191 191 settings = {}
192 192 for each in ret:
193 193 settings['rhodecode_' + each.app_settings_name] = \
194 194 each.app_settings_value
195 195
196 196 return settings
197 197
198 198 @classmethod
199 199 def get_ldap_settings(cls, cache=False):
200 200 ret = cls.query()\
201 201 .filter(cls.app_settings_name.startswith('ldap_')).all()
202 202 fd = {}
203 203 for row in ret:
204 204 fd.update({row.app_settings_name:row.app_settings_value})
205 205
206 206 return fd
207 207
208 208
209 209 class RhodeCodeUi(Base, BaseModel):
210 210 __tablename__ = 'rhodecode_ui'
211 211 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
212 212
213 213 HOOK_UPDATE = 'changegroup.update'
214 214 HOOK_REPO_SIZE = 'changegroup.repo_size'
215 215 HOOK_PUSH = 'pretxnchangegroup.push_logger'
216 216 HOOK_PULL = 'preoutgoing.pull_logger'
217 217
218 218 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
219 219 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
220 220 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
221 221 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
222 222 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
223 223
224 224
225 225 @classmethod
226 226 def get_by_key(cls, key):
227 227 return cls.query().filter(cls.ui_key == key)
228 228
229 229
230 230 @classmethod
231 231 def get_builtin_hooks(cls):
232 232 q = cls.query()
233 233 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
234 234 cls.HOOK_REPO_SIZE,
235 235 cls.HOOK_PUSH, cls.HOOK_PULL]))
236 236 return q.all()
237 237
238 238 @classmethod
239 239 def get_custom_hooks(cls):
240 240 q = cls.query()
241 241 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
242 242 cls.HOOK_REPO_SIZE,
243 243 cls.HOOK_PUSH, cls.HOOK_PULL]))
244 244 q = q.filter(cls.ui_section == 'hooks')
245 245 return q.all()
246 246
247 247 @classmethod
248 248 def create_or_update_hook(cls, key, val):
249 249 new_ui = cls.get_by_key(key).scalar() or cls()
250 250 new_ui.ui_section = 'hooks'
251 251 new_ui.ui_active = True
252 252 new_ui.ui_key = key
253 253 new_ui.ui_value = val
254 254
255 Session().add(new_ui)
256 Session().commit()
255 Session.add(new_ui)
257 256
258 257
259 258 class User(Base, BaseModel):
260 259 __tablename__ = 'users'
261 260 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
262 261 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
263 262 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
264 263 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
265 264 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
266 265 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
267 266 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
268 267 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
269 268 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
270 269 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
271 270 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
272 271 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
273 272
274 273 user_log = relationship('UserLog', cascade='all')
275 274 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
276 275
277 276 repositories = relationship('Repository')
278 277 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
279 278 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
280 279
281 280 group_member = relationship('UsersGroupMember', cascade='all')
282 281
283 282 notifications = relationship('UserNotification',)
284 283
285 284 @property
286 285 def full_name(self):
287 286 return '%s %s' % (self.name, self.lastname)
288 287
289 288 @property
290 289 def full_contact(self):
291 290 return '%s %s <%s>' % (self.name, self.lastname, self.email)
292 291
293 292 @property
294 293 def short_contact(self):
295 294 return '%s %s' % (self.name, self.lastname)
296 295
297 296 @property
298 297 def is_admin(self):
299 298 return self.admin
300 299
301 300 def __repr__(self):
302 301 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
303 302 self.user_id, self.username)
304 303
305 304
306 305 @classmethod
307 306 def get_by_username(cls, username, case_insensitive=False, cache=False):
308 307 if case_insensitive:
309 308 q = cls.query().filter(cls.username.ilike(username))
310 309 else:
311 310 q = cls.query().filter(cls.username == username)
312 311
313 312 if cache:
314 313 q = q.options(FromCache("sql_cache_short",
315 314 "get_user_%s" % username))
316 315 return q.scalar()
317 316
318 317 @classmethod
319 318 def get_by_api_key(cls, api_key, cache=False):
320 319 q = cls.query().filter(cls.api_key == api_key)
321 320
322 321 if cache:
323 322 q = q.options(FromCache("sql_cache_short",
324 323 "get_api_key_%s" % api_key))
325 324 return q.scalar()
326 325
327 326 @classmethod
328 327 def get_by_email(cls, email, cache=False):
329 328 q = cls.query().filter(cls.email == email)
330 329
331 330 if cache:
332 331 q = q.options(FromCache("sql_cache_short",
333 332 "get_api_key_%s" % email))
334 333 return q.scalar()
335 334
336 335 def update_lastlogin(self):
337 336 """Update user lastlogin"""
338
339 337 self.last_login = datetime.datetime.now()
340 Session().add(self)
341 Session().commit()
338 Session.add(self)
342 339 log.debug('updated user %s lastlogin', self.username)
343 340
344 341
345 342 class UserLog(Base, BaseModel):
346 343 __tablename__ = 'user_logs'
347 344 __table_args__ = {'extend_existing':True}
348 345 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
349 346 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
350 347 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
351 348 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
352 349 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
353 350 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
354 351 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
355 352
356 353 @property
357 354 def action_as_day(self):
358 355 return datetime.date(*self.action_date.timetuple()[:3])
359 356
360 357 user = relationship('User')
361 358 repository = relationship('Repository',cascade='')
362 359
363 360
364 361 class UsersGroup(Base, BaseModel):
365 362 __tablename__ = 'users_groups'
366 363 __table_args__ = {'extend_existing':True}
367 364
368 365 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
369 366 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
370 367 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
371 368
372 369 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
373 370
374 371 def __repr__(self):
375 372 return '<userGroup(%s)>' % (self.users_group_name)
376 373
377 374 @classmethod
378 375 def get_by_group_name(cls, group_name, cache=False,
379 376 case_insensitive=False):
380 377 if case_insensitive:
381 378 q = cls.query().filter(cls.users_group_name.ilike(group_name))
382 379 else:
383 380 q = cls.query().filter(cls.users_group_name == group_name)
384 381 if cache:
385 382 q = q.options(FromCache("sql_cache_short",
386 383 "get_user_%s" % group_name))
387 384 return q.scalar()
388 385
389
390 386 @classmethod
391 387 def get(cls, users_group_id, cache=False):
392 388 users_group = cls.query()
393 389 if cache:
394 390 users_group = users_group.options(FromCache("sql_cache_short",
395 391 "get_users_group_%s" % users_group_id))
396 392 return users_group.get(users_group_id)
397 393
398 @classmethod
399 def create(cls, form_data):
400 try:
401 new_users_group = cls()
402 for k, v in form_data.items():
403 setattr(new_users_group, k, v)
404
405 Session().add(new_users_group)
406 Session().commit()
407 return new_users_group
408 except:
409 log.error(traceback.format_exc())
410 Session().rollback()
411 raise
412
413 @classmethod
414 def update(cls, users_group_id, form_data):
415
416 try:
417 users_group = cls.get(users_group_id, cache=False)
418
419 for k, v in form_data.items():
420 if k == 'users_group_members':
421 users_group.members = []
422 Session().flush()
423 members_list = []
424 if v:
425 v = [v] if isinstance(v, basestring) else v
426 for u_id in set(v):
427 member = UsersGroupMember(users_group_id, u_id)
428 members_list.append(member)
429 setattr(users_group, 'members', members_list)
430 setattr(users_group, k, v)
431
432 Session().add(users_group)
433 Session().commit()
434 except:
435 log.error(traceback.format_exc())
436 Session().rollback()
437 raise
438
439 @classmethod
440 def delete(cls, users_group_id):
441 try:
442
443 # check if this group is not assigned to repo
444 assigned_groups = UsersGroupRepoToPerm.query()\
445 .filter(UsersGroupRepoToPerm.users_group_id ==
446 users_group_id).all()
447
448 if assigned_groups:
449 raise UsersGroupsAssignedException('RepoGroup assigned to %s' %
450 assigned_groups)
451
452 users_group = cls.get(users_group_id, cache=False)
453 Session().delete(users_group)
454 Session().commit()
455 except:
456 log.error(traceback.format_exc())
457 Session().rollback()
458 raise
459
460 394 class UsersGroupMember(Base, BaseModel):
461 395 __tablename__ = 'users_groups_members'
462 396 __table_args__ = {'extend_existing':True}
463 397
464 398 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
465 399 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
466 400 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
467 401
468 402 user = relationship('User', lazy='joined')
469 403 users_group = relationship('UsersGroup')
470 404
471 405 def __init__(self, gr_id='', u_id=''):
472 406 self.users_group_id = gr_id
473 407 self.user_id = u_id
474 408
475 409 @staticmethod
476 410 def add_user_to_group(group, user):
477 411 ugm = UsersGroupMember()
478 412 ugm.users_group = group
479 413 ugm.user = user
480 Session().add(ugm)
481 Session().commit()
414 Session.add(ugm)
415 Session.commit()
482 416 return ugm
483 417
484 418 class Repository(Base, BaseModel):
485 419 __tablename__ = 'repositories'
486 420 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
487 421
488 422 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
489 423 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
490 424 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
491 425 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
492 426 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
493 427 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
494 428 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
495 429 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
496 430 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
497 431 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
498 432
499 433 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
500 434 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
501 435
502 436
503 437 user = relationship('User')
504 438 fork = relationship('Repository', remote_side=repo_id)
505 439 group = relationship('RepoGroup')
506 440 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
507 441 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
508 442 stats = relationship('Statistics', cascade='all', uselist=False)
509 443
510 444 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
511 445
512 446 logs = relationship('UserLog')
513 447
514 448 def __repr__(self):
515 449 return "<%s('%s:%s')>" % (self.__class__.__name__,
516 450 self.repo_id, self.repo_name)
517 451
518 452 @classmethod
519 453 def url_sep(cls):
520 454 return '/'
521 455
522 456 @classmethod
523 457 def get_by_repo_name(cls, repo_name):
524 q = Session().query(cls).filter(cls.repo_name == repo_name)
458 q = Session.query(cls).filter(cls.repo_name == repo_name)
525 459 q = q.options(joinedload(Repository.fork))\
526 460 .options(joinedload(Repository.user))\
527 461 .options(joinedload(Repository.group))
528 return q.one()
462 return q.scalar()
529 463
530 464 @classmethod
531 465 def get_repo_forks(cls, repo_id):
532 466 return cls.query().filter(Repository.fork_id == repo_id)
533 467
534 468 @classmethod
535 469 def base_path(cls):
536 470 """
537 471 Returns base path when all repos are stored
538 472
539 473 :param cls:
540 474 """
541 q = Session().query(RhodeCodeUi)\
475 q = Session.query(RhodeCodeUi)\
542 476 .filter(RhodeCodeUi.ui_key == cls.url_sep())
543 477 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
544 478 return q.one().ui_value
545 479
546 480 @property
547 481 def just_name(self):
548 482 return self.repo_name.split(Repository.url_sep())[-1]
549 483
550 484 @property
551 485 def groups_with_parents(self):
552 486 groups = []
553 487 if self.group is None:
554 488 return groups
555 489
556 490 cur_gr = self.group
557 491 groups.insert(0, cur_gr)
558 492 while 1:
559 493 gr = getattr(cur_gr, 'parent_group', None)
560 494 cur_gr = cur_gr.parent_group
561 495 if gr is None:
562 496 break
563 497 groups.insert(0, gr)
564 498
565 499 return groups
566 500
567 501 @property
568 502 def groups_and_repo(self):
569 503 return self.groups_with_parents, self.just_name
570 504
571 505 @LazyProperty
572 506 def repo_path(self):
573 507 """
574 508 Returns base full path for that repository means where it actually
575 509 exists on a filesystem
576 510 """
577 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
511 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
578 512 Repository.url_sep())
579 513 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
580 514 return q.one().ui_value
581 515
582 516 @property
583 517 def repo_full_path(self):
584 518 p = [self.repo_path]
585 519 # we need to split the name by / since this is how we store the
586 520 # names in the database, but that eventually needs to be converted
587 521 # into a valid system path
588 522 p += self.repo_name.split(Repository.url_sep())
589 523 return os.path.join(*p)
590 524
591 525 def get_new_name(self, repo_name):
592 526 """
593 527 returns new full repository name based on assigned group and new new
594 528
595 529 :param group_name:
596 530 """
597 531 path_prefix = self.group.full_path_splitted if self.group else []
598 532 return Repository.url_sep().join(path_prefix + [repo_name])
599 533
600 534 @property
601 535 def _ui(self):
602 536 """
603 537 Creates an db based ui object for this repository
604 538 """
605 539 from mercurial import ui
606 540 from mercurial import config
607 541 baseui = ui.ui()
608 542
609 543 #clean the baseui object
610 544 baseui._ocfg = config.config()
611 545 baseui._ucfg = config.config()
612 546 baseui._tcfg = config.config()
613 547
614 548
615 549 ret = RhodeCodeUi.query()\
616 550 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
617 551
618 552 hg_ui = ret
619 553 for ui_ in hg_ui:
620 554 if ui_.ui_active:
621 555 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
622 556 ui_.ui_key, ui_.ui_value)
623 557 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
624 558
625 559 return baseui
626 560
627 561 @classmethod
628 562 def is_valid(cls, repo_name):
629 563 """
630 564 returns True if given repo name is a valid filesystem repository
631 565
632 566 @param cls:
633 567 @param repo_name:
634 568 """
635 569 from rhodecode.lib.utils import is_valid_repo
636 570
637 571 return is_valid_repo(repo_name, cls.base_path())
638 572
639 573
640 574 #==========================================================================
641 575 # SCM PROPERTIES
642 576 #==========================================================================
643 577
644 578 def get_changeset(self, rev):
645 579 return get_changeset_safe(self.scm_instance, rev)
646 580
647 581 @property
648 582 def tip(self):
649 583 return self.get_changeset('tip')
650 584
651 585 @property
652 586 def author(self):
653 587 return self.tip.author
654 588
655 589 @property
656 590 def last_change(self):
657 591 return self.scm_instance.last_change
658 592
659 593 #==========================================================================
660 594 # SCM CACHE INSTANCE
661 595 #==========================================================================
662 596
663 597 @property
664 598 def invalidate(self):
665 599 return CacheInvalidation.invalidate(self.repo_name)
666 600
667 601 def set_invalidate(self):
668 602 """
669 603 set a cache for invalidation for this instance
670 604 """
671 605 CacheInvalidation.set_invalidate(self.repo_name)
672 606
673 607 @LazyProperty
674 608 def scm_instance(self):
675 609 return self.__get_instance()
676 610
677 611 @property
678 612 def scm_instance_cached(self):
679 613 @cache_region('long_term')
680 614 def _c(repo_name):
681 615 return self.__get_instance()
682 616 rn = self.repo_name
683 617 log.debug('Getting cached instance of repo')
684 618 inv = self.invalidate
685 619 if inv is not None:
686 620 region_invalidate(_c, None, rn)
687 621 # update our cache
688 622 CacheInvalidation.set_valid(inv.cache_key)
689 623 return _c(rn)
690 624
691 625 def __get_instance(self):
692 626 repo_full_path = self.repo_full_path
693 627 try:
694 628 alias = get_scm(repo_full_path)[0]
695 629 log.debug('Creating instance of %s repository', alias)
696 630 backend = get_backend(alias)
697 631 except VCSError:
698 632 log.error(traceback.format_exc())
699 633 log.error('Perhaps this repository is in db and not in '
700 634 'filesystem run rescan repositories with '
701 635 '"destroy old data " option from admin panel')
702 636 return
703 637
704 638 if alias == 'hg':
705 639 repo = backend(safe_str(repo_full_path), create=False,
706 640 baseui=self._ui)
707 641 # skip hidden web repository
708 642 if repo._get_hidden():
709 643 return
710 644 else:
711 645 repo = backend(repo_full_path, create=False)
712 646
713 647 return repo
714 648
715 649
716 650 class RepoGroup(Base, BaseModel):
717 651 __tablename__ = 'groups'
718 652 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
719 653 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
720 654 __mapper_args__ = {'order_by':'group_name'}
721 655
722 656 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
723 657 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
724 658 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
725 659 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
726 660
727 661 parent_group = relationship('RepoGroup', remote_side=group_id)
728 662
729 663
730 664 def __init__(self, group_name='', parent_group=None):
731 665 self.group_name = group_name
732 666 self.parent_group = parent_group
733 667
734 668 def __repr__(self):
735 669 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
736 670 self.group_name)
737 671
738 672 @classmethod
739 673 def groups_choices(cls):
740 674 from webhelpers.html import literal as _literal
741 675 repo_groups = [('', '')]
742 676 sep = ' &raquo; '
743 677 _name = lambda k: _literal(sep.join(k))
744 678
745 679 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
746 680 for x in cls.query().all()])
747 681
748 682 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
749 683 return repo_groups
750 684
751 685 @classmethod
752 686 def url_sep(cls):
753 687 return '/'
754 688
755 689 @classmethod
756 690 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
757 691 if case_insensitive:
758 692 gr = cls.query()\
759 693 .filter(cls.group_name.ilike(group_name))
760 694 else:
761 695 gr = cls.query()\
762 696 .filter(cls.group_name == group_name)
763 697 if cache:
764 698 gr = gr.options(FromCache("sql_cache_short",
765 699 "get_group_%s" % group_name))
766 700 return gr.scalar()
767 701
768 702 @property
769 703 def parents(self):
770 704 parents_recursion_limit = 5
771 705 groups = []
772 706 if self.parent_group is None:
773 707 return groups
774 708 cur_gr = self.parent_group
775 709 groups.insert(0, cur_gr)
776 710 cnt = 0
777 711 while 1:
778 712 cnt += 1
779 713 gr = getattr(cur_gr, 'parent_group', None)
780 714 cur_gr = cur_gr.parent_group
781 715 if gr is None:
782 716 break
783 717 if cnt == parents_recursion_limit:
784 718 # this will prevent accidental infinit loops
785 719 log.error('group nested more than %s' %
786 720 parents_recursion_limit)
787 721 break
788 722
789 723 groups.insert(0, gr)
790 724 return groups
791 725
792 726 @property
793 727 def children(self):
794 728 return RepoGroup.query().filter(RepoGroup.parent_group == self)
795 729
796 730 @property
797 731 def name(self):
798 732 return self.group_name.split(RepoGroup.url_sep())[-1]
799 733
800 734 @property
801 735 def full_path(self):
802 736 return self.group_name
803 737
804 738 @property
805 739 def full_path_splitted(self):
806 740 return self.group_name.split(RepoGroup.url_sep())
807 741
808 742 @property
809 743 def repositories(self):
810 744 return Repository.query().filter(Repository.group == self)
811 745
812 746 @property
813 747 def repositories_recursive_count(self):
814 748 cnt = self.repositories.count()
815 749
816 750 def children_count(group):
817 751 cnt = 0
818 752 for child in group.children:
819 753 cnt += child.repositories.count()
820 754 cnt += children_count(child)
821 755 return cnt
822 756
823 757 return cnt + children_count(self)
824 758
825 759
826 760 def get_new_name(self, group_name):
827 761 """
828 762 returns new full group name based on parent and new name
829 763
830 764 :param group_name:
831 765 """
832 766 path_prefix = (self.parent_group.full_path_splitted if
833 767 self.parent_group else [])
834 768 return RepoGroup.url_sep().join(path_prefix + [group_name])
835 769
836 770
837 771 class Permission(Base, BaseModel):
838 772 __tablename__ = 'permissions'
839 773 __table_args__ = {'extend_existing':True}
840 774 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
841 775 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
842 776 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
843 777
844 778 def __repr__(self):
845 779 return "<%s('%s:%s')>" % (self.__class__.__name__,
846 780 self.permission_id, self.permission_name)
847 781
848 782 @classmethod
849 783 def get_by_key(cls, key):
850 784 return cls.query().filter(cls.permission_name == key).scalar()
851 785
852 786 @classmethod
853 787 def get_default_perms(cls, default_user_id):
854 q = Session().query(UserRepoToPerm, Repository, cls)\
788 q = Session.query(UserRepoToPerm, Repository, cls)\
855 789 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
856 790 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
857 791 .filter(UserRepoToPerm.user_id == default_user_id)
858 792
859 793 return q.all()
860 794
861 795
862 796 class UserRepoToPerm(Base, BaseModel):
863 797 __tablename__ = 'repo_to_perm'
864 798 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
865 799 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
866 800 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
867 801 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
868 802 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
869 803
870 804 user = relationship('User')
871 805 permission = relationship('Permission')
872 806 repository = relationship('Repository')
873 807
874 808 @classmethod
875 809 def create(cls, user, repository, permission):
876 810 n = cls()
877 811 n.user = user
878 812 n.repository = repository
879 813 n.permission = permission
880 Session().add(n)
814 Session.add(n)
881 815 return n
882 816
883 817 def __repr__(self):
884 818 return '<user:%s => %s >' % (self.user, self.repository)
885 819
886 820 class UserToPerm(Base, BaseModel):
887 821 __tablename__ = 'user_to_perm'
888 822 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
889 823 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
890 824 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
891 825 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
892 826
893 827 user = relationship('User')
894 828 permission = relationship('Permission', lazy='joined')
895 829
896 @classmethod
897 def has_perm(cls, user_id, perm):
898 if not isinstance(perm, Permission):
899 raise Exception('perm needs to be an instance of Permission class')
900
901 return cls.query().filter(cls.user_id == user_id)\
902 .filter(cls.permission == perm).scalar() is not None
903
904 @classmethod
905 def grant_perm(cls, user_id, perm):
906 if not isinstance(perm, Permission):
907 raise Exception('perm needs to be an instance of Permission class')
908
909 new = cls()
910 new.user_id = user_id
911 new.permission = perm
912 try:
913 Session().add(new)
914 Session().commit()
915 except:
916 Session().rollback()
917
918
919 @classmethod
920 def revoke_perm(cls, user_id, perm):
921 if not isinstance(perm, Permission):
922 raise Exception('perm needs to be an instance of Permission class')
923
924 try:
925 obj = cls.query().filter(cls.user_id == user_id)\
926 .filter(cls.permission == perm).one()
927 Session().delete(obj)
928 Session().commit()
929 except:
930 Session().rollback()
931 830
932 831 class UsersGroupRepoToPerm(Base, BaseModel):
933 832 __tablename__ = 'users_group_repo_to_perm'
934 833 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
935 834 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
936 835 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
937 836 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
938 837 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
939 838
940 839 users_group = relationship('UsersGroup')
941 840 permission = relationship('Permission')
942 841 repository = relationship('Repository')
943 842
944 843 @classmethod
945 844 def create(cls, users_group, repository, permission):
946 845 n = cls()
947 846 n.users_group = users_group
948 847 n.repository = repository
949 848 n.permission = permission
950 Session().add(n)
849 Session.add(n)
951 850 return n
952 851
953 852 def __repr__(self):
954 853 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
955 854
956 855 class UsersGroupToPerm(Base, BaseModel):
957 856 __tablename__ = 'users_group_to_perm'
958 857 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
959 858 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
960 859 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
961 860
962 861 users_group = relationship('UsersGroup')
963 862 permission = relationship('Permission')
964 863
965 864
966 @classmethod
967 def has_perm(cls, users_group_id, perm):
968 if not isinstance(perm, Permission):
969 raise Exception('perm needs to be an instance of Permission class')
970
971 return cls.query().filter(cls.users_group_id ==
972 users_group_id)\
973 .filter(cls.permission == perm)\
974 .scalar() is not None
975
976 @classmethod
977 def grant_perm(cls, users_group_id, perm):
978 if not isinstance(perm, Permission):
979 raise Exception('perm needs to be an instance of Permission class')
980
981 new = cls()
982 new.users_group_id = users_group_id
983 new.permission = perm
984 try:
985 Session().add(new)
986 Session().commit()
987 except:
988 Session().rollback()
989
990
991 @classmethod
992 def revoke_perm(cls, users_group_id, perm):
993 if not isinstance(perm, Permission):
994 raise Exception('perm needs to be an instance of Permission class')
995
996 try:
997 obj = cls.query().filter(cls.users_group_id == users_group_id)\
998 .filter(cls.permission == perm).one()
999 Session().delete(obj)
1000 Session().commit()
1001 except:
1002 Session().rollback()
1003
1004
1005 865 class UserRepoGroupToPerm(Base, BaseModel):
1006 866 __tablename__ = 'group_to_perm'
1007 867 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
1008 868
1009 869 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1010 870 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1011 871 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1012 872 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1013 873
1014 874 user = relationship('User')
1015 875 permission = relationship('Permission')
1016 876 group = relationship('RepoGroup')
1017 877
1018 878 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1019 879 __tablename__ = 'users_group_repo_group_to_perm'
1020 880 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
1021 881
1022 882 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)
1023 883 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1024 884 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1025 885 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1026 886
1027 887 users_group = relationship('UsersGroup')
1028 888 permission = relationship('Permission')
1029 889 group = relationship('RepoGroup')
1030 890
1031 891 class Statistics(Base, BaseModel):
1032 892 __tablename__ = 'statistics'
1033 893 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
1034 894 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1035 895 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1036 896 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1037 897 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1038 898 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1039 899 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1040 900
1041 901 repository = relationship('Repository', single_parent=True)
1042 902
1043 903 class UserFollowing(Base, BaseModel):
1044 904 __tablename__ = 'user_followings'
1045 905 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
1046 906 UniqueConstraint('user_id', 'follows_user_id')
1047 907 , {'extend_existing':True})
1048 908
1049 909 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1050 910 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1051 911 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1052 912 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1053 913 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1054 914
1055 915 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1056 916
1057 917 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1058 918 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1059 919
1060 920
1061 921 @classmethod
1062 922 def get_repo_followers(cls, repo_id):
1063 923 return cls.query().filter(cls.follows_repo_id == repo_id)
1064 924
1065 925 class CacheInvalidation(Base, BaseModel):
1066 926 __tablename__ = 'cache_invalidation'
1067 927 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
1068 928 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1069 929 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1070 930 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1071 931 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1072 932
1073 933
1074 934 def __init__(self, cache_key, cache_args=''):
1075 935 self.cache_key = cache_key
1076 936 self.cache_args = cache_args
1077 937 self.cache_active = False
1078 938
1079 939 def __repr__(self):
1080 940 return "<%s('%s:%s')>" % (self.__class__.__name__,
1081 941 self.cache_id, self.cache_key)
1082 942
1083 943 @classmethod
1084 944 def invalidate(cls, key):
1085 945 """
1086 946 Returns Invalidation object if this given key should be invalidated
1087 947 None otherwise. `cache_active = False` means that this cache
1088 948 state is not valid and needs to be invalidated
1089 949
1090 950 :param key:
1091 951 """
1092 952 return cls.query()\
1093 953 .filter(CacheInvalidation.cache_key == key)\
1094 954 .filter(CacheInvalidation.cache_active == False)\
1095 955 .scalar()
1096 956
1097 957 @classmethod
1098 958 def set_invalidate(cls, key):
1099 959 """
1100 960 Mark this Cache key for invalidation
1101 961
1102 962 :param key:
1103 963 """
1104 964
1105 965 log.debug('marking %s for invalidation' % key)
1106 inv_obj = Session().query(cls)\
966 inv_obj = Session.query(cls)\
1107 967 .filter(cls.cache_key == key).scalar()
1108 968 if inv_obj:
1109 969 inv_obj.cache_active = False
1110 970 else:
1111 971 log.debug('cache key not found in invalidation db -> creating one')
1112 972 inv_obj = CacheInvalidation(key)
1113 973
1114 974 try:
1115 Session().add(inv_obj)
1116 Session().commit()
975 Session.add(inv_obj)
976 Session.commit()
1117 977 except Exception:
1118 978 log.error(traceback.format_exc())
1119 Session().rollback()
979 Session.rollback()
1120 980
1121 981 @classmethod
1122 982 def set_valid(cls, key):
1123 983 """
1124 984 Mark this cache key as active and currently cached
1125 985
1126 986 :param key:
1127 987 """
1128 988 inv_obj = CacheInvalidation.query()\
1129 989 .filter(CacheInvalidation.cache_key == key).scalar()
1130 990 inv_obj.cache_active = True
1131 Session().add(inv_obj)
1132 Session().commit()
991 Session.add(inv_obj)
992 Session.commit()
1133 993
1134 994
1135 995 class ChangesetComment(Base, BaseModel):
1136 996 __tablename__ = 'changeset_comments'
1137 997 __table_args__ = ({'extend_existing':True},)
1138 998 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1139 999 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1140 1000 revision = Column('revision', String(40), nullable=False)
1141 1001 line_no = Column('line_no', Unicode(10), nullable=True)
1142 1002 f_path = Column('f_path', Unicode(1000), nullable=True)
1143 1003 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1144 1004 text = Column('text', Unicode(25000), nullable=False)
1145 1005 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1146 1006
1147 1007 author = relationship('User', lazy='joined')
1148 1008 repo = relationship('Repository')
1149 1009
1150 1010
1151 1011 @classmethod
1152 1012 def get_users(cls, revision):
1153 1013 """
1154 1014 Returns user associated with this changesetComment. ie those
1155 1015 who actually commented
1156 1016
1157 1017 :param cls:
1158 1018 :param revision:
1159 1019 """
1160 return Session().query(User)\
1020 return Session.query(User)\
1161 1021 .filter(cls.revision == revision)\
1162 1022 .join(ChangesetComment.author).all()
1163 1023
1164 1024
1165 1025 class Notification(Base, BaseModel):
1166 1026 __tablename__ = 'notifications'
1167 1027 __table_args__ = ({'extend_existing':True})
1168 1028
1169 1029 TYPE_CHANGESET_COMMENT = u'cs_comment'
1170 1030 TYPE_MESSAGE = u'message'
1171 1031 TYPE_MENTION = u'mention'
1172 1032 TYPE_REGISTRATION = u'registration'
1173 1033
1174 1034 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1175 1035 subject = Column('subject', Unicode(512), nullable=True)
1176 1036 body = Column('body', Unicode(50000), nullable=True)
1177 1037 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1178 1038 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1179 1039 type_ = Column('type', Unicode(256))
1180 1040
1181 1041 created_by_user = relationship('User')
1182 1042 notifications_to_users = relationship('UserNotification', lazy='joined',
1183 1043 cascade="all, delete, delete-orphan")
1184 1044
1185 1045 @property
1186 1046 def recipients(self):
1187 1047 return [x.user for x in UserNotification.query()\
1188 1048 .filter(UserNotification.notification == self).all()]
1189 1049
1190 1050 @classmethod
1191 1051 def create(cls, created_by, subject, body, recipients, type_=None):
1192 1052 if type_ is None:
1193 1053 type_ = Notification.TYPE_MESSAGE
1194 1054
1195 1055 notification = cls()
1196 1056 notification.created_by_user = created_by
1197 1057 notification.subject = subject
1198 1058 notification.body = body
1199 1059 notification.type_ = type_
1200 1060 notification.created_on = datetime.datetime.now()
1201 1061
1202 1062 for u in recipients:
1203 1063 assoc = UserNotification()
1204 1064 assoc.notification = notification
1205 1065 u.notifications.append(assoc)
1206 Session().add(notification)
1066 Session.add(notification)
1207 1067 return notification
1208 1068
1209 1069 @property
1210 1070 def description(self):
1211 1071 from rhodecode.model.notification import NotificationModel
1212 1072 return NotificationModel().make_description(self)
1213 1073
1214 1074 class UserNotification(Base, BaseModel):
1215 1075 __tablename__ = 'user_to_notification'
1216 1076 __table_args__ = (UniqueConstraint('user_id', 'notification_id'),
1217 1077 {'extend_existing':True})
1218 1078 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), primary_key=True)
1219 1079 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1220 1080 read = Column('read', Boolean, default=False)
1221 1081 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1222 1082
1223 1083 user = relationship('User', lazy="joined")
1224 1084 notification = relationship('Notification', lazy="joined",
1225 1085 order_by=lambda:Notification.created_on.desc(),)
1226 1086
1227 1087 def mark_as_read(self):
1228 1088 self.read = True
1229 Session().add(self)
1089 Session.add(self)
1230 1090
1231 1091 class DbMigrateVersion(Base, BaseModel):
1232 1092 __tablename__ = 'db_migrate_version'
1233 1093 __table_args__ = {'extend_existing':True}
1234 1094 repository_id = Column('repository_id', String(250), primary_key=True)
1235 1095 repository_path = Column('repository_path', Text)
1236 1096 version = Column('version', Integer)
1237 1097
@@ -1,684 +1,682 b''
1 1 """ this is forms validation classes
2 2 http://formencode.org/module-formencode.validators.html
3 3 for list off all availible validators
4 4
5 5 we can create our own validators
6 6
7 7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 8 pre_validators [] These validators will be applied before the schema
9 9 chained_validators [] These validators will be applied after the schema
10 10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
13 13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
14 14
15 15
16 16 <name> = formencode.validators.<name of validator>
17 17 <name> must equal form name
18 18 list=[1,2,3,4,5]
19 19 for SELECT use formencode.All(OneOf(list), Int())
20 20
21 21 """
22 22 import os
23 23 import re
24 24 import logging
25 25 import traceback
26 26
27 27 import formencode
28 28 from formencode import All
29 29 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
30 30 Email, Bool, StringBoolean, Set
31 31
32 32 from pylons.i18n.translation import _
33 33 from webhelpers.pylonslib.secure_form import authentication_token
34 34
35 35 from rhodecode.config.routing import ADMIN_PREFIX
36 36 from rhodecode.lib.utils import repo_name_slug
37 37 from rhodecode.lib.auth import authenticate, get_crypt_password
38 38 from rhodecode.lib.exceptions import LdapImportError
39 from rhodecode.model.user import UserModel
40 from rhodecode.model.repo import RepoModel
41 from rhodecode.model.db import User, UsersGroup, RepoGroup
39 from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository
42 40 from rhodecode import BACKENDS
43 41
44 42 log = logging.getLogger(__name__)
45 43
46 44 #this is needed to translate the messages using _() in validators
47 45 class State_obj(object):
48 46 _ = staticmethod(_)
49 47
50 48 #==============================================================================
51 49 # VALIDATORS
52 50 #==============================================================================
53 51 class ValidAuthToken(formencode.validators.FancyValidator):
54 52 messages = {'invalid_token':_('Token mismatch')}
55 53
56 54 def validate_python(self, value, state):
57 55
58 56 if value != authentication_token():
59 57 raise formencode.Invalid(self.message('invalid_token', state,
60 58 search_number=value), value, state)
61 59
62 60 def ValidUsername(edit, old_data):
63 61 class _ValidUsername(formencode.validators.FancyValidator):
64 62
65 63 def validate_python(self, value, state):
66 64 if value in ['default', 'new_user']:
67 65 raise formencode.Invalid(_('Invalid username'), value, state)
68 66 #check if user is unique
69 67 old_un = None
70 68 if edit:
71 old_un = UserModel().get(old_data.get('user_id')).username
69 old_un = User.get(old_data.get('user_id')).username
72 70
73 71 if old_un != value or not edit:
74 72 if User.get_by_username(value, case_insensitive=True):
75 73 raise formencode.Invalid(_('This username already '
76 74 'exists') , value, state)
77 75
78 76 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
79 77 raise formencode.Invalid(_('Username may only contain '
80 78 'alphanumeric characters '
81 79 'underscores, periods or dashes '
82 80 'and must begin with alphanumeric '
83 81 'character'), value, state)
84 82
85 83 return _ValidUsername
86 84
87 85
88 86 def ValidUsersGroup(edit, old_data):
89 87
90 88 class _ValidUsersGroup(formencode.validators.FancyValidator):
91 89
92 90 def validate_python(self, value, state):
93 91 if value in ['default']:
94 92 raise formencode.Invalid(_('Invalid group name'), value, state)
95 93 #check if group is unique
96 94 old_ugname = None
97 95 if edit:
98 96 old_ugname = UsersGroup.get(
99 97 old_data.get('users_group_id')).users_group_name
100 98
101 99 if old_ugname != value or not edit:
102 100 if UsersGroup.get_by_group_name(value, cache=False,
103 101 case_insensitive=True):
104 102 raise formencode.Invalid(_('This users group '
105 103 'already exists') , value,
106 104 state)
107 105
108 106
109 107 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
110 108 raise formencode.Invalid(_('RepoGroup name may only contain '
111 109 'alphanumeric characters '
112 110 'underscores, periods or dashes '
113 111 'and must begin with alphanumeric '
114 112 'character'), value, state)
115 113
116 114 return _ValidUsersGroup
117 115
118 116
119 117 def ValidReposGroup(edit, old_data):
120 118 class _ValidReposGroup(formencode.validators.FancyValidator):
121 119
122 120 def validate_python(self, value, state):
123 121 # TODO WRITE VALIDATIONS
124 122 group_name = value.get('group_name')
125 123 group_parent_id = value.get('group_parent_id')
126 124
127 125 # slugify repo group just in case :)
128 126 slug = repo_name_slug(group_name)
129 127
130 128 # check for parent of self
131 129 parent_of_self = lambda:(old_data['group_id'] == int(group_parent_id)
132 130 if group_parent_id else False)
133 131 if edit and parent_of_self():
134 132 e_dict = {'group_parent_id':_('Cannot assign this group '
135 133 'as parent')}
136 134 raise formencode.Invalid('', value, state,
137 135 error_dict=e_dict)
138 136
139 137 old_gname = None
140 138 if edit:
141 139 old_gname = RepoGroup.get(old_data.get('group_id')).group_name
142 140
143 141 if old_gname != group_name or not edit:
144 142
145 143 # check filesystem
146 144 gr = RepoGroup.query().filter(RepoGroup.group_name == slug)\
147 145 .filter(RepoGroup.group_parent_id == group_parent_id).scalar()
148 146
149 147 if gr:
150 148 e_dict = {'group_name':_('This group already exists')}
151 149 raise formencode.Invalid('', value, state,
152 150 error_dict=e_dict)
153 151
154 152 return _ValidReposGroup
155 153
156 154 class ValidPassword(formencode.validators.FancyValidator):
157 155
158 156 def to_python(self, value, state):
159 157
160 158 if value:
161 159
162 160 if value.get('password'):
163 161 try:
164 162 value['password'] = get_crypt_password(value['password'])
165 163 except UnicodeEncodeError:
166 164 e_dict = {'password':_('Invalid characters in password')}
167 165 raise formencode.Invalid('', value, state, error_dict=e_dict)
168 166
169 167 if value.get('password_confirmation'):
170 168 try:
171 169 value['password_confirmation'] = \
172 170 get_crypt_password(value['password_confirmation'])
173 171 except UnicodeEncodeError:
174 172 e_dict = {'password_confirmation':_('Invalid characters in password')}
175 173 raise formencode.Invalid('', value, state, error_dict=e_dict)
176 174
177 175 if value.get('new_password'):
178 176 try:
179 177 value['new_password'] = \
180 178 get_crypt_password(value['new_password'])
181 179 except UnicodeEncodeError:
182 180 e_dict = {'new_password':_('Invalid characters in password')}
183 181 raise formencode.Invalid('', value, state, error_dict=e_dict)
184 182
185 183 return value
186 184
187 185 class ValidPasswordsMatch(formencode.validators.FancyValidator):
188 186
189 187 def validate_python(self, value, state):
190 188
191 189 pass_val = value.get('password') or value.get('new_password')
192 190 if pass_val != value['password_confirmation']:
193 191 e_dict = {'password_confirmation':
194 192 _('Passwords do not match')}
195 193 raise formencode.Invalid('', value, state, error_dict=e_dict)
196 194
197 195 class ValidAuth(formencode.validators.FancyValidator):
198 196 messages = {
199 197 'invalid_password':_('invalid password'),
200 198 'invalid_login':_('invalid user name'),
201 199 'disabled_account':_('Your account is disabled')
202 200 }
203 201
204 202 # error mapping
205 203 e_dict = {'username':messages['invalid_login'],
206 204 'password':messages['invalid_password']}
207 205 e_dict_disable = {'username':messages['disabled_account']}
208 206
209 207 def validate_python(self, value, state):
210 208 password = value['password']
211 209 username = value['username']
212 210 user = User.get_by_username(username)
213 211
214 212 if authenticate(username, password):
215 213 return value
216 214 else:
217 215 if user and user.active is False:
218 216 log.warning('user %s is disabled', username)
219 217 raise formencode.Invalid(self.message('disabled_account',
220 218 state=State_obj),
221 219 value, state,
222 220 error_dict=self.e_dict_disable)
223 221 else:
224 222 log.warning('user %s not authenticated', username)
225 223 raise formencode.Invalid(self.message('invalid_password',
226 224 state=State_obj), value, state,
227 225 error_dict=self.e_dict)
228 226
229 227 class ValidRepoUser(formencode.validators.FancyValidator):
230 228
231 229 def to_python(self, value, state):
232 230 try:
233 231 User.query().filter(User.active == True)\
234 232 .filter(User.username == value).one()
235 233 except Exception:
236 234 raise formencode.Invalid(_('This username is not valid'),
237 235 value, state)
238 236 return value
239 237
240 238 def ValidRepoName(edit, old_data):
241 239 class _ValidRepoName(formencode.validators.FancyValidator):
242 240 def to_python(self, value, state):
243 241
244 242 repo_name = value.get('repo_name')
245 243
246 244 slug = repo_name_slug(repo_name)
247 245 if slug in [ADMIN_PREFIX, '']:
248 246 e_dict = {'repo_name': _('This repository name is disallowed')}
249 247 raise formencode.Invalid('', value, state, error_dict=e_dict)
250 248
251 249
252 250 if value.get('repo_group'):
253 251 gr = RepoGroup.get(value.get('repo_group'))
254 252 group_path = gr.full_path
255 253 # value needs to be aware of group name in order to check
256 254 # db key This is an actual just the name to store in the
257 255 # database
258 256 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
259 257
260 258 else:
261 259 group_path = ''
262 260 repo_name_full = repo_name
263 261
264 262
265 263 value['repo_name_full'] = repo_name_full
266 264 rename = old_data.get('repo_name') != repo_name_full
267 265 create = not edit
268 266 if rename or create:
269 267
270 268 if group_path != '':
271 if RepoModel().get_by_repo_name(repo_name_full,):
269 if Repository.get_by_repo_name(repo_name_full):
272 270 e_dict = {'repo_name':_('This repository already '
273 271 'exists in a group "%s"') %
274 272 gr.group_name}
275 273 raise formencode.Invalid('', value, state,
276 274 error_dict=e_dict)
277 275 elif RepoGroup.get_by_group_name(repo_name_full):
278 276 e_dict = {'repo_name':_('There is a group with this'
279 277 ' name already "%s"') %
280 278 repo_name_full}
281 279 raise formencode.Invalid('', value, state,
282 280 error_dict=e_dict)
283 281
284 elif RepoModel().get_by_repo_name(repo_name_full):
282 elif Repository.get_by_repo_name(repo_name_full):
285 283 e_dict = {'repo_name':_('This repository '
286 284 'already exists')}
287 285 raise formencode.Invalid('', value, state,
288 286 error_dict=e_dict)
289 287
290 288 return value
291 289
292 290 return _ValidRepoName
293 291
294 292 def ValidForkName(*args, **kwargs):
295 293 return ValidRepoName(*args, **kwargs)
296 294
297 295
298 296 def SlugifyName():
299 297 class _SlugifyName(formencode.validators.FancyValidator):
300 298
301 299 def to_python(self, value, state):
302 300 return repo_name_slug(value)
303 301
304 302 return _SlugifyName
305 303
306 304 def ValidCloneUri():
307 305 from mercurial.httprepo import httprepository, httpsrepository
308 306 from rhodecode.lib.utils import make_ui
309 307
310 308 class _ValidCloneUri(formencode.validators.FancyValidator):
311 309
312 310 def to_python(self, value, state):
313 311 if not value:
314 312 pass
315 313 elif value.startswith('https'):
316 314 try:
317 315 httpsrepository(make_ui('db'), value).capabilities
318 316 except Exception, e:
319 317 log.error(traceback.format_exc())
320 318 raise formencode.Invalid(_('invalid clone url'), value,
321 319 state)
322 320 elif value.startswith('http'):
323 321 try:
324 322 httprepository(make_ui('db'), value).capabilities
325 323 except Exception, e:
326 324 log.error(traceback.format_exc())
327 325 raise formencode.Invalid(_('invalid clone url'), value,
328 326 state)
329 327 else:
330 328 raise formencode.Invalid(_('Invalid clone url, provide a '
331 329 'valid clone http\s url'), value,
332 330 state)
333 331 return value
334 332
335 333 return _ValidCloneUri
336 334
337 335 def ValidForkType(old_data):
338 336 class _ValidForkType(formencode.validators.FancyValidator):
339 337
340 338 def to_python(self, value, state):
341 339 if old_data['repo_type'] != value:
342 340 raise formencode.Invalid(_('Fork have to be the same '
343 341 'type as original'), value, state)
344 342
345 343 return value
346 344 return _ValidForkType
347 345
348 346 class ValidPerms(formencode.validators.FancyValidator):
349 347 messages = {'perm_new_member_name':_('This username or users group name'
350 348 ' is not valid')}
351 349
352 350 def to_python(self, value, state):
353 351 perms_update = []
354 352 perms_new = []
355 353 #build a list of permission to update and new permission to create
356 354 for k, v in value.items():
357 355 #means new added member to permissions
358 356 if k.startswith('perm_new_member'):
359 357 new_perm = value.get('perm_new_member', False)
360 358 new_member = value.get('perm_new_member_name', False)
361 359 new_type = value.get('perm_new_member_type')
362 360
363 361 if new_member and new_perm:
364 362 if (new_member, new_perm, new_type) not in perms_new:
365 363 perms_new.append((new_member, new_perm, new_type))
366 364 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
367 365 member = k[7:]
368 366 t = {'u':'user',
369 367 'g':'users_group'}[k[0]]
370 368 if member == 'default':
371 369 if value['private']:
372 370 #set none for default when updating to private repo
373 371 v = 'repository.none'
374 372 perms_update.append((member, v, t))
375 373
376 374 value['perms_updates'] = perms_update
377 375 value['perms_new'] = perms_new
378 376
379 377 #update permissions
380 378 for k, v, t in perms_new:
381 379 try:
382 380 if t is 'user':
383 381 self.user_db = User.query()\
384 382 .filter(User.active == True)\
385 383 .filter(User.username == k).one()
386 384 if t is 'users_group':
387 385 self.user_db = UsersGroup.query()\
388 386 .filter(UsersGroup.users_group_active == True)\
389 387 .filter(UsersGroup.users_group_name == k).one()
390 388
391 389 except Exception:
392 390 msg = self.message('perm_new_member_name',
393 391 state=State_obj)
394 392 raise formencode.Invalid(msg, value, state,
395 393 error_dict={'perm_new_member_name':msg})
396 394 return value
397 395
398 396 class ValidSettings(formencode.validators.FancyValidator):
399 397
400 398 def to_python(self, value, state):
401 399 #settings form can't edit user
402 400 if value.has_key('user'):
403 401 del['value']['user']
404 402
405 403 return value
406 404
407 405 class ValidPath(formencode.validators.FancyValidator):
408 406 def to_python(self, value, state):
409 407
410 408 if not os.path.isdir(value):
411 409 msg = _('This is not a valid path')
412 410 raise formencode.Invalid(msg, value, state,
413 411 error_dict={'paths_root_path':msg})
414 412 return value
415 413
416 414 def UniqSystemEmail(old_data):
417 415 class _UniqSystemEmail(formencode.validators.FancyValidator):
418 416 def to_python(self, value, state):
419 417 value = value.lower()
420 418 if old_data.get('email') != value:
421 419 user = User.query().filter(User.email == value).scalar()
422 420 if user:
423 421 raise formencode.Invalid(
424 422 _("This e-mail address is already taken"),
425 423 value, state)
426 424 return value
427 425
428 426 return _UniqSystemEmail
429 427
430 428 class ValidSystemEmail(formencode.validators.FancyValidator):
431 429 def to_python(self, value, state):
432 430 value = value.lower()
433 431 user = User.query().filter(User.email == value).scalar()
434 432 if user is None:
435 433 raise formencode.Invalid(_("This e-mail address doesn't exist.") ,
436 434 value, state)
437 435
438 436 return value
439 437
440 438 class LdapLibValidator(formencode.validators.FancyValidator):
441 439
442 440 def to_python(self, value, state):
443 441
444 442 try:
445 443 import ldap
446 444 except ImportError:
447 445 raise LdapImportError
448 446 return value
449 447
450 448 class AttrLoginValidator(formencode.validators.FancyValidator):
451 449
452 450 def to_python(self, value, state):
453 451
454 452 if not value or not isinstance(value, (str, unicode)):
455 453 raise formencode.Invalid(_("The LDAP Login attribute of the CN "
456 454 "must be specified - this is the name "
457 455 "of the attribute that is equivalent "
458 456 "to 'username'"),
459 457 value, state)
460 458
461 459 return value
462 460
463 461 #===============================================================================
464 462 # FORMS
465 463 #===============================================================================
466 464 class LoginForm(formencode.Schema):
467 465 allow_extra_fields = True
468 466 filter_extra_fields = True
469 467 username = UnicodeString(
470 468 strip=True,
471 469 min=1,
472 470 not_empty=True,
473 471 messages={
474 472 'empty':_('Please enter a login'),
475 473 'tooShort':_('Enter a value %(min)i characters long or more')}
476 474 )
477 475
478 476 password = UnicodeString(
479 477 strip=True,
480 478 min=3,
481 479 not_empty=True,
482 480 messages={
483 481 'empty':_('Please enter a password'),
484 482 'tooShort':_('Enter %(min)i characters or more')}
485 483 )
486 484
487 485 chained_validators = [ValidAuth]
488 486
489 487 def UserForm(edit=False, old_data={}):
490 488 class _UserForm(formencode.Schema):
491 489 allow_extra_fields = True
492 490 filter_extra_fields = True
493 491 username = All(UnicodeString(strip=True, min=1, not_empty=True),
494 492 ValidUsername(edit, old_data))
495 493 if edit:
496 494 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
497 495 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=False))
498 496 admin = StringBoolean(if_missing=False)
499 497 else:
500 498 password = All(UnicodeString(strip=True, min=6, not_empty=True))
501 499 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=False))
502 500
503 501 active = StringBoolean(if_missing=False)
504 502 name = UnicodeString(strip=True, min=1, not_empty=True)
505 503 lastname = UnicodeString(strip=True, min=1, not_empty=True)
506 504 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
507 505
508 506 chained_validators = [ValidPasswordsMatch, ValidPassword]
509 507
510 508 return _UserForm
511 509
512 510
513 511 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
514 512 class _UsersGroupForm(formencode.Schema):
515 513 allow_extra_fields = True
516 514 filter_extra_fields = True
517 515
518 516 users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
519 517 ValidUsersGroup(edit, old_data))
520 518
521 519 users_group_active = StringBoolean(if_missing=False)
522 520
523 521 if edit:
524 522 users_group_members = OneOf(available_members, hideList=False,
525 523 testValueList=True,
526 524 if_missing=None, not_empty=False)
527 525
528 526 return _UsersGroupForm
529 527
530 528 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
531 529 class _ReposGroupForm(formencode.Schema):
532 530 allow_extra_fields = True
533 531 filter_extra_fields = True
534 532
535 533 group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
536 534 SlugifyName())
537 535 group_description = UnicodeString(strip=True, min=1,
538 536 not_empty=True)
539 537 group_parent_id = OneOf(available_groups, hideList=False,
540 538 testValueList=True,
541 539 if_missing=None, not_empty=False)
542 540
543 541 chained_validators = [ValidReposGroup(edit, old_data)]
544 542
545 543 return _ReposGroupForm
546 544
547 545 def RegisterForm(edit=False, old_data={}):
548 546 class _RegisterForm(formencode.Schema):
549 547 allow_extra_fields = True
550 548 filter_extra_fields = True
551 549 username = All(ValidUsername(edit, old_data),
552 550 UnicodeString(strip=True, min=1, not_empty=True))
553 551 password = All(UnicodeString(strip=True, min=6, not_empty=True))
554 552 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
555 553 active = StringBoolean(if_missing=False)
556 554 name = UnicodeString(strip=True, min=1, not_empty=True)
557 555 lastname = UnicodeString(strip=True, min=1, not_empty=True)
558 556 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
559 557
560 558 chained_validators = [ValidPasswordsMatch, ValidPassword]
561 559
562 560 return _RegisterForm
563 561
564 562 def PasswordResetForm():
565 563 class _PasswordResetForm(formencode.Schema):
566 564 allow_extra_fields = True
567 565 filter_extra_fields = True
568 566 email = All(ValidSystemEmail(), Email(not_empty=True))
569 567 return _PasswordResetForm
570 568
571 569 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
572 570 repo_groups=[]):
573 571 class _RepoForm(formencode.Schema):
574 572 allow_extra_fields = True
575 573 filter_extra_fields = False
576 574 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
577 575 SlugifyName())
578 576 clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False),
579 577 ValidCloneUri()())
580 578 repo_group = OneOf(repo_groups, hideList=True)
581 579 repo_type = OneOf(supported_backends)
582 580 description = UnicodeString(strip=True, min=1, not_empty=True)
583 581 private = StringBoolean(if_missing=False)
584 582 enable_statistics = StringBoolean(if_missing=False)
585 583 enable_downloads = StringBoolean(if_missing=False)
586 584
587 585 if edit:
588 586 #this is repo owner
589 587 user = All(UnicodeString(not_empty=True), ValidRepoUser)
590 588
591 589 chained_validators = [ValidRepoName(edit, old_data), ValidPerms]
592 590 return _RepoForm
593 591
594 592 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
595 593 repo_groups=[]):
596 594 class _RepoForkForm(formencode.Schema):
597 595 allow_extra_fields = True
598 596 filter_extra_fields = False
599 597 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
600 598 SlugifyName())
601 599 repo_group = OneOf(repo_groups, hideList=True)
602 600 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
603 601 description = UnicodeString(strip=True, min=1, not_empty=True)
604 602 private = StringBoolean(if_missing=False)
605 603 copy_permissions = StringBoolean(if_missing=False)
606 604 update_after_clone = StringBoolean(if_missing=False)
607 605 fork_parent_id = UnicodeString()
608 606 chained_validators = [ValidForkName(edit, old_data)]
609 607
610 608 return _RepoForkForm
611 609
612 610 def RepoSettingsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
613 611 repo_groups=[]):
614 612 class _RepoForm(formencode.Schema):
615 613 allow_extra_fields = True
616 614 filter_extra_fields = False
617 615 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
618 616 SlugifyName())
619 617 description = UnicodeString(strip=True, min=1, not_empty=True)
620 618 repo_group = OneOf(repo_groups, hideList=True)
621 619 private = StringBoolean(if_missing=False)
622 620
623 621 chained_validators = [ValidRepoName(edit, old_data), ValidPerms,
624 622 ValidSettings]
625 623 return _RepoForm
626 624
627 625
628 626 def ApplicationSettingsForm():
629 627 class _ApplicationSettingsForm(formencode.Schema):
630 628 allow_extra_fields = True
631 629 filter_extra_fields = False
632 630 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
633 631 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
634 632 rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
635 633
636 634 return _ApplicationSettingsForm
637 635
638 636 def ApplicationUiSettingsForm():
639 637 class _ApplicationUiSettingsForm(formencode.Schema):
640 638 allow_extra_fields = True
641 639 filter_extra_fields = False
642 640 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
643 641 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
644 642 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
645 643 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
646 644 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
647 645 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
648 646
649 647 return _ApplicationUiSettingsForm
650 648
651 649 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
652 650 class _DefaultPermissionsForm(formencode.Schema):
653 651 allow_extra_fields = True
654 652 filter_extra_fields = True
655 653 overwrite_default = StringBoolean(if_missing=False)
656 654 anonymous = OneOf(['True', 'False'], if_missing=False)
657 655 default_perm = OneOf(perms_choices)
658 656 default_register = OneOf(register_choices)
659 657 default_create = OneOf(create_choices)
660 658
661 659 return _DefaultPermissionsForm
662 660
663 661
664 662 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices):
665 663 class _LdapSettingsForm(formencode.Schema):
666 664 allow_extra_fields = True
667 665 filter_extra_fields = True
668 666 pre_validators = [LdapLibValidator]
669 667 ldap_active = StringBoolean(if_missing=False)
670 668 ldap_host = UnicodeString(strip=True,)
671 669 ldap_port = Number(strip=True,)
672 670 ldap_tls_kind = OneOf(tls_kind_choices)
673 671 ldap_tls_reqcert = OneOf(tls_reqcert_choices)
674 672 ldap_dn_user = UnicodeString(strip=True,)
675 673 ldap_dn_pass = UnicodeString(strip=True,)
676 674 ldap_base_dn = UnicodeString(strip=True,)
677 675 ldap_filter = UnicodeString(strip=True,)
678 676 ldap_search_scope = OneOf(search_scope_choices)
679 677 ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
680 678 ldap_attr_firstname = UnicodeString(strip=True,)
681 679 ldap_attr_lastname = UnicodeString(strip=True,)
682 680 ldap_attr_email = UnicodeString(strip=True,)
683 681
684 682 return _LdapSettingsForm
@@ -1,440 +1,430 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.repo
4 4 ~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Repository model for rhodecode
7 7
8 8 :created_on: Jun 5, 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 import os
26 26 import shutil
27 27 import logging
28 28 import traceback
29 29 from datetime import datetime
30 30
31 31 from vcs.utils.lazy import LazyProperty
32 32 from vcs.backends import get_backend
33 33
34 34 from rhodecode.lib import safe_str
35 35 from rhodecode.lib.caching_query import FromCache
36 36
37 37 from rhodecode.model import BaseModel
38 38 from rhodecode.model.db import Repository, UserRepoToPerm, User, Permission, \
39 39 Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, RepoGroup
40 40
41 41 log = logging.getLogger(__name__)
42 42
43 43
44 44 class RepoModel(BaseModel):
45 45
46 46 @LazyProperty
47 47 def repos_path(self):
48 48 """
49 49 Get's the repositories root path from database
50 50 """
51 51
52 52 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
53 53 return q.ui_value
54 54
55 55 def get(self, repo_id, cache=False):
56 56 repo = self.sa.query(Repository)\
57 57 .filter(Repository.repo_id == repo_id)
58 58
59 59 if cache:
60 60 repo = repo.options(FromCache("sql_cache_short",
61 61 "get_repo_%s" % repo_id))
62 62 return repo.scalar()
63 63
64 64 def get_by_repo_name(self, repo_name, cache=False):
65 65 repo = self.sa.query(Repository)\
66 66 .filter(Repository.repo_name == repo_name)
67 67
68 68 if cache:
69 69 repo = repo.options(FromCache("sql_cache_short",
70 70 "get_repo_%s" % repo_name))
71 71 return repo.scalar()
72 72
73 73
74 74 def get_users_js(self):
75 75
76 76 users = self.sa.query(User).filter(User.active == True).all()
77 77 u_tmpl = '''{id:%s, fname:"%s", lname:"%s", nname:"%s"},'''
78 78 users_array = '[%s]' % '\n'.join([u_tmpl % (u.user_id, u.name,
79 79 u.lastname, u.username)
80 80 for u in users])
81 81 return users_array
82 82
83 83 def get_users_groups_js(self):
84 84 users_groups = self.sa.query(UsersGroup)\
85 85 .filter(UsersGroup.users_group_active == True).all()
86 86
87 87 g_tmpl = '''{id:%s, grname:"%s",grmembers:"%s"},'''
88 88
89 89 users_groups_array = '[%s]' % '\n'.join([g_tmpl % \
90 90 (gr.users_group_id, gr.users_group_name,
91 91 len(gr.members))
92 92 for gr in users_groups])
93 93 return users_groups_array
94 94
95 95 def _get_defaults(self, repo_name):
96 96 """
97 97 Get's information about repository, and returns a dict for
98 98 usage in forms
99 99
100 100 :param repo_name:
101 101 """
102 102
103 103 repo_info = Repository.get_by_repo_name(repo_name)
104 104
105 105 if repo_info is None:
106 106 return None
107 107
108 108 defaults = repo_info.get_dict()
109 109 group, repo_name = repo_info.groups_and_repo
110 110 defaults['repo_name'] = repo_name
111 111 defaults['repo_group'] = getattr(group[-1] if group else None,
112 112 'group_id', None)
113 113
114 114 # fill owner
115 115 if repo_info.user:
116 116 defaults.update({'user': repo_info.user.username})
117 117 else:
118 118 replacement_user = User.query().filter(User.admin ==
119 119 True).first().username
120 120 defaults.update({'user': replacement_user})
121 121
122 122 # fill repository users
123 123 for p in repo_info.repo_to_perm:
124 124 defaults.update({'u_perm_%s' % p.user.username:
125 125 p.permission.permission_name})
126 126
127 127 # fill repository groups
128 128 for p in repo_info.users_group_to_perm:
129 129 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
130 130 p.permission.permission_name})
131 131
132 132 return defaults
133 133
134 134
135 135 def update(self, repo_name, form_data):
136 136 try:
137 137 cur_repo = self.get_by_repo_name(repo_name, cache=False)
138 138
139 139 # update permissions
140 140 for member, perm, member_type in form_data['perms_updates']:
141 141 if member_type == 'user':
142 142 _member = User.get_by_username(member)
143 143 r2p = self.sa.query(UserRepoToPerm)\
144 144 .filter(UserRepoToPerm.user == _member)\
145 145 .filter(UserRepoToPerm.repository == cur_repo)\
146 146 .one()
147 147
148 148 r2p.permission = self.sa.query(Permission)\
149 149 .filter(Permission.permission_name ==
150 150 perm).scalar()
151 151 self.sa.add(r2p)
152 152 else:
153 153 g2p = self.sa.query(UsersGroupRepoToPerm)\
154 154 .filter(UsersGroupRepoToPerm.users_group ==
155 155 UsersGroup.get_by_group_name(member))\
156 156 .filter(UsersGroupRepoToPerm.repository ==
157 157 cur_repo).one()
158 158
159 159 g2p.permission = self.sa.query(Permission)\
160 160 .filter(Permission.permission_name ==
161 161 perm).scalar()
162 162 self.sa.add(g2p)
163 163
164 164 # set new permissions
165 165 for member, perm, member_type in form_data['perms_new']:
166 166 if member_type == 'user':
167 167 r2p = UserRepoToPerm()
168 168 r2p.repository = cur_repo
169 169 r2p.user = User.get_by_username(member)
170 170
171 171 r2p.permission = self.sa.query(Permission)\
172 172 .filter(Permission.
173 173 permission_name == perm)\
174 174 .scalar()
175 175 self.sa.add(r2p)
176 176 else:
177 177 g2p = UsersGroupRepoToPerm()
178 178 g2p.repository = cur_repo
179 179 g2p.users_group = UsersGroup.get_by_group_name(member)
180 180 g2p.permission = self.sa.query(Permission)\
181 181 .filter(Permission.
182 182 permission_name == perm)\
183 183 .scalar()
184 184 self.sa.add(g2p)
185 185
186 186 # update current repo
187 187 for k, v in form_data.items():
188 188 if k == 'user':
189 189 cur_repo.user = User.get_by_username(v)
190 190 elif k == 'repo_name':
191 191 pass
192 192 elif k == 'repo_group':
193 193 cur_repo.group = RepoGroup.get(v)
194 194
195 195 else:
196 196 setattr(cur_repo, k, v)
197 197
198 198 new_name = cur_repo.get_new_name(form_data['repo_name'])
199 199 cur_repo.repo_name = new_name
200 200
201 201 self.sa.add(cur_repo)
202 202
203 203 if repo_name != new_name:
204 204 # rename repository
205 205 self.__rename_repo(old=repo_name, new=new_name)
206 206
207 self.sa.commit()
208 207 return cur_repo
209 208 except:
210 209 log.error(traceback.format_exc())
211 self.sa.rollback()
212 210 raise
213 211
214 212 def create(self, form_data, cur_user, just_db=False, fork=False):
215 213 from rhodecode.model.scm import ScmModel
216 214
217 215 try:
218 216 if fork:
219 217 fork_parent_id = form_data['fork_parent_id']
220 218
221 219 # repo name is just a name of repository
222 220 # while repo_name_full is a full qualified name that is combined
223 221 # with name and path of group
224 222 repo_name = form_data['repo_name']
225 223 repo_name_full = form_data['repo_name_full']
226 224
227 225 new_repo = Repository()
228 226 new_repo.enable_statistics = False
229 227
230 228 for k, v in form_data.items():
231 229 if k == 'repo_name':
232 230 v = repo_name_full
233 231 if k == 'repo_group':
234 232 k = 'group_id'
235 233 if k == 'description':
236 234 v = v or repo_name
237 235
238 236 setattr(new_repo, k, v)
239 237
240 238 if fork:
241 239 parent_repo = Repository.get(fork_parent_id)
242 240 new_repo.fork = parent_repo
243 241
244 242 new_repo.user_id = cur_user.user_id
245 243 self.sa.add(new_repo)
246 244
247 245 def _create_default_perms():
248 246 # create default permission
249 247 repo_to_perm = UserRepoToPerm()
250 248 default = 'repository.read'
251 249 for p in User.get_by_username('default').user_perms:
252 250 if p.permission.permission_name.startswith('repository.'):
253 251 default = p.permission.permission_name
254 252 break
255 253
256 254 default_perm = 'repository.none' if form_data['private'] else default
257 255
258 256 repo_to_perm.permission_id = self.sa.query(Permission)\
259 257 .filter(Permission.permission_name == default_perm)\
260 258 .one().permission_id
261 259
262 260 repo_to_perm.repository = new_repo
263 261 repo_to_perm.user_id = User.get_by_username('default').user_id
264 262
265 263 self.sa.add(repo_to_perm)
266 264
267 265
268 266 if fork:
269 267 if form_data.get('copy_permissions'):
270 268 repo = Repository.get(fork_parent_id)
271 269 user_perms = UserRepoToPerm.query()\
272 270 .filter(UserRepoToPerm.repository == repo).all()
273 271 group_perms = UsersGroupRepoToPerm.query()\
274 272 .filter(UsersGroupRepoToPerm.repository == repo).all()
275 273
276 274 for perm in user_perms:
277 275 UserRepoToPerm.create(perm.user, new_repo,
278 276 perm.permission)
279 277
280 278 for perm in group_perms:
281 279 UsersGroupRepoToPerm.create(perm.users_group, new_repo,
282 280 perm.permission)
283 281 else:
284 282 _create_default_perms()
285 283 else:
286 284 _create_default_perms()
287 285
288 286 if not just_db:
289 287 self.__create_repo(repo_name, form_data['repo_type'],
290 288 form_data['repo_group'],
291 289 form_data['clone_uri'])
292 290
293 291 # now automatically start following this repository as owner
294 292 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
295 293 cur_user.user_id)
296 294 return new_repo
297 295 except:
298 296 log.error(traceback.format_exc())
299 297 raise
300 298
301 299 def create_fork(self, form_data, cur_user):
302 300 """
303 301 Simple wrapper into executing celery task for fork creation
304 302
305 303 :param form_data:
306 304 :param cur_user:
307 305 """
308 306 from rhodecode.lib.celerylib import tasks, run_task
309 307 run_task(tasks.create_repo_fork, form_data, cur_user)
310 308
311 309 def delete(self, repo):
312 310 try:
313 311 self.sa.delete(repo)
314 312 self.__delete_repo(repo)
315 self.sa.commit()
316 313 except:
317 314 log.error(traceback.format_exc())
318 self.sa.rollback()
319 315 raise
320 316
321 317 def delete_perm_user(self, form_data, repo_name):
322 318 try:
323 319 obj = self.sa.query(UserRepoToPerm)\
324 320 .filter(UserRepoToPerm.repository \
325 321 == self.get_by_repo_name(repo_name))\
326 322 .filter(UserRepoToPerm.user_id == form_data['user_id']).one()
327 323 self.sa.delete(obj)
328 self.sa.commit()
329 324 except:
330 325 log.error(traceback.format_exc())
331 self.sa.rollback()
332 326 raise
333 327
334 328 def delete_perm_users_group(self, form_data, repo_name):
335 329 try:
336 330 obj = self.sa.query(UsersGroupRepoToPerm)\
337 331 .filter(UsersGroupRepoToPerm.repository \
338 332 == self.get_by_repo_name(repo_name))\
339 333 .filter(UsersGroupRepoToPerm.users_group_id
340 334 == form_data['users_group_id']).one()
341 335 self.sa.delete(obj)
342 self.sa.commit()
343 336 except:
344 337 log.error(traceback.format_exc())
345 self.sa.rollback()
346 338 raise
347 339
348 340 def delete_stats(self, repo_name):
349 341 """
350 342 removes stats for given repo
351 343
352 344 :param repo_name:
353 345 """
354 346 try:
355 347 obj = self.sa.query(Statistics)\
356 348 .filter(Statistics.repository == \
357 349 self.get_by_repo_name(repo_name)).one()
358 350 self.sa.delete(obj)
359 self.sa.commit()
360 351 except:
361 352 log.error(traceback.format_exc())
362 self.sa.rollback()
363 353 raise
364 354
365 355 def __create_repo(self, repo_name, alias, new_parent_id, clone_uri=False):
366 356 """
367 357 makes repository on filesystem. It's group aware means it'll create
368 358 a repository within a group, and alter the paths accordingly of
369 359 group location
370 360
371 361 :param repo_name:
372 362 :param alias:
373 363 :param parent_id:
374 364 :param clone_uri:
375 365 """
376 366 from rhodecode.lib.utils import is_valid_repo, is_valid_repos_group
377 367
378 368 if new_parent_id:
379 369 paths = RepoGroup.get(new_parent_id)\
380 370 .full_path.split(RepoGroup.url_sep())
381 371 new_parent_path = os.sep.join(paths)
382 372 else:
383 373 new_parent_path = ''
384 374
385 375 repo_path = os.path.join(*map(lambda x:safe_str(x),
386 376 [self.repos_path, new_parent_path, repo_name]))
387 377
388 378
389 379 # check if this path is not a repository
390 380 if is_valid_repo(repo_path, self.repos_path):
391 381 raise Exception('This path %s is a valid repository' % repo_path)
392 382
393 383 # check if this path is a group
394 384 if is_valid_repos_group(repo_path, self.repos_path):
395 385 raise Exception('This path %s is a valid group' % repo_path)
396 386
397 387 log.info('creating repo %s in %s @ %s', repo_name, repo_path,
398 388 clone_uri)
399 389 backend = get_backend(alias)
400 390
401 391 backend(repo_path, create=True, src_url=clone_uri)
402 392
403 393
404 394 def __rename_repo(self, old, new):
405 395 """
406 396 renames repository on filesystem
407 397
408 398 :param old: old name
409 399 :param new: new name
410 400 """
411 401 log.info('renaming repo from %s to %s', old, new)
412 402
413 403 old_path = os.path.join(self.repos_path, old)
414 404 new_path = os.path.join(self.repos_path, new)
415 405 if os.path.isdir(new_path):
416 406 raise Exception('Was trying to rename to already existing dir %s' \
417 407 % new_path)
418 408 shutil.move(old_path, new_path)
419 409
420 410 def __delete_repo(self, repo):
421 411 """
422 412 removes repo from filesystem, the removal is acctually made by
423 413 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
424 414 repository is no longer valid for rhodecode, can be undeleted later on
425 415 by reverting the renames on this repository
426 416
427 417 :param repo: repo object
428 418 """
429 419 rm_path = os.path.join(self.repos_path, repo.repo_name)
430 420 log.info("Removing %s", rm_path)
431 #disable hg/git
421 # disable hg/git
432 422 alias = repo.repo_type
433 423 shutil.move(os.path.join(rm_path, '.%s' % alias),
434 424 os.path.join(rm_path, 'rm__.%s' % alias))
435 #disable repo
425 # disable repo
436 426 shutil.move(rm_path, os.path.join(self.repos_path, 'rm__%s__%s' \
437 427 % (datetime.today()\
438 428 .strftime('%Y%m%d_%H%M%S_%f'),
439 429 repo.repo_name)))
440 430
@@ -1,479 +1,504 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 def __get_user(self, user):
58 return self._get_instance(User, user)
59
57 60 def get(self, user_id, cache=False):
58 61 user = self.sa.query(User)
59 62 if cache:
60 63 user = user.options(FromCache("sql_cache_short",
61 64 "get_user_%s" % user_id))
62 65 return user.get(user_id)
63 66
64 67 def get_by_username(self, username, cache=False, case_insensitive=False):
65 68
66 69 if case_insensitive:
67 70 user = self.sa.query(User).filter(User.username.ilike(username))
68 71 else:
69 72 user = self.sa.query(User)\
70 73 .filter(User.username == username)
71 74 if cache:
72 75 user = user.options(FromCache("sql_cache_short",
73 76 "get_user_%s" % username))
74 77 return user.scalar()
75 78
76 79 def get_by_api_key(self, api_key, cache=False):
77 80 return User.get_by_api_key(api_key, cache)
78 81
79 82 def create(self, form_data):
80 83 try:
81 84 new_user = User()
82 85 for k, v in form_data.items():
83 86 setattr(new_user, k, v)
84 87
85 88 new_user.api_key = generate_api_key(form_data['username'])
86 89 self.sa.add(new_user)
87 self.sa.commit()
88 90 return new_user
89 91 except:
90 92 log.error(traceback.format_exc())
91 self.sa.rollback()
92 93 raise
93 94
94 95
95 96 def create_or_update(self, username, password, email, name, lastname,
96 97 active=True, admin=False, ldap_dn=None):
97 98 """
98 99 Creates a new instance if not found, or updates current one
99 100
100 101 :param username:
101 102 :param password:
102 103 :param email:
103 104 :param active:
104 105 :param name:
105 106 :param lastname:
106 107 :param active:
107 108 :param admin:
108 109 :param ldap_dn:
109 110 """
110 111
111 112 from rhodecode.lib.auth import get_crypt_password
112 113
113 114 log.debug('Checking for %s account in RhodeCode database', username)
114 115 user = User.get_by_username(username, case_insensitive=True)
115 116 if user is None:
116 117 log.debug('creating new user %s', username)
117 118 new_user = User()
118 119 else:
119 120 log.debug('updating user %s', username)
120 121 new_user = user
121 122
122 123 try:
123 124 new_user.username = username
124 125 new_user.admin = admin
125 126 new_user.password = get_crypt_password(password)
126 127 new_user.api_key = generate_api_key(username)
127 128 new_user.email = email
128 129 new_user.active = active
129 130 new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None
130 131 new_user.name = name
131 132 new_user.lastname = lastname
132 133 self.sa.add(new_user)
133 134 return new_user
134 135 except (DatabaseError,):
135 136 log.error(traceback.format_exc())
136 137 raise
137 138
138 139
139 140 def create_for_container_auth(self, username, attrs):
140 141 """
141 142 Creates the given user if it's not already in the database
142 143
143 144 :param username:
144 145 :param attrs:
145 146 """
146 147 if self.get_by_username(username, case_insensitive=True) is None:
147 148
148 149 # autogenerate email for container account without one
149 150 generate_email = lambda usr: '%s@container_auth.account' % usr
150 151
151 152 try:
152 153 new_user = User()
153 154 new_user.username = username
154 155 new_user.password = None
155 156 new_user.api_key = generate_api_key(username)
156 157 new_user.email = attrs['email']
157 158 new_user.active = attrs.get('active', True)
158 159 new_user.name = attrs['name'] or generate_email(username)
159 160 new_user.lastname = attrs['lastname']
160 161
161 162 self.sa.add(new_user)
162 self.sa.commit()
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 self.sa.commit()
204 203 return new_user
205 204 except (DatabaseError,):
206 205 log.error(traceback.format_exc())
207 206 self.sa.rollback()
208 207 raise
209 208 log.debug('this %s user exists skipping creation of ldap account',
210 209 username)
211 210 return None
212 211
213 212 def create_registration(self, form_data):
214 213 from rhodecode.model.notification import NotificationModel
215 214
216 215 try:
217 216 new_user = User()
218 217 for k, v in form_data.items():
219 218 if k != 'admin':
220 219 setattr(new_user, k, v)
221 220
222 221 self.sa.add(new_user)
223 222 self.sa.flush()
224 223
225 224 # notification to admins
226 225 subject = _('new user registration')
227 226 body = ('New user registration\n'
228 227 '---------------------\n'
229 228 '- Username: %s\n'
230 229 '- Full Name: %s\n'
231 230 '- Email: %s\n')
232 231 body = body % (new_user.username, new_user.full_name,
233 232 new_user.email)
234 233 edit_url = url('edit_user', id=new_user.user_id, qualified=True)
235 234 kw = {'registered_user_url':edit_url}
236 235 NotificationModel().create(created_by=new_user, subject=subject,
237 236 body=body, recipients=None,
238 237 type_=Notification.TYPE_REGISTRATION,
239 238 email_kwargs=kw)
240 239
241 240 except:
242 241 log.error(traceback.format_exc())
243 242 raise
244 243
245 244 def update(self, user_id, form_data):
246 245 try:
247 246 user = self.get(user_id, cache=False)
248 247 if user.username == 'default':
249 248 raise DefaultUserException(
250 249 _("You can't Edit this user since it's"
251 250 " crucial for entire application"))
252 251
253 252 for k, v in form_data.items():
254 253 if k == 'new_password' and v != '':
255 254 user.password = v
256 255 user.api_key = generate_api_key(user.username)
257 256 else:
258 257 setattr(user, k, v)
259 258
260 259 self.sa.add(user)
261 self.sa.commit()
262 260 except:
263 261 log.error(traceback.format_exc())
264 self.sa.rollback()
265 262 raise
266 263
267 264 def update_my_account(self, user_id, form_data):
268 265 try:
269 266 user = self.get(user_id, cache=False)
270 267 if user.username == 'default':
271 268 raise DefaultUserException(
272 269 _("You can't Edit this user since it's"
273 270 " crucial for entire application"))
274 271 for k, v in form_data.items():
275 272 if k == 'new_password' and v != '':
276 273 user.password = v
277 274 user.api_key = generate_api_key(user.username)
278 275 else:
279 276 if k not in ['admin', 'active']:
280 277 setattr(user, k, v)
281 278
282 279 self.sa.add(user)
283 self.sa.commit()
284 280 except:
285 281 log.error(traceback.format_exc())
286 self.sa.rollback()
287 282 raise
288 283
289 284 def delete(self, user_id):
290 285 try:
291 286 user = self.get(user_id, cache=False)
292 287 if user.username == 'default':
293 288 raise DefaultUserException(
294 289 _("You can't remove this user since it's"
295 290 " crucial for entire application"))
296 291 if user.repositories:
297 292 raise UserOwnsReposException(_('This user still owns %s '
298 293 'repositories and cannot be '
299 294 'removed. Switch owners or '
300 295 'remove those repositories') \
301 296 % user.repositories)
302 297 self.sa.delete(user)
303 self.sa.commit()
304 298 except:
305 299 log.error(traceback.format_exc())
306 self.sa.rollback()
307 300 raise
308 301
309 302 def reset_password_link(self, data):
310 303 from rhodecode.lib.celerylib import tasks, run_task
311 304 run_task(tasks.send_password_link, data['email'])
312 305
313 306 def reset_password(self, data):
314 307 from rhodecode.lib.celerylib import tasks, run_task
315 308 run_task(tasks.reset_user_password, data['email'])
316 309
317 310 def fill_data(self, auth_user, user_id=None, api_key=None):
318 311 """
319 312 Fetches auth_user by user_id,or api_key if present.
320 313 Fills auth_user attributes with those taken from database.
321 314 Additionally set's is_authenitated if lookup fails
322 315 present in database
323 316
324 317 :param auth_user: instance of user to set attributes
325 318 :param user_id: user id to fetch by
326 319 :param api_key: api key to fetch by
327 320 """
328 321 if user_id is None and api_key is None:
329 322 raise Exception('You need to pass user_id or api_key')
330 323
331 324 try:
332 325 if api_key:
333 326 dbuser = self.get_by_api_key(api_key)
334 327 else:
335 328 dbuser = self.get(user_id)
336 329
337 330 if dbuser is not None and dbuser.active:
338 331 log.debug('filling %s data', dbuser)
339 332 for k, v in dbuser.get_dict().items():
340 333 setattr(auth_user, k, v)
341 334 else:
342 335 return False
343 336
344 337 except:
345 338 log.error(traceback.format_exc())
346 339 auth_user.is_authenticated = False
347 340 return False
348 341
349 342 return True
350 343
351 344 def fill_perms(self, user):
352 345 """
353 346 Fills user permission attribute with permissions taken from database
354 347 works for permissions given for repositories, and for permissions that
355 348 are granted to groups
356 349
357 350 :param user: user instance to fill his perms
358 351 """
359 352
360 353 user.permissions['repositories'] = {}
361 354 user.permissions['global'] = set()
362 355
363 356 #======================================================================
364 357 # fetch default permissions
365 358 #======================================================================
366 359 default_user = User.get_by_username('default', cache=True)
367 360 default_user_id = default_user.user_id
368 361
369 362 default_perms = Permission.get_default_perms(default_user_id)
370 363
371 364 if user.is_admin:
372 365 #==================================================================
373 366 # #admin have all default rights set to admin
374 367 #==================================================================
375 368 user.permissions['global'].add('hg.admin')
376 369
377 370 for perm in default_perms:
378 371 p = 'repository.admin'
379 372 user.permissions['repositories'][perm.UserRepoToPerm.
380 373 repository.repo_name] = p
381 374
382 375 else:
383 376 #==================================================================
384 377 # set default permissions
385 378 #==================================================================
386 379 uid = user.user_id
387 380
388 381 # default global
389 382 default_global_perms = self.sa.query(UserToPerm)\
390 383 .filter(UserToPerm.user_id == default_user_id)
391 384
392 385 for perm in default_global_perms:
393 386 user.permissions['global'].add(perm.permission.permission_name)
394 387
395 388 # default for repositories
396 389 for perm in default_perms:
397 390 if perm.Repository.private and not (perm.Repository.user_id ==
398 391 uid):
399 392 # disable defaults for private repos,
400 393 p = 'repository.none'
401 394 elif perm.Repository.user_id == uid:
402 395 # set admin if owner
403 396 p = 'repository.admin'
404 397 else:
405 398 p = perm.Permission.permission_name
406 399
407 400 user.permissions['repositories'][perm.UserRepoToPerm.
408 401 repository.repo_name] = p
409 402
410 403 #==================================================================
411 404 # overwrite default with user permissions if any
412 405 #==================================================================
413 406
414 407 # user global
415 408 user_perms = self.sa.query(UserToPerm)\
416 409 .options(joinedload(UserToPerm.permission))\
417 410 .filter(UserToPerm.user_id == uid).all()
418 411
419 412 for perm in user_perms:
420 413 user.permissions['global'].add(perm.permission.permission_name)
421 414
422 415 # user repositories
423 416 user_repo_perms = self.sa.query(UserRepoToPerm, Permission,
424 417 Repository)\
425 418 .join((Repository, UserRepoToPerm.repository_id ==
426 419 Repository.repo_id))\
427 420 .join((Permission, UserRepoToPerm.permission_id ==
428 421 Permission.permission_id))\
429 422 .filter(UserRepoToPerm.user_id == uid).all()
430 423
431 424 for perm in user_repo_perms:
432 425 # set admin if owner
433 426 if perm.Repository.user_id == uid:
434 427 p = 'repository.admin'
435 428 else:
436 429 p = perm.Permission.permission_name
437 430 user.permissions['repositories'][perm.UserRepoToPerm.
438 431 repository.repo_name] = p
439 432
440 433 #==================================================================
441 434 # check if user is part of groups for this repository and fill in
442 435 # (or replace with higher) permissions
443 436 #==================================================================
444 437
445 438 # users group global
446 439 user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
447 440 .options(joinedload(UsersGroupToPerm.permission))\
448 441 .join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
449 442 UsersGroupMember.users_group_id))\
450 443 .filter(UsersGroupMember.user_id == uid).all()
451 444
452 445 for perm in user_perms_from_users_groups:
453 446 user.permissions['global'].add(perm.permission.permission_name)
454 447
455 448 # users group repositories
456 449 user_repo_perms_from_users_groups = self.sa.query(
457 450 UsersGroupRepoToPerm,
458 451 Permission, Repository,)\
459 452 .join((Repository, UsersGroupRepoToPerm.repository_id ==
460 453 Repository.repo_id))\
461 454 .join((Permission, UsersGroupRepoToPerm.permission_id ==
462 455 Permission.permission_id))\
463 456 .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id ==
464 457 UsersGroupMember.users_group_id))\
465 458 .filter(UsersGroupMember.user_id == uid).all()
466 459
467 460 for perm in user_repo_perms_from_users_groups:
468 461 p = perm.Permission.permission_name
469 462 cur_perm = user.permissions['repositories'][perm.
470 463 UsersGroupRepoToPerm.
471 464 repository.repo_name]
472 465 # overwrite permission only if it's greater than permission
473 466 # given from other sources
474 467 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
475 468 user.permissions['repositories'][perm.UsersGroupRepoToPerm.
476 469 repository.repo_name] = p
477 470
478 471 return user
479 472
473
474
475 def has_perm(self, user, perm):
476 if not isinstance(perm, Permission):
477 raise Exception('perm needs to be an instance of Permission class')
478
479 user = self.__get_user(user)
480
481 return UserToPerm.query().filter(UserToPerm.user == user.user)\
482 .filter(UserToPerm.permission == perm).scalar() is not None
483
484 def grant_perm(self, user, perm):
485 if not isinstance(perm, Permission):
486 raise Exception('perm needs to be an instance of Permission class')
487
488 user = self.__get_user(user)
489
490 new = UserToPerm()
491 new.user = user.user
492 new.permission = perm
493 self.sa.add(new)
494
495
496 def revoke_perm(self, user, perm):
497 if not isinstance(perm, Permission):
498 raise Exception('perm needs to be an instance of Permission class')
499
500 user = self.__get_user(user)
501
502 obj = UserToPerm.query().filter(UserToPerm.user == user.user)\
503 .filter(UserToPerm.permission == perm).one()
504 self.sa.delete(obj)
@@ -1,75 +1,152 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.users_group
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 users group model for RhodeCode
7 7
8 8 :created_on: Oct 1, 2011
9 9 :author: nvinot
10 10 :copyright: (C) 2011-2011 Nicolas Vinot <aeris@imirhil.fr>
11 11 :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 rhodecode.model import BaseModel
30 from rhodecode.model.db import UsersGroupMember, UsersGroup
30 from rhodecode.model.db import UsersGroupMember, UsersGroup,\
31 UsersGroupRepoToPerm, Permission, UsersGroupToPerm
32 from rhodecode.lib.exceptions import UsersGroupsAssignedException
31 33
32 34 log = logging.getLogger(__name__)
33 35
34 36
35 37 class UsersGroupModel(BaseModel):
36 38
37 39 def __get_users_group(self, users_group):
38 40 return self._get_instance(UsersGroup, users_group)
39 41
40 42 def get(self, users_group_id, cache=False):
41 43 return UsersGroup.get(users_group_id)
42 44
43 45 def get_by_name(self, name, cache=False, case_insensitive=False):
44 46 return UsersGroup.get_by_group_name(name, cache, case_insensitive)
45 47
46 48 def create(self, name, active=True):
47 new = UsersGroup()
48 new.users_group_name = name
49 new.users_group_active = active
50 self.sa.add(new)
51 return new
49 try:
50 new = UsersGroup()
51 new.users_group_name = name
52 new.users_group_active = active
53 self.sa.add(new)
54 return new
55 except:
56 log.error(traceback.format_exc())
57 raise
58
59 def update(self, users_group, form_data):
60
61 try:
62 users_group = self.__get_users_group(users_group)
63
64 for k, v in form_data.items():
65 if k == 'users_group_members':
66 users_group.members = []
67 self.sa.flush()
68 members_list = []
69 if v:
70 v = [v] if isinstance(v, basestring) else v
71 for u_id in set(v):
72 member = UsersGroupMember(users_group.users_group_id, u_id)
73 members_list.append(member)
74 setattr(users_group, 'members', members_list)
75 setattr(users_group, k, v)
76
77 self.sa.add(users_group)
78 except:
79 log.error(traceback.format_exc())
80 raise
52 81
53 82 def delete(self, users_group):
54 obj = self.__get_users_group(users_group)
55 self.sa.delete(obj)
83 try:
84 users_group = self.__get_users_group(users_group)
85
86 # check if this group is not assigned to repo
87 assigned_groups = UsersGroupRepoToPerm.query()\
88 .filter(UsersGroupRepoToPerm.users_group == users_group).all()
89
90 if assigned_groups:
91 raise UsersGroupsAssignedException('RepoGroup assigned to %s' %
92 assigned_groups)
93
94 self.sa.delete(users_group)
95 except:
96 log.error(traceback.format_exc())
97 raise
56 98
57 99 def add_user_to_group(self, users_group, user):
58 100 for m in users_group.members:
59 101 u = m.user
60 102 if u.user_id == user.user_id:
61 103 return m
62 104
63 105 try:
64 106 users_group_member = UsersGroupMember()
65 107 users_group_member.user = user
66 108 users_group_member.users_group = users_group
67 109
68 110 users_group.members.append(users_group_member)
69 111 user.group_member.append(users_group_member)
70 112
71 113 self.sa.add(users_group_member)
72 114 return users_group_member
73 115 except:
74 116 log.error(traceback.format_exc())
75 117 raise
118
119 def has_perm(self, users_group, perm):
120 if not isinstance(perm, Permission):
121 raise Exception('perm needs to be an instance of Permission class')
122
123 users_group = self.__get_users_group(users_group)
124
125 return UsersGroupToPerm.query()\
126 .filter(UsersGroupToPerm.users_group == users_group)\
127 .filter(UsersGroupToPerm.permission == perm).scalar() is not None
128
129 def grant_perm(self, users_group, perm):
130 if not isinstance(perm, Permission):
131 raise Exception('perm needs to be an instance of Permission class')
132
133 users_group = self.__get_users_group(users_group)
134
135 new = UsersGroupToPerm()
136 new.users_group = users_group
137 new.permission = perm
138 self.sa.add(new)
139
140
141 def revoke_perm(self, users_group, perm):
142 if not isinstance(perm, Permission):
143 raise Exception('perm needs to be an instance of Permission class')
144
145 users_group = self.__get_users_group(users_group)
146
147 obj = UsersGroupToPerm.query()\
148 .filter(UsersGroupToPerm.users_group == users_group)\
149 .filter(UsersGroupToPerm.permission == perm).one()
150 self.sa.delete(obj)
151
152
@@ -1,48 +1,48 b''
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title()">
4 4 ${c.repo_name} ${_('Files')} - ${c.rhodecode_name}
5 5 </%def>
6 6
7 7 <%def name="breadcrumbs_links()">
8 8 ${h.link_to(u'Home',h.url('/'))}
9 9 &raquo;
10 10 ${h.link_to(c.repo_name,h.url('files_home',repo_name=c.repo_name))}
11 11 &raquo;
12 12 ${_('files')}
13 13 %if c.file:
14 14 @ r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}
15 15 %endif
16 16 </%def>
17 17
18 18 <%def name="page_nav()">
19 19 ${self.menu('files')}
20 20 </%def>
21 21
22 22 <%def name="main()">
23 23 <div class="box">
24 24 <!-- box / title -->
25 25 <div class="title">
26 26 ${self.breadcrumbs()}
27 27 <ul class="links">
28 28 <li>
29 29 <span style="text-transform: uppercase;"><a href="#">${_('branch')}: ${c.changeset.branch}</a></span>
30 30 </li>
31 31 </ul>
32 32 </div>
33 33 <div class="table">
34 34 <div id="files_data">
35 35 <%include file='files_ypjax.html'/>
36 36 </div>
37 37 </div>
38 38 </div>
39 39 <script type="text/javascript">
40 40 var YPJAX_TITLE = "${c.repo_name} ${_('Files')} - ${c.rhodecode_name}";
41 41 var current_url = "${h.url.current()}";
42 var node_list_url = '${h.url("files_home",repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.file.path)}';
42 var node_list_url = '${h.url("files_home",repo_name=c.repo_name,revision=c.changeset.raw_id,f_path='__FPATH__')}';
43 43 var url_base = '${h.url("files_nodelist_home",repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.file.path)}';
44 44 var truncated_lbl = "${_('search truncated')}";
45 45 var nomatch_lbl = "${_('no matching files')}";
46 46 fileBrowserListeners(current_url, node_list_url, url_base, truncated_lbl, nomatch_lbl);
47 47 </script>
48 48 </%def> No newline at end of file
@@ -1,206 +1,206 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.tests.test_hg_operations
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Test suite for making push/pull operations
7 7
8 8 :created_on: Dec 30, 2010
9 9 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 10 :license: GPLv3, see COPYING for more details.
11 11 """
12 12 # This program is free software: you can redistribute it and/or modify
13 13 # it under the terms of the GNU General Public License as published by
14 14 # the Free Software Foundation, either version 3 of the License, or
15 15 # (at your option) any later version.
16 16 #
17 17 # This program is distributed in the hope that it will be useful,
18 18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 20 # GNU General Public License for more details.
21 21 #
22 22 # You should have received a copy of the GNU General Public License
23 23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 24
25 25 import os
26 26 import sys
27 27 import shutil
28 28 import logging
29 29 from os.path import join as jn
30 30 from os.path import dirname as dn
31 31
32 32 from tempfile import _RandomNameSequence
33 33 from subprocess import Popen, PIPE
34 34
35 35 from paste.deploy import appconfig
36 36 from pylons import config
37 37 from sqlalchemy import engine_from_config
38 38
39 39 from rhodecode.lib.utils import add_cache
40 40 from rhodecode.model import init_model
41 41 from rhodecode.model import meta
42 42 from rhodecode.model.db import User, Repository
43 43 from rhodecode.lib.auth import get_crypt_password
44 44
45 45 from rhodecode.tests import TESTS_TMP_PATH, NEW_HG_REPO, HG_REPO
46 46 from rhodecode.config.environment import load_environment
47 47
48 48 rel_path = dn(dn(dn(os.path.abspath(__file__))))
49 49 conf = appconfig('config:development.ini', relative_to=rel_path)
50 50 load_environment(conf.global_conf, conf.local_conf)
51 51
52 52 add_cache(conf)
53 53
54 54 USER = 'test_admin'
55 55 PASS = 'test12'
56 56 HOST = 'hg.local'
57 57 METHOD = 'pull'
58 58 DEBUG = True
59 59 log = logging.getLogger(__name__)
60 60
61 61
62 62 class Command(object):
63 63
64 64 def __init__(self, cwd):
65 65 self.cwd = cwd
66 66
67 67 def execute(self, cmd, *args):
68 68 """Runs command on the system with given ``args``.
69 69 """
70 70
71 71 command = cmd + ' ' + ' '.join(args)
72 72 log.debug('Executing %s' % command)
73 73 if DEBUG:
74 74 print command
75 75 p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, cwd=self.cwd)
76 76 stdout, stderr = p.communicate()
77 77 if DEBUG:
78 78 print stdout, stderr
79 79 return stdout, stderr
80 80
81 81 def get_session():
82 82 engine = engine_from_config(conf, 'sqlalchemy.db1.')
83 83 init_model(engine)
84 sa = meta.Session()
84 sa = meta.Session
85 85 return sa
86 86
87 87
88 88 def create_test_user(force=True):
89 89 print 'creating test user'
90 90 sa = get_session()
91 91
92 92 user = sa.query(User).filter(User.username == USER).scalar()
93 93
94 94 if force and user is not None:
95 95 print 'removing current user'
96 96 for repo in sa.query(Repository).filter(Repository.user == user).all():
97 97 sa.delete(repo)
98 98 sa.delete(user)
99 99 sa.commit()
100 100
101 101 if user is None or force:
102 102 print 'creating new one'
103 103 new_usr = User()
104 104 new_usr.username = USER
105 105 new_usr.password = get_crypt_password(PASS)
106 106 new_usr.email = 'mail@mail.com'
107 107 new_usr.name = 'test'
108 108 new_usr.lastname = 'lasttestname'
109 109 new_usr.active = True
110 110 new_usr.admin = True
111 111 sa.add(new_usr)
112 112 sa.commit()
113 113
114 114 print 'done'
115 115
116 116
117 117 def create_test_repo(force=True):
118 118 print 'creating test repo'
119 119 from rhodecode.model.repo import RepoModel
120 120 sa = get_session()
121 121
122 122 user = sa.query(User).filter(User.username == USER).scalar()
123 123 if user is None:
124 124 raise Exception('user not found')
125 125
126 126
127 127 repo = sa.query(Repository).filter(Repository.repo_name == HG_REPO).scalar()
128 128
129 129 if repo is None:
130 130 print 'repo not found creating'
131 131
132 132 form_data = {'repo_name':HG_REPO,
133 133 'repo_type':'hg',
134 134 'private':False,
135 135 'clone_uri':'' }
136 136 rm = RepoModel(sa)
137 137 rm.base_path = '/home/hg'
138 138 rm.create(form_data, user)
139 139
140 140 print 'done'
141 141
142 142 def set_anonymous_access(enable=True):
143 143 sa = get_session()
144 144 user = sa.query(User).filter(User.username == 'default').one()
145 145 user.active = enable
146 146 sa.add(user)
147 147 sa.commit()
148 148
149 149 def get_anonymous_access():
150 150 sa = get_session()
151 151 return sa.query(User).filter(User.username == 'default').one().active
152 152
153 153
154 154 #==============================================================================
155 155 # TESTS
156 156 #==============================================================================
157 157 def test_clone_with_credentials(no_errors=False, repo=HG_REPO, method=METHOD,
158 158 seq=None):
159 159 cwd = path = jn(TESTS_TMP_PATH, repo)
160 160
161 161 if seq == None:
162 162 seq = _RandomNameSequence().next()
163 163
164 164 try:
165 165 shutil.rmtree(path, ignore_errors=True)
166 166 os.makedirs(path)
167 167 #print 'made dirs %s' % jn(path)
168 168 except OSError:
169 169 raise
170 170
171 171
172 172 clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s' % \
173 173 {'user':USER,
174 174 'pass':PASS,
175 175 'host':HOST,
176 176 'cloned_repo':repo, }
177 177
178 178 dest = path + seq
179 179 if method == 'pull':
180 180 stdout, stderr = Command(cwd).execute('hg', method, '--cwd', dest, clone_url)
181 181 else:
182 182 stdout, stderr = Command(cwd).execute('hg', method, clone_url, dest)
183 183
184 184 if no_errors is False:
185 185 assert """adding file changes""" in stdout, 'no messages about cloning'
186 186 assert """abort""" not in stderr , 'got error from clone'
187 187
188 188 if __name__ == '__main__':
189 189 try:
190 190 create_test_user(force=False)
191 191 seq = None
192 192 import time
193 193
194 194 if METHOD == 'pull':
195 195 seq = _RandomNameSequence().next()
196 196 test_clone_with_credentials(repo=sys.argv[1], method='clone',
197 197 seq=seq)
198 198 s = time.time()
199 199 for i in range(int(sys.argv[2])):
200 200 print 'take', i
201 201 test_clone_with_credentials(repo=sys.argv[1], method=METHOD,
202 202 seq=seq)
203 203 print 'time taken %.3f' % (time.time() - s)
204 204 except Exception, e:
205 205 raise
206 206 sys.exit('stop on %s' % e)
@@ -1,119 +1,119 b''
1 1 from rhodecode.tests import *
2 2 from rhodecode.model.db import Notification, User, UserNotification
3 3
4 4 from rhodecode.model.user import UserModel
5 5 from rhodecode.model.notification import NotificationModel
6 6 from rhodecode.model.meta import Session
7 7
8 8 class TestNotificationsController(TestController):
9 9
10 10
11 11 def tearDown(self):
12 12 for n in Notification.query().all():
13 13 inst = Notification.get(n.notification_id)
14 Session().delete(inst)
15 Session().commit()
14 Session.delete(inst)
15 Session.commit()
16 16
17 17 def test_index(self):
18 18 self.log_user()
19 19
20 20 u1 = UserModel().create_or_update(username='u1', password='qweqwe',
21 21 email='u1@rhodecode.org',
22 22 name='u1', lastname='u1').user_id
23 23
24 24 response = self.app.get(url('notifications'))
25 25 self.assertTrue('''<div class="table">No notifications here yet</div>'''
26 26 in response.body)
27 27
28 28 cur_user = self._get_logged_user()
29 29
30 30 NotificationModel().create(created_by=u1, subject=u'test_notification_1',
31 31 body=u'notification_1',
32 32 recipients=[cur_user])
33 Session().commit()
33 Session.commit()
34 34 response = self.app.get(url('notifications'))
35 35 self.assertTrue(u'test_notification_1' in response.body)
36 36
37 37 # def test_index_as_xml(self):
38 38 # response = self.app.get(url('formatted_notifications', format='xml'))
39 39 #
40 40 # def test_create(self):
41 41 # response = self.app.post(url('notifications'))
42 42 #
43 43 # def test_new(self):
44 44 # response = self.app.get(url('new_notification'))
45 45 #
46 46 # def test_new_as_xml(self):
47 47 # response = self.app.get(url('formatted_new_notification', format='xml'))
48 48 #
49 49 # def test_update(self):
50 50 # response = self.app.put(url('notification', notification_id=1))
51 51 #
52 52 # def test_update_browser_fakeout(self):
53 53 # response = self.app.post(url('notification', notification_id=1), params=dict(_method='put'))
54 54
55 55 def test_delete(self):
56 56 self.log_user()
57 57 cur_user = self._get_logged_user()
58 58
59 59 u1 = UserModel().create_or_update(username='u1', password='qweqwe',
60 60 email='u1@rhodecode.org',
61 61 name='u1', lastname='u1')
62 62 u2 = UserModel().create_or_update(username='u2', password='qweqwe',
63 63 email='u2@rhodecode.org',
64 64 name='u2', lastname='u2')
65 65
66 66 # make notifications
67 67 notification = NotificationModel().create(created_by=cur_user,
68 68 subject=u'test',
69 69 body=u'hi there',
70 70 recipients=[cur_user, u1, u2])
71 Session().commit()
71 Session.commit()
72 72 u1 = User.get(u1.user_id)
73 73 u2 = User.get(u2.user_id)
74 74
75 75 # check DB
76 76 get_notif = lambda un:[x.notification for x in un]
77 77 self.assertEqual(get_notif(cur_user.notifications), [notification])
78 78 self.assertEqual(get_notif(u1.notifications), [notification])
79 79 self.assertEqual(get_notif(u2.notifications), [notification])
80 80 cur_usr_id = cur_user.user_id
81 81
82 82
83 83 response = self.app.delete(url('notification',
84 84 notification_id=
85 85 notification.notification_id))
86 86
87 87 cur_user = User.get(cur_usr_id)
88 88 self.assertEqual(cur_user.notifications, [])
89 89
90 90
91 91 # def test_delete_browser_fakeout(self):
92 92 # response = self.app.post(url('notification', notification_id=1), params=dict(_method='delete'))
93 93
94 94 def test_show(self):
95 95 self.log_user()
96 96 cur_user = self._get_logged_user()
97 97 u1 = UserModel().create_or_update(username='u1', password='qweqwe',
98 98 email='u1@rhodecode.org',
99 99 name='u1', lastname='u1')
100 100 u2 = UserModel().create_or_update(username='u2', password='qweqwe',
101 101 email='u2@rhodecode.org',
102 102 name='u2', lastname='u2')
103 103
104 104 notification = NotificationModel().create(created_by=cur_user,
105 105 subject=u'test',
106 106 body=u'hi there',
107 107 recipients=[cur_user, u1, u2])
108 108
109 109 response = self.app.get(url('notification',
110 110 notification_id=notification.notification_id))
111 111
112 112 # def test_show_as_xml(self):
113 113 # response = self.app.get(url('formatted_notification', notification_id=1, format='xml'))
114 114 #
115 115 # def test_edit(self):
116 116 # response = self.app.get(url('edit_notification', notification_id=1))
117 117 #
118 118 # def test_edit_as_xml(self):
119 119 # response = self.app.get(url('formatted_edit_notification', notification_id=1, format='xml'))
@@ -1,213 +1,212 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 import os
4 4 import vcs
5 5
6 6 from rhodecode.model.db import Repository
7 7 from rhodecode.tests import *
8 8
9 9 class TestAdminReposController(TestController):
10 10
11 11
12 12 def __make_repo(self):
13 13 pass
14 14
15 15
16 16 def test_index(self):
17 17 self.log_user()
18 18 response = self.app.get(url('repos'))
19 19 # Test response...
20 20
21 21 def test_index_as_xml(self):
22 22 response = self.app.get(url('formatted_repos', format='xml'))
23 23
24 24 def test_create_hg(self):
25 25 self.log_user()
26 26 repo_name = NEW_HG_REPO
27 27 description = 'description for newly created repo'
28 28 private = False
29 29 response = self.app.post(url('repos'), {'repo_name':repo_name,
30 30 'repo_type':'hg',
31 31 'clone_uri':'',
32 32 'repo_group':'',
33 33 'description':description,
34 34 'private':private})
35
36 35 self.checkSessionFlash(response, 'created repository %s' % (repo_name))
37 36
38 37 #test if the repo was created in the database
39 new_repo = self.Session().query(Repository).filter(Repository.repo_name ==
38 new_repo = self.Session.query(Repository).filter(Repository.repo_name ==
40 39 repo_name).one()
41 40
42 41 self.assertEqual(new_repo.repo_name, repo_name)
43 42 self.assertEqual(new_repo.description, description)
44 43
45 44 #test if repository is visible in the list ?
46 45 response = response.follow()
47 46
48 47 self.assertTrue(repo_name in response.body)
49 48
50 49
51 50 #test if repository was created on filesystem
52 51 try:
53 52 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
54 53 except:
55 54 self.fail('no repo in filesystem')
56 55
57 56
58 57 def test_create_hg_non_ascii(self):
59 58 self.log_user()
60 59 non_ascii = "ąęł"
61 60 repo_name = "%s%s" % (NEW_HG_REPO, non_ascii)
62 61 repo_name_unicode = repo_name.decode('utf8')
63 62 description = 'description for newly created repo' + non_ascii
64 63 description_unicode = description.decode('utf8')
65 64 private = False
66 65 response = self.app.post(url('repos'), {'repo_name':repo_name,
67 66 'repo_type':'hg',
68 67 'clone_uri':'',
69 68 'repo_group':'',
70 69 'description':description,
71 70 'private':private})
72 71 self.checkSessionFlash(response,
73 72 'created repository %s' % (repo_name_unicode))
74 73
75 74 #test if the repo was created in the database
76 new_repo = self.Session().query(Repository).filter(Repository.repo_name ==
75 new_repo = self.Session.query(Repository).filter(Repository.repo_name ==
77 76 repo_name_unicode).one()
78 77
79 78 self.assertEqual(new_repo.repo_name, repo_name_unicode)
80 79 self.assertEqual(new_repo.description, description_unicode)
81 80
82 81 #test if repository is visible in the list ?
83 82 response = response.follow()
84 83
85 84 self.assertTrue(repo_name in response.body)
86 85
87 86 #test if repository was created on filesystem
88 87 try:
89 88 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
90 89 except:
91 90 self.fail('no repo in filesystem')
92 91
93 92
94 93 def test_create_hg_in_group(self):
95 94 #TODO: write test !
96 95 pass
97 96
98 97 def test_create_git(self):
99 98 return
100 99 self.log_user()
101 100 repo_name = NEW_GIT_REPO
102 101 description = 'description for newly created repo'
103 102 private = False
104 103 response = self.app.post(url('repos'), {'repo_name':repo_name,
105 104 'repo_type':'git',
106 105 'clone_uri':'',
107 106 'repo_group':'',
108 107 'description':description,
109 108 'private':private})
110 109
111 110
112 111 #test if we have a message for that repository
113 112 assert '''created repository %s''' % (repo_name) in response.session['flash'][0], 'No flash message about new repo'
114 113
115 114 #test if the fork was created in the database
116 new_repo = self.Session().query(Repository).filter(Repository.repo_name == repo_name).one()
115 new_repo = self.Session.query(Repository).filter(Repository.repo_name == repo_name).one()
117 116
118 117 assert new_repo.repo_name == repo_name, 'wrong name of repo name in db'
119 118 assert new_repo.description == description, 'wrong description'
120 119
121 120 #test if repository is visible in the list ?
122 121 response = response.follow()
123 122
124 123 assert repo_name in response.body, 'missing new repo from the main repos list'
125 124
126 125 #test if repository was created on filesystem
127 126 try:
128 127 vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name))
129 128 except:
130 129 assert False , 'no repo in filesystem'
131 130
132 131 def test_new(self):
133 132 self.log_user()
134 133 response = self.app.get(url('new_repo'))
135 134
136 135 def test_new_as_xml(self):
137 136 response = self.app.get(url('formatted_new_repo', format='xml'))
138 137
139 138 def test_update(self):
140 139 response = self.app.put(url('repo', repo_name=HG_REPO))
141 140
142 141 def test_update_browser_fakeout(self):
143 142 response = self.app.post(url('repo', repo_name=HG_REPO),
144 143 params=dict(_method='put'))
145 144
146 145 def test_delete(self):
147 146 self.log_user()
148 147 repo_name = 'vcs_test_new_to_delete'
149 148 description = 'description for newly created repo'
150 149 private = False
151 150
152 151 response = self.app.post(url('repos'), {'repo_name':repo_name,
153 152 'repo_type':'hg',
154 153 'clone_uri':'',
155 154 'repo_group':'',
156 155 'description':description,
157 156 'private':private})
158 157 self.assertTrue('flash' in response.session)
159 158
160 159 #test if we have a message for that repository
161 160 self.assertTrue('''created repository %s''' % (repo_name) in
162 161 response.session['flash'][0])
163 162
164 163 #test if the repo was created in the database
165 new_repo = self.Session().query(Repository).filter(Repository.repo_name ==
164 new_repo = self.Session.query(Repository).filter(Repository.repo_name ==
166 165 repo_name).one()
167 166
168 167 self.assertEqual(new_repo.repo_name, repo_name)
169 168 self.assertEqual(new_repo.description, description)
170 169
171 170 #test if repository is visible in the list ?
172 171 response = response.follow()
173 172
174 173 self.assertTrue(repo_name in response.body)
175 174
176 175
177 176 response = self.app.delete(url('repo', repo_name=repo_name))
178 177
179 178 self.assertTrue('''deleted repository %s''' % (repo_name) in
180 179 response.session['flash'][0])
181 180
182 181 response.follow()
183 182
184 183 #check if repo was deleted from db
185 deleted_repo = self.Session().query(Repository).filter(Repository.repo_name
184 deleted_repo = self.Session.query(Repository).filter(Repository.repo_name
186 185 == repo_name).scalar()
187 186
188 187 self.assertEqual(deleted_repo, None)
189 188
190 189
191 190 def test_delete_repo_with_group(self):
192 191 #TODO:
193 192 pass
194 193
195 194
196 195 def test_delete_browser_fakeout(self):
197 196 response = self.app.post(url('repo', repo_name=HG_REPO),
198 197 params=dict(_method='delete'))
199 198
200 199 def test_show(self):
201 200 self.log_user()
202 201 response = self.app.get(url('repo', repo_name=HG_REPO))
203 202
204 203 def test_show_as_xml(self):
205 204 response = self.app.get(url('formatted_repo', repo_name=HG_REPO,
206 205 format='xml'))
207 206
208 207 def test_edit(self):
209 208 response = self.app.get(url('edit_repo', repo_name=HG_REPO))
210 209
211 210 def test_edit_as_xml(self):
212 211 response = self.app.get(url('formatted_edit_repo', repo_name=HG_REPO,
213 212 format='xml'))
@@ -1,212 +1,212 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 from rhodecode.lib.auth import get_crypt_password, check_password
4 4 from rhodecode.model.db import User, RhodeCodeSetting
5 5 from rhodecode.tests import *
6 6
7 7 class TestAdminSettingsController(TestController):
8 8
9 9 def test_index(self):
10 10 response = self.app.get(url('admin_settings'))
11 11 # Test response...
12 12
13 13 def test_index_as_xml(self):
14 14 response = self.app.get(url('formatted_admin_settings', format='xml'))
15 15
16 16 def test_create(self):
17 17 response = self.app.post(url('admin_settings'))
18 18
19 19 def test_new(self):
20 20 response = self.app.get(url('admin_new_setting'))
21 21
22 22 def test_new_as_xml(self):
23 23 response = self.app.get(url('formatted_admin_new_setting', format='xml'))
24 24
25 25 def test_update(self):
26 26 response = self.app.put(url('admin_setting', setting_id=1))
27 27
28 28 def test_update_browser_fakeout(self):
29 29 response = self.app.post(url('admin_setting', setting_id=1), params=dict(_method='put'))
30 30
31 31 def test_delete(self):
32 32 response = self.app.delete(url('admin_setting', setting_id=1))
33 33
34 34 def test_delete_browser_fakeout(self):
35 35 response = self.app.post(url('admin_setting', setting_id=1), params=dict(_method='delete'))
36 36
37 37 def test_show(self):
38 38 response = self.app.get(url('admin_setting', setting_id=1))
39 39
40 40 def test_show_as_xml(self):
41 41 response = self.app.get(url('formatted_admin_setting', setting_id=1, format='xml'))
42 42
43 43 def test_edit(self):
44 44 response = self.app.get(url('admin_edit_setting', setting_id=1))
45 45
46 46 def test_edit_as_xml(self):
47 47 response = self.app.get(url('formatted_admin_edit_setting',
48 48 setting_id=1, format='xml'))
49 49
50 50
51 51 def test_ga_code_active(self):
52 52 self.log_user()
53 53 old_title = 'RhodeCode'
54 54 old_realm = 'RhodeCode authentication'
55 55 new_ga_code = 'ga-test-123456789'
56 56 response = self.app.post(url('admin_setting', setting_id='global'),
57 57 params=dict(
58 58 _method='put',
59 59 rhodecode_title=old_title,
60 60 rhodecode_realm=old_realm,
61 61 rhodecode_ga_code=new_ga_code
62 62 ))
63 63
64 64 self.checkSessionFlash(response, 'Updated application settings')
65 65
66 66 self.assertEqual(RhodeCodeSetting
67 67 .get_app_settings()['rhodecode_ga_code'], new_ga_code)
68 68
69 69 response = response.follow()
70 70 self.assertTrue("""_gaq.push(['_setAccount', '%s']);""" % new_ga_code
71 71 in response.body)
72 72
73 73 def test_ga_code_inactive(self):
74 74 self.log_user()
75 75 old_title = 'RhodeCode'
76 76 old_realm = 'RhodeCode authentication'
77 77 new_ga_code = ''
78 78 response = self.app.post(url('admin_setting', setting_id='global'),
79 79 params=dict(
80 80 _method='put',
81 81 rhodecode_title=old_title,
82 82 rhodecode_realm=old_realm,
83 83 rhodecode_ga_code=new_ga_code
84 84 ))
85 85
86 86 self.assertTrue('Updated application settings' in
87 87 response.session['flash'][0][1])
88 88 self.assertEqual(RhodeCodeSetting
89 89 .get_app_settings()['rhodecode_ga_code'], new_ga_code)
90 90
91 91 response = response.follow()
92 92 self.assertTrue("""_gaq.push(['_setAccount', '%s']);""" % new_ga_code
93 93 not in response.body)
94 94
95 95
96 96 def test_title_change(self):
97 97 self.log_user()
98 98 old_title = 'RhodeCode'
99 99 new_title = old_title + '_changed'
100 100 old_realm = 'RhodeCode authentication'
101 101
102 102 for new_title in ['Changed', 'Żółwik', old_title]:
103 103 response = self.app.post(url('admin_setting', setting_id='global'),
104 104 params=dict(
105 105 _method='put',
106 106 rhodecode_title=new_title,
107 107 rhodecode_realm=old_realm,
108 108 rhodecode_ga_code=''
109 109 ))
110 110
111 111 self.checkSessionFlash(response, 'Updated application settings')
112 112 self.assertEqual(RhodeCodeSetting
113 113 .get_app_settings()['rhodecode_title'],
114 114 new_title.decode('utf-8'))
115 115
116 116 response = response.follow()
117 117 self.assertTrue("""<h1><a href="/">%s</a></h1>""" % new_title
118 118 in response.body)
119 119
120 120
121 121 def test_my_account(self):
122 122 self.log_user()
123 123 response = self.app.get(url('admin_settings_my_account'))
124 124
125 125 self.assertTrue('value="test_admin' in response.body)
126 126
127 127 def test_my_account_update(self):
128 128 self.log_user()
129 129
130 130 new_email = 'new@mail.pl'
131 131 new_name = 'NewName'
132 132 new_lastname = 'NewLastname'
133 133 new_password = 'test123'
134 134
135 135
136 136 response = self.app.post(url('admin_settings_my_account_update'),
137 137 params=dict(_method='put',
138 138 username='test_admin',
139 139 new_password=new_password,
140 140 password_confirmation = new_password,
141 141 password='',
142 142 name=new_name,
143 143 lastname=new_lastname,
144 144 email=new_email,))
145 145 response.follow()
146 146
147 147 assert 'Your account was updated successfully' in response.session['flash'][0][1], 'no flash message about success of change'
148 user = self.Session().query(User).filter(User.username == 'test_admin').one()
148 user = self.Session.query(User).filter(User.username == 'test_admin').one()
149 149 assert user.email == new_email , 'incorrect user email after update got %s vs %s' % (user.email, new_email)
150 150 assert user.name == new_name, 'updated field mismatch %s vs %s' % (user.name, new_name)
151 151 assert user.lastname == new_lastname, 'updated field mismatch %s vs %s' % (user.lastname, new_lastname)
152 152 assert check_password(new_password, user.password) is True, 'password field mismatch %s vs %s' % (user.password, new_password)
153 153
154 154 #bring back the admin settings
155 155 old_email = 'test_admin@mail.com'
156 156 old_name = 'RhodeCode'
157 157 old_lastname = 'Admin'
158 158 old_password = 'test12'
159 159
160 160 response = self.app.post(url('admin_settings_my_account_update'), params=dict(
161 161 _method='put',
162 162 username='test_admin',
163 163 new_password=old_password,
164 164 password_confirmation = old_password,
165 165 password='',
166 166 name=old_name,
167 167 lastname=old_lastname,
168 168 email=old_email,))
169 169
170 170 response.follow()
171 171 self.checkSessionFlash(response,
172 172 'Your account was updated successfully')
173 173
174 user = self.Session().query(User).filter(User.username == 'test_admin').one()
174 user = self.Session.query(User).filter(User.username == 'test_admin').one()
175 175 assert user.email == old_email , 'incorrect user email after update got %s vs %s' % (user.email, old_email)
176 176
177 177 assert user.email == old_email , 'incorrect user email after update got %s vs %s' % (user.email, old_email)
178 178 assert user.name == old_name, 'updated field mismatch %s vs %s' % (user.name, old_name)
179 179 assert user.lastname == old_lastname, 'updated field mismatch %s vs %s' % (user.lastname, old_lastname)
180 180 assert check_password(old_password, user.password) is True , 'password updated field mismatch %s vs %s' % (user.password, old_password)
181 181
182 182
183 183 def test_my_account_update_err_email_exists(self):
184 184 self.log_user()
185 185
186 186 new_email = 'test_regular@mail.com'#already exisitn email
187 187 response = self.app.post(url('admin_settings_my_account_update'), params=dict(
188 188 _method='put',
189 189 username='test_admin',
190 190 new_password='test12',
191 191 password_confirmation = 'test122',
192 192 name='NewName',
193 193 lastname='NewLastname',
194 194 email=new_email,))
195 195
196 196 assert 'This e-mail address is already taken' in response.body, 'Missing error message about existing email'
197 197
198 198
199 199 def test_my_account_update_err(self):
200 200 self.log_user('test_regular2', 'test12')
201 201
202 202 new_email = 'newmail.pl'
203 203 response = self.app.post(url('admin_settings_my_account_update'), params=dict(
204 204 _method='put',
205 205 username='test_admin',
206 206 new_password='test12',
207 207 password_confirmation = 'test122',
208 208 name='NewName',
209 209 lastname='NewLastname',
210 210 email=new_email,))
211 211 assert 'An email address must contain a single @' in response.body, 'Missing error message about wrong email'
212 212 assert 'This username already exists' in response.body, 'Missing error message about existing user'
@@ -1,122 +1,122 b''
1 1 from rhodecode.tests import *
2 2 from rhodecode.model.db import User
3 3 from rhodecode.lib.auth import check_password
4 4 from sqlalchemy.orm.exc import NoResultFound
5 5
6 6 class TestAdminUsersController(TestController):
7 7
8 8 def test_index(self):
9 9 response = self.app.get(url('users'))
10 10 # Test response...
11 11
12 12 def test_index_as_xml(self):
13 13 response = self.app.get(url('formatted_users', format='xml'))
14 14
15 15 def test_create(self):
16 16 self.log_user()
17 17 username = 'newtestuser'
18 18 password = 'test12'
19 19 password_confirmation = password
20 20 name = 'name'
21 21 lastname = 'lastname'
22 22 email = 'mail@mail.com'
23 23
24 24 response = self.app.post(url('users'), {'username':username,
25 25 'password':password,
26 26 'password_confirmation':password_confirmation,
27 27 'name':name,
28 28 'active':True,
29 29 'lastname':lastname,
30 30 'email':email})
31 31
32 32
33 33 assert '''created user %s''' % (username) in response.session['flash'][0], 'No flash message about new user'
34 34
35 new_user = self.Session().query(User).filter(User.username == username).one()
35 new_user = self.Session.query(User).filter(User.username == username).one()
36 36
37 37
38 38 assert new_user.username == username, 'wrong info about username'
39 39 assert check_password(password, new_user.password) == True , 'wrong info about password'
40 40 assert new_user.name == name, 'wrong info about name'
41 41 assert new_user.lastname == lastname, 'wrong info about lastname'
42 42 assert new_user.email == email, 'wrong info about email'
43 43
44 44
45 45 response.follow()
46 46 response = response.follow()
47 47 assert """edit">newtestuser</a>""" in response.body
48 48
49 49 def test_create_err(self):
50 50 self.log_user()
51 51 username = 'new_user'
52 52 password = ''
53 53 name = 'name'
54 54 lastname = 'lastname'
55 55 email = 'errmail.com'
56 56
57 57 response = self.app.post(url('users'), {'username':username,
58 58 'password':password,
59 59 'name':name,
60 60 'active':False,
61 61 'lastname':lastname,
62 62 'email':email})
63 63
64 64 assert """<span class="error-message">Invalid username</span>""" in response.body
65 65 assert """<span class="error-message">Please enter a value</span>""" in response.body
66 66 assert """<span class="error-message">An email address must contain a single @</span>""" in response.body
67 67
68 68 def get_user():
69 self.Session().query(User).filter(User.username == username).one()
69 self.Session.query(User).filter(User.username == username).one()
70 70
71 71 self.assertRaises(NoResultFound, get_user), 'found user in database'
72 72
73 73 def test_new(self):
74 74 response = self.app.get(url('new_user'))
75 75
76 76 def test_new_as_xml(self):
77 77 response = self.app.get(url('formatted_new_user', format='xml'))
78 78
79 79 def test_update(self):
80 80 response = self.app.put(url('user', id=1))
81 81
82 82 def test_update_browser_fakeout(self):
83 83 response = self.app.post(url('user', id=1), params=dict(_method='put'))
84 84
85 85 def test_delete(self):
86 86 self.log_user()
87 87 username = 'newtestuserdeleteme'
88 88 password = 'test12'
89 89 name = 'name'
90 90 lastname = 'lastname'
91 91 email = 'todeletemail@mail.com'
92 92
93 93 response = self.app.post(url('users'), {'username':username,
94 94 'password':password,
95 95 'password_confirmation':password,
96 96 'name':name,
97 97 'active':True,
98 98 'lastname':lastname,
99 99 'email':email})
100 100
101 101 response = response.follow()
102 102
103 new_user = self.Session().query(User).filter(User.username == username).one()
103 new_user = self.Session.query(User).filter(User.username == username).one()
104 104 response = self.app.delete(url('user', id=new_user.user_id))
105 105
106 106 assert """successfully deleted user""" in response.session['flash'][0], 'No info about user deletion'
107 107
108 108
109 109 def test_delete_browser_fakeout(self):
110 110 response = self.app.post(url('user', id=1), params=dict(_method='delete'))
111 111
112 112 def test_show(self):
113 113 response = self.app.get(url('user', id=1))
114 114
115 115 def test_show_as_xml(self):
116 116 response = self.app.get(url('formatted_user', id=1, format='xml'))
117 117
118 118 def test_edit(self):
119 119 response = self.app.get(url('edit_user', id=1))
120 120
121 121 def test_edit_as_xml(self):
122 122 response = self.app.get(url('formatted_edit_user', id=1, format='xml'))
@@ -1,95 +1,91 b''
1 1 from rhodecode.tests import *
2 2 from rhodecode.model.db import UsersGroup
3 3
4 4 TEST_USERS_GROUP = 'admins_test'
5 5
6 6 class TestAdminUsersGroupsController(TestController):
7 7
8 8 def test_index(self):
9 9 response = self.app.get(url('users_groups'))
10 10 # Test response...
11 11
12 12 def test_index_as_xml(self):
13 13 response = self.app.get(url('formatted_users_groups', format='xml'))
14 14
15 15 def test_create(self):
16 16 self.log_user()
17 17 users_group_name = TEST_USERS_GROUP
18 18 response = self.app.post(url('users_groups'),
19 19 {'users_group_name':users_group_name,
20 20 'active':True})
21 21 response.follow()
22 22
23 23 self.checkSessionFlash(response,
24 24 'created users group %s' % TEST_USERS_GROUP)
25 25
26
27
28
29
30 26 def test_new(self):
31 27 response = self.app.get(url('new_users_group'))
32 28
33 29 def test_new_as_xml(self):
34 30 response = self.app.get(url('formatted_new_users_group', format='xml'))
35 31
36 32 def test_update(self):
37 33 response = self.app.put(url('users_group', id=1))
38 34
39 35 def test_update_browser_fakeout(self):
40 36 response = self.app.post(url('users_group', id=1),
41 37 params=dict(_method='put'))
42 38
43 39 def test_delete(self):
44 40 self.log_user()
45 41 users_group_name = TEST_USERS_GROUP + 'another'
46 42 response = self.app.post(url('users_groups'),
47 43 {'users_group_name':users_group_name,
48 44 'active':True})
49 45 response.follow()
50 46
51 47 self.checkSessionFlash(response,
52 48 'created users group %s' % users_group_name)
53 49
54 50
55 gr = self.Session().query(UsersGroup)\
51 gr = self.Session.query(UsersGroup)\
56 52 .filter(UsersGroup.users_group_name ==
57 53 users_group_name).one()
58 54
59 55 response = self.app.delete(url('users_group', id=gr.users_group_id))
60 56
61 gr = self.Session().query(UsersGroup)\
57 gr = self.Session.query(UsersGroup)\
62 58 .filter(UsersGroup.users_group_name ==
63 59 users_group_name).scalar()
64 60
65 61 self.assertEqual(gr, None)
66 62
67 63
68 64 def test_delete_browser_fakeout(self):
69 65 response = self.app.post(url('users_group', id=1),
70 66 params=dict(_method='delete'))
71 67
72 68 def test_show(self):
73 69 response = self.app.get(url('users_group', id=1))
74 70
75 71 def test_show_as_xml(self):
76 72 response = self.app.get(url('formatted_users_group', id=1, format='xml'))
77 73
78 74 def test_edit(self):
79 75 response = self.app.get(url('edit_users_group', id=1))
80 76
81 77 def test_edit_as_xml(self):
82 78 response = self.app.get(url('formatted_edit_users_group', id=1, format='xml'))
83 79
84 80 def test_assign_members(self):
85 81 pass
86 82
87 83 def test_add_create_permission(self):
88 84 pass
89 85
90 86 def test_revoke_members(self):
91 87 pass
92 88
93 89
94 90
95 91
@@ -1,143 +1,143 b''
1 1 from rhodecode.tests import *
2 2 from rhodecode.model.db import ChangesetComment, Notification, User, \
3 3 UserNotification
4 4
5 5 class TestChangeSetCommentrController(TestController):
6 6
7 7 def setUp(self):
8 8 for x in ChangesetComment.query().all():
9 self.Session().delete(x)
10 self.Session().commit()
9 self.Session.delete(x)
10 self.Session.commit()
11 11
12 12 for x in Notification.query().all():
13 self.Session().delete(x)
14 self.Session().commit()
13 self.Session.delete(x)
14 self.Session.commit()
15 15
16 16 def tearDown(self):
17 17 for x in ChangesetComment.query().all():
18 self.Session().delete(x)
19 self.Session().commit()
18 self.Session.delete(x)
19 self.Session.commit()
20 20
21 21 for x in Notification.query().all():
22 self.Session().delete(x)
23 self.Session().commit()
22 self.Session.delete(x)
23 self.Session.commit()
24 24
25 25 def test_create(self):
26 26 self.log_user()
27 27 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
28 28 text = u'CommentOnRevision'
29 29
30 30 params = {'text':text}
31 31 response = self.app.post(url(controller='changeset', action='comment',
32 32 repo_name=HG_REPO, revision=rev),
33 33 params=params)
34 34 # Test response...
35 35 self.assertEqual(response.status, '302 Found')
36 36 response.follow()
37 37
38 38 response = self.app.get(url(controller='changeset', action='index',
39 39 repo_name=HG_REPO, revision=rev))
40 40 # test DB
41 41 self.assertEqual(ChangesetComment.query().count(), 1)
42 42 self.assertTrue('''<div class="comments-number">%s '''
43 43 '''comment(s) (0 inline)</div>''' % 1 in response.body)
44 44
45 45
46 46 self.assertEqual(Notification.query().count(), 1)
47 47 notification = Notification.query().all()[0]
48 48
49 49 self.assertEqual(notification.type_, Notification.TYPE_CHANGESET_COMMENT)
50 50 self.assertTrue((u'/vcs_test_hg/changeset/27cd5cce30c96924232df'
51 51 'fcd24178a07ffeb5dfc#comment-1') in notification.subject)
52 52
53 53 def test_create_inline(self):
54 54 self.log_user()
55 55 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
56 56 text = u'CommentOnRevision'
57 57 f_path = 'vcs/web/simplevcs/views/repository.py'
58 58 line = 'n1'
59 59
60 60 params = {'text':text, 'f_path':f_path, 'line':line}
61 61 response = self.app.post(url(controller='changeset', action='comment',
62 62 repo_name=HG_REPO, revision=rev),
63 63 params=params)
64 64 # Test response...
65 65 self.assertEqual(response.status, '302 Found')
66 66 response.follow()
67 67
68 68 response = self.app.get(url(controller='changeset', action='index',
69 69 repo_name=HG_REPO, revision=rev))
70 70 #test DB
71 71 self.assertEqual(ChangesetComment.query().count(), 1)
72 72 self.assertTrue('''<div class="comments-number">0 comment(s)'''
73 73 ''' (%s inline)</div>''' % 1 in response.body)
74 74 self.assertTrue('''<div class="inline-comment-placeholder-line"'''
75 75 ''' line="n1" target_id="vcswebsimplevcsviews'''
76 76 '''repositorypy">''' in response.body)
77 77
78 78 self.assertEqual(Notification.query().count(), 1)
79 79 notification = Notification.query().all()[0]
80 80
81 81 self.assertEqual(notification.type_, Notification.TYPE_CHANGESET_COMMENT)
82 82 self.assertTrue((u'/vcs_test_hg/changeset/27cd5cce30c96924232df'
83 83 'fcd24178a07ffeb5dfc#comment-1') in notification.subject)
84 84
85 85 def test_create_with_mention(self):
86 86 self.log_user()
87 87
88 88 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
89 89 text = u'@test_regular check CommentOnRevision'
90 90
91 91 params = {'text':text}
92 92 response = self.app.post(url(controller='changeset', action='comment',
93 93 repo_name=HG_REPO, revision=rev),
94 94 params=params)
95 95 # Test response...
96 96 self.assertEqual(response.status, '302 Found')
97 97 response.follow()
98 98
99 99 response = self.app.get(url(controller='changeset', action='index',
100 100 repo_name=HG_REPO, revision=rev))
101 101 # test DB
102 102 self.assertEqual(ChangesetComment.query().count(), 1)
103 103 self.assertTrue('''<div class="comments-number">%s '''
104 104 '''comment(s) (0 inline)</div>''' % 1 in response.body)
105 105
106 106
107 107 self.assertEqual(Notification.query().count(), 2)
108 108 users = [x.user.username for x in UserNotification.query().all()]
109 109
110 110 # test_regular get's notification by @mention
111 111 self.assertEqual(sorted(users), [u'test_admin', u'test_regular'])
112 112
113 113 def test_delete(self):
114 114 self.log_user()
115 115 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
116 116 text = u'CommentOnRevision'
117 117
118 118 params = {'text':text}
119 119 response = self.app.post(url(controller='changeset', action='comment',
120 120 repo_name=HG_REPO, revision=rev),
121 121 params=params)
122 122
123 123 comments = ChangesetComment.query().all()
124 124 self.assertEqual(len(comments), 1)
125 125 comment_id = comments[0].comment_id
126 126
127 127
128 128 self.app.delete(url(controller='changeset',
129 129 action='delete_comment',
130 130 repo_name=HG_REPO,
131 131 comment_id=comment_id))
132 132
133 133 comments = ChangesetComment.query().all()
134 134 self.assertEqual(len(comments), 0)
135 135
136 136 response = self.app.get(url(controller='changeset', action='index',
137 137 repo_name=HG_REPO, revision=rev))
138 138 self.assertTrue('''<div class="comments-number">0 comment(s)'''
139 139 ''' (0 inline)</div>''' in response.body)
140 140
141 141
142 142
143 143
@@ -1,313 +1,326 b''
1 1 from rhodecode.tests import *
2 2
3 3 ARCHIVE_SPECS = {
4 4 '.tar.bz2': ('application/x-bzip2', 'tbz2', ''),
5 5 '.tar.gz': ('application/x-gzip', 'tgz', ''),
6 6 '.zip': ('application/zip', 'zip', ''),
7 7 }
8 8
9 9 class TestFilesController(TestController):
10 10
11 11 def test_index(self):
12 12 self.log_user()
13 13 response = self.app.get(url(controller='files', action='index',
14 14 repo_name=HG_REPO,
15 15 revision='tip',
16 16 f_path='/'))
17 17 # Test response...
18 18 assert '<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/docs">docs</a>' in response.body, 'missing dir'
19 19 assert '<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/tests">tests</a>' in response.body, 'missing dir'
20 20 assert '<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/vcs">vcs</a>' in response.body, 'missing dir'
21 21 assert '<a class="browser-file ypjax-link" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/.hgignore">.hgignore</a>' in response.body, 'missing file'
22 22 assert '<a class="browser-file ypjax-link" href="/vcs_test_hg/files/27cd5cce30c96924232dffcd24178a07ffeb5dfc/MANIFEST.in">MANIFEST.in</a>' in response.body, 'missing file'
23 23
24 24
25 25 def test_index_revision(self):
26 26 self.log_user()
27 27
28 28 response = self.app.get(url(controller='files', action='index',
29 29 repo_name=HG_REPO,
30 30 revision='7ba66bec8d6dbba14a2155be32408c435c5f4492',
31 31 f_path='/'))
32 32
33 33
34 34
35 35 #Test response...
36 36
37 37 assert '<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/docs">docs</a>' in response.body, 'missing dir'
38 38 assert '<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/tests">tests</a>' in response.body, 'missing dir'
39 39 assert '<a class="browser-file ypjax-link" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/README.rst">README.rst</a>' in response.body, 'missing file'
40 40 assert '1.1 KiB' in response.body, 'missing size of setup.py'
41 41 assert 'text/x-python' in response.body, 'missing mimetype of setup.py'
42 42
43 43
44 44
45 45 def test_index_different_branch(self):
46 46 self.log_user()
47 47
48 48 response = self.app.get(url(controller='files', action='index',
49 49 repo_name=HG_REPO,
50 50 revision='97e8b885c04894463c51898e14387d80c30ed1ee',
51 51 f_path='/'))
52 52
53 53
54 54
55 55 assert """<span style="text-transform: uppercase;"><a href="#">branch: git</a></span>""" in response.body, 'missing or wrong branch info'
56 56
57 57
58 58
59 59 def test_index_paging(self):
60 60 self.log_user()
61 61
62 62 for r in [(73, 'a066b25d5df7016b45a41b7e2a78c33b57adc235'),
63 63 (92, 'cc66b61b8455b264a7a8a2d8ddc80fcfc58c221e'),
64 64 (109, '75feb4c33e81186c87eac740cee2447330288412'),
65 65 (1, '3d8f361e72ab303da48d799ff1ac40d5ac37c67e'),
66 66 (0, 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]:
67 67
68 68 response = self.app.get(url(controller='files', action='index',
69 69 repo_name=HG_REPO,
70 70 revision=r[1],
71 71 f_path='/'))
72 72
73 73 assert """@ r%s:%s""" % (r[0], r[1][:12]) in response.body, 'missing info about current revision'
74 74
75 75 def test_file_source(self):
76 76 self.log_user()
77 77 response = self.app.get(url(controller='files', action='index',
78 78 repo_name=HG_REPO,
79 79 revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
80 80 f_path='vcs/nodes.py'))
81 81
82 82 #test or history
83 83 assert """<optgroup label="Changesets">
84 84 <option selected="selected" value="8911406ad776fdd3d0b9932a2e89677e57405a48">r167:8911406ad776</option>
85 85 <option value="aa957ed78c35a1541f508d2ec90e501b0a9e3167">r165:aa957ed78c35</option>
86 86 <option value="48e11b73e94c0db33e736eaeea692f990cb0b5f1">r140:48e11b73e94c</option>
87 87 <option value="adf3cbf483298563b968a6c673cd5bde5f7d5eea">r126:adf3cbf48329</option>
88 88 <option value="6249fd0fb2cfb1411e764129f598e2cf0de79a6f">r113:6249fd0fb2cf</option>
89 89 <option value="75feb4c33e81186c87eac740cee2447330288412">r109:75feb4c33e81</option>
90 90 <option value="9a4dc232ecdc763ef2e98ae2238cfcbba4f6ad8d">r108:9a4dc232ecdc</option>
91 91 <option value="595cce4efa21fda2f2e4eeb4fe5f2a6befe6fa2d">r107:595cce4efa21</option>
92 92 <option value="4a8bd421fbc2dfbfb70d85a3fe064075ab2c49da">r104:4a8bd421fbc2</option>
93 93 <option value="57be63fc8f85e65a0106a53187f7316f8c487ffa">r102:57be63fc8f85</option>
94 94 <option value="5530bd87f7e2e124a64d07cb2654c997682128be">r101:5530bd87f7e2</option>
95 95 <option value="e516008b1c93f142263dc4b7961787cbad654ce1">r99:e516008b1c93</option>
96 96 <option value="41f43fc74b8b285984554532eb105ac3be5c434f">r93:41f43fc74b8b</option>
97 97 <option value="cc66b61b8455b264a7a8a2d8ddc80fcfc58c221e">r92:cc66b61b8455</option>
98 98 <option value="73ab5b616b3271b0518682fb4988ce421de8099f">r91:73ab5b616b32</option>
99 99 <option value="e0da75f308c0f18f98e9ce6257626009fdda2b39">r82:e0da75f308c0</option>
100 100 <option value="fb2e41e0f0810be4d7103bc2a4c7be16ee3ec611">r81:fb2e41e0f081</option>
101 101 <option value="602ae2f5e7ade70b3b66a58cdd9e3e613dc8a028">r76:602ae2f5e7ad</option>
102 102 <option value="a066b25d5df7016b45a41b7e2a78c33b57adc235">r73:a066b25d5df7</option>
103 103 <option value="637a933c905958ce5151f154147c25c1c7b68832">r61:637a933c9059</option>
104 104 <option value="0c21004effeb8ce2d2d5b4a8baf6afa8394b6fbc">r60:0c21004effeb</option>
105 105 <option value="a1f39c56d3f1d52d5fb5920370a2a2716cd9a444">r59:a1f39c56d3f1</option>
106 106 <option value="97d32df05c715a3bbf936bf3cc4e32fb77fe1a7f">r58:97d32df05c71</option>
107 107 <option value="08eaf14517718dccea4b67755a93368341aca919">r57:08eaf1451771</option>
108 108 <option value="22f71ad265265a53238359c883aa976e725aa07d">r56:22f71ad26526</option>
109 109 <option value="97501f02b7b4330924b647755663a2d90a5e638d">r49:97501f02b7b4</option>
110 110 <option value="86ede6754f2b27309452bb11f997386ae01d0e5a">r47:86ede6754f2b</option>
111 111 <option value="014c40c0203c423dc19ecf94644f7cac9d4cdce0">r45:014c40c0203c</option>
112 112 <option value="ee87846a61c12153b51543bf860e1026c6d3dcba">r30:ee87846a61c1</option>
113 113 <option value="9bb326a04ae5d98d437dece54be04f830cf1edd9">r26:9bb326a04ae5</option>
114 114 <option value="536c1a19428381cfea92ac44985304f6a8049569">r24:536c1a194283</option>
115 115 <option value="dc5d2c0661b61928834a785d3e64a3f80d3aad9c">r8:dc5d2c0661b6</option>
116 116 <option value="3803844fdbd3b711175fc3da9bdacfcd6d29a6fb">r7:3803844fdbd3</option>
117 117 </optgroup>
118 118 <optgroup label="Branches">
119 119 <option value="27cd5cce30c96924232dffcd24178a07ffeb5dfc">default</option>
120 120 <option value="97e8b885c04894463c51898e14387d80c30ed1ee">git</option>
121 121 <option value="2e6a2bf9356ca56df08807f4ad86d480da72a8f4">web</option>
122 122 </optgroup>
123 123 <optgroup label="Tags">
124 124 <option value="27cd5cce30c96924232dffcd24178a07ffeb5dfc">tip</option>
125 125 <option value="fd4bdb5e9b2a29b4393a4ac6caef48c17ee1a200">0.1.4</option>
126 126 <option value="17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">0.1.3</option>
127 127 <option value="a7e60bff65d57ac3a1a1ce3b12a70f8a9e8a7720">0.1.2</option>
128 128 <option value="eb3a60fc964309c1a318b8dfe26aa2d1586c85ae">0.1.1</option>
129 129 </optgroup>""" in response.body
130 130
131 131
132 132 assert """<div class="commit">Partially implemented #16. filecontent/commit message/author/node name are safe_unicode now.
133 133 In addition some other __str__ are unicode as well
134 134 Added test for unicode
135 135 Improved test to clone into uniq repository.
136 136 removed extra unicode conversion in diff.</div>""" in response.body
137 137
138 138 assert """<span style="text-transform: uppercase;"><a href="#">branch: default</a></span>""" in response.body, 'missing or wrong branch info'
139 139
140 140 def test_file_annotation(self):
141 141 self.log_user()
142 142 response = self.app.get(url(controller='files', action='annotate',
143 143 repo_name=HG_REPO,
144 144 revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
145 145 f_path='vcs/nodes.py'))
146 146
147 147 print response.body
148 148 assert """<optgroup label="Changesets">
149 149 <option selected="selected" value="8911406ad776fdd3d0b9932a2e89677e57405a48">r167:8911406ad776</option>
150 150 <option value="aa957ed78c35a1541f508d2ec90e501b0a9e3167">r165:aa957ed78c35</option>
151 151 <option value="48e11b73e94c0db33e736eaeea692f990cb0b5f1">r140:48e11b73e94c</option>
152 152 <option value="adf3cbf483298563b968a6c673cd5bde5f7d5eea">r126:adf3cbf48329</option>
153 153 <option value="6249fd0fb2cfb1411e764129f598e2cf0de79a6f">r113:6249fd0fb2cf</option>
154 154 <option value="75feb4c33e81186c87eac740cee2447330288412">r109:75feb4c33e81</option>
155 155 <option value="9a4dc232ecdc763ef2e98ae2238cfcbba4f6ad8d">r108:9a4dc232ecdc</option>
156 156 <option value="595cce4efa21fda2f2e4eeb4fe5f2a6befe6fa2d">r107:595cce4efa21</option>
157 157 <option value="4a8bd421fbc2dfbfb70d85a3fe064075ab2c49da">r104:4a8bd421fbc2</option>
158 158 <option value="57be63fc8f85e65a0106a53187f7316f8c487ffa">r102:57be63fc8f85</option>
159 159 <option value="5530bd87f7e2e124a64d07cb2654c997682128be">r101:5530bd87f7e2</option>
160 160 <option value="e516008b1c93f142263dc4b7961787cbad654ce1">r99:e516008b1c93</option>
161 161 <option value="41f43fc74b8b285984554532eb105ac3be5c434f">r93:41f43fc74b8b</option>
162 162 <option value="cc66b61b8455b264a7a8a2d8ddc80fcfc58c221e">r92:cc66b61b8455</option>
163 163 <option value="73ab5b616b3271b0518682fb4988ce421de8099f">r91:73ab5b616b32</option>
164 164 <option value="e0da75f308c0f18f98e9ce6257626009fdda2b39">r82:e0da75f308c0</option>
165 165 <option value="fb2e41e0f0810be4d7103bc2a4c7be16ee3ec611">r81:fb2e41e0f081</option>
166 166 <option value="602ae2f5e7ade70b3b66a58cdd9e3e613dc8a028">r76:602ae2f5e7ad</option>
167 167 <option value="a066b25d5df7016b45a41b7e2a78c33b57adc235">r73:a066b25d5df7</option>
168 168 <option value="637a933c905958ce5151f154147c25c1c7b68832">r61:637a933c9059</option>
169 169 <option value="0c21004effeb8ce2d2d5b4a8baf6afa8394b6fbc">r60:0c21004effeb</option>
170 170 <option value="a1f39c56d3f1d52d5fb5920370a2a2716cd9a444">r59:a1f39c56d3f1</option>
171 171 <option value="97d32df05c715a3bbf936bf3cc4e32fb77fe1a7f">r58:97d32df05c71</option>
172 172 <option value="08eaf14517718dccea4b67755a93368341aca919">r57:08eaf1451771</option>
173 173 <option value="22f71ad265265a53238359c883aa976e725aa07d">r56:22f71ad26526</option>
174 174 <option value="97501f02b7b4330924b647755663a2d90a5e638d">r49:97501f02b7b4</option>
175 175 <option value="86ede6754f2b27309452bb11f997386ae01d0e5a">r47:86ede6754f2b</option>
176 176 <option value="014c40c0203c423dc19ecf94644f7cac9d4cdce0">r45:014c40c0203c</option>
177 177 <option value="ee87846a61c12153b51543bf860e1026c6d3dcba">r30:ee87846a61c1</option>
178 178 <option value="9bb326a04ae5d98d437dece54be04f830cf1edd9">r26:9bb326a04ae5</option>
179 179 <option value="536c1a19428381cfea92ac44985304f6a8049569">r24:536c1a194283</option>
180 180 <option value="dc5d2c0661b61928834a785d3e64a3f80d3aad9c">r8:dc5d2c0661b6</option>
181 181 <option value="3803844fdbd3b711175fc3da9bdacfcd6d29a6fb">r7:3803844fdbd3</option>
182 182 </optgroup>
183 183 <optgroup label="Branches">
184 184 <option value="27cd5cce30c96924232dffcd24178a07ffeb5dfc">default</option>
185 185 <option value="97e8b885c04894463c51898e14387d80c30ed1ee">git</option>
186 186 <option value="2e6a2bf9356ca56df08807f4ad86d480da72a8f4">web</option>
187 187 </optgroup>
188 188 <optgroup label="Tags">
189 189 <option value="27cd5cce30c96924232dffcd24178a07ffeb5dfc">tip</option>
190 190 <option value="fd4bdb5e9b2a29b4393a4ac6caef48c17ee1a200">0.1.4</option>
191 191 <option value="17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">0.1.3</option>
192 192 <option value="a7e60bff65d57ac3a1a1ce3b12a70f8a9e8a7720">0.1.2</option>
193 193 <option value="eb3a60fc964309c1a318b8dfe26aa2d1586c85ae">0.1.1</option>
194 194 </optgroup>""" in response.body, 'missing or wrong history in annotation'
195 195
196 196 assert """<span style="text-transform: uppercase;"><a href="#">branch: default</a></span>""" in response.body, 'missing or wrong branch info'
197 197
198 198
199 199
200 200 def test_archival(self):
201 201 self.log_user()
202 202
203 203 for arch_ext, info in ARCHIVE_SPECS.items():
204 204 fname = '27cd5cce30c96924232dffcd24178a07ffeb5dfc%s' % arch_ext
205 205 filename = '%s-%s' % (HG_REPO, fname)
206 206
207 207 response = self.app.get(url(controller='files', action='archivefile',
208 208 repo_name=HG_REPO,
209 209 fname=fname))
210 210
211 211 assert response.status == '200 OK', 'wrong response code'
212 212 assert response.response._headers.items() == [('Pragma', 'no-cache'),
213 213 ('Cache-Control', 'no-cache'),
214 214 ('Content-Type', '%s; charset=utf-8' % info[0]),
215 215 ('Content-Disposition', 'attachment; filename=%s' % filename), ], 'wrong headers'
216 216
217 217 def test_archival_wrong_ext(self):
218 218 self.log_user()
219 219
220 220 for arch_ext in ['tar', 'rar', 'x', '..ax', '.zipz']:
221 221 fname = '27cd5cce30c96924232dffcd24178a07ffeb5dfc%s' % arch_ext
222 222
223 223 response = self.app.get(url(controller='files', action='archivefile',
224 224 repo_name=HG_REPO,
225 225 fname=fname))
226 226 assert 'Unknown archive type' in response.body
227 227
228 228
229 229 def test_archival_wrong_revision(self):
230 230 self.log_user()
231 231
232 232 for rev in ['00x000000', 'tar', 'wrong', '@##$@$424213232', '232dffcd']:
233 233 fname = '%s.zip' % rev
234 234
235 235 response = self.app.get(url(controller='files', action='archivefile',
236 236 repo_name=HG_REPO,
237 237 fname=fname))
238 238 assert 'Unknown revision' in response.body
239 239
240 240 #==========================================================================
241 241 # RAW FILE
242 242 #==========================================================================
243 243 def test_raw_file_ok(self):
244 244 self.log_user()
245 245 response = self.app.get(url(controller='files', action='rawfile',
246 246 repo_name=HG_REPO,
247 247 revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
248 248 f_path='vcs/nodes.py'))
249 249
250 250 assert response.content_disposition == "attachment; filename=nodes.py"
251 251 assert response.content_type == "text/x-python"
252 252
253 253 def test_raw_file_wrong_cs(self):
254 254 self.log_user()
255 255 rev = u'ERRORce30c96924232dffcd24178a07ffeb5dfc'
256 256 f_path = 'vcs/nodes.py'
257 257
258 258 response = self.app.get(url(controller='files', action='rawfile',
259 259 repo_name=HG_REPO,
260 260 revision=rev,
261 261 f_path=f_path))
262 262
263 263 assert """Revision %r does not exist for this repository""" % (rev) in response.session['flash'][0][1], 'No flash message'
264 264 assert """%s""" % (HG_REPO) in response.session['flash'][0][1], 'No flash message'
265 265
266 266
267 267
268 268 def test_raw_file_wrong_f_path(self):
269 269 self.log_user()
270 270 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
271 271 f_path = 'vcs/ERRORnodes.py'
272 272 response = self.app.get(url(controller='files', action='rawfile',
273 273 repo_name=HG_REPO,
274 274 revision=rev,
275 275 f_path=f_path))
276 276 assert "There is no file nor directory at the given path: %r at revision %r" % (f_path, rev[:12]) in response.session['flash'][0][1], 'No flash message'
277 277
278 278 #==========================================================================
279 279 # RAW RESPONSE - PLAIN
280 280 #==========================================================================
281 281 def test_raw_ok(self):
282 282 self.log_user()
283 283 response = self.app.get(url(controller='files', action='raw',
284 284 repo_name=HG_REPO,
285 285 revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
286 286 f_path='vcs/nodes.py'))
287 287
288 288 assert response.content_type == "text/plain"
289 289
290 290 def test_raw_wrong_cs(self):
291 291 self.log_user()
292 292 rev = u'ERRORcce30c96924232dffcd24178a07ffeb5dfc'
293 293 f_path = 'vcs/nodes.py'
294 294
295 295 response = self.app.get(url(controller='files', action='raw',
296 296 repo_name=HG_REPO,
297 297 revision=rev,
298 298 f_path=f_path))
299 299
300 300 assert """Revision %r does not exist for this repository""" % (rev) in response.session['flash'][0][1], 'No flash message'
301 301 assert """%s""" % (HG_REPO) in response.session['flash'][0][1], 'No flash message'
302 302
303 303
304 304 def test_raw_wrong_f_path(self):
305 305 self.log_user()
306 306 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
307 307 f_path = 'vcs/ERRORnodes.py'
308 308 response = self.app.get(url(controller='files', action='raw',
309 309 repo_name=HG_REPO,
310 310 revision=rev,
311 311 f_path=f_path))
312 312
313 313 assert "There is no file nor directory at the given path: %r at revision %r" % (f_path, rev[:12]) in response.session['flash'][0][1], 'No flash message'
314
315 def test_ajaxed_files_list(self):
316 self.log_user()
317 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
318 response = self.app.get(url('files_nodelist_home',repo_name=HG_REPO,
319 f_path='/',
320 revision=rev),
321 extra_environ={'HTTP_X_PARTIAL_XHR':'1'},
322 )
323 self.assertTrue("vcs/web/simplevcs/views/repository.py" in response.body)
324
325
326
@@ -1,86 +1,86 b''
1 1 from rhodecode.tests import *
2 2
3 3 from rhodecode.model.db import Repository
4 4
5 5 class TestForksController(TestController):
6 6
7 7 def test_index(self):
8 8 self.log_user()
9 9 repo_name = HG_REPO
10 10 response = self.app.get(url(controller='forks', action='forks',
11 11 repo_name=repo_name))
12 12
13 13 self.assertTrue("""There are no forks yet""" in response.body)
14 14
15 15
16 16 def test_index_with_fork(self):
17 17 self.log_user()
18 18
19 19 # create a fork
20 20 fork_name = HG_FORK
21 21 description = 'fork of vcs test'
22 22 repo_name = HG_REPO
23 23 org_repo = Repository.get_by_repo_name(repo_name)
24 24 response = self.app.post(url(controller='forks',
25 25 action='fork_create',
26 26 repo_name=repo_name),
27 27 {'repo_name':fork_name,
28 28 'repo_group':'',
29 29 'fork_parent_id':org_repo.repo_id,
30 30 'repo_type':'hg',
31 31 'description':description,
32 32 'private':'False'})
33 33
34 34 response = self.app.get(url(controller='forks', action='forks',
35 35 repo_name=repo_name))
36 36
37 37
38 38 self.assertTrue("""<a href="/%s/summary">"""
39 39 """vcs_test_hg_fork</a>""" % fork_name
40 40 in response.body)
41 41
42 42 #remove this fork
43 43 response = self.app.delete(url('repo', repo_name=fork_name))
44 44
45 45
46 46
47 47
48 48 def test_z_fork_create(self):
49 49 self.log_user()
50 50 fork_name = HG_FORK
51 51 description = 'fork of vcs test'
52 52 repo_name = HG_REPO
53 53 org_repo = Repository.get_by_repo_name(repo_name)
54 54 response = self.app.post(url(controller='forks', action='fork_create',
55 55 repo_name=repo_name),
56 56 {'repo_name':fork_name,
57 57 'repo_group':'',
58 58 'fork_parent_id':org_repo.repo_id,
59 59 'repo_type':'hg',
60 60 'description':description,
61 61 'private':'False'})
62 62
63 63 #test if we have a message that fork is ok
64 64 self.assertTrue('forked %s repository as %s' \
65 65 % (repo_name, fork_name) in response.session['flash'][0])
66 66
67 67 #test if the fork was created in the database
68 fork_repo = self.Session().query(Repository)\
68 fork_repo = self.Session.query(Repository)\
69 69 .filter(Repository.repo_name == fork_name).one()
70 70
71 71 self.assertEqual(fork_repo.repo_name, fork_name)
72 72 self.assertEqual(fork_repo.fork.repo_name, repo_name)
73 73
74 74
75 75 #test if fork is visible in the list ?
76 76 response = response.follow()
77 77
78 78
79 79 # check if fork is marked as fork
80 80 # wait for cache to expire
81 81 import time
82 82 time.sleep(10)
83 83 response = self.app.get(url(controller='summary', action='index',
84 84 repo_name=fork_name))
85 85
86 86 self.assertTrue('Fork of %s' % repo_name in response.body)
@@ -1,42 +1,42 b''
1 1 from rhodecode.tests import *
2 2 from rhodecode.model.db import UserFollowing, User, Repository
3 3 from rhodecode.lib.helpers import get_token
4 4 import datetime
5 5
6 6 class TestJournalController(TestController):
7 7
8 8 def test_index(self):
9 9 self.log_user()
10 10 response = self.app.get(url(controller='journal', action='index'))
11 11
12 12 # Test response...
13 13 assert """ <span id="follow_toggle_1" class="following" title="Stop following this repository""" in response.body, 'no info about stop follwoing repo id 1'
14 14
15 15 assert """<div class="journal_day">%s</div>""" % datetime.date.today() in response.body, 'no info about action journal day'
16 16
17 17 def test_stop_following_repository(self):
18 18 session = self.log_user()
19 # usr = self.Session().query(User).filter(User.username == 'test_admin').one()
20 # repo = self.Session().query(Repository).filter(Repository.repo_name == HG_REPO).one()
19 # usr = self.Session.query(User).filter(User.username == 'test_admin').one()
20 # repo = self.Session.query(Repository).filter(Repository.repo_name == HG_REPO).one()
21 21 #
22 # followings = self.Session().query(UserFollowing)\
22 # followings = self.Session.query(UserFollowing)\
23 23 # .filter(UserFollowing.user == usr)\
24 24 # .filter(UserFollowing.follows_repository == repo).all()
25 25 #
26 26 # assert len(followings) == 1, 'Not following any repository'
27 27 #
28 28 # response = self.app.post(url(controller='journal',
29 29 # action='toggle_following'),
30 30 # {'auth_token':get_token(session),
31 31 # 'follows_repo_id':repo.repo_id})
32 32
33 33 def test_start_following_repository(self):
34 34 self.log_user()
35 35 response = self.app.get(url(controller='journal', action='index'),)
36 36
37 37
38 38 def __add_repo(self):
39 39 pass
40 40
41 41 def __remove_repo(self):
42 42 pass
@@ -1,267 +1,267 b''
1 1 # -*- coding: utf-8 -*-
2 2 from rhodecode.tests import *
3 3 from rhodecode.model.db import User, Notification
4 4 from rhodecode.lib import generate_api_key
5 5 from rhodecode.lib.auth import check_password
6 6 from rhodecode.model.meta import Session
7 7
8 8 class TestLoginController(TestController):
9 9
10 10 def tearDown(self):
11 11 for n in Notification.query().all():
12 Session().delete(n)
12 Session.delete(n)
13 13
14 Session().commit()
14 Session.commit()
15 15 self.assertEqual(Notification.query().all(), [])
16 16
17 17 def test_index(self):
18 18 response = self.app.get(url(controller='login', action='index'))
19 19 self.assertEqual(response.status, '200 OK')
20 20 # Test response...
21 21
22 22 def test_login_admin_ok(self):
23 23 response = self.app.post(url(controller='login', action='index'),
24 24 {'username':'test_admin',
25 25 'password':'test12'})
26 26 self.assertEqual(response.status, '302 Found')
27 27 self.assertEqual(response.session['rhodecode_user'].get('username') ,
28 28 'test_admin')
29 29 response = response.follow()
30 30 self.assertTrue('%s repository' % HG_REPO in response.body)
31 31
32 32 def test_login_regular_ok(self):
33 33 response = self.app.post(url(controller='login', action='index'),
34 34 {'username':'test_regular',
35 35 'password':'test12'})
36 36
37 37 self.assertEqual(response.status, '302 Found')
38 38 self.assertEqual(response.session['rhodecode_user'].get('username') ,
39 39 'test_regular')
40 40 response = response.follow()
41 41 self.assertTrue('%s repository' % HG_REPO in response.body)
42 42 self.assertTrue('<a title="Admin" href="/_admin">' not in response.body)
43 43
44 44 def test_login_ok_came_from(self):
45 45 test_came_from = '/_admin/users'
46 46 response = self.app.post(url(controller='login', action='index',
47 47 came_from=test_came_from),
48 48 {'username':'test_admin',
49 49 'password':'test12'})
50 50 self.assertEqual(response.status, '302 Found')
51 51 response = response.follow()
52 52
53 53 self.assertEqual(response.status, '200 OK')
54 54 self.assertTrue('Users administration' in response.body)
55 55
56 56
57 57 def test_login_short_password(self):
58 58 response = self.app.post(url(controller='login', action='index'),
59 59 {'username':'test_admin',
60 60 'password':'as'})
61 61 self.assertEqual(response.status, '200 OK')
62 62
63 63 self.assertTrue('Enter 3 characters or more' in response.body)
64 64
65 65 def test_login_wrong_username_password(self):
66 66 response = self.app.post(url(controller='login', action='index'),
67 67 {'username':'error',
68 68 'password':'test12'})
69 69 self.assertEqual(response.status , '200 OK')
70 70
71 71 self.assertTrue('invalid user name' in response.body)
72 72 self.assertTrue('invalid password' in response.body)
73 73
74 74 #==========================================================================
75 75 # REGISTRATIONS
76 76 #==========================================================================
77 77 def test_register(self):
78 78 response = self.app.get(url(controller='login', action='register'))
79 79 self.assertTrue('Sign Up to RhodeCode' in response.body)
80 80
81 81 def test_register_err_same_username(self):
82 82 response = self.app.post(url(controller='login', action='register'),
83 83 {'username':'test_admin',
84 84 'password':'test12',
85 85 'password_confirmation':'test12',
86 86 'email':'goodmail@domain.com',
87 87 'name':'test',
88 88 'lastname':'test'})
89 89
90 90 self.assertEqual(response.status , '200 OK')
91 91 self.assertTrue('This username already exists' in response.body)
92 92
93 93 def test_register_err_same_email(self):
94 94 response = self.app.post(url(controller='login', action='register'),
95 95 {'username':'test_admin_0',
96 96 'password':'test12',
97 97 'password_confirmation':'test12',
98 98 'email':'test_admin@mail.com',
99 99 'name':'test',
100 100 'lastname':'test'})
101 101
102 102 self.assertEqual(response.status , '200 OK')
103 103 assert 'This e-mail address is already taken' in response.body
104 104
105 105 def test_register_err_same_email_case_sensitive(self):
106 106 response = self.app.post(url(controller='login', action='register'),
107 107 {'username':'test_admin_1',
108 108 'password':'test12',
109 109 'password_confirmation':'test12',
110 110 'email':'TesT_Admin@mail.COM',
111 111 'name':'test',
112 112 'lastname':'test'})
113 113 self.assertEqual(response.status , '200 OK')
114 114 assert 'This e-mail address is already taken' in response.body
115 115
116 116 def test_register_err_wrong_data(self):
117 117 response = self.app.post(url(controller='login', action='register'),
118 118 {'username':'xs',
119 119 'password':'test',
120 120 'password_confirmation':'test',
121 121 'email':'goodmailm',
122 122 'name':'test',
123 123 'lastname':'test'})
124 124 self.assertEqual(response.status , '200 OK')
125 125 assert 'An email address must contain a single @' in response.body
126 126 assert 'Enter a value 6 characters long or more' in response.body
127 127
128 128
129 129 def test_register_err_username(self):
130 130 response = self.app.post(url(controller='login', action='register'),
131 131 {'username':'error user',
132 132 'password':'test12',
133 133 'password_confirmation':'test12',
134 134 'email':'goodmailm',
135 135 'name':'test',
136 136 'lastname':'test'})
137 137
138 138 self.assertEqual(response.status , '200 OK')
139 139 assert 'An email address must contain a single @' in response.body
140 140 assert ('Username may only contain '
141 141 'alphanumeric characters underscores, '
142 142 'periods or dashes and must begin with '
143 143 'alphanumeric character') in response.body
144 144
145 145 def test_register_err_case_sensitive(self):
146 146 response = self.app.post(url(controller='login', action='register'),
147 147 {'username':'Test_Admin',
148 148 'password':'test12',
149 149 'password_confirmation':'test12',
150 150 'email':'goodmailm',
151 151 'name':'test',
152 152 'lastname':'test'})
153 153
154 154 self.assertEqual(response.status , '200 OK')
155 155 self.assertTrue('An email address must contain a single @' in response.body)
156 156 self.assertTrue('This username already exists' in response.body)
157 157
158 158
159 159
160 160 def test_register_special_chars(self):
161 161 response = self.app.post(url(controller='login', action='register'),
162 162 {'username':'xxxaxn',
163 163 'password':'ąćźżąśśśś',
164 164 'password_confirmation':'ąćźżąśśśś',
165 165 'email':'goodmailm@test.plx',
166 166 'name':'test',
167 167 'lastname':'test'})
168 168
169 169 self.assertEqual(response.status , '200 OK')
170 170 self.assertTrue('Invalid characters in password' in response.body)
171 171
172 172
173 173 def test_register_password_mismatch(self):
174 174 response = self.app.post(url(controller='login', action='register'),
175 175 {'username':'xs',
176 176 'password':'123qwe',
177 177 'password_confirmation':'qwe123',
178 178 'email':'goodmailm@test.plxa',
179 179 'name':'test',
180 180 'lastname':'test'})
181 181
182 182 self.assertEqual(response.status , '200 OK')
183 183 assert 'Passwords do not match' in response.body
184 184
185 185 def test_register_ok(self):
186 186 username = 'test_regular4'
187 187 password = 'qweqwe'
188 188 email = 'marcin@test.com'
189 189 name = 'testname'
190 190 lastname = 'testlastname'
191 191
192 192 response = self.app.post(url(controller='login', action='register'),
193 193 {'username':username,
194 194 'password':password,
195 195 'password_confirmation':password,
196 196 'email':email,
197 197 'name':name,
198 198 'lastname':lastname})
199 199 self.assertEqual(response.status , '302 Found')
200 200 assert 'You have successfully registered into rhodecode' in response.session['flash'][0], 'No flash message about user registration'
201 201
202 ret = self.Session().query(User).filter(User.username == 'test_regular4').one()
202 ret = self.Session.query(User).filter(User.username == 'test_regular4').one()
203 203 assert ret.username == username , 'field mismatch %s %s' % (ret.username, username)
204 204 assert check_password(password, ret.password) == True , 'password mismatch'
205 205 assert ret.email == email , 'field mismatch %s %s' % (ret.email, email)
206 206 assert ret.name == name , 'field mismatch %s %s' % (ret.name, name)
207 207 assert ret.lastname == lastname , 'field mismatch %s %s' % (ret.lastname, lastname)
208 208
209 209
210 210 def test_forgot_password_wrong_mail(self):
211 211 response = self.app.post(url(controller='login', action='password_reset'),
212 212 {'email':'marcin@wrongmail.org', })
213 213
214 214 assert "This e-mail address doesn't exist" in response.body, 'Missing error message about wrong email'
215 215
216 216 def test_forgot_password(self):
217 217 response = self.app.get(url(controller='login',
218 218 action='password_reset'))
219 219 self.assertEqual(response.status , '200 OK')
220 220
221 221 username = 'test_password_reset_1'
222 222 password = 'qweqwe'
223 223 email = 'marcin@python-works.com'
224 224 name = 'passwd'
225 225 lastname = 'reset'
226 226
227 227 new = User()
228 228 new.username = username
229 229 new.password = password
230 230 new.email = email
231 231 new.name = name
232 232 new.lastname = lastname
233 233 new.api_key = generate_api_key(username)
234 self.Session().add(new)
235 self.Session().commit()
234 self.Session.add(new)
235 self.Session.commit()
236 236
237 237 response = self.app.post(url(controller='login',
238 238 action='password_reset'),
239 239 {'email':email, })
240 240
241 241 self.checkSessionFlash(response, 'Your password reset link was sent')
242 242
243 243 response = response.follow()
244 244
245 245 # BAD KEY
246 246
247 247 key = "bad"
248 248 response = self.app.get(url(controller='login',
249 249 action='password_reset_confirmation',
250 250 key=key))
251 251 self.assertEqual(response.status, '302 Found')
252 252 self.assertTrue(response.location.endswith(url('reset_password')))
253 253
254 254 # GOOD KEY
255 255
256 256 key = User.get_by_username(username).api_key
257 257 response = self.app.get(url(controller='login',
258 258 action='password_reset_confirmation',
259 259 key=key))
260 260 self.assertEqual(response.status, '302 Found')
261 261 self.assertTrue(response.location.endswith(url('login_home')))
262 262
263 263 self.checkSessionFlash(response,
264 264 ('Your password reset was successful, '
265 265 'new password has been sent to your email'))
266 266
267 267 response = response.follow()
@@ -1,47 +1,47 b''
1 1 from rhodecode.tests import *
2 2 from rhodecode.model.db import Repository
3 3 from rhodecode.lib.utils import invalidate_cache
4 4
5 5 class TestSummaryController(TestController):
6 6
7 7 def test_index(self):
8 8 self.log_user()
9 9 response = self.app.get(url(controller='summary',
10 10 action='index', repo_name=HG_REPO))
11 11
12 12 #repo type
13 13 self.assertTrue("""<img style="margin-bottom:2px" class="icon" """
14 14 """title="Mercurial repository" alt="Mercurial """
15 15 """repository" src="/images/icons/hgicon.png"/>"""
16 16 in response.body)
17 17 self.assertTrue("""<img style="margin-bottom:2px" class="icon" """
18 18 """title="public repository" alt="public """
19 19 """repository" src="/images/icons/lock_open.png"/>"""
20 20 in response.body)
21 21
22 22 #codes stats
23 23 self._enable_stats()
24 24
25 25
26 26 invalidate_cache('get_repo_cached_%s' % HG_REPO)
27 27 response = self.app.get(url(controller='summary', action='index',
28 28 repo_name=HG_REPO))
29 29
30 30 self.assertTrue("""var data = {"py": {"count": 42, "desc": """
31 31 """["Python"]}, "rst": {"count": 11, "desc": """
32 32 """["Rst"]}, "sh": {"count": 2, "desc": ["Bash"]}, """
33 33 """"makefile": {"count": 1, "desc": ["Makefile", """
34 34 """"Makefile"]}, "cfg": {"count": 1, "desc": ["Ini"]},"""
35 35 """ "css": {"count": 1, "desc": ["Css"]}, "bat": """
36 36 """{"count": 1, "desc": ["Batch"]}};"""
37 37 in response.body)
38 38
39 39 # clone url...
40 self.assertTrue("""<input type="text" id="clone_url" readonly="readonly" value="hg clone http://test_admin@localhost:80/%s" size="70"/>""" % HG_REPO in response.body)
40 self.assertTrue("""<input type="text" id="clone_url" readonly="readonly" value="http://test_admin@localhost:80/%s" size="70"/>""" % HG_REPO in response.body)
41 41
42 42
43 43 def _enable_stats(self):
44 44 r = Repository.get_by_repo_name(HG_REPO)
45 45 r.enable_statistics = True
46 self.Session().add(r)
47 self.Session().commit()
46 self.Session.add(r)
47 self.Session.commit()
@@ -1,362 +1,362 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 8 UsersGroup, UsersGroupMember
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 Session().rollback()
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
162 162 def test_create_and_remove(self):
163 163 usr = UserModel().create_or_update(username=u'test_user', password=u'qweqwe',
164 164 email=u'u232@rhodecode.org',
165 165 name=u'u1', lastname=u'u1')
166 Session().commit()
166 Session.commit()
167 167 self.assertEqual(User.get_by_username(u'test_user'), usr)
168 168
169 169 # make users group
170 170 users_group = UsersGroupModel().create('some_example_group')
171 Session().commit()
171 Session.commit()
172 172
173 173 UsersGroupModel().add_user_to_group(users_group, usr)
174 Session().commit()
174 Session.commit()
175 175
176 176 self.assertEqual(UsersGroup.get(users_group.users_group_id), users_group)
177 177 self.assertEqual(UsersGroupMember.query().count(), 1)
178 178 UserModel().delete(usr.user_id)
179 Session().commit()
179 Session.commit()
180 180
181 181 self.assertEqual(UsersGroupMember.query().all(), [])
182 182
183 183
184 184 class TestNotifications(unittest.TestCase):
185 185
186 186 def __init__(self, methodName='runTest'):
187 187 self.u1 = UserModel().create_or_update(username=u'u1',
188 188 password=u'qweqwe',
189 189 email=u'u1@rhodecode.org',
190 190 name=u'u1', lastname=u'u1')
191 191 Session.commit()
192 192 self.u1 = self.u1.user_id
193 193
194 194 self.u2 = UserModel().create_or_update(username=u'u2',
195 195 password=u'qweqwe',
196 196 email=u'u2@rhodecode.org',
197 197 name=u'u2', lastname=u'u3')
198 198 Session.commit()
199 199 self.u2 = self.u2.user_id
200 200
201 201 self.u3 = UserModel().create_or_update(username=u'u3',
202 202 password=u'qweqwe',
203 203 email=u'u3@rhodecode.org',
204 204 name=u'u3', lastname=u'u3')
205 205 Session.commit()
206 206 self.u3 = self.u3.user_id
207 207
208 208 super(TestNotifications, self).__init__(methodName=methodName)
209 209
210 210 def _clean_notifications(self):
211 211 for n in Notification.query().all():
212 Session().delete(n)
212 Session.delete(n)
213 213
214 Session().commit()
214 Session.commit()
215 215 self.assertEqual(Notification.query().all(), [])
216 216
217 217
218 218 def test_create_notification(self):
219 219 self.assertEqual([], Notification.query().all())
220 220 self.assertEqual([], UserNotification.query().all())
221 221
222 222 usrs = [self.u1, self.u2]
223 223 notification = NotificationModel().create(created_by=self.u1,
224 224 subject=u'subj', body=u'hi there',
225 225 recipients=usrs)
226 Session().commit()
226 Session.commit()
227 227 u1 = User.get(self.u1)
228 228 u2 = User.get(self.u2)
229 229 u3 = User.get(self.u3)
230 230 notifications = Notification.query().all()
231 231 self.assertEqual(len(notifications), 1)
232 232
233 233 unotification = UserNotification.query()\
234 234 .filter(UserNotification.notification == notification).all()
235 235
236 236 self.assertEqual(notifications[0].recipients, [u1, u2])
237 237 self.assertEqual(notification.notification_id,
238 238 notifications[0].notification_id)
239 239 self.assertEqual(len(unotification), len(usrs))
240 240 self.assertEqual([x.user.user_id for x in unotification], usrs)
241 241
242 242 self._clean_notifications()
243 243
244 244 def test_user_notifications(self):
245 245 self.assertEqual([], Notification.query().all())
246 246 self.assertEqual([], UserNotification.query().all())
247 247
248 248 notification1 = NotificationModel().create(created_by=self.u1,
249 249 subject=u'subj', body=u'hi there1',
250 250 recipients=[self.u3])
251 Session().commit()
251 Session.commit()
252 252 notification2 = NotificationModel().create(created_by=self.u1,
253 253 subject=u'subj', body=u'hi there2',
254 254 recipients=[self.u3])
255 Session().commit()
256 u3 = Session().query(User).get(self.u3)
255 Session.commit()
256 u3 = Session.query(User).get(self.u3)
257 257
258 258 self.assertEqual(sorted([x.notification for x in u3.notifications]),
259 259 sorted([notification2, notification1]))
260 260 self._clean_notifications()
261 261
262 262 def test_delete_notifications(self):
263 263 self.assertEqual([], Notification.query().all())
264 264 self.assertEqual([], UserNotification.query().all())
265 265
266 266 notification = NotificationModel().create(created_by=self.u1,
267 267 subject=u'title', body=u'hi there3',
268 268 recipients=[self.u3, self.u1, self.u2])
269 Session().commit()
269 Session.commit()
270 270 notifications = Notification.query().all()
271 271 self.assertTrue(notification in notifications)
272 272
273 273 Notification.delete(notification.notification_id)
274 Session().commit()
274 Session.commit()
275 275
276 276 notifications = Notification.query().all()
277 277 self.assertFalse(notification in notifications)
278 278
279 279 un = UserNotification.query().filter(UserNotification.notification
280 280 == notification).all()
281 281 self.assertEqual(un, [])
282 282
283 283 self._clean_notifications()
284 284
285 285 def test_delete_association(self):
286 286
287 287 self.assertEqual([], Notification.query().all())
288 288 self.assertEqual([], UserNotification.query().all())
289 289
290 290 notification = NotificationModel().create(created_by=self.u1,
291 291 subject=u'title', body=u'hi there3',
292 292 recipients=[self.u3, self.u1, self.u2])
293 Session().commit()
293 Session.commit()
294 294
295 295 unotification = UserNotification.query()\
296 296 .filter(UserNotification.notification ==
297 297 notification)\
298 298 .filter(UserNotification.user_id == self.u3)\
299 299 .scalar()
300 300
301 301 self.assertEqual(unotification.user_id, self.u3)
302 302
303 303 NotificationModel().delete(self.u3,
304 304 notification.notification_id)
305 Session().commit()
305 Session.commit()
306 306
307 307 u3notification = UserNotification.query()\
308 308 .filter(UserNotification.notification ==
309 309 notification)\
310 310 .filter(UserNotification.user_id == self.u3)\
311 311 .scalar()
312 312
313 313 self.assertEqual(u3notification, None)
314 314
315 315 # notification object is still there
316 316 self.assertEqual(Notification.query().all(), [notification])
317 317
318 318 #u1 and u2 still have assignments
319 319 u1notification = UserNotification.query()\
320 320 .filter(UserNotification.notification ==
321 321 notification)\
322 322 .filter(UserNotification.user_id == self.u1)\
323 323 .scalar()
324 324 self.assertNotEqual(u1notification, None)
325 325 u2notification = UserNotification.query()\
326 326 .filter(UserNotification.notification ==
327 327 notification)\
328 328 .filter(UserNotification.user_id == self.u2)\
329 329 .scalar()
330 330 self.assertNotEqual(u2notification, None)
331 331
332 332 self._clean_notifications()
333 333
334 334 def test_notification_counter(self):
335 335 self._clean_notifications()
336 336 self.assertEqual([], Notification.query().all())
337 337 self.assertEqual([], UserNotification.query().all())
338 338
339 339 NotificationModel().create(created_by=self.u1,
340 340 subject=u'title', body=u'hi there_delete',
341 341 recipients=[self.u3, self.u1])
342 Session().commit()
342 Session.commit()
343 343
344 344 self.assertEqual(NotificationModel()
345 345 .get_unread_cnt_for_user(self.u1), 1)
346 346 self.assertEqual(NotificationModel()
347 347 .get_unread_cnt_for_user(self.u2), 0)
348 348 self.assertEqual(NotificationModel()
349 349 .get_unread_cnt_for_user(self.u3), 1)
350 350
351 351 notification = NotificationModel().create(created_by=self.u1,
352 352 subject=u'title', body=u'hi there3',
353 353 recipients=[self.u3, self.u1, self.u2])
354 Session().commit()
354 Session.commit()
355 355
356 356 self.assertEqual(NotificationModel()
357 357 .get_unread_cnt_for_user(self.u1), 2)
358 358 self.assertEqual(NotificationModel()
359 359 .get_unread_cnt_for_user(self.u2), 1)
360 360 self.assertEqual(NotificationModel()
361 361 .get_unread_cnt_for_user(self.u3), 2)
362 362 self._clean_notifications()
@@ -1,50 +1,50 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.websetup
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 Weboperations and setup for rhodecode
7 7
8 8 :created_on: Dec 11, 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
29 29 from rhodecode.config.environment import load_environment
30 30 from rhodecode.lib.db_manage import DbManage
31 31 from rhodecode.model.meta import Session
32 32
33 33
34 34 log = logging.getLogger(__name__)
35 35
36 36
37 37 def setup_app(command, conf, vars):
38 38 """Place any commands to setup rhodecode here"""
39 39 dbconf = conf['sqlalchemy.db1.url']
40 40 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=conf['here'],
41 41 tests=False)
42 42 dbmanage.create_tables(override=True)
43 43 dbmanage.set_db_version()
44 44 dbmanage.create_settings(dbmanage.config_prompt(None))
45 45 dbmanage.create_default_user()
46 46 dbmanage.admin_prompt()
47 47 dbmanage.create_permissions()
48 48 dbmanage.populate_default_permissions()
49 Session().commit()
49 Session.commit()
50 50 load_environment(conf.global_conf, conf.local_conf, initial=True)
General Comments 0
You need to be logged in to leave comments. Login now