##// END OF EJS Templates
fixes issue #16 reimplementation of database repository, for using generic pk instead of repo naming as pk. Which caused to many problems....
marcink -
r367:a26f48ad default
parent child Browse files
Show More
@@ -1,98 +1,100
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # settings controller for pylons
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 """
21 Created on June 30, 2010
22 settings controller for pylons
23 @author: marcink
24 """
20 25 from formencode import htmlfill
21 26 from pylons import tmpl_context as c, request, url
22 27 from pylons.controllers.util import redirect
23 28 from pylons.i18n.translation import _
24 29 from pylons_app.lib.auth import LoginRequired, HasRepoPermissionAllDecorator
25 30 from pylons_app.lib.base import BaseController, render
26 31 from pylons_app.lib.utils import invalidate_cache
27 32 from pylons_app.model.forms import RepoSettingsForm
28 33 from pylons_app.model.repo_model import RepoModel
29 34 import formencode
30 35 import logging
31 36 import pylons_app.lib.helpers as h
32 37 import traceback
33 """
34 Created on June 30, 2010
35 settings controller for pylons
36 @author: marcink
37 """
38
38 39 log = logging.getLogger(__name__)
39 40
40 41 class SettingsController(BaseController):
41 42
42 43 @LoginRequired()
43 44 @HasRepoPermissionAllDecorator('repository.admin')
44 45 def __before__(self):
45 46 super(SettingsController, self).__before__()
46 47
47 48 def index(self, repo_name):
48 49 repo_model = RepoModel()
49 50 c.repo_info = repo = repo_model.get(repo_name)
50 51 if not repo:
51 52 h.flash(_('%s repository is not mapped to db perhaps'
52 53 ' it was created or renamed from the filesystem'
53 54 ' please run the application again'
54 55 ' in order to rescan repositories') % repo_name,
55 56 category='error')
56 57
57 58 return redirect(url('repos'))
58 59 defaults = c.repo_info.__dict__
59 60 defaults.update({'user':c.repo_info.user.username})
60 61 c.users_array = repo_model.get_users_js()
61 62
62 63 for p in c.repo_info.repo2perm:
63 64 defaults.update({'perm_%s' % p.user.username:
64 65 p.permission.permission_name})
65 66
66 67 return htmlfill.render(
67 68 render('settings/repo_settings.html'),
68 69 defaults=defaults,
69 70 encoding="UTF-8",
70 71 force_defaults=False
71 72 )
72 73
73 74 def update(self, repo_name):
74 75 repo_model = RepoModel()
76 changed_name = repo_name
75 77 _form = RepoSettingsForm(edit=True, old_data={'repo_name':repo_name})()
76 78 try:
77 79 form_result = _form.to_python(dict(request.POST))
78 80 repo_model.update(repo_name, form_result)
79 81 invalidate_cache('cached_repo_list')
80 82 h.flash(_('Repository %s updated succesfully' % repo_name),
81 83 category='success')
82
84 changed_name = form_result['repo_name']
83 85 except formencode.Invalid as errors:
84 86 c.repo_info = repo_model.get(repo_name)
85 87 c.users_array = repo_model.get_users_js()
86 88 errors.value.update({'user':c.repo_info.user.username})
87 89 return htmlfill.render(
88 90 render('settings/repo_settings.html'),
89 91 defaults=errors.value,
90 92 errors=errors.error_dict or {},
91 93 prefix_error=False,
92 94 encoding="UTF-8")
93 95 except Exception:
94 96 log.error(traceback.format_exc())
95 97 h.flash(_('error occured during update of repository %s') \
96 % form_result['repo_name'], category='error')
98 % repo_name, category='error')
97 99
98 return redirect(url('repo_settings_home', repo_name=form_result['repo_name']))
100 return redirect(url('repo_settings_home', repo_name=changed_name))
@@ -1,415 +1,419
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # authentication and permission libraries
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20 """
21 21 Created on April 4, 2010
22 22
23 23 @author: marcink
24 24 """
25 25 from beaker.cache import cache_region
26 26 from functools import wraps
27 27 from pylons import config, session, url, request
28 28 from pylons.controllers.util import abort, redirect
29 29 from pylons_app.lib.utils import get_repo_slug
30 30 from pylons_app.model import meta
31 31 from pylons_app.model.db import User, Repo2Perm, Repository, Permission
32 32 from sqlalchemy.exc import OperationalError
33 33 from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
34 34 import crypt
35 35 import logging
36 36
37 37 log = logging.getLogger(__name__)
38 38
39 39 def get_crypt_password(password):
40 40 """
41 41 Cryptographic function used for password hashing
42 42 @param password: password to hash
43 43 """
44 44 return crypt.crypt(password, '6a')
45 45
46 46
47 47 @cache_region('super_short_term', 'cached_user')
48 48 def get_user_cached(username):
49 49 sa = meta.Session
50 50 try:
51 51 user = sa.query(User).filter(User.username == username).one()
52 52 finally:
53 53 meta.Session.remove()
54 54 return user
55 55
56 56 def authfunc(environ, username, password):
57 57 password_crypt = get_crypt_password(password)
58 58 try:
59 59 user = get_user_cached(username)
60 60 except (NoResultFound, MultipleResultsFound, OperationalError) as e:
61 61 log.error(e)
62 62 user = None
63 63
64 64 if user:
65 65 if user.active:
66 66 if user.username == username and user.password == password_crypt:
67 67 log.info('user %s authenticated correctly', username)
68 68 return True
69 69 else:
70 70 log.error('user %s is disabled', username)
71 71
72 72 return False
73 73
74 74 class AuthUser(object):
75 75 """
76 76 A simple object that handles a mercurial username for authentication
77 77 """
78 78 def __init__(self):
79 79 self.username = 'None'
80 80 self.name = ''
81 81 self.lastname = ''
82 82 self.user_id = None
83 83 self.is_authenticated = False
84 84 self.is_admin = False
85 85 self.permissions = {}
86 86
87 87
88 88 def set_available_permissions(config):
89 89 """
90 90 This function will propagate pylons globals with all available defined
91 91 permission given in db. We don't wannt to check each time from db for new
92 92 permissions since adding a new permission also requires application restart
93 93 ie. to decorate new views with the newly created permission
94 94 @param config:
95 95 """
96 96 log.info('getting information about all available permissions')
97 97 try:
98 98 sa = meta.Session
99 99 all_perms = sa.query(Permission).all()
100 100 finally:
101 101 meta.Session.remove()
102 102
103 103 config['available_permissions'] = [x.permission_name for x in all_perms]
104 104
105 105 def set_base_path(config):
106 106 config['base_path'] = config['pylons.app_globals'].base_path
107 107
108 108 def fill_perms(user):
109 """
110 Fills user permission attribute with permissions taken from database
111 @param user:
112 """
113
109 114 sa = meta.Session
110 115 user.permissions['repositories'] = {}
111 116
112 117 #first fetch default permissions
113 118 default_perms = sa.query(Repo2Perm, Repository, Permission)\
114 .join((Repository, Repo2Perm.repository == Repository.repo_name))\
119 .join((Repository, Repo2Perm.repository_id == Repository.repo_id))\
115 120 .join((Permission, Repo2Perm.permission_id == Permission.permission_id))\
116 121 .filter(Repo2Perm.user_id == sa.query(User).filter(User.username ==
117 122 'default').one().user_id).all()
118 123
119 124 if user.is_admin:
120 125 user.permissions['global'] = set(['hg.admin'])
121 126 #admin have all rights full
122 127 for perm in default_perms:
123 128 p = 'repository.admin'
124 user.permissions['repositories'][perm.Repo2Perm.repository] = p
129 user.permissions['repositories'][perm.Repo2Perm.repository.repo_name] = p
125 130
126 131 else:
127 132 user.permissions['global'] = set()
128 133 for perm in default_perms:
129 134 if perm.Repository.private:
130 135 #disable defaults for private repos,
131 136 p = 'repository.none'
132 137 elif perm.Repository.user_id == user.user_id:
133 138 #set admin if owner
134 139 p = 'repository.admin'
135 140 else:
136 141 p = perm.Permission.permission_name
137 142
138 user.permissions['repositories'][perm.Repo2Perm.repository] = p
143 user.permissions['repositories'][perm.Repo2Perm.repository.repo_name] = p
139 144
140 145
141 146 user_perms = sa.query(Repo2Perm, Permission, Repository)\
142 .join((Repository, Repo2Perm.repository == Repository.repo_name))\
147 .join((Repository, Repo2Perm.repository_id == Repository.repo_id))\
143 148 .join((Permission, Repo2Perm.permission_id == Permission.permission_id))\
144 149 .filter(Repo2Perm.user_id == user.user_id).all()
145 150 #overwrite userpermissions with defaults
146 151 for perm in user_perms:
147 152 #set write if owner
148 153 if perm.Repository.user_id == user.user_id:
149 154 p = 'repository.write'
150 155 else:
151 156 p = perm.Permission.permission_name
152 user.permissions['repositories'][perm.Repo2Perm.repository] = p
157 user.permissions['repositories'][perm.Repo2Perm.repository.repo_name] = p
153 158 meta.Session.remove()
154 159 return user
155 160
156 161 def get_user(session):
157 162 """
158 163 Gets user from session, and wraps permissions into user
159 164 @param session:
160 165 """
161 166 user = session.get('hg_app_user', AuthUser())
162 167
163 168 if user.is_authenticated:
164 169 user = fill_perms(user)
165
166 170 session['hg_app_user'] = user
167 171 session.save()
168 172 return user
169 173
170 174 #===============================================================================
171 175 # CHECK DECORATORS
172 176 #===============================================================================
173 177 class LoginRequired(object):
174 178 """
175 179 Must be logged in to execute this function else redirect to login page
176 180 """
177 181
178 182 def __call__(self, func):
179 183 @wraps(func)
180 184 def _wrapper(*fargs, **fkwargs):
181 185 user = session.get('hg_app_user', AuthUser())
182 186 log.debug('Checking login required for user:%s', user.username)
183 187 if user.is_authenticated:
184 188 log.debug('user %s is authenticated', user.username)
185 189 func(*fargs)
186 190 else:
187 191 log.warn('user %s not authenticated', user.username)
188 192 log.debug('redirecting to login page')
189 193 return redirect(url('login_home'))
190 194
191 195 return _wrapper
192 196
193 197 class PermsDecorator(object):
194 198 """
195 199 Base class for decorators
196 200 """
197 201
198 202 def __init__(self, *required_perms):
199 203 available_perms = config['available_permissions']
200 204 for perm in required_perms:
201 205 if perm not in available_perms:
202 206 raise Exception("'%s' permission is not defined" % perm)
203 207 self.required_perms = set(required_perms)
204 208 self.user_perms = None
205 209
206 210 def __call__(self, func):
207 211 @wraps(func)
208 212 def _wrapper(*fargs, **fkwargs):
209 213 self.user_perms = session.get('hg_app_user', AuthUser()).permissions
210 214 log.debug('checking %s permissions %s for %s',
211 215 self.__class__.__name__, self.required_perms, func.__name__)
212 216
213 217 if self.check_permissions():
214 218 log.debug('Permission granted for %s', func.__name__)
215 219 return func(*fargs)
216 220
217 221 else:
218 222 log.warning('Permission denied for %s', func.__name__)
219 223 #redirect with forbidden ret code
220 224 return abort(403)
221 225 return _wrapper
222 226
223 227
224 228 def check_permissions(self):
225 229 """
226 230 Dummy function for overriding
227 231 """
228 232 raise Exception('You have to write this function in child class')
229 233
230 234 class HasPermissionAllDecorator(PermsDecorator):
231 235 """
232 236 Checks for access permission for all given predicates. All of them have to
233 237 be meet in order to fulfill the request
234 238 """
235 239
236 240 def check_permissions(self):
237 241 if self.required_perms.issubset(self.user_perms.get('global')):
238 242 return True
239 243 return False
240 244
241 245
242 246 class HasPermissionAnyDecorator(PermsDecorator):
243 247 """
244 248 Checks for access permission for any of given predicates. In order to
245 249 fulfill the request any of predicates must be meet
246 250 """
247 251
248 252 def check_permissions(self):
249 253 if self.required_perms.intersection(self.user_perms.get('global')):
250 254 return True
251 255 return False
252 256
253 257 class HasRepoPermissionAllDecorator(PermsDecorator):
254 258 """
255 259 Checks for access permission for all given predicates for specific
256 260 repository. All of them have to be meet in order to fulfill the request
257 261 """
258 262
259 263 def check_permissions(self):
260 264 repo_name = get_repo_slug(request)
261 265 try:
262 266 user_perms = set([self.user_perms['repositories'][repo_name]])
263 267 except KeyError:
264 268 return False
265 269 if self.required_perms.issubset(user_perms):
266 270 return True
267 271 return False
268 272
269 273
270 274 class HasRepoPermissionAnyDecorator(PermsDecorator):
271 275 """
272 276 Checks for access permission for any of given predicates for specific
273 277 repository. In order to fulfill the request any of predicates must be meet
274 278 """
275 279
276 280 def check_permissions(self):
277 281 repo_name = get_repo_slug(request)
278 282
279 283 try:
280 284 user_perms = set([self.user_perms['repositories'][repo_name]])
281 285 except KeyError:
282 286 return False
283 287 if self.required_perms.intersection(user_perms):
284 288 return True
285 289 return False
286 290 #===============================================================================
287 291 # CHECK FUNCTIONS
288 292 #===============================================================================
289 293
290 294 class PermsFunction(object):
291 295 """
292 296 Base function for other check functions
293 297 """
294 298
295 299 def __init__(self, *perms):
296 300 available_perms = config['available_permissions']
297 301
298 302 for perm in perms:
299 303 if perm not in available_perms:
300 304 raise Exception("'%s' permission in not defined" % perm)
301 305 self.required_perms = set(perms)
302 306 self.user_perms = None
303 307 self.granted_for = ''
304 308 self.repo_name = None
305 309
306 310 def __call__(self, check_Location=''):
307 311 user = session.get('hg_app_user', False)
308 312 if not user:
309 313 return False
310 314 self.user_perms = user.permissions
311 315 self.granted_for = user.username
312 316 log.debug('checking %s %s', self.__class__.__name__, self.required_perms)
313 317
314 318 if self.check_permissions():
315 319 log.debug('Permission granted for %s @%s', self.granted_for,
316 320 check_Location)
317 321 return True
318 322
319 323 else:
320 324 log.warning('Permission denied for %s @%s', self.granted_for,
321 325 check_Location)
322 326 return False
323 327
324 328 def check_permissions(self):
325 329 """
326 330 Dummy function for overriding
327 331 """
328 332 raise Exception('You have to write this function in child class')
329 333
330 334 class HasPermissionAll(PermsFunction):
331 335 def check_permissions(self):
332 336 if self.required_perms.issubset(self.user_perms.get('global')):
333 337 return True
334 338 return False
335 339
336 340 class HasPermissionAny(PermsFunction):
337 341 def check_permissions(self):
338 342 if self.required_perms.intersection(self.user_perms.get('global')):
339 343 return True
340 344 return False
341 345
342 346 class HasRepoPermissionAll(PermsFunction):
343 347
344 348 def __call__(self, repo_name=None, check_Location=''):
345 349 self.repo_name = repo_name
346 350 return super(HasRepoPermissionAll, self).__call__(check_Location)
347 351
348 352 def check_permissions(self):
349 353 if not self.repo_name:
350 354 self.repo_name = get_repo_slug(request)
351 355
352 356 try:
353 357 self.user_perms = set([self.user_perms['repositories']\
354 358 [self.repo_name]])
355 359 except KeyError:
356 360 return False
357 361 self.granted_for = self.repo_name
358 362 if self.required_perms.issubset(self.user_perms):
359 363 return True
360 364 return False
361 365
362 366 class HasRepoPermissionAny(PermsFunction):
363 367
364 368
365 369 def __call__(self, repo_name=None, check_Location=''):
366 370 self.repo_name = repo_name
367 371 return super(HasRepoPermissionAny, self).__call__(check_Location)
368 372
369 373 def check_permissions(self):
370 374 if not self.repo_name:
371 375 self.repo_name = get_repo_slug(request)
372 376
373 377 try:
374 378 self.user_perms = set([self.user_perms['repositories']\
375 379 [self.repo_name]])
376 380 except KeyError:
377 381 return False
378 382 self.granted_for = self.repo_name
379 383 if self.required_perms.intersection(self.user_perms):
380 384 return True
381 385 return False
382 386
383 387 #===============================================================================
384 388 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
385 389 #===============================================================================
386 390
387 391 class HasPermissionAnyMiddleware(object):
388 392 def __init__(self, *perms):
389 393 self.required_perms = set(perms)
390 394
391 395 def __call__(self, user, repo_name):
392 396 usr = AuthUser()
393 397 usr.user_id = user.user_id
394 398 usr.username = user.username
395 399 usr.is_admin = user.admin
396 400
397 401 try:
398 402 self.user_perms = set([fill_perms(usr)\
399 403 .permissions['repositories'][repo_name]])
400 404 except:
401 405 self.user_perms = set()
402 406 self.granted_for = ''
403 407 self.username = user.username
404 408 self.repo_name = repo_name
405 409 return self.check_permissions()
406 410
407 411 def check_permissions(self):
408 412 log.debug('checking mercurial protocol '
409 413 'permissions for user:%s repository:%s',
410 414 self.username, self.repo_name)
411 415 if self.required_perms.intersection(self.user_perms):
412 416 log.debug('permission granted')
413 417 return True
414 418 log.debug('permission denied')
415 419 return False
@@ -1,192 +1,192
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # database managment for hg app
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20
21 21 """
22 22 Created on April 10, 2010
23 23 database managment and creation for hg app
24 24 @author: marcink
25 25 """
26 26
27 27 from os.path import dirname as dn, join as jn
28 28 import os
29 29 import sys
30 30 import uuid
31 31 ROOT = dn(dn(dn(os.path.realpath(__file__))))
32 32 sys.path.append(ROOT)
33 33
34 34 from pylons_app.lib.auth import get_crypt_password
35 35 from pylons_app.lib.utils import ask_ok
36 36 from pylons_app.model import init_model
37 37 from pylons_app.model.db import User, Permission, HgAppUi, HgAppSettings
38 38 from pylons_app.model import meta
39 39 from sqlalchemy.engine import create_engine
40 40 import logging
41 41
42 42 log = logging.getLogger(__name__)
43 43
44 44 class DbManage(object):
45 45 def __init__(self, log_sql):
46 46 self.dbname = 'hg_app.db'
47 47 dburi = 'sqlite:////%s' % jn(ROOT, self.dbname)
48 48 engine = create_engine(dburi, echo=log_sql)
49 49 init_model(engine)
50 50 self.sa = meta.Session
51 51 self.db_exists = False
52 52
53 53 def check_for_db(self, override):
54 54 log.info('checking for exisiting db')
55 55 if os.path.isfile(jn(ROOT, self.dbname)):
56 56 self.db_exists = True
57 57 log.info('database exisist')
58 58 if not override:
59 59 raise Exception('database already exists')
60 60
61 61 def create_tables(self, override=False):
62 62 """
63 63 Create a auth database
64 64 """
65 65 self.check_for_db(override)
66 66 if override:
67 67 log.info("database exisist and it's going to be destroyed")
68 68 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
69 69 if not destroy:
70 70 sys.exit()
71 71 if self.db_exists and destroy:
72 72 os.remove(jn(ROOT, self.dbname))
73 73 checkfirst = not override
74 74 meta.Base.metadata.create_all(checkfirst=checkfirst)
75 75 log.info('Created tables for %s', self.dbname)
76 76
77 77 def admin_prompt(self):
78 78 import getpass
79 79 username = raw_input('Specify admin username:')
80 80 password = getpass.getpass('Specify admin password:')
81 81 self.create_user(username, password, True)
82 82
83 83 def config_prompt(self):
84 84 log.info('Setting up repositories config')
85 85
86 86 path = raw_input('Specify valid full path to your repositories'
87 87 ' you can change this later in application settings:')
88 88
89 89 if not os.path.isdir(path):
90 90 log.error('You entered wrong path')
91 91 sys.exit()
92 92
93 93 hooks = HgAppUi()
94 94 hooks.ui_section = 'hooks'
95 95 hooks.ui_key = 'changegroup'
96 96 hooks.ui_value = 'hg update >&2'
97 97
98 98 web1 = HgAppUi()
99 99 web1.ui_section = 'web'
100 100 web1.ui_key = 'push_ssl'
101 101 web1.ui_value = 'false'
102 102
103 103 web2 = HgAppUi()
104 104 web2.ui_section = 'web'
105 105 web2.ui_key = 'allow_archive'
106 106 web2.ui_value = 'gz zip bz2'
107 107
108 108 web3 = HgAppUi()
109 109 web3.ui_section = 'web'
110 110 web3.ui_key = 'allow_push'
111 111 web3.ui_value = '*'
112 112
113 113 web4 = HgAppUi()
114 114 web4.ui_section = 'web'
115 115 web4.ui_key = 'baseurl'
116 116 web4.ui_value = '/'
117 117
118 118 paths = HgAppUi()
119 119 paths.ui_section = 'paths'
120 120 paths.ui_key = '/'
121 121 paths.ui_value = os.path.join(path, '*')
122 122
123 123
124 124 hgsettings = HgAppSettings()
125 125 hgsettings.app_auth_realm = 'hg-app authentication'
126 126 hgsettings.app_title = 'hg-app'
127 127
128 128 try:
129 self.sa.add(hooks)
129 #self.sa.add(hooks)
130 130 self.sa.add(web1)
131 131 self.sa.add(web2)
132 132 self.sa.add(web3)
133 133 self.sa.add(web4)
134 134 self.sa.add(paths)
135 135 self.sa.add(hgsettings)
136 136 self.sa.commit()
137 137 except:
138 138 self.sa.rollback()
139 139 raise
140 140 log.info('created ui config')
141 141
142 142 def create_user(self, username, password, admin=False):
143 143
144 144 log.info('creating default user')
145 145 #create default user for handling default permissions.
146 146 def_user = User()
147 147 def_user.username = 'default'
148 148 def_user.password = get_crypt_password(str(uuid.uuid1())[:8])
149 149 def_user.name = 'default'
150 150 def_user.lastname = 'default'
151 151 def_user.email = 'default@default.com'
152 152 def_user.admin = False
153 153 def_user.active = False
154 154
155 155 log.info('creating administrator user %s', username)
156 156 new_user = User()
157 157 new_user.username = username
158 158 new_user.password = get_crypt_password(password)
159 159 new_user.name = 'Hg'
160 160 new_user.lastname = 'Admin'
161 161 new_user.email = 'admin@localhost'
162 162 new_user.admin = admin
163 163 new_user.active = True
164 164
165 165 try:
166 166 self.sa.add(def_user)
167 167 self.sa.add(new_user)
168 168 self.sa.commit()
169 169 except:
170 170 self.sa.rollback()
171 171 raise
172 172
173 173 def create_permissions(self):
174 174 #module.(access|create|change|delete)_[name]
175 175 #module.(read|write|owner)
176 176 perms = [('repository.none', 'Repository no access'),
177 177 ('repository.read', 'Repository read access'),
178 178 ('repository.write', 'Repository write access'),
179 179 ('repository.admin', 'Repository admin access'),
180 180 ('hg.admin', 'Hg Administrator'),
181 181 ]
182 182
183 183 for p in perms:
184 184 new_perm = Permission()
185 185 new_perm.permission_name = p[0]
186 186 new_perm.permission_longname = p[1]
187 187 try:
188 188 self.sa.add(new_perm)
189 189 self.sa.commit()
190 190 except:
191 191 self.sa.rollback()
192 192 raise
@@ -1,329 +1,329
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # Utilities for hg app
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 # This program is free software; you can redistribute it and/or
6 6 # modify it under the terms of the GNU General Public License
7 7 # as published by the Free Software Foundation; version 2
8 8 # of the License or (at your opinion) any later version of the license.
9 9 #
10 10 # This program is distributed in the hope that it will be useful,
11 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 13 # GNU General Public License for more details.
14 14 #
15 15 # You should have received a copy of the GNU General Public License
16 16 # along with this program; if not, write to the Free Software
17 17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 18 # MA 02110-1301, USA.
19 19
20 20 """
21 21 Created on April 18, 2010
22 22 Utilities for hg app
23 23 @author: marcink
24 24 """
25 25 from beaker.cache import cache_region
26 26 from mercurial import ui, config, hg
27 27 from mercurial.error import RepoError
28 28 from pylons_app.model import meta
29 29 from pylons_app.model.db import Repository, User, HgAppUi, HgAppSettings
30 30 from vcs.backends.base import BaseChangeset
31 31 from vcs.utils.lazy import LazyProperty
32 32 import logging
33 33 import os
34 34 log = logging.getLogger(__name__)
35 35
36 36
37 37 def get_repo_slug(request):
38 38 return request.environ['pylons.routes_dict'].get('repo_name')
39 39
40 40 def is_mercurial(environ):
41 41 """
42 42 Returns True if request's target is mercurial server - header
43 43 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
44 44 """
45 45 http_accept = environ.get('HTTP_ACCEPT')
46 46 if http_accept and http_accept.startswith('application/mercurial'):
47 47 return True
48 48 return False
49 49
50 50 def check_repo_dir(paths):
51 51 repos_path = paths[0][1].split('/')
52 52 if repos_path[-1] in ['*', '**']:
53 53 repos_path = repos_path[:-1]
54 54 if repos_path[0] != '/':
55 55 repos_path[0] = '/'
56 56 if not os.path.isdir(os.path.join(*repos_path)):
57 57 raise Exception('Not a valid repository in %s' % paths[0][1])
58 58
59 59 def check_repo_fast(repo_name, base_path):
60 60 if os.path.isdir(os.path.join(base_path, repo_name)):return False
61 61 return True
62 62
63 63 def check_repo(repo_name, base_path, verify=True):
64 64
65 65 repo_path = os.path.join(base_path, repo_name)
66 66
67 67 try:
68 68 if not check_repo_fast(repo_name, base_path):
69 69 return False
70 70 r = hg.repository(ui.ui(), repo_path)
71 71 if verify:
72 72 hg.verify(r)
73 73 #here we hnow that repo exists it was verified
74 74 log.info('%s repo is already created', repo_name)
75 75 return False
76 76 except RepoError:
77 77 #it means that there is no valid repo there...
78 78 log.info('%s repo is free for creation', repo_name)
79 79 return True
80 80
81 81 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
82 82 while True:
83 83 ok = raw_input(prompt)
84 84 if ok in ('y', 'ye', 'yes'): return True
85 85 if ok in ('n', 'no', 'nop', 'nope'): return False
86 86 retries = retries - 1
87 87 if retries < 0: raise IOError
88 88 print complaint
89 89
90 90 @cache_region('super_short_term', 'cached_hg_ui')
91 91 def get_hg_ui_cached():
92 92 try:
93 93 sa = meta.Session
94 94 ret = sa.query(HgAppUi).all()
95 95 finally:
96 96 meta.Session.remove()
97 97 return ret
98 98
99 99
100 100 def get_hg_settings():
101 101 try:
102 102 sa = meta.Session
103 103 ret = sa.query(HgAppSettings).scalar()
104 104 finally:
105 105 meta.Session.remove()
106 106
107 107 if not ret:
108 108 raise Exception('Could not get application settings !')
109 109 return ret
110 110
111 111 def make_ui(read_from='file', path=None, checkpaths=True):
112 112 """
113 113 A function that will read python rc files or database
114 114 and make an mercurial ui object from read options
115 115
116 116 @param path: path to mercurial config file
117 117 @param checkpaths: check the path
118 118 @param read_from: read from 'file' or 'db'
119 119 """
120 120 #propagated from mercurial documentation
121 121 sections = ['alias', 'auth',
122 122 'decode/encode', 'defaults',
123 123 'diff', 'email',
124 124 'extensions', 'format',
125 125 'merge-patterns', 'merge-tools',
126 126 'hooks', 'http_proxy',
127 127 'smtp', 'patch',
128 128 'paths', 'profiling',
129 129 'server', 'trusted',
130 130 'ui', 'web', ]
131 131 baseui = ui.ui()
132 132
133 133
134 134 if read_from == 'file':
135 135 if not os.path.isfile(path):
136 136 log.warning('Unable to read config file %s' % path)
137 137 return False
138 138
139 139 cfg = config.config()
140 140 cfg.read(path)
141 141 for section in sections:
142 142 for k, v in cfg.items(section):
143 143 baseui.setconfig(section, k, v)
144 144 if checkpaths:check_repo_dir(cfg.items('paths'))
145 145
146 146
147 147 elif read_from == 'db':
148 148 hg_ui = get_hg_ui_cached()
149 149 for ui_ in hg_ui:
150 150 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
151 151
152 152
153 153 return baseui
154 154
155 155
156 156 def set_hg_app_config(config):
157 157 hgsettings = get_hg_settings()
158 158 config['hg_app_auth_realm'] = hgsettings.app_auth_realm
159 159 config['hg_app_name'] = hgsettings.app_title
160 160
161 161 def invalidate_cache(name, *args):
162 162 """Invalidates given name cache"""
163 163
164 164 from beaker.cache import region_invalidate
165 165 log.info('INVALIDATING CACHE FOR %s', name)
166 166
167 167 """propagate our arguments to make sure invalidation works. First
168 168 argument has to be the name of cached func name give to cache decorator
169 169 without that the invalidation would not work"""
170 170 tmp = [name]
171 171 tmp.extend(args)
172 172 args = tuple(tmp)
173 173
174 174 if name == 'cached_repo_list':
175 175 from pylons_app.model.hg_model import _get_repos_cached
176 176 region_invalidate(_get_repos_cached, None, *args)
177 177
178 178 if name == 'full_changelog':
179 179 from pylons_app.model.hg_model import _full_changelog_cached
180 180 region_invalidate(_full_changelog_cached, None, *args)
181 181
182 182 class EmptyChangeset(BaseChangeset):
183 183
184 184 revision = -1
185 185 message = ''
186 186
187 187 @LazyProperty
188 188 def raw_id(self):
189 189 """
190 190 Returns raw string identifing this changeset, useful for web
191 191 representation.
192 192 """
193 193 return '0' * 12
194 194
195 195
196 196 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
197 197 """
198 198 maps all found repositories into db
199 199 """
200 200 from pylons_app.model.repo_model import RepoModel
201 201
202 202 sa = meta.Session
203 203 user = sa.query(User).filter(User.admin == True).first()
204 204
205 205 rm = RepoModel()
206 206
207 207 for name, repo in initial_repo_list.items():
208 if not sa.query(Repository).get(name):
208 if not sa.query(Repository).filter(Repository.repo_name == name).scalar():
209 209 log.info('repository %s not found creating default', name)
210 210
211 211 form_data = {
212 212 'repo_name':name,
213 213 'description':repo.description if repo.description != 'unknown' else \
214 214 'auto description for %s' % name,
215 215 'private':False
216 216 }
217 217 rm.create(form_data, user, just_db=True)
218 218
219 219
220 220 if remove_obsolete:
221 221 #remove from database those repositories that are not in the filesystem
222 222 for repo in sa.query(Repository).all():
223 223 if repo.repo_name not in initial_repo_list.keys():
224 224 sa.delete(repo)
225 225 sa.commit()
226 226
227 227
228 228 meta.Session.remove()
229 229
230 230 from UserDict import DictMixin
231 231
232 232 class OrderedDict(dict, DictMixin):
233 233
234 234 def __init__(self, *args, **kwds):
235 235 if len(args) > 1:
236 236 raise TypeError('expected at most 1 arguments, got %d' % len(args))
237 237 try:
238 238 self.__end
239 239 except AttributeError:
240 240 self.clear()
241 241 self.update(*args, **kwds)
242 242
243 243 def clear(self):
244 244 self.__end = end = []
245 245 end += [None, end, end] # sentinel node for doubly linked list
246 246 self.__map = {} # key --> [key, prev, next]
247 247 dict.clear(self)
248 248
249 249 def __setitem__(self, key, value):
250 250 if key not in self:
251 251 end = self.__end
252 252 curr = end[1]
253 253 curr[2] = end[1] = self.__map[key] = [key, curr, end]
254 254 dict.__setitem__(self, key, value)
255 255
256 256 def __delitem__(self, key):
257 257 dict.__delitem__(self, key)
258 258 key, prev, next = self.__map.pop(key)
259 259 prev[2] = next
260 260 next[1] = prev
261 261
262 262 def __iter__(self):
263 263 end = self.__end
264 264 curr = end[2]
265 265 while curr is not end:
266 266 yield curr[0]
267 267 curr = curr[2]
268 268
269 269 def __reversed__(self):
270 270 end = self.__end
271 271 curr = end[1]
272 272 while curr is not end:
273 273 yield curr[0]
274 274 curr = curr[1]
275 275
276 276 def popitem(self, last=True):
277 277 if not self:
278 278 raise KeyError('dictionary is empty')
279 279 if last:
280 280 key = reversed(self).next()
281 281 else:
282 282 key = iter(self).next()
283 283 value = self.pop(key)
284 284 return key, value
285 285
286 286 def __reduce__(self):
287 287 items = [[k, self[k]] for k in self]
288 288 tmp = self.__map, self.__end
289 289 del self.__map, self.__end
290 290 inst_dict = vars(self).copy()
291 291 self.__map, self.__end = tmp
292 292 if inst_dict:
293 293 return (self.__class__, (items,), inst_dict)
294 294 return self.__class__, (items,)
295 295
296 296 def keys(self):
297 297 return list(self)
298 298
299 299 setdefault = DictMixin.setdefault
300 300 update = DictMixin.update
301 301 pop = DictMixin.pop
302 302 values = DictMixin.values
303 303 items = DictMixin.items
304 304 iterkeys = DictMixin.iterkeys
305 305 itervalues = DictMixin.itervalues
306 306 iteritems = DictMixin.iteritems
307 307
308 308 def __repr__(self):
309 309 if not self:
310 310 return '%s()' % (self.__class__.__name__,)
311 311 return '%s(%r)' % (self.__class__.__name__, self.items())
312 312
313 313 def copy(self):
314 314 return self.__class__(self)
315 315
316 316 @classmethod
317 317 def fromkeys(cls, iterable, value=None):
318 318 d = cls()
319 319 for key in iterable:
320 320 d[key] = value
321 321 return d
322 322
323 323 def __eq__(self, other):
324 324 if isinstance(other, OrderedDict):
325 325 return len(self) == len(other) and self.items() == other.items()
326 326 return dict.__eq__(self, other)
327 327
328 328 def __ne__(self, other):
329 329 return not self == other
@@ -1,86 +1,87
1 1 from pylons_app.model.meta import Base
2 2 from sqlalchemy.orm import relation, backref
3 3 from sqlalchemy import *
4 4 from vcs.utils.lazy import LazyProperty
5 5
6 6 class HgAppSettings(Base):
7 7 __tablename__ = 'hg_app_settings'
8 8 __table_args__ = {'useexisting':True}
9 9 app_settings_id = Column("app_settings_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
10 10 app_title = Column("app_title", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
11 11 app_auth_realm = Column("auth_realm", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
12 12
13 13 class HgAppUi(Base):
14 14 __tablename__ = 'hg_app_ui'
15 15 __table_args__ = {'useexisting':True}
16 16 ui_id = Column("ui_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
17 17 ui_section = Column("ui_section", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
18 18 ui_key = Column("ui_key", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
19 19 ui_value = Column("ui_value", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
20 20
21 21 class User(Base):
22 22 __tablename__ = 'users'
23 23 __table_args__ = {'useexisting':True}
24 24 user_id = Column("user_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
25 25 username = Column("username", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
26 26 password = Column("password", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
27 27 active = Column("active", BOOLEAN(), nullable=True, unique=None, default=None)
28 28 admin = Column("admin", BOOLEAN(), nullable=True, unique=None, default=False)
29 29 name = Column("name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
30 30 lastname = Column("lastname", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
31 31 email = Column("email", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
32 32 last_login = Column("last_login", DATETIME(timezone=False), nullable=True, unique=None, default=None)
33 33
34 34 user_log = relation('UserLog')
35 35
36 36 @LazyProperty
37 37 def full_contact(self):
38 38 return '%s %s <%s>' % (self.name, self.lastname, self.email)
39 39
40 40 def __repr__(self):
41 41 return "<User('%s:%s')>" % (self.user_id, self.username)
42 42
43 43 class UserLog(Base):
44 44 __tablename__ = 'user_logs'
45 45 __table_args__ = {'useexisting':True}
46 46 user_log_id = Column("user_log_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
47 47 user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
48 48 user_ip = Column("user_ip", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
49 49 repository = Column("repository", TEXT(length=None, convert_unicode=False, assert_unicode=None), ForeignKey(u'repositories.repo_name'), nullable=False, unique=None, default=None)
50 50 action = Column("action", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
51 51 action_date = Column("action_date", DATETIME(timezone=False), nullable=True, unique=None, default=None)
52 52
53 53 user = relation('User')
54 54
55 55 class Repository(Base):
56 56 __tablename__ = 'repositories'
57 __table_args__ = {'useexisting':True}
58 repo_name = Column("repo_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None, primary_key=True)
57 __table_args__ = (UniqueConstraint('repo_name'), {'useexisting':True},)
58 repo_id = Column("repo_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
59 repo_name = Column("repo_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
59 60 user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=False, default=None)
60 61 private = Column("private", BOOLEAN(), nullable=True, unique=None, default=None)
61 62 description = Column("description", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
62 63
63 64 user = relation('User')
64 65 repo2perm = relation('Repo2Perm', cascade='all')
65 66
66 67 class Permission(Base):
67 68 __tablename__ = 'permissions'
68 69 __table_args__ = {'useexisting':True}
69 70 permission_id = Column("permission_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
70 71 permission_name = Column("permission_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
71 72 permission_longname = Column("permission_longname", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
72 73
73 74 def __repr__(self):
74 75 return "<Permission('%s:%s')>" % (self.permission_id, self.permission_name)
75 76
76 77 class Repo2Perm(Base):
77 78 __tablename__ = 'repo_to_perm'
78 __table_args__ = (UniqueConstraint('user_id', 'repository'), {'useexisting':True})
79 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'useexisting':True})
79 80 repo2perm_id = Column("repo2perm_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
80 81 user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None)
81 82 permission_id = Column("permission_id", INTEGER(), ForeignKey(u'permissions.permission_id'), nullable=False, unique=None, default=None)
82 repository = Column("repository", TEXT(length=None, convert_unicode=False, assert_unicode=None), ForeignKey(u'repositories.repo_name'), nullable=False, unique=None, default=None)
83 repository_id = Column("repository_id", INTEGER(), ForeignKey(u'repositories.repo_id'), nullable=False, unique=None, default=None)
83 84
84 85 user = relation('User')
85 86 permission = relation('Permission')
86
87 repository = relation('Repository')
@@ -1,307 +1,306
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 from formencode import All
23 23 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
24 24 Email, Bool, StringBoolean
25 25 from pylons import session
26 26 from pylons.i18n.translation import _
27 27 from pylons_app.lib.auth import get_crypt_password
28 28 import pylons_app.lib.helpers as h
29 29 from pylons_app.model import meta
30 30 from pylons_app.model.db import User, Repository
31 31 from sqlalchemy.exc import OperationalError
32 32 from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
33 33 from webhelpers.pylonslib.secure_form import authentication_token
34 34 import datetime
35 35 import formencode
36 36 import logging
37 37 log = logging.getLogger(__name__)
38 38
39 39
40 40 #this is needed to translate the messages using _() in validators
41 41 class State_obj(object):
42 42 _ = staticmethod(_)
43 43
44 44 #===============================================================================
45 45 # VALIDATORS
46 46 #===============================================================================
47 47 class ValidAuthToken(formencode.validators.FancyValidator):
48 48 messages = {'invalid_token':_('Token mismatch')}
49 49
50 50 def validate_python(self, value, state):
51 51
52 52 if value != authentication_token():
53 53 raise formencode.Invalid(self.message('invalid_token', state,
54 54 search_number=value), value, state)
55 55
56 56 def ValidUsername(edit, old_data):
57 57 class _ValidUsername(formencode.validators.FancyValidator):
58 58
59 59 def validate_python(self, value, state):
60 60 if value in ['default', 'new_user']:
61 61 raise formencode.Invalid(_('Invalid username'), value, state)
62 62 #check if user is uniq
63 63 sa = meta.Session
64 64 old_un = None
65 65 if edit:
66 66 old_un = sa.query(User).get(old_data.get('user_id')).username
67 67
68 68 if old_un != value or not edit:
69 69 if sa.query(User).filter(User.username == value).scalar():
70 70 raise formencode.Invalid(_('This username already exists') ,
71 71 value, state)
72 72 meta.Session.remove()
73 73
74 74 return _ValidUsername
75 75
76 76 class ValidPassword(formencode.validators.FancyValidator):
77 77
78 78 def to_python(self, value, state):
79 79 if value:
80 80 return get_crypt_password(value)
81 81
82 82 class ValidAuth(formencode.validators.FancyValidator):
83 83 messages = {
84 84 'invalid_password':_('invalid password'),
85 85 'invalid_login':_('invalid user name'),
86 86 'disabled_account':_('Your acccount is disabled')
87 87
88 88 }
89 89 #error mapping
90 90 e_dict = {'username':messages['invalid_login'],
91 91 'password':messages['invalid_password']}
92 92 e_dict_disable = {'username':messages['disabled_account']}
93 93
94 94 def validate_python(self, value, state):
95 95 sa = meta.Session
96 96 crypted_passwd = get_crypt_password(value['password'])
97 97 username = value['username']
98 98 try:
99 99 user = sa.query(User).filter(User.username == username).one()
100 100 except (NoResultFound, MultipleResultsFound, OperationalError) as e:
101 101 log.error(e)
102 102 user = None
103 103 raise formencode.Invalid(self.message('invalid_password',
104 104 state=State_obj), value, state,
105 105 error_dict=self.e_dict)
106 106 if user:
107 107 if user.active:
108 108 if user.username == username and user.password == crypted_passwd:
109 109 from pylons_app.lib.auth import AuthUser
110 110 auth_user = AuthUser()
111 111 auth_user.username = username
112 112 auth_user.is_authenticated = True
113 113 auth_user.is_admin = user.admin
114 114 auth_user.user_id = user.user_id
115 115 auth_user.name = user.name
116 116 auth_user.lastname = user.lastname
117 117 session['hg_app_user'] = auth_user
118 118 session.save()
119 119 log.info('user %s is now authenticated', username)
120 120
121 121 try:
122 122 user.last_login = datetime.datetime.now()
123 123 sa.add(user)
124 124 sa.commit()
125 125 except (OperationalError) as e:
126 126 log.error(e)
127 127 sa.rollback()
128 128
129 129 return value
130 130 else:
131 131 log.warning('user %s not authenticated', username)
132 132 raise formencode.Invalid(self.message('invalid_password',
133 133 state=State_obj), value, state,
134 134 error_dict=self.e_dict)
135 135 else:
136 136 log.warning('user %s is disabled', username)
137 137 raise formencode.Invalid(self.message('disabled_account',
138 138 state=State_obj),
139 139 value, state,
140 140 error_dict=self.e_dict_disable)
141 141
142 142 meta.Session.remove()
143 143
144 144
145 145 class ValidRepoUser(formencode.validators.FancyValidator):
146 146
147 147 def to_python(self, value, state):
148 148 sa = meta.Session
149 149 try:
150 150 self.user_db = sa.query(User)\
151 151 .filter(User.active == True)\
152 152 .filter(User.username == value).one()
153 153 except Exception:
154 154 raise formencode.Invalid(_('This username is not valid'),
155 155 value, state)
156 156 meta.Session.remove()
157 157 return self.user_db.user_id
158 158
159 159 def ValidRepoName(edit, old_data):
160 160 class _ValidRepoName(formencode.validators.FancyValidator):
161 161
162 162 def to_python(self, value, state):
163 163 slug = h.repo_name_slug(value)
164 164 if slug in ['_admin']:
165 165 raise formencode.Invalid(_('This repository name is disallowed'),
166 166 value, state)
167
168 167 if old_data.get('repo_name') != value or not edit:
169 168 sa = meta.Session
170 if sa.query(Repository).get(slug):
169 if sa.query(Repository).filter(Repository.repo_name == slug).scalar():
171 170 raise formencode.Invalid(_('This repository already exists') ,
172 171 value, state)
173 172 meta.Session.remove()
174 173 return slug
175 174
176 175
177 176 return _ValidRepoName
178 177
179 178 class ValidPerms(formencode.validators.FancyValidator):
180 179 messages = {'perm_new_user_name':_('This username is not valid')}
181 180
182 181 def to_python(self, value, state):
183 182 perms_update = []
184 183 perms_new = []
185 184 #build a list of permission to update and new permission to create
186 185 for k, v in value.items():
187 186 if k.startswith('perm_'):
188 187 if k.startswith('perm_new_user'):
189 188 new_perm = value.get('perm_new_user', False)
190 189 new_user = value.get('perm_new_user_name', False)
191 190 if new_user and new_perm:
192 191 if (new_user, new_perm) not in perms_new:
193 192 perms_new.append((new_user, new_perm))
194 193 else:
195 194 usr = k[5:]
196 195 if usr == 'default':
197 196 if value['private']:
198 197 #set none for default when updating to private repo
199 198 v = 'repository.none'
200 199 perms_update.append((usr, v))
201 200 value['perms_updates'] = perms_update
202 201 value['perms_new'] = perms_new
203 202 sa = meta.Session
204 203 for k, v in perms_new:
205 204 try:
206 205 self.user_db = sa.query(User)\
207 206 .filter(User.active == True)\
208 207 .filter(User.username == k).one()
209 208 except Exception:
210 209 msg = self.message('perm_new_user_name',
211 210 state=State_obj)
212 211 raise formencode.Invalid(msg, value, state, error_dict={'perm_new_user_name':msg})
213 212 return value
214 213
215 214 class ValidSettings(formencode.validators.FancyValidator):
216 215
217 216 def to_python(self, value, state):
218 217 #settings form can't edit user
219 218 if value.has_key('user'):
220 219 del['value']['user']
221 220
222 221 return value
223 222 #===============================================================================
224 223 # FORMS
225 224 #===============================================================================
226 225 class LoginForm(formencode.Schema):
227 226 allow_extra_fields = True
228 227 filter_extra_fields = True
229 228 username = UnicodeString(
230 229 strip=True,
231 230 min=3,
232 231 not_empty=True,
233 232 messages={
234 233 'empty':_('Please enter a login'),
235 234 'tooShort':_('Enter a value %(min)i characters long or more')}
236 235 )
237 236
238 237 password = UnicodeString(
239 238 strip=True,
240 239 min=3,
241 240 not_empty=True,
242 241 messages={
243 242 'empty':_('Please enter a password'),
244 243 'tooShort':_('Enter a value %(min)i characters long or more')}
245 244 )
246 245
247 246
248 247 #chained validators have access to all data
249 248 chained_validators = [ValidAuth]
250 249
251 250 def UserForm(edit=False, old_data={}):
252 251 class _UserForm(formencode.Schema):
253 252 allow_extra_fields = True
254 253 filter_extra_fields = True
255 254 username = All(UnicodeString(strip=True, min=3, not_empty=True), ValidUsername(edit, old_data))
256 255 if edit:
257 256 new_password = All(UnicodeString(strip=True, min=3, not_empty=False), ValidPassword)
258 257 admin = StringBoolean(if_missing=False)
259 258 else:
260 259 password = All(UnicodeString(strip=True, min=8, not_empty=True), ValidPassword)
261 260 active = StringBoolean(if_missing=False)
262 261 name = UnicodeString(strip=True, min=3, not_empty=True)
263 262 lastname = UnicodeString(strip=True, min=3, not_empty=True)
264 263 email = Email(not_empty=True)
265 264
266 265 return _UserForm
267 266
268 267 RegisterForm = UserForm
269 268
270 269
271 270 def RepoForm(edit=False, old_data={}):
272 271 class _RepoForm(formencode.Schema):
273 272 allow_extra_fields = True
274 273 filter_extra_fields = False
275 274 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data))
276 275 description = UnicodeString(strip=True, min=3, not_empty=True)
277 276 private = StringBoolean(if_missing=False)
278 277
279 278 if edit:
280 279 user = All(Int(not_empty=True), ValidRepoUser)
281 280
282 281 chained_validators = [ValidPerms]
283 282 return _RepoForm
284 283
285 284 def RepoSettingsForm(edit=False, old_data={}):
286 285 class _RepoForm(formencode.Schema):
287 286 allow_extra_fields = True
288 287 filter_extra_fields = False
289 288 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data))
290 289 description = UnicodeString(strip=True, min=3, not_empty=True)
291 290 private = StringBoolean(if_missing=False)
292 291
293 292 chained_validators = [ValidPerms, ValidSettings]
294 293 return _RepoForm
295 294
296 295
297 296 def ApplicationSettingsForm():
298 297 class _ApplicationSettingsForm(formencode.Schema):
299 298 allow_extra_fields = True
300 299 filter_extra_fields = False
301 300 app_title = UnicodeString(strip=True, min=3, not_empty=True)
302 301 app_auth_realm = UnicodeString(strip=True, min=3, not_empty=True)
303 302
304 303 return _ApplicationSettingsForm
305 304
306 305
307 306
@@ -1,170 +1,178
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # model for handling repositories actions
4 4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 5 # This program is free software; you can redistribute it and/or
6 6 # modify it under the terms of the GNU General Public License
7 7 # as published by the Free Software Foundation; version 2
8 8 # of the License or (at your opinion) any later version of the license.
9 9 #
10 10 # This program is distributed in the hope that it will be useful,
11 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 13 # GNU General Public License for more details.
14 14 #
15 15 # You should have received a copy of the GNU General Public License
16 16 # along with this program; if not, write to the Free Software
17 17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 18 # MA 02110-1301, USA.
19
20 19 """
21 20 Created on Jun 5, 2010
22 21 model for handling repositories actions
23 22 @author: marcink
24 23 """
25 from pylons_app.model.meta import Session
24 from datetime import datetime
25 from pylons import app_globals as g
26 from pylons_app.lib.utils import check_repo
26 27 from pylons_app.model.db import Repository, Repo2Perm, User, Permission
27 import shutil
28 from pylons_app.model.meta import Session
29 import logging
28 30 import os
29 from datetime import datetime
30 from pylons_app.lib.utils import check_repo
31 from pylons import app_globals as g
31 import shutil
32 32 import traceback
33 import logging
34 33 log = logging.getLogger(__name__)
35 34
36 35 class RepoModel(object):
37 36
38 37 def __init__(self):
39 38 self.sa = Session()
40 39
41 40 def get(self, id):
42 return self.sa.query(Repository).get(id)
41 return self.sa.query(Repository).filter(Repository.repo_name == id).scalar()
43 42
44 43 def get_users_js(self):
45 44
46 45 users = self.sa.query(User).filter(User.active == True).all()
47 46 u_tmpl = '''{id:%s, fname:"%s", lname:"%s", nname:"%s"},'''
48 47 users_array = '[%s];' % '\n'.join([u_tmpl % (u.user_id, u.name,
49 48 u.lastname, u.username)
50 49 for u in users])
51 50 return users_array
52 51
53 52
54 def update(self, repo_id, form_data):
53 def update(self, repo_name, form_data):
55 54 try:
56 if repo_id != form_data['repo_name']:
57 #rename our data
58 self.__rename_repo(repo_id, form_data['repo_name'])
59 cur_repo = self.sa.query(Repository).get(repo_id)
60 for k, v in form_data.items():
61 if k == 'user':
62 cur_repo.user_id = v
63 else:
64 setattr(cur_repo, k, v)
65 55
66 56 #update permissions
67 57 for username, perm in form_data['perms_updates']:
68 58 r2p = self.sa.query(Repo2Perm)\
69 59 .filter(Repo2Perm.user == self.sa.query(User)\
70 60 .filter(User.username == username).one())\
71 .filter(Repo2Perm.repository == repo_id).one()
61 .filter(Repo2Perm.repository == self.get(repo_name))\
62 .one()
72 63
73 64 r2p.permission_id = self.sa.query(Permission).filter(
74 65 Permission.permission_name ==
75 66 perm).one().permission_id
76 67 self.sa.add(r2p)
77 68
69 #set new permissions
78 70 for username, perm in form_data['perms_new']:
79 71 r2p = Repo2Perm()
80 r2p.repository = repo_id
72 r2p.repository = self.get(repo_name)
81 73 r2p.user = self.sa.query(User)\
82 74 .filter(User.username == username).one()
83 75
84 76 r2p.permission_id = self.sa.query(Permission).filter(
85 Permission.permission_name ==
86 perm).one().permission_id
77 Permission.permission_name == perm)\
78 .one().permission_id
87 79 self.sa.add(r2p)
88 80
81 #update current repo
82 cur_repo = self.get(repo_name)
83
84 for k, v in form_data.items():
85 if k == 'user':
86 cur_repo.user_id = v
87 else:
88 setattr(cur_repo, k, v)
89
89 90 self.sa.add(cur_repo)
91
92 if repo_name != form_data['repo_name']:
93 #rename our data
94 self.__rename_repo(repo_name, form_data['repo_name'])
95
90 96 self.sa.commit()
91 97 except:
92 98 log.error(traceback.format_exc())
93 99 self.sa.rollback()
94 100 raise
95 101
96 102 def create(self, form_data, cur_user, just_db=False):
97 103 try:
98 104 repo_name = form_data['repo_name']
99 105 new_repo = Repository()
100 106 for k, v in form_data.items():
101 107 setattr(new_repo, k, v)
102 108
103 109 new_repo.user_id = cur_user.user_id
104 110 self.sa.add(new_repo)
105 111
106 112 #create default permission
107 113 repo2perm = Repo2Perm()
108 114 default_perm = 'repository.none' if form_data['private'] \
109 115 else 'repository.read'
110 116 repo2perm.permission_id = self.sa.query(Permission)\
111 117 .filter(Permission.permission_name == default_perm)\
112 118 .one().permission_id
113 119
114 repo2perm.repository = repo_name
120 repo2perm.repository_id = new_repo.repo_id
115 121 repo2perm.user_id = self.sa.query(User)\
116 122 .filter(User.username == 'default').one().user_id
117 123
118 124 self.sa.add(repo2perm)
119 125 self.sa.commit()
120 126 if not just_db:
121 127 self.__create_repo(repo_name)
122 128 except:
123 129 log.error(traceback.format_exc())
124 130 self.sa.rollback()
125 131 raise
126 132
127 133 def delete(self, repo):
128 134 try:
129 135 self.sa.delete(repo)
130 136 self.sa.commit()
131 137 self.__delete_repo(repo.repo_name)
132 138 except:
133 139 log.error(traceback.format_exc())
134 140 self.sa.rollback()
135 141 raise
142
136 143 def delete_perm_user(self, form_data, repo_name):
137 144 try:
138 r2p = self.sa.query(Repo2Perm).filter(Repo2Perm.repository == repo_name)\
145 self.sa.query(Repo2Perm)\
146 .filter(Repo2Perm.repository == self.get(repo_name))\
139 147 .filter(Repo2Perm.user_id == form_data['user_id']).delete()
140 148 self.sa.commit()
141 149 except:
142 150 log.error(traceback.format_exc())
143 151 self.sa.rollback()
144 152 raise
145 153
146 154 def __create_repo(self, repo_name):
147 155 repo_path = os.path.join(g.base_path, repo_name)
148 156 if check_repo(repo_name, g.base_path):
149 157 log.info('creating repo %s in %s', repo_name, repo_path)
150 158 from vcs.backends.hg import MercurialRepository
151 159 MercurialRepository(repo_path, create=True)
152 160
153 161 def __rename_repo(self, old, new):
154 162 log.info('renaming repo from %s to %s', old, new)
155 163
156 164 old_path = os.path.join(g.base_path, old)
157 165 new_path = os.path.join(g.base_path, new)
158 166 if os.path.isdir(new_path):
159 167 raise Exception('Was trying to rename to already existing dir %s',
160 168 new_path)
161 169 shutil.move(old_path, new_path)
162 170
163 171 def __delete_repo(self, name):
164 172 rm_path = os.path.join(g.base_path, name)
165 173 log.info("Removing %s", rm_path)
166 174 #disable hg
167 175 shutil.move(os.path.join(rm_path, '.hg'), os.path.join(rm_path, 'rm__.hg'))
168 176 #disable repo
169 177 shutil.move(rm_path, os.path.join(g.base_path, 'rm__%s__%s' \
170 178 % (datetime.today(), name)))
General Comments 0
You need to be logged in to leave comments. Login now