##// END OF EJS Templates
added session remove in forms, and added name and lastname to auth user
marcink -
r355:5bbcc0ca default
parent child Browse files
Show More
@@ -1,413 +1,415
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 self.name = ''
81 self.lastname = ''
80 82 self.user_id = None
81 83 self.is_authenticated = False
82 84 self.is_admin = False
83 85 self.permissions = {}
84 86
85 87
86 88 def set_available_permissions(config):
87 89 """
88 90 This function will propagate pylons globals with all available defined
89 91 permission given in db. We don't wannt to check each time from db for new
90 92 permissions since adding a new permission also requires application restart
91 93 ie. to decorate new views with the newly created permission
92 94 @param config:
93 95 """
94 96 log.info('getting information about all available permissions')
95 97 try:
96 98 sa = meta.Session
97 99 all_perms = sa.query(Permission).all()
98 100 finally:
99 101 meta.Session.remove()
100 102
101 103 config['available_permissions'] = [x.permission_name for x in all_perms]
102 104
103 105 def set_base_path(config):
104 106 config['base_path'] = config['pylons.app_globals'].base_path
105 107
106 108 def fill_perms(user):
107 109 sa = meta.Session
108 110 user.permissions['repositories'] = {}
109 111
110 112 #first fetch default permissions
111 113 default_perms = sa.query(Repo2Perm, Repository, Permission)\
112 114 .join((Repository, Repo2Perm.repository == Repository.repo_name))\
113 115 .join((Permission, Repo2Perm.permission_id == Permission.permission_id))\
114 116 .filter(Repo2Perm.user_id == sa.query(User).filter(User.username ==
115 117 'default').one().user_id).all()
116 118
117 119 if user.is_admin:
118 120 user.permissions['global'] = set(['hg.admin'])
119 121 #admin have all rights full
120 122 for perm in default_perms:
121 123 p = 'repository.admin'
122 124 user.permissions['repositories'][perm.Repo2Perm.repository] = p
123 125
124 126 else:
125 127 user.permissions['global'] = set()
126 128 for perm in default_perms:
127 129 if perm.Repository.private:
128 130 #disable defaults for private repos,
129 131 p = 'repository.none'
130 132 elif perm.Repository.user_id == user.user_id:
131 133 #set admin if owner
132 134 p = 'repository.admin'
133 135 else:
134 136 p = perm.Permission.permission_name
135 137
136 138 user.permissions['repositories'][perm.Repo2Perm.repository] = p
137 139
138 140
139 141 user_perms = sa.query(Repo2Perm, Permission, Repository)\
140 142 .join((Repository, Repo2Perm.repository == Repository.repo_name))\
141 143 .join((Permission, Repo2Perm.permission_id == Permission.permission_id))\
142 144 .filter(Repo2Perm.user_id == user.user_id).all()
143 145 #overwrite userpermissions with defaults
144 146 for perm in user_perms:
145 147 #set write if owner
146 148 if perm.Repository.user_id == user.user_id:
147 149 p = 'repository.write'
148 150 else:
149 151 p = perm.Permission.permission_name
150 152 user.permissions['repositories'][perm.Repo2Perm.repository] = p
151 153 meta.Session.remove()
152 154 return user
153 155
154 156 def get_user(session):
155 157 """
156 158 Gets user from session, and wraps permissions into user
157 159 @param session:
158 160 """
159 161 user = session.get('hg_app_user', AuthUser())
160 162
161 163 if user.is_authenticated:
162 164 user = fill_perms(user)
163 165
164 166 session['hg_app_user'] = user
165 167 session.save()
166 168 return user
167 169
168 170 #===============================================================================
169 171 # CHECK DECORATORS
170 172 #===============================================================================
171 173 class LoginRequired(object):
172 174 """
173 175 Must be logged in to execute this function else redirect to login page
174 176 """
175 177
176 178 def __call__(self, func):
177 179 @wraps(func)
178 180 def _wrapper(*fargs, **fkwargs):
179 181 user = session.get('hg_app_user', AuthUser())
180 182 log.debug('Checking login required for user:%s', user.username)
181 183 if user.is_authenticated:
182 184 log.debug('user %s is authenticated', user.username)
183 185 func(*fargs)
184 186 else:
185 187 log.warn('user %s not authenticated', user.username)
186 188 log.debug('redirecting to login page')
187 189 return redirect(url('login_home'))
188 190
189 191 return _wrapper
190 192
191 193 class PermsDecorator(object):
192 194 """
193 195 Base class for decorators
194 196 """
195 197
196 198 def __init__(self, *required_perms):
197 199 available_perms = config['available_permissions']
198 200 for perm in required_perms:
199 201 if perm not in available_perms:
200 202 raise Exception("'%s' permission is not defined" % perm)
201 203 self.required_perms = set(required_perms)
202 204 self.user_perms = None
203 205
204 206 def __call__(self, func):
205 207 @wraps(func)
206 208 def _wrapper(*fargs, **fkwargs):
207 209 self.user_perms = session.get('hg_app_user', AuthUser()).permissions
208 210 log.debug('checking %s permissions %s for %s',
209 211 self.__class__.__name__, self.required_perms, func.__name__)
210 212
211 213 if self.check_permissions():
212 214 log.debug('Permission granted for %s', func.__name__)
213 215 return func(*fargs)
214 216
215 217 else:
216 218 log.warning('Permission denied for %s', func.__name__)
217 219 #redirect with forbidden ret code
218 220 return abort(403)
219 221 return _wrapper
220 222
221 223
222 224 def check_permissions(self):
223 225 """
224 226 Dummy function for overriding
225 227 """
226 228 raise Exception('You have to write this function in child class')
227 229
228 230 class HasPermissionAllDecorator(PermsDecorator):
229 231 """
230 232 Checks for access permission for all given predicates. All of them have to
231 233 be meet in order to fulfill the request
232 234 """
233 235
234 236 def check_permissions(self):
235 237 if self.required_perms.issubset(self.user_perms.get('global')):
236 238 return True
237 239 return False
238 240
239 241
240 242 class HasPermissionAnyDecorator(PermsDecorator):
241 243 """
242 244 Checks for access permission for any of given predicates. In order to
243 245 fulfill the request any of predicates must be meet
244 246 """
245 247
246 248 def check_permissions(self):
247 249 if self.required_perms.intersection(self.user_perms.get('global')):
248 250 return True
249 251 return False
250 252
251 253 class HasRepoPermissionAllDecorator(PermsDecorator):
252 254 """
253 255 Checks for access permission for all given predicates for specific
254 256 repository. All of them have to be meet in order to fulfill the request
255 257 """
256 258
257 259 def check_permissions(self):
258 260 repo_name = get_repo_slug(request)
259 261 try:
260 262 user_perms = set([self.user_perms['repositories'][repo_name]])
261 263 except KeyError:
262 264 return False
263 265 if self.required_perms.issubset(user_perms):
264 266 return True
265 267 return False
266 268
267 269
268 270 class HasRepoPermissionAnyDecorator(PermsDecorator):
269 271 """
270 272 Checks for access permission for any of given predicates for specific
271 273 repository. In order to fulfill the request any of predicates must be meet
272 274 """
273 275
274 276 def check_permissions(self):
275 277 repo_name = get_repo_slug(request)
276 278
277 279 try:
278 280 user_perms = set([self.user_perms['repositories'][repo_name]])
279 281 except KeyError:
280 282 return False
281 283 if self.required_perms.intersection(user_perms):
282 284 return True
283 285 return False
284 286 #===============================================================================
285 287 # CHECK FUNCTIONS
286 288 #===============================================================================
287 289
288 290 class PermsFunction(object):
289 291 """
290 292 Base function for other check functions
291 293 """
292 294
293 295 def __init__(self, *perms):
294 296 available_perms = config['available_permissions']
295 297
296 298 for perm in perms:
297 299 if perm not in available_perms:
298 300 raise Exception("'%s' permission in not defined" % perm)
299 301 self.required_perms = set(perms)
300 302 self.user_perms = None
301 303 self.granted_for = ''
302 304 self.repo_name = None
303 305
304 306 def __call__(self, check_Location=''):
305 307 user = session.get('hg_app_user', False)
306 308 if not user:
307 309 return False
308 310 self.user_perms = user.permissions
309 311 self.granted_for = user.username
310 312 log.debug('checking %s %s', self.__class__.__name__, self.required_perms)
311 313
312 314 if self.check_permissions():
313 315 log.debug('Permission granted for %s @%s', self.granted_for,
314 316 check_Location)
315 317 return True
316 318
317 319 else:
318 320 log.warning('Permission denied for %s @%s', self.granted_for,
319 321 check_Location)
320 322 return False
321 323
322 324 def check_permissions(self):
323 325 """
324 326 Dummy function for overriding
325 327 """
326 328 raise Exception('You have to write this function in child class')
327 329
328 330 class HasPermissionAll(PermsFunction):
329 331 def check_permissions(self):
330 332 if self.required_perms.issubset(self.user_perms.get('global')):
331 333 return True
332 334 return False
333 335
334 336 class HasPermissionAny(PermsFunction):
335 337 def check_permissions(self):
336 338 if self.required_perms.intersection(self.user_perms.get('global')):
337 339 return True
338 340 return False
339 341
340 342 class HasRepoPermissionAll(PermsFunction):
341 343
342 344 def __call__(self, repo_name=None, check_Location=''):
343 345 self.repo_name = repo_name
344 346 return super(HasRepoPermissionAll, self).__call__(check_Location)
345 347
346 348 def check_permissions(self):
347 349 if not self.repo_name:
348 350 self.repo_name = get_repo_slug(request)
349 351
350 352 try:
351 353 self.user_perms = set([self.user_perms['repositories']\
352 354 [self.repo_name]])
353 355 except KeyError:
354 356 return False
355 357 self.granted_for = self.repo_name
356 358 if self.required_perms.issubset(self.user_perms):
357 359 return True
358 360 return False
359 361
360 362 class HasRepoPermissionAny(PermsFunction):
361 363
362 364
363 365 def __call__(self, repo_name=None, check_Location=''):
364 366 self.repo_name = repo_name
365 367 return super(HasRepoPermissionAny, self).__call__(check_Location)
366 368
367 369 def check_permissions(self):
368 370 if not self.repo_name:
369 371 self.repo_name = get_repo_slug(request)
370 372
371 373 try:
372 374 self.user_perms = set([self.user_perms['repositories']\
373 375 [self.repo_name]])
374 376 except KeyError:
375 377 return False
376 378 self.granted_for = self.repo_name
377 379 if self.required_perms.intersection(self.user_perms):
378 380 return True
379 381 return False
380 382
381 383 #===============================================================================
382 384 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
383 385 #===============================================================================
384 386
385 387 class HasPermissionAnyMiddleware(object):
386 388 def __init__(self, *perms):
387 389 self.required_perms = set(perms)
388 390
389 391 def __call__(self, user, repo_name):
390 392 usr = AuthUser()
391 393 usr.user_id = user.user_id
392 394 usr.username = user.username
393 395 usr.is_admin = user.admin
394 396
395 397 try:
396 398 self.user_perms = set([fill_perms(usr)\
397 399 .permissions['repositories'][repo_name]])
398 400 except:
399 401 self.user_perms = set()
400 402 self.granted_for = ''
401 403 self.username = user.username
402 404 self.repo_name = repo_name
403 405 return self.check_permissions()
404 406
405 407 def check_permissions(self):
406 408 log.debug('checking mercurial protocol '
407 409 'permissions for user:%s repository:%s',
408 410 self.username, self.repo_name)
409 411 if self.required_perms.intersection(self.user_perms):
410 412 log.debug('permission granted')
411 413 return True
412 414 log.debug('permission denied')
413 415 return False
@@ -1,280 +1,282
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 class ValidUsername(formencode.validators.FancyValidator):
56 56
57 57 def validate_python(self, value, state):
58 58 if value in ['default', 'new_user']:
59 59 raise formencode.Invalid(_('Invalid username'), value, state)
60 60
61 61 class ValidPassword(formencode.validators.FancyValidator):
62 62
63 63 def to_python(self, value, state):
64 64 if value:
65 65 return get_crypt_password(value)
66 66
67 67 class ValidAuth(formencode.validators.FancyValidator):
68 68 messages = {
69 69 'invalid_password':_('invalid password'),
70 70 'invalid_login':_('invalid user name'),
71 71 'disabled_account':_('Your acccount is disabled')
72 72
73 73 }
74 74 #error mapping
75 75 e_dict = {'username':messages['invalid_login'],
76 76 'password':messages['invalid_password']}
77 77 e_dict_disable = {'username':messages['disabled_account']}
78 78
79 79 def validate_python(self, value, state):
80 80 sa = meta.Session
81 81 crypted_passwd = get_crypt_password(value['password'])
82 82 username = value['username']
83 83 try:
84 84 user = sa.query(User).filter(User.username == username).one()
85 85 except (NoResultFound, MultipleResultsFound, OperationalError) as e:
86 86 log.error(e)
87 87 user = None
88 88 raise formencode.Invalid(self.message('invalid_password',
89 89 state=State_obj), value, state,
90 90 error_dict=self.e_dict)
91 91 if user:
92 92 if user.active:
93 93 if user.username == username and user.password == crypted_passwd:
94 94 from pylons_app.lib.auth import AuthUser
95 95 auth_user = AuthUser()
96 96 auth_user.username = username
97 97 auth_user.is_authenticated = True
98 98 auth_user.is_admin = user.admin
99 99 auth_user.user_id = user.user_id
100 auth_user.name = user.name
101 auth_user.lastname = user.lastname
100 102 session['hg_app_user'] = auth_user
101 103 session.save()
102 104 log.info('user %s is now authenticated', username)
103 105
104 106 try:
105 107 user.last_login = datetime.datetime.now()
106 108 sa.add(user)
107 109 sa.commit()
108 110 except (OperationalError) as e:
109 111 log.error(e)
110 112 sa.rollback()
111 113
112 114 return value
113 115 else:
114 116 log.warning('user %s not authenticated', username)
115 117 raise formencode.Invalid(self.message('invalid_password',
116 118 state=State_obj), value, state,
117 119 error_dict=self.e_dict)
118 120 else:
119 121 log.warning('user %s is disabled', username)
120 122 raise formencode.Invalid(self.message('disabled_account',
121 123 state=State_obj),
122 124 value, state,
123 125 error_dict=self.e_dict_disable)
124 126
125
127 meta.Session.remove()
126 128 class ValidRepoUser(formencode.validators.FancyValidator):
127 129
128 130 def to_python(self, value, state):
129 131 sa = meta.Session
130 132 try:
131 133 self.user_db = sa.query(User)\
132 134 .filter(User.active == True)\
133 135 .filter(User.username == value).one()
134 136 except Exception:
135 137 raise formencode.Invalid(_('This username is not valid'),
136 138 value, state)
137 139 return self.user_db.user_id
138 140
139 141 def ValidRepoName(edit=False):
140 142 class _ValidRepoName(formencode.validators.FancyValidator):
141 143
142 144 def to_python(self, value, state):
143 145 slug = h.repo_name_slug(value)
144 146 if slug in ['_admin']:
145 147 raise formencode.Invalid(_('This repository name is disallowed'),
146 148 value, state)
147 149 sa = meta.Session
148 150 if sa.query(Repository).get(slug) and not edit:
149 151 raise formencode.Invalid(_('This repository already exists'),
150 152 value, state)
151 153
152 154 return slug
153 155 return _ValidRepoName
154 156
155 157 class ValidPerms(formencode.validators.FancyValidator):
156 158 messages = {'perm_new_user_name':_('This username is not valid')}
157 159
158 160 def to_python(self, value, state):
159 161 perms_update = []
160 162 perms_new = []
161 163 #build a list of permission to update and new permission to create
162 164 for k, v in value.items():
163 165 if k.startswith('perm_'):
164 166 if k.startswith('perm_new_user'):
165 167 new_perm = value.get('perm_new_user', False)
166 168 new_user = value.get('perm_new_user_name', False)
167 169 if new_user and new_perm:
168 170 if (new_user, new_perm) not in perms_new:
169 171 perms_new.append((new_user, new_perm))
170 172 else:
171 173 usr = k[5:]
172 174 if usr == 'default':
173 175 if value['private']:
174 176 #set none for default when updating to private repo
175 177 v = 'repository.none'
176 178 perms_update.append((usr, v))
177 179 value['perms_updates'] = perms_update
178 180 value['perms_new'] = perms_new
179 181 sa = meta.Session
180 182 for k, v in perms_new:
181 183 try:
182 184 self.user_db = sa.query(User)\
183 185 .filter(User.active == True)\
184 186 .filter(User.username == k).one()
185 187 except Exception:
186 188 msg = self.message('perm_new_user_name',
187 189 state=State_obj)
188 190 raise formencode.Invalid(msg, value, state, error_dict={'perm_new_user_name':msg})
189 191 return value
190 192
191 193 class ValidSettings(formencode.validators.FancyValidator):
192 194
193 195 def to_python(self, value, state):
194 196 #settings form can't edit user
195 197 if value.has_key('user'):
196 198 del['value']['user']
197 199
198 200 return value
199 201 #===============================================================================
200 202 # FORMS
201 203 #===============================================================================
202 204 class LoginForm(formencode.Schema):
203 205 allow_extra_fields = True
204 206 filter_extra_fields = True
205 207 username = UnicodeString(
206 208 strip=True,
207 209 min=3,
208 210 not_empty=True,
209 211 messages={
210 212 'empty':_('Please enter a login'),
211 213 'tooShort':_('Enter a value %(min)i characters long or more')}
212 214 )
213 215
214 216 password = UnicodeString(
215 217 strip=True,
216 218 min=3,
217 219 not_empty=True,
218 220 messages={
219 221 'empty':_('Please enter a password'),
220 222 'tooShort':_('Enter a value %(min)i characters long or more')}
221 223 )
222 224
223 225
224 226 #chained validators have access to all data
225 227 chained_validators = [ValidAuth]
226 228
227 229 def UserForm(edit=False):
228 230 class _UserForm(formencode.Schema):
229 231 allow_extra_fields = True
230 232 filter_extra_fields = True
231 233 username = All(UnicodeString(strip=True, min=3, not_empty=True), ValidUsername)
232 234 if edit:
233 235 new_password = All(UnicodeString(strip=True, min=3, not_empty=False), ValidPassword)
234 236 admin = StringBoolean(if_missing=False)
235 237 else:
236 238 password = All(UnicodeString(strip=True, min=3, not_empty=False), ValidPassword)
237 239 active = StringBoolean(if_missing=False)
238 240 name = UnicodeString(strip=True, min=3, not_empty=True)
239 241 lastname = UnicodeString(strip=True, min=3, not_empty=True)
240 242 email = Email(not_empty=True)
241 243
242 244 return _UserForm
243 245
244 246 def RepoForm(edit=False):
245 247 class _RepoForm(formencode.Schema):
246 248 allow_extra_fields = True
247 249 filter_extra_fields = False
248 250 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit))
249 251 description = UnicodeString(strip=True, min=3, not_empty=True)
250 252 private = StringBoolean(if_missing=False)
251 253
252 254 if edit:
253 255 user = All(Int(not_empty=True), ValidRepoUser)
254 256
255 257 chained_validators = [ValidPerms]
256 258 return _RepoForm
257 259
258 260 def RepoSettingsForm(edit=False):
259 261 class _RepoForm(formencode.Schema):
260 262 allow_extra_fields = True
261 263 filter_extra_fields = False
262 264 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit))
263 265 description = UnicodeString(strip=True, min=3, not_empty=True)
264 266 private = StringBoolean(if_missing=False)
265 267
266 268 chained_validators = [ValidPerms, ValidSettings]
267 269 return _RepoForm
268 270
269 271
270 272 def ApplicationSettingsForm():
271 273 class _ApplicationSettingsForm(formencode.Schema):
272 274 allow_extra_fields = True
273 275 filter_extra_fields = False
274 276 app_title = UnicodeString(strip=True, min=3, not_empty=True)
275 277 app_auth_realm = UnicodeString(strip=True, min=3, not_empty=True)
276 278
277 279 return _ApplicationSettingsForm
278 280
279 281
280 282
General Comments 0
You need to be logged in to leave comments. Login now