##// END OF EJS Templates
Added validation into user email map
marcink -
r2479:92255976 beta
parent child Browse files
Show More
@@ -1,247 +1,253
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) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29
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 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 AuthUser
40 40 from rhodecode.lib.base import BaseController, render
41 41
42 42 from rhodecode.model.db import User, Permission, UserEmailMap
43 43 from rhodecode.model.forms import UserForm
44 44 from rhodecode.model.user import UserModel
45 45 from rhodecode.model.meta import Session
46 46 from rhodecode.lib.utils import action_logger
47 47
48 48 log = logging.getLogger(__name__)
49 49
50 50
51 51 class UsersController(BaseController):
52 52 """REST Controller styled on the Atom Publishing Protocol"""
53 53 # To properly map this controller, ensure your config/routing.py
54 54 # file has a resource setup:
55 55 # map.resource('user', 'users')
56 56
57 57 @LoginRequired()
58 58 @HasPermissionAllDecorator('hg.admin')
59 59 def __before__(self):
60 60 c.admin_user = session.get('admin_user')
61 61 c.admin_username = session.get('admin_username')
62 62 super(UsersController, self).__before__()
63 63 c.available_permissions = config['available_permissions']
64 64
65 65 def index(self, format='html'):
66 66 """GET /users: All items in the collection"""
67 67 # url('users')
68 68
69 69 c.users_list = self.sa.query(User).all()
70 70 return render('admin/users/users.html')
71 71
72 72 def create(self):
73 73 """POST /users: Create a new item"""
74 74 # url('users')
75 75
76 76 user_model = UserModel()
77 77 user_form = UserForm()()
78 78 try:
79 79 form_result = user_form.to_python(dict(request.POST))
80 80 user_model.create(form_result)
81 81 usr = form_result['username']
82 82 action_logger(self.rhodecode_user, 'admin_created_user:%s' % usr,
83 83 None, self.ip_addr, self.sa)
84 84 h.flash(_('created user %s') % usr,
85 85 category='success')
86 86 Session.commit()
87 87 except formencode.Invalid, errors:
88 88 return htmlfill.render(
89 89 render('admin/users/user_add.html'),
90 90 defaults=errors.value,
91 91 errors=errors.error_dict or {},
92 92 prefix_error=False,
93 93 encoding="UTF-8")
94 94 except Exception:
95 95 log.error(traceback.format_exc())
96 96 h.flash(_('error occurred during creation of user %s') \
97 97 % request.POST.get('username'), category='error')
98 98 return redirect(url('users'))
99 99
100 100 def new(self, format='html'):
101 101 """GET /users/new: Form to create a new item"""
102 102 # url('new_user')
103 103 return render('admin/users/user_add.html')
104 104
105 105 def update(self, id):
106 106 """PUT /users/id: Update an existing item"""
107 107 # Forms posted to this method should contain a hidden field:
108 108 # <input type="hidden" name="_method" value="PUT" />
109 109 # Or using helpers:
110 110 # h.form(url('update_user', id=ID),
111 111 # method='put')
112 112 # url('user', id=ID)
113 113 user_model = UserModel()
114 114 c.user = user_model.get(id)
115 115 c.perm_user = AuthUser(user_id=id)
116 116 _form = UserForm(edit=True, old_data={'user_id': id,
117 117 'email': c.user.email})()
118 118 form_result = {}
119 119 try:
120 120 form_result = _form.to_python(dict(request.POST))
121 121 user_model.update(id, form_result)
122 122 usr = form_result['username']
123 123 action_logger(self.rhodecode_user, 'admin_updated_user:%s' % usr,
124 124 None, self.ip_addr, self.sa)
125 125 h.flash(_('User updated successfully'), category='success')
126 126 Session.commit()
127 127 except formencode.Invalid, errors:
128 c.user_email_map = UserEmailMap.query()\
129 .filter(UserEmailMap.user == c.user).all()
130 defaults = errors.value
128 131 e = errors.error_dict or {}
129 132 perm = Permission.get_by_key('hg.create.repository')
130 e.update({'create_repo_perm': user_model.has_perm(id, perm)})
133 defaults.update({'create_repo_perm': user_model.has_perm(id, perm)})
131 134 return htmlfill.render(
132 135 render('admin/users/user_edit.html'),
133 defaults=errors.value,
136 defaults=defaults,
134 137 errors=e,
135 138 prefix_error=False,
136 139 encoding="UTF-8")
137 140 except Exception:
138 141 log.error(traceback.format_exc())
139 142 h.flash(_('error occurred during update of user %s') \
140 143 % form_result.get('username'), category='error')
141 144
142 145 return redirect(url('users'))
143 146
144 147 def delete(self, id):
145 148 """DELETE /users/id: Delete an existing item"""
146 149 # Forms posted to this method should contain a hidden field:
147 150 # <input type="hidden" name="_method" value="DELETE" />
148 151 # Or using helpers:
149 152 # h.form(url('delete_user', id=ID),
150 153 # method='delete')
151 154 # url('user', id=ID)
152 155 user_model = UserModel()
153 156 try:
154 157 user_model.delete(id)
155 158 Session.commit()
156 159 h.flash(_('successfully deleted user'), category='success')
157 160 except (UserOwnsReposException, DefaultUserException), e:
158 161 h.flash(e, category='warning')
159 162 except Exception:
160 163 log.error(traceback.format_exc())
161 164 h.flash(_('An error occurred during deletion of user'),
162 165 category='error')
163 166 return redirect(url('users'))
164 167
165 168 def show(self, id, format='html'):
166 169 """GET /users/id: Show a specific item"""
167 170 # url('user', id=ID)
168 171
169 172 def edit(self, id, format='html'):
170 173 """GET /users/id/edit: Form to edit an existing item"""
171 174 # url('edit_user', id=ID)
172 175 c.user = User.get(id)
173 176 if not c.user:
174 177 return redirect(url('users'))
175 178 if c.user.username == 'default':
176 179 h.flash(_("You can't edit this user"), category='warning')
177 180 return redirect(url('users'))
178 181 c.perm_user = AuthUser(user_id=id)
179 182 c.user.permissions = {}
180 183 c.granted_permissions = UserModel().fill_perms(c.user)\
181 184 .permissions['global']
182 185 c.user_email_map = UserEmailMap.query()\
183 186 .filter(UserEmailMap.user == c.user).all()
184 187 defaults = c.user.get_dict()
185 188 perm = Permission.get_by_key('hg.create.repository')
186 189 defaults.update({'create_repo_perm': UserModel().has_perm(id, perm)})
187 190
188 191 return htmlfill.render(
189 192 render('admin/users/user_edit.html'),
190 193 defaults=defaults,
191 194 encoding="UTF-8",
192 195 force_defaults=False
193 196 )
194 197
195 198 def update_perm(self, id):
196 199 """PUT /users_perm/id: Update an existing item"""
197 200 # url('user_perm', id=ID, method='put')
198 201
199 202 grant_perm = request.POST.get('create_repo_perm', False)
200 203 user_model = UserModel()
201 204
202 205 if grant_perm:
203 206 perm = Permission.get_by_key('hg.create.none')
204 207 user_model.revoke_perm(id, perm)
205 208
206 209 perm = Permission.get_by_key('hg.create.repository')
207 210 user_model.grant_perm(id, perm)
208 211 h.flash(_("Granted 'repository create' permission to user"),
209 212 category='success')
210 213 Session.commit()
211 214 else:
212 215 perm = Permission.get_by_key('hg.create.repository')
213 216 user_model.revoke_perm(id, perm)
214 217
215 218 perm = Permission.get_by_key('hg.create.none')
216 219 user_model.grant_perm(id, perm)
217 220 h.flash(_("Revoked 'repository create' permission to user"),
218 221 category='success')
219 222 Session.commit()
220 223 return redirect(url('edit_user', id=id))
221 224
222 225 def add_email(self, id):
223 226 """POST /user_emails:Add an existing item"""
224 227 # url('user_emails', id=ID, method='put')
225 228
226 229 #TODO: validation and form !!!
227 230 email = request.POST.get('new_email')
228 231 user_model = UserModel()
229 232
230 233 try:
231 234 user_model.add_extra_email(id, email)
232 235 Session.commit()
233 236 h.flash(_("Added email %s to user" % email), category='success')
237 except formencode.Invalid, error:
238 msg = error.error_dict['email']
239 h.flash(msg, category='error')
234 240 except Exception:
235 241 log.error(traceback.format_exc())
236 242 h.flash(_('An error occurred during email saving'),
237 243 category='error')
238 244 return redirect(url('edit_user', id=id))
239 245
240 246 def delete_email(self, id):
241 247 """DELETE /user_emails_delete/id: Delete an existing item"""
242 248 # url('user_emails_delete', id=ID, method='delete')
243 249 user_model = UserModel()
244 250 user_model.delete_extra_email(id, request.POST.get('del_email'))
245 251 Session.commit()
246 252 h.flash(_("Removed email from user"), category='success')
247 253 return redirect(url('edit_user', id=id))
@@ -1,822 +1,821
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 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import random
27 27 import logging
28 28 import traceback
29 29 import hashlib
30 30
31 31 from tempfile import _RandomNameSequence
32 32 from decorator import decorator
33 33
34 34 from pylons import config, url, request
35 35 from pylons.controllers.util import abort, redirect
36 36 from pylons.i18n.translation import _
37 37
38 38 from rhodecode import __platform__, PLATFORM_WIN, PLATFORM_OTHERS
39 39 from rhodecode.model.meta import Session
40 40
41 if __platform__ in PLATFORM_WIN:
42 from hashlib import sha256
43 if __platform__ in PLATFORM_OTHERS:
44 import bcrypt
45
46 41 from rhodecode.lib.utils2 import str2bool, safe_unicode
47 42 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
48 43 from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug
49 44 from rhodecode.lib.auth_ldap import AuthLdap
50 45
51 46 from rhodecode.model import meta
52 47 from rhodecode.model.user import UserModel
53 48 from rhodecode.model.db import Permission, RhodeCodeSetting, User
54 49
55 50 log = logging.getLogger(__name__)
56 51
57 52
58 53 class PasswordGenerator(object):
59 54 """
60 55 This is a simple class for generating password from different sets of
61 56 characters
62 57 usage::
63 58
64 59 passwd_gen = PasswordGenerator()
65 60 #print 8-letter password containing only big and small letters
66 61 of alphabet
67 62 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
68 63 """
69 64 ALPHABETS_NUM = r'''1234567890'''
70 65 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
71 66 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
72 67 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
73 68 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
74 69 + ALPHABETS_NUM + ALPHABETS_SPECIAL
75 70 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
76 71 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
77 72 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
78 73 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
79 74
80 75 def __init__(self, passwd=''):
81 76 self.passwd = passwd
82 77
83 78 def gen_password(self, length, type_=None):
84 79 if type_ is None:
85 80 type_ = self.ALPHABETS_FULL
86 81 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
87 82 return self.passwd
88 83
89 84
90 85 class RhodeCodeCrypto(object):
91 86
92 87 @classmethod
93 88 def hash_string(cls, str_):
94 89 """
95 90 Cryptographic function used for password hashing based on pybcrypt
96 91 or pycrypto in windows
97 92
98 93 :param password: password to hash
99 94 """
100 95 if __platform__ in PLATFORM_WIN:
96 from hashlib import sha256
101 97 return sha256(str_).hexdigest()
102 98 elif __platform__ in PLATFORM_OTHERS:
99 import bcrypt
103 100 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
104 101 else:
105 102 raise Exception('Unknown or unsupported platform %s' \
106 103 % __platform__)
107 104
108 105 @classmethod
109 106 def hash_check(cls, password, hashed):
110 107 """
111 108 Checks matching password with it's hashed value, runs different
112 109 implementation based on platform it runs on
113 110
114 111 :param password: password
115 112 :param hashed: password in hashed form
116 113 """
117 114
118 115 if __platform__ in PLATFORM_WIN:
116 from hashlib import sha256
119 117 return sha256(password).hexdigest() == hashed
120 118 elif __platform__ in PLATFORM_OTHERS:
119 import bcrypt
121 120 return bcrypt.hashpw(password, hashed) == hashed
122 121 else:
123 122 raise Exception('Unknown or unsupported platform %s' \
124 123 % __platform__)
125 124
126 125
127 126 def get_crypt_password(password):
128 127 return RhodeCodeCrypto.hash_string(password)
129 128
130 129
131 130 def check_password(password, hashed):
132 131 return RhodeCodeCrypto.hash_check(password, hashed)
133 132
134 133
135 134 def generate_api_key(str_, salt=None):
136 135 """
137 136 Generates API KEY from given string
138 137
139 138 :param str_:
140 139 :param salt:
141 140 """
142 141
143 142 if salt is None:
144 143 salt = _RandomNameSequence().next()
145 144
146 145 return hashlib.sha1(str_ + salt).hexdigest()
147 146
148 147
149 148 def authfunc(environ, username, password):
150 149 """
151 150 Dummy authentication wrapper function used in Mercurial and Git for
152 151 access control.
153 152
154 153 :param environ: needed only for using in Basic auth
155 154 """
156 155 return authenticate(username, password)
157 156
158 157
159 158 def authenticate(username, password):
160 159 """
161 160 Authentication function used for access control,
162 161 firstly checks for db authentication then if ldap is enabled for ldap
163 162 authentication, also creates ldap user if not in database
164 163
165 164 :param username: username
166 165 :param password: password
167 166 """
168 167
169 168 user_model = UserModel()
170 169 user = User.get_by_username(username)
171 170
172 171 log.debug('Authenticating user using RhodeCode account')
173 172 if user is not None and not user.ldap_dn:
174 173 if user.active:
175 174 if user.username == 'default' and user.active:
176 175 log.info('user %s authenticated correctly as anonymous user' %
177 176 username)
178 177 return True
179 178
180 179 elif user.username == username and check_password(password,
181 180 user.password):
182 181 log.info('user %s authenticated correctly' % username)
183 182 return True
184 183 else:
185 184 log.warning('user %s tried auth but is disabled' % username)
186 185
187 186 else:
188 187 log.debug('Regular authentication failed')
189 188 user_obj = User.get_by_username(username, case_insensitive=True)
190 189
191 190 if user_obj is not None and not user_obj.ldap_dn:
192 191 log.debug('this user already exists as non ldap')
193 192 return False
194 193
195 194 ldap_settings = RhodeCodeSetting.get_ldap_settings()
196 195 #======================================================================
197 196 # FALLBACK TO LDAP AUTH IF ENABLE
198 197 #======================================================================
199 198 if str2bool(ldap_settings.get('ldap_active')):
200 199 log.debug("Authenticating user using ldap")
201 200 kwargs = {
202 201 'server': ldap_settings.get('ldap_host', ''),
203 202 'base_dn': ldap_settings.get('ldap_base_dn', ''),
204 203 'port': ldap_settings.get('ldap_port'),
205 204 'bind_dn': ldap_settings.get('ldap_dn_user'),
206 205 'bind_pass': ldap_settings.get('ldap_dn_pass'),
207 206 'tls_kind': ldap_settings.get('ldap_tls_kind'),
208 207 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
209 208 'ldap_filter': ldap_settings.get('ldap_filter'),
210 209 'search_scope': ldap_settings.get('ldap_search_scope'),
211 210 'attr_login': ldap_settings.get('ldap_attr_login'),
212 211 'ldap_version': 3,
213 212 }
214 213 log.debug('Checking for ldap authentication')
215 214 try:
216 215 aldap = AuthLdap(**kwargs)
217 216 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
218 217 password)
219 218 log.debug('Got ldap DN response %s' % user_dn)
220 219
221 220 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
222 221 .get(k), [''])[0]
223 222
224 223 user_attrs = {
225 224 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
226 225 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
227 226 'email': get_ldap_attr('ldap_attr_email'),
228 227 }
229 228
230 229 # don't store LDAP password since we don't need it. Override
231 230 # with some random generated password
232 231 _password = PasswordGenerator().gen_password(length=8)
233 232 # create this user on the fly if it doesn't exist in rhodecode
234 233 # database
235 234 if user_model.create_ldap(username, _password, user_dn,
236 235 user_attrs):
237 236 log.info('created new ldap user %s' % username)
238 237
239 238 Session.commit()
240 239 return True
241 240 except (LdapUsernameError, LdapPasswordError,):
242 241 pass
243 242 except (Exception,):
244 243 log.error(traceback.format_exc())
245 244 pass
246 245 return False
247 246
248 247
249 248 def login_container_auth(username):
250 249 user = User.get_by_username(username)
251 250 if user is None:
252 251 user_attrs = {
253 252 'name': username,
254 253 'lastname': None,
255 254 'email': None,
256 255 }
257 256 user = UserModel().create_for_container_auth(username, user_attrs)
258 257 if not user:
259 258 return None
260 259 log.info('User %s was created by container authentication' % username)
261 260
262 261 if not user.active:
263 262 return None
264 263
265 264 user.update_lastlogin()
266 265 Session.commit()
267 266
268 267 log.debug('User %s is now logged in by container authentication',
269 268 user.username)
270 269 return user
271 270
272 271
273 272 def get_container_username(environ, config):
274 273 username = None
275 274
276 275 if str2bool(config.get('container_auth_enabled', False)):
277 276 from paste.httpheaders import REMOTE_USER
278 277 username = REMOTE_USER(environ)
279 278
280 279 if not username and str2bool(config.get('proxypass_auth_enabled', False)):
281 280 username = environ.get('HTTP_X_FORWARDED_USER')
282 281
283 282 if username:
284 283 # Removing realm and domain from username
285 284 username = username.partition('@')[0]
286 285 username = username.rpartition('\\')[2]
287 286 log.debug('Received username %s from container' % username)
288 287
289 288 return username
290 289
291 290
292 291 class CookieStoreWrapper(object):
293 292
294 293 def __init__(self, cookie_store):
295 294 self.cookie_store = cookie_store
296 295
297 296 def __repr__(self):
298 297 return 'CookieStore<%s>' % (self.cookie_store)
299 298
300 299 def get(self, key, other=None):
301 300 if isinstance(self.cookie_store, dict):
302 301 return self.cookie_store.get(key, other)
303 302 elif isinstance(self.cookie_store, AuthUser):
304 303 return self.cookie_store.__dict__.get(key, other)
305 304
306 305
307 306 class AuthUser(object):
308 307 """
309 308 A simple object that handles all attributes of user in RhodeCode
310 309
311 310 It does lookup based on API key,given user, or user present in session
312 311 Then it fills all required information for such user. It also checks if
313 312 anonymous access is enabled and if so, it returns default user as logged
314 313 in
315 314 """
316 315
317 316 def __init__(self, user_id=None, api_key=None, username=None):
318 317
319 318 self.user_id = user_id
320 319 self.api_key = None
321 320 self.username = username
322 321
323 322 self.name = ''
324 323 self.lastname = ''
325 324 self.email = ''
326 325 self.is_authenticated = False
327 326 self.admin = False
328 327 self.permissions = {}
329 328 self._api_key = api_key
330 329 self.propagate_data()
331 330 self._instance = None
332 331
333 332 def propagate_data(self):
334 333 user_model = UserModel()
335 334 self.anonymous_user = User.get_by_username('default', cache=True)
336 335 is_user_loaded = False
337 336
338 337 # try go get user by api key
339 338 if self._api_key and self._api_key != self.anonymous_user.api_key:
340 339 log.debug('Auth User lookup by API KEY %s' % self._api_key)
341 340 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
342 341 # lookup by userid
343 342 elif (self.user_id is not None and
344 343 self.user_id != self.anonymous_user.user_id):
345 344 log.debug('Auth User lookup by USER ID %s' % self.user_id)
346 345 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
347 346 # lookup by username
348 347 elif self.username and \
349 348 str2bool(config.get('container_auth_enabled', False)):
350 349
351 350 log.debug('Auth User lookup by USER NAME %s' % self.username)
352 351 dbuser = login_container_auth(self.username)
353 352 if dbuser is not None:
354 353 for k, v in dbuser.get_dict().items():
355 354 setattr(self, k, v)
356 355 self.set_authenticated()
357 356 is_user_loaded = True
358 357 else:
359 358 log.debug('No data in %s that could been used to log in' % self)
360 359
361 360 if not is_user_loaded:
362 361 # if we cannot authenticate user try anonymous
363 362 if self.anonymous_user.active is True:
364 363 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
365 364 # then we set this user is logged in
366 365 self.is_authenticated = True
367 366 else:
368 367 self.user_id = None
369 368 self.username = None
370 369 self.is_authenticated = False
371 370
372 371 if not self.username:
373 372 self.username = 'None'
374 373
375 374 log.debug('Auth User is now %s' % self)
376 375 user_model.fill_perms(self)
377 376
378 377 @property
379 378 def is_admin(self):
380 379 return self.admin
381 380
382 381 def __repr__(self):
383 382 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
384 383 self.is_authenticated)
385 384
386 385 def set_authenticated(self, authenticated=True):
387 386 if self.user_id != self.anonymous_user.user_id:
388 387 self.is_authenticated = authenticated
389 388
390 389 def get_cookie_store(self):
391 390 return {'username': self.username,
392 391 'user_id': self.user_id,
393 392 'is_authenticated': self.is_authenticated}
394 393
395 394 @classmethod
396 395 def from_cookie_store(cls, cookie_store):
397 396 """
398 397 Creates AuthUser from a cookie store
399 398
400 399 :param cls:
401 400 :param cookie_store:
402 401 """
403 402 user_id = cookie_store.get('user_id')
404 403 username = cookie_store.get('username')
405 404 api_key = cookie_store.get('api_key')
406 405 return AuthUser(user_id, api_key, username)
407 406
408 407
409 408 def set_available_permissions(config):
410 409 """
411 410 This function will propagate pylons globals with all available defined
412 411 permission given in db. We don't want to check each time from db for new
413 412 permissions since adding a new permission also requires application restart
414 413 ie. to decorate new views with the newly created permission
415 414
416 415 :param config: current pylons config instance
417 416
418 417 """
419 418 log.info('getting information about all available permissions')
420 419 try:
421 420 sa = meta.Session
422 421 all_perms = sa.query(Permission).all()
423 422 except Exception:
424 423 pass
425 424 finally:
426 425 meta.Session.remove()
427 426
428 427 config['available_permissions'] = [x.permission_name for x in all_perms]
429 428
430 429
431 430 #==============================================================================
432 431 # CHECK DECORATORS
433 432 #==============================================================================
434 433 class LoginRequired(object):
435 434 """
436 435 Must be logged in to execute this function else
437 436 redirect to login page
438 437
439 438 :param api_access: if enabled this checks only for valid auth token
440 439 and grants access based on valid token
441 440 """
442 441
443 442 def __init__(self, api_access=False):
444 443 self.api_access = api_access
445 444
446 445 def __call__(self, func):
447 446 return decorator(self.__wrapper, func)
448 447
449 448 def __wrapper(self, func, *fargs, **fkwargs):
450 449 cls = fargs[0]
451 450 user = cls.rhodecode_user
452 451
453 452 api_access_ok = False
454 453 if self.api_access:
455 454 log.debug('Checking API KEY access for %s' % cls)
456 455 if user.api_key == request.GET.get('api_key'):
457 456 api_access_ok = True
458 457 else:
459 458 log.debug("API KEY token not valid")
460 459 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
461 460 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
462 461 if user.is_authenticated or api_access_ok:
463 462 reason = 'RegularAuth' if user.is_authenticated else 'APIAuth'
464 463 log.info('user %s is authenticated and granted access to %s '
465 464 'using %s' % (user.username, loc, reason)
466 465 )
467 466 return func(*fargs, **fkwargs)
468 467 else:
469 468 log.warn('user %s NOT authenticated on func: %s' % (
470 469 user, loc)
471 470 )
472 471 p = url.current()
473 472
474 473 log.debug('redirecting to login page with %s' % p)
475 474 return redirect(url('login_home', came_from=p))
476 475
477 476
478 477 class NotAnonymous(object):
479 478 """
480 479 Must be logged in to execute this function else
481 480 redirect to login page"""
482 481
483 482 def __call__(self, func):
484 483 return decorator(self.__wrapper, func)
485 484
486 485 def __wrapper(self, func, *fargs, **fkwargs):
487 486 cls = fargs[0]
488 487 self.user = cls.rhodecode_user
489 488
490 489 log.debug('Checking if user is not anonymous @%s' % cls)
491 490
492 491 anonymous = self.user.username == 'default'
493 492
494 493 if anonymous:
495 494 p = url.current()
496 495
497 496 import rhodecode.lib.helpers as h
498 497 h.flash(_('You need to be a registered user to '
499 498 'perform this action'),
500 499 category='warning')
501 500 return redirect(url('login_home', came_from=p))
502 501 else:
503 502 return func(*fargs, **fkwargs)
504 503
505 504
506 505 class PermsDecorator(object):
507 506 """Base class for controller decorators"""
508 507
509 508 def __init__(self, *required_perms):
510 509 available_perms = config['available_permissions']
511 510 for perm in required_perms:
512 511 if perm not in available_perms:
513 512 raise Exception("'%s' permission is not defined" % perm)
514 513 self.required_perms = set(required_perms)
515 514 self.user_perms = None
516 515
517 516 def __call__(self, func):
518 517 return decorator(self.__wrapper, func)
519 518
520 519 def __wrapper(self, func, *fargs, **fkwargs):
521 520 cls = fargs[0]
522 521 self.user = cls.rhodecode_user
523 522 self.user_perms = self.user.permissions
524 523 log.debug('checking %s permissions %s for %s %s',
525 524 self.__class__.__name__, self.required_perms, cls, self.user)
526 525
527 526 if self.check_permissions():
528 527 log.debug('Permission granted for %s %s' % (cls, self.user))
529 528 return func(*fargs, **fkwargs)
530 529
531 530 else:
532 531 log.debug('Permission denied for %s %s' % (cls, self.user))
533 532 anonymous = self.user.username == 'default'
534 533
535 534 if anonymous:
536 535 p = url.current()
537 536
538 537 import rhodecode.lib.helpers as h
539 538 h.flash(_('You need to be a signed in to '
540 539 'view this page'),
541 540 category='warning')
542 541 return redirect(url('login_home', came_from=p))
543 542
544 543 else:
545 544 # redirect with forbidden ret code
546 545 return abort(403)
547 546
548 547 def check_permissions(self):
549 548 """Dummy function for overriding"""
550 549 raise Exception('You have to write this function in child class')
551 550
552 551
553 552 class HasPermissionAllDecorator(PermsDecorator):
554 553 """
555 554 Checks for access permission for all given predicates. All of them
556 555 have to be meet in order to fulfill the request
557 556 """
558 557
559 558 def check_permissions(self):
560 559 if self.required_perms.issubset(self.user_perms.get('global')):
561 560 return True
562 561 return False
563 562
564 563
565 564 class HasPermissionAnyDecorator(PermsDecorator):
566 565 """
567 566 Checks for access permission for any of given predicates. In order to
568 567 fulfill the request any of predicates must be meet
569 568 """
570 569
571 570 def check_permissions(self):
572 571 if self.required_perms.intersection(self.user_perms.get('global')):
573 572 return True
574 573 return False
575 574
576 575
577 576 class HasRepoPermissionAllDecorator(PermsDecorator):
578 577 """
579 578 Checks for access permission for all given predicates for specific
580 579 repository. All of them have to be meet in order to fulfill the request
581 580 """
582 581
583 582 def check_permissions(self):
584 583 repo_name = get_repo_slug(request)
585 584 try:
586 585 user_perms = set([self.user_perms['repositories'][repo_name]])
587 586 except KeyError:
588 587 return False
589 588 if self.required_perms.issubset(user_perms):
590 589 return True
591 590 return False
592 591
593 592
594 593 class HasRepoPermissionAnyDecorator(PermsDecorator):
595 594 """
596 595 Checks for access permission for any of given predicates for specific
597 596 repository. In order to fulfill the request any of predicates must be meet
598 597 """
599 598
600 599 def check_permissions(self):
601 600 repo_name = get_repo_slug(request)
602 601
603 602 try:
604 603 user_perms = set([self.user_perms['repositories'][repo_name]])
605 604 except KeyError:
606 605 return False
607 606
608 607 if self.required_perms.intersection(user_perms):
609 608 return True
610 609 return False
611 610
612 611
613 612 class HasReposGroupPermissionAllDecorator(PermsDecorator):
614 613 """
615 614 Checks for access permission for all given predicates for specific
616 615 repository. All of them have to be meet in order to fulfill the request
617 616 """
618 617
619 618 def check_permissions(self):
620 619 group_name = get_repos_group_slug(request)
621 620 try:
622 621 user_perms = set([self.user_perms['repositories_groups'][group_name]])
623 622 except KeyError:
624 623 return False
625 624 if self.required_perms.issubset(user_perms):
626 625 return True
627 626 return False
628 627
629 628
630 629 class HasReposGroupPermissionAnyDecorator(PermsDecorator):
631 630 """
632 631 Checks for access permission for any of given predicates for specific
633 632 repository. In order to fulfill the request any of predicates must be meet
634 633 """
635 634
636 635 def check_permissions(self):
637 636 group_name = get_repos_group_slug(request)
638 637
639 638 try:
640 639 user_perms = set([self.user_perms['repositories_groups'][group_name]])
641 640 except KeyError:
642 641 return False
643 642 if self.required_perms.intersection(user_perms):
644 643 return True
645 644 return False
646 645
647 646
648 647 #==============================================================================
649 648 # CHECK FUNCTIONS
650 649 #==============================================================================
651 650 class PermsFunction(object):
652 651 """Base function for other check functions"""
653 652
654 653 def __init__(self, *perms):
655 654 available_perms = config['available_permissions']
656 655
657 656 for perm in perms:
658 657 if perm not in available_perms:
659 658 raise Exception("'%s' permission is not defined" % perm)
660 659 self.required_perms = set(perms)
661 660 self.user_perms = None
662 661 self.repo_name = None
663 662 self.group_name = None
664 663
665 664 def __call__(self, check_Location=''):
666 665 user = request.user
667 666 cls_name = self.__class__.__name__
668 667 check_scope = {
669 668 'HasPermissionAll': '',
670 669 'HasPermissionAny': '',
671 670 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
672 671 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
673 672 'HasReposGroupPermissionAll': 'group:%s' % self.group_name,
674 673 'HasReposGroupPermissionAny': 'group:%s' % self.group_name,
675 674 }.get(cls_name, '?')
676 675 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
677 676 self.required_perms, user, check_scope,
678 677 check_Location or 'unspecified location')
679 678 if not user:
680 679 log.debug('Empty request user')
681 680 return False
682 681 self.user_perms = user.permissions
683 682 if self.check_permissions():
684 683 log.debug('Permission granted for user: %s @ %s', user,
685 684 check_Location or 'unspecified location')
686 685 return True
687 686
688 687 else:
689 688 log.debug('Permission denied for user: %s @ %s', user,
690 689 check_Location or 'unspecified location')
691 690 return False
692 691
693 692 def check_permissions(self):
694 693 """Dummy function for overriding"""
695 694 raise Exception('You have to write this function in child class')
696 695
697 696
698 697 class HasPermissionAll(PermsFunction):
699 698 def check_permissions(self):
700 699 if self.required_perms.issubset(self.user_perms.get('global')):
701 700 return True
702 701 return False
703 702
704 703
705 704 class HasPermissionAny(PermsFunction):
706 705 def check_permissions(self):
707 706 if self.required_perms.intersection(self.user_perms.get('global')):
708 707 return True
709 708 return False
710 709
711 710
712 711 class HasRepoPermissionAll(PermsFunction):
713 712 def __call__(self, repo_name=None, check_Location=''):
714 713 self.repo_name = repo_name
715 714 return super(HasRepoPermissionAll, self).__call__(check_Location)
716 715
717 716 def check_permissions(self):
718 717 if not self.repo_name:
719 718 self.repo_name = get_repo_slug(request)
720 719
721 720 try:
722 721 self._user_perms = set(
723 722 [self.user_perms['repositories'][self.repo_name]]
724 723 )
725 724 except KeyError:
726 725 return False
727 726 if self.required_perms.issubset(self._user_perms):
728 727 return True
729 728 return False
730 729
731 730
732 731 class HasRepoPermissionAny(PermsFunction):
733 732 def __call__(self, repo_name=None, check_Location=''):
734 733 self.repo_name = repo_name
735 734 return super(HasRepoPermissionAny, self).__call__(check_Location)
736 735
737 736 def check_permissions(self):
738 737 if not self.repo_name:
739 738 self.repo_name = get_repo_slug(request)
740 739
741 740 try:
742 741 self._user_perms = set(
743 742 [self.user_perms['repositories'][self.repo_name]]
744 743 )
745 744 except KeyError:
746 745 return False
747 746 if self.required_perms.intersection(self._user_perms):
748 747 return True
749 748 return False
750 749
751 750
752 751 class HasReposGroupPermissionAny(PermsFunction):
753 752 def __call__(self, group_name=None, check_Location=''):
754 753 self.group_name = group_name
755 754 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
756 755
757 756 def check_permissions(self):
758 757 try:
759 758 self._user_perms = set(
760 759 [self.user_perms['repositories_groups'][self.group_name]]
761 760 )
762 761 except KeyError:
763 762 return False
764 763 if self.required_perms.intersection(self._user_perms):
765 764 return True
766 765 return False
767 766
768 767
769 768 class HasReposGroupPermissionAll(PermsFunction):
770 769 def __call__(self, group_name=None, check_Location=''):
771 770 self.group_name = group_name
772 771 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
773 772
774 773 def check_permissions(self):
775 774 try:
776 775 self._user_perms = set(
777 776 [self.user_perms['repositories_groups'][self.group_name]]
778 777 )
779 778 except KeyError:
780 779 return False
781 780 if self.required_perms.issubset(self._user_perms):
782 781 return True
783 782 return False
784 783
785 784
786 785 #==============================================================================
787 786 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
788 787 #==============================================================================
789 788 class HasPermissionAnyMiddleware(object):
790 789 def __init__(self, *perms):
791 790 self.required_perms = set(perms)
792 791
793 792 def __call__(self, user, repo_name):
794 793 # repo_name MUST be unicode, since we handle keys in permission
795 794 # dict by unicode
796 795 repo_name = safe_unicode(repo_name)
797 796 usr = AuthUser(user.user_id)
798 797 try:
799 798 self.user_perms = set([usr.permissions['repositories'][repo_name]])
800 799 except Exception:
801 800 log.error('Exception while accessing permissions %s' %
802 801 traceback.format_exc())
803 802 self.user_perms = set()
804 803 self.username = user.username
805 804 self.repo_name = repo_name
806 805 return self.check_permissions()
807 806
808 807 def check_permissions(self):
809 808 log.debug('checking mercurial protocol '
810 809 'permissions %s for user:%s repository:%s', self.user_perms,
811 810 self.username, self.repo_name)
812 811 if self.required_perms.intersection(self.user_perms):
813 812 log.debug('permission granted for user:%s on repo:%s' % (
814 813 self.username, self.repo_name
815 814 )
816 815 )
817 816 return True
818 817 log.debug('permission denied for user:%s on repo:%s' % (
819 818 self.username, self.repo_name
820 819 )
821 820 )
822 821 return False
@@ -1,301 +1,308
1 1 """ this is forms validation classes
2 2 http://formencode.org/module-formencode.validators.html
3 3 for list off all availible validators
4 4
5 5 we can create our own validators
6 6
7 7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 8 pre_validators [] These validators will be applied before the schema
9 9 chained_validators [] These validators will be applied after the schema
10 10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
13 13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
14 14
15 15
16 16 <name> = formencode.validators.<name of validator>
17 17 <name> must equal form name
18 18 list=[1,2,3,4,5]
19 19 for SELECT use formencode.All(OneOf(list), Int())
20 20
21 21 """
22 22 import logging
23 23
24 24 import formencode
25 25 from formencode import All
26 26
27 27 from pylons.i18n.translation import _
28 28
29 29 from rhodecode.model import validators as v
30 30 from rhodecode import BACKENDS
31 31
32 32 log = logging.getLogger(__name__)
33 33
34 34
35 35 class LoginForm(formencode.Schema):
36 36 allow_extra_fields = True
37 37 filter_extra_fields = True
38 38 username = v.UnicodeString(
39 39 strip=True,
40 40 min=1,
41 41 not_empty=True,
42 42 messages={
43 43 'empty': _(u'Please enter a login'),
44 44 'tooShort': _(u'Enter a value %(min)i characters long or more')}
45 45 )
46 46
47 47 password = v.UnicodeString(
48 48 strip=False,
49 49 min=3,
50 50 not_empty=True,
51 51 messages={
52 52 'empty': _(u'Please enter a password'),
53 53 'tooShort': _(u'Enter %(min)i characters or more')}
54 54 )
55 55
56 56 remember = v.StringBoolean(if_missing=False)
57 57
58 58 chained_validators = [v.ValidAuth()]
59 59
60 60
61 61 def UserForm(edit=False, old_data={}):
62 62 class _UserForm(formencode.Schema):
63 63 allow_extra_fields = True
64 64 filter_extra_fields = True
65 65 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
66 66 v.ValidUsername(edit, old_data))
67 67 if edit:
68 68 new_password = All(
69 69 v.UnicodeString(strip=False, min=6, not_empty=False)
70 70 )
71 71 password_confirmation = All(
72 72 v.ValidPassword(),
73 73 v.UnicodeString(strip=False, min=6, not_empty=False),
74 74 )
75 75 admin = v.StringBoolean(if_missing=False)
76 76 else:
77 77 password = All(
78 78 v.ValidPassword(),
79 79 v.UnicodeString(strip=False, min=6, not_empty=True)
80 80 )
81 81 password_confirmation = All(
82 82 v.ValidPassword(),
83 83 v.UnicodeString(strip=False, min=6, not_empty=False)
84 84 )
85 85
86 86 active = v.StringBoolean(if_missing=False)
87 87 name = v.UnicodeString(strip=True, min=1, not_empty=False)
88 88 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
89 89 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
90 90
91 91 chained_validators = [v.ValidPasswordsMatch()]
92 92
93 93 return _UserForm
94 94
95 95
96 96 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
97 97 class _UsersGroupForm(formencode.Schema):
98 98 allow_extra_fields = True
99 99 filter_extra_fields = True
100 100
101 101 users_group_name = All(
102 102 v.UnicodeString(strip=True, min=1, not_empty=True),
103 103 v.ValidUsersGroup(edit, old_data)
104 104 )
105 105
106 106 users_group_active = v.StringBoolean(if_missing=False)
107 107
108 108 if edit:
109 109 users_group_members = v.OneOf(
110 110 available_members, hideList=False, testValueList=True,
111 111 if_missing=None, not_empty=False
112 112 )
113 113
114 114 return _UsersGroupForm
115 115
116 116
117 117 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
118 118 class _ReposGroupForm(formencode.Schema):
119 119 allow_extra_fields = True
120 120 filter_extra_fields = False
121 121
122 122 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
123 123 v.SlugifyName())
124 124 group_description = v.UnicodeString(strip=True, min=1,
125 125 not_empty=True)
126 126 group_parent_id = v.OneOf(available_groups, hideList=False,
127 127 testValueList=True,
128 128 if_missing=None, not_empty=False)
129 129
130 130 chained_validators = [v.ValidReposGroup(edit, old_data),
131 131 v.ValidPerms('group')]
132 132
133 133 return _ReposGroupForm
134 134
135 135
136 136 def RegisterForm(edit=False, old_data={}):
137 137 class _RegisterForm(formencode.Schema):
138 138 allow_extra_fields = True
139 139 filter_extra_fields = True
140 140 username = All(
141 141 v.ValidUsername(edit, old_data),
142 142 v.UnicodeString(strip=True, min=1, not_empty=True)
143 143 )
144 144 password = All(
145 145 v.ValidPassword(),
146 146 v.UnicodeString(strip=False, min=6, not_empty=True)
147 147 )
148 148 password_confirmation = All(
149 149 v.ValidPassword(),
150 150 v.UnicodeString(strip=False, min=6, not_empty=True)
151 151 )
152 152 active = v.StringBoolean(if_missing=False)
153 153 name = v.UnicodeString(strip=True, min=1, not_empty=False)
154 154 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
155 155 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
156 156
157 157 chained_validators = [v.ValidPasswordsMatch()]
158 158
159 159 return _RegisterForm
160 160
161 161
162 162 def PasswordResetForm():
163 163 class _PasswordResetForm(formencode.Schema):
164 164 allow_extra_fields = True
165 165 filter_extra_fields = True
166 166 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
167 167 return _PasswordResetForm
168 168
169 169
170 170 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
171 171 repo_groups=[], landing_revs=[]):
172 172 class _RepoForm(formencode.Schema):
173 173 allow_extra_fields = True
174 174 filter_extra_fields = False
175 175 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
176 176 v.SlugifyName())
177 177 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
178 178 repo_group = v.OneOf(repo_groups, hideList=True)
179 179 repo_type = v.OneOf(supported_backends)
180 180 description = v.UnicodeString(strip=True, min=1, not_empty=False)
181 181 private = v.StringBoolean(if_missing=False)
182 182 enable_statistics = v.StringBoolean(if_missing=False)
183 183 enable_downloads = v.StringBoolean(if_missing=False)
184 184 landing_rev = v.OneOf(landing_revs, hideList=True)
185 185
186 186 if edit:
187 187 #this is repo owner
188 188 user = All(v.UnicodeString(not_empty=True), v.ValidRepoUser())
189 189
190 190 chained_validators = [v.ValidCloneUri(),
191 191 v.ValidRepoName(edit, old_data),
192 192 v.ValidPerms()]
193 193 return _RepoForm
194 194
195 195
196 196 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
197 197 repo_groups=[]):
198 198 class _RepoForkForm(formencode.Schema):
199 199 allow_extra_fields = True
200 200 filter_extra_fields = False
201 201 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
202 202 v.SlugifyName())
203 203 repo_group = v.OneOf(repo_groups, hideList=True)
204 204 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
205 205 description = v.UnicodeString(strip=True, min=1, not_empty=True)
206 206 private = v.StringBoolean(if_missing=False)
207 207 copy_permissions = v.StringBoolean(if_missing=False)
208 208 update_after_clone = v.StringBoolean(if_missing=False)
209 209 fork_parent_id = v.UnicodeString()
210 210 chained_validators = [v.ValidForkName(edit, old_data)]
211 211
212 212 return _RepoForkForm
213 213
214 214
215 215 def RepoSettingsForm(edit=False, old_data={},
216 216 supported_backends=BACKENDS.keys(), repo_groups=[],
217 217 landing_revs=[]):
218 218 class _RepoForm(formencode.Schema):
219 219 allow_extra_fields = True
220 220 filter_extra_fields = False
221 221 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
222 222 v.SlugifyName())
223 223 description = v.UnicodeString(strip=True, min=1, not_empty=True)
224 224 repo_group = v.OneOf(repo_groups, hideList=True)
225 225 private = v.StringBoolean(if_missing=False)
226 226 landing_rev = v.OneOf(landing_revs, hideList=True)
227 227 chained_validators = [v.ValidRepoName(edit, old_data), v.ValidPerms(),
228 228 v.ValidSettings()]
229 229 return _RepoForm
230 230
231 231
232 232 def ApplicationSettingsForm():
233 233 class _ApplicationSettingsForm(formencode.Schema):
234 234 allow_extra_fields = True
235 235 filter_extra_fields = False
236 236 rhodecode_title = v.UnicodeString(strip=True, min=1, not_empty=True)
237 237 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
238 238 rhodecode_ga_code = v.UnicodeString(strip=True, min=1, not_empty=False)
239 239
240 240 return _ApplicationSettingsForm
241 241
242 242
243 243 def ApplicationUiSettingsForm():
244 244 class _ApplicationUiSettingsForm(formencode.Schema):
245 245 allow_extra_fields = True
246 246 filter_extra_fields = False
247 247 web_push_ssl = v.OneOf(['true', 'false'], if_missing='false')
248 248 paths_root_path = All(
249 249 v.ValidPath(),
250 250 v.UnicodeString(strip=True, min=1, not_empty=True)
251 251 )
252 252 hooks_changegroup_update = v.OneOf(['True', 'False'],
253 253 if_missing=False)
254 254 hooks_changegroup_repo_size = v.OneOf(['True', 'False'],
255 255 if_missing=False)
256 256 hooks_changegroup_push_logger = v.OneOf(['True', 'False'],
257 257 if_missing=False)
258 258 hooks_preoutgoing_pull_logger = v.OneOf(['True', 'False'],
259 259 if_missing=False)
260 260
261 261 return _ApplicationUiSettingsForm
262 262
263 263
264 264 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
265 265 class _DefaultPermissionsForm(formencode.Schema):
266 266 allow_extra_fields = True
267 267 filter_extra_fields = True
268 268 overwrite_default = v.StringBoolean(if_missing=False)
269 269 anonymous = v.OneOf(['True', 'False'], if_missing=False)
270 270 default_perm = v.OneOf(perms_choices)
271 271 default_register = v.OneOf(register_choices)
272 272 default_create = v.OneOf(create_choices)
273 273
274 274 return _DefaultPermissionsForm
275 275
276 276
277 277 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices,
278 278 tls_kind_choices):
279 279 class _LdapSettingsForm(formencode.Schema):
280 280 allow_extra_fields = True
281 281 filter_extra_fields = True
282 282 #pre_validators = [LdapLibValidator]
283 283 ldap_active = v.StringBoolean(if_missing=False)
284 284 ldap_host = v.UnicodeString(strip=True,)
285 285 ldap_port = v.Number(strip=True,)
286 286 ldap_tls_kind = v.OneOf(tls_kind_choices)
287 287 ldap_tls_reqcert = v.OneOf(tls_reqcert_choices)
288 288 ldap_dn_user = v.UnicodeString(strip=True,)
289 289 ldap_dn_pass = v.UnicodeString(strip=True,)
290 290 ldap_base_dn = v.UnicodeString(strip=True,)
291 291 ldap_filter = v.UnicodeString(strip=True,)
292 292 ldap_search_scope = v.OneOf(search_scope_choices)
293 293 ldap_attr_login = All(
294 294 v.AttrLoginValidator(),
295 295 v.UnicodeString(strip=True,)
296 296 )
297 297 ldap_attr_firstname = v.UnicodeString(strip=True,)
298 298 ldap_attr_lastname = v.UnicodeString(strip=True,)
299 299 ldap_attr_email = v.UnicodeString(strip=True,)
300 300
301 301 return _LdapSettingsForm
302
303
304 def UserExtraEmailForm():
305 class _UserExtraEmailForm(formencode.Schema):
306 email = All(v.UniqSystemEmail(), v.Email)
307
308 return _UserExtraEmailForm No newline at end of file
@@ -1,613 +1,616
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.user
4 4 ~~~~~~~~~~~~~~~~~~~~
5 5
6 6 users model for RhodeCode
7 7
8 8 :created_on: Apr 9, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28
29 29 from pylons import url
30 30 from pylons.i18n.translation import _
31 31
32 from sqlalchemy.exc import DatabaseError
33 from sqlalchemy.orm import joinedload
34
32 35 from rhodecode.lib.utils2 import safe_unicode, generate_api_key
33 36 from rhodecode.lib.caching_query import FromCache
34
35 37 from rhodecode.model import BaseModel
36 38 from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
37 39 UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \
38 40 Notification, RepoGroup, UserRepoGroupToPerm, UsersGroupRepoGroupToPerm, \
39 41 UserEmailMap
40 42 from rhodecode.lib.exceptions import DefaultUserException, \
41 43 UserOwnsReposException
42 44
43 from sqlalchemy.exc import DatabaseError
44
45 from sqlalchemy.orm import joinedload
46 45
47 46 log = logging.getLogger(__name__)
48 47
49 48
50 49 PERM_WEIGHTS = {
51 50 'repository.none': 0,
52 51 'repository.read': 1,
53 52 'repository.write': 3,
54 53 'repository.admin': 4,
55 54 'group.none': 0,
56 55 'group.read': 1,
57 56 'group.write': 3,
58 57 'group.admin': 4,
59 58 }
60 59
61 60
62 61 class UserModel(BaseModel):
63 62
64 63 def get(self, user_id, cache=False):
65 64 user = self.sa.query(User)
66 65 if cache:
67 66 user = user.options(FromCache("sql_cache_short",
68 67 "get_user_%s" % user_id))
69 68 return user.get(user_id)
70 69
71 70 def get_user(self, user):
72 71 return self._get_user(user)
73 72
74 73 def get_by_username(self, username, cache=False, case_insensitive=False):
75 74
76 75 if case_insensitive:
77 76 user = self.sa.query(User).filter(User.username.ilike(username))
78 77 else:
79 78 user = self.sa.query(User)\
80 79 .filter(User.username == username)
81 80 if cache:
82 81 user = user.options(FromCache("sql_cache_short",
83 82 "get_user_%s" % username))
84 83 return user.scalar()
85 84
86 85 def get_by_api_key(self, api_key, cache=False):
87 86 return User.get_by_api_key(api_key, cache)
88 87
89 88 def create(self, form_data):
90 89 from rhodecode.lib.auth import get_crypt_password
91 90 try:
92 91 new_user = User()
93 92 for k, v in form_data.items():
94 93 if k == 'password':
95 94 v = get_crypt_password(v)
96 95 setattr(new_user, k, v)
97 96
98 97 new_user.api_key = generate_api_key(form_data['username'])
99 98 self.sa.add(new_user)
100 99 return new_user
101 100 except:
102 101 log.error(traceback.format_exc())
103 102 raise
104 103
105 104 def create_or_update(self, username, password, email, name, lastname,
106 105 active=True, admin=False, ldap_dn=None):
107 106 """
108 107 Creates a new instance if not found, or updates current one
109 108
110 109 :param username:
111 110 :param password:
112 111 :param email:
113 112 :param active:
114 113 :param name:
115 114 :param lastname:
116 115 :param active:
117 116 :param admin:
118 117 :param ldap_dn:
119 118 """
120 119
121 120 from rhodecode.lib.auth import get_crypt_password
122 121
123 122 log.debug('Checking for %s account in RhodeCode database' % username)
124 123 user = User.get_by_username(username, case_insensitive=True)
125 124 if user is None:
126 125 log.debug('creating new user %s' % username)
127 126 new_user = User()
128 127 else:
129 128 log.debug('updating user %s' % username)
130 129 new_user = user
131 130
132 131 try:
133 132 new_user.username = username
134 133 new_user.admin = admin
135 134 new_user.password = get_crypt_password(password)
136 135 new_user.api_key = generate_api_key(username)
137 136 new_user.email = email
138 137 new_user.active = active
139 138 new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None
140 139 new_user.name = name
141 140 new_user.lastname = lastname
142 141 self.sa.add(new_user)
143 142 return new_user
144 143 except (DatabaseError,):
145 144 log.error(traceback.format_exc())
146 145 raise
147 146
148 147 def create_for_container_auth(self, username, attrs):
149 148 """
150 149 Creates the given user if it's not already in the database
151 150
152 151 :param username:
153 152 :param attrs:
154 153 """
155 154 if self.get_by_username(username, case_insensitive=True) is None:
156 155
157 156 # autogenerate email for container account without one
158 157 generate_email = lambda usr: '%s@container_auth.account' % usr
159 158
160 159 try:
161 160 new_user = User()
162 161 new_user.username = username
163 162 new_user.password = None
164 163 new_user.api_key = generate_api_key(username)
165 164 new_user.email = attrs['email']
166 165 new_user.active = attrs.get('active', True)
167 166 new_user.name = attrs['name'] or generate_email(username)
168 167 new_user.lastname = attrs['lastname']
169 168
170 169 self.sa.add(new_user)
171 170 return new_user
172 171 except (DatabaseError,):
173 172 log.error(traceback.format_exc())
174 173 self.sa.rollback()
175 174 raise
176 175 log.debug('User %s already exists. Skipping creation of account'
177 176 ' for container auth.', username)
178 177 return None
179 178
180 179 def create_ldap(self, username, password, user_dn, attrs):
181 180 """
182 181 Checks if user is in database, if not creates this user marked
183 182 as ldap user
184 183
185 184 :param username:
186 185 :param password:
187 186 :param user_dn:
188 187 :param attrs:
189 188 """
190 189 from rhodecode.lib.auth import get_crypt_password
191 190 log.debug('Checking for such ldap account in RhodeCode database')
192 191 if self.get_by_username(username, case_insensitive=True) is None:
193 192
194 193 # autogenerate email for ldap account without one
195 194 generate_email = lambda usr: '%s@ldap.account' % usr
196 195
197 196 try:
198 197 new_user = User()
199 198 username = username.lower()
200 199 # add ldap account always lowercase
201 200 new_user.username = username
202 201 new_user.password = get_crypt_password(password)
203 202 new_user.api_key = generate_api_key(username)
204 203 new_user.email = attrs['email'] or generate_email(username)
205 204 new_user.active = attrs.get('active', True)
206 205 new_user.ldap_dn = safe_unicode(user_dn)
207 206 new_user.name = attrs['name']
208 207 new_user.lastname = attrs['lastname']
209 208
210 209 self.sa.add(new_user)
211 210 return new_user
212 211 except (DatabaseError,):
213 212 log.error(traceback.format_exc())
214 213 self.sa.rollback()
215 214 raise
216 215 log.debug('this %s user exists skipping creation of ldap account',
217 216 username)
218 217 return None
219 218
220 219 def create_registration(self, form_data):
221 220 from rhodecode.model.notification import NotificationModel
222 221
223 222 try:
224 223 form_data['admin'] = False
225 224 new_user = self.create(form_data)
226 225
227 226 self.sa.add(new_user)
228 227 self.sa.flush()
229 228
230 229 # notification to admins
231 230 subject = _('new user registration')
232 231 body = ('New user registration\n'
233 232 '---------------------\n'
234 233 '- Username: %s\n'
235 234 '- Full Name: %s\n'
236 235 '- Email: %s\n')
237 236 body = body % (new_user.username, new_user.full_name,
238 237 new_user.email)
239 238 edit_url = url('edit_user', id=new_user.user_id, qualified=True)
240 239 kw = {'registered_user_url': edit_url}
241 240 NotificationModel().create(created_by=new_user, subject=subject,
242 241 body=body, recipients=None,
243 242 type_=Notification.TYPE_REGISTRATION,
244 243 email_kwargs=kw)
245 244
246 245 except:
247 246 log.error(traceback.format_exc())
248 247 raise
249 248
250 249 def update(self, user_id, form_data):
251 250 try:
252 251 user = self.get(user_id, cache=False)
253 252 if user.username == 'default':
254 253 raise DefaultUserException(
255 254 _("You can't Edit this user since it's"
256 255 " crucial for entire application"))
257 256
258 257 for k, v in form_data.items():
259 258 if k == 'new_password' and v != '':
260 259 user.password = v
261 260 user.api_key = generate_api_key(user.username)
262 261 else:
263 262 setattr(user, k, v)
264 263
265 264 self.sa.add(user)
266 265 except:
267 266 log.error(traceback.format_exc())
268 267 raise
269 268
270 269 def update_my_account(self, user_id, form_data):
271 270 from rhodecode.lib.auth import get_crypt_password
272 271 try:
273 272 user = self.get(user_id, cache=False)
274 273 if user.username == 'default':
275 274 raise DefaultUserException(
276 275 _("You can't Edit this user since it's"
277 276 " crucial for entire application")
278 277 )
279 278 for k, v in form_data.items():
280 279 if k == 'new_password' and v != '':
281 280 user.password = get_crypt_password(v)
282 281 user.api_key = generate_api_key(user.username)
283 282 else:
284 283 if k not in ['admin', 'active']:
285 284 setattr(user, k, v)
286 285
287 286 self.sa.add(user)
288 287 except:
289 288 log.error(traceback.format_exc())
290 289 raise
291 290
292 291 def delete(self, user):
293 292 user = self._get_user(user)
294 293
295 294 try:
296 295 if user.username == 'default':
297 296 raise DefaultUserException(
298 297 _(u"You can't remove this user since it's"
299 298 " crucial for entire application")
300 299 )
301 300 if user.repositories:
302 301 repos = [x.repo_name for x in user.repositories]
303 302 raise UserOwnsReposException(
304 303 _(u'user "%s" still owns %s repositories and cannot be '
305 304 'removed. Switch owners or remove those repositories. %s')
306 305 % (user.username, len(repos), ', '.join(repos))
307 306 )
308 307 self.sa.delete(user)
309 308 except:
310 309 log.error(traceback.format_exc())
311 310 raise
312 311
313 312 def reset_password_link(self, data):
314 313 from rhodecode.lib.celerylib import tasks, run_task
315 314 run_task(tasks.send_password_link, data['email'])
316 315
317 316 def reset_password(self, data):
318 317 from rhodecode.lib.celerylib import tasks, run_task
319 318 run_task(tasks.reset_user_password, data['email'])
320 319
321 320 def fill_data(self, auth_user, user_id=None, api_key=None):
322 321 """
323 322 Fetches auth_user by user_id,or api_key if present.
324 323 Fills auth_user attributes with those taken from database.
325 324 Additionally set's is_authenitated if lookup fails
326 325 present in database
327 326
328 327 :param auth_user: instance of user to set attributes
329 328 :param user_id: user id to fetch by
330 329 :param api_key: api key to fetch by
331 330 """
332 331 if user_id is None and api_key is None:
333 332 raise Exception('You need to pass user_id or api_key')
334 333
335 334 try:
336 335 if api_key:
337 336 dbuser = self.get_by_api_key(api_key)
338 337 else:
339 338 dbuser = self.get(user_id)
340 339
341 340 if dbuser is not None and dbuser.active:
342 341 log.debug('filling %s data' % dbuser)
343 342 for k, v in dbuser.get_dict().items():
344 343 setattr(auth_user, k, v)
345 344 else:
346 345 return False
347 346
348 347 except:
349 348 log.error(traceback.format_exc())
350 349 auth_user.is_authenticated = False
351 350 return False
352 351
353 352 return True
354 353
355 354 def fill_perms(self, user):
356 355 """
357 356 Fills user permission attribute with permissions taken from database
358 357 works for permissions given for repositories, and for permissions that
359 358 are granted to groups
360 359
361 360 :param user: user instance to fill his perms
362 361 """
363 362 RK = 'repositories'
364 363 GK = 'repositories_groups'
365 364 GLOBAL = 'global'
366 365 user.permissions[RK] = {}
367 366 user.permissions[GK] = {}
368 367 user.permissions[GLOBAL] = set()
369 368
370 369 #======================================================================
371 370 # fetch default permissions
372 371 #======================================================================
373 372 default_user = User.get_by_username('default', cache=True)
374 373 default_user_id = default_user.user_id
375 374
376 375 default_repo_perms = Permission.get_default_perms(default_user_id)
377 376 default_repo_groups_perms = Permission.get_default_group_perms(default_user_id)
378 377
379 378 if user.is_admin:
380 379 #==================================================================
381 380 # admin user have all default rights for repositories
382 381 # and groups set to admin
383 382 #==================================================================
384 383 user.permissions[GLOBAL].add('hg.admin')
385 384
386 385 # repositories
387 386 for perm in default_repo_perms:
388 387 r_k = perm.UserRepoToPerm.repository.repo_name
389 388 p = 'repository.admin'
390 389 user.permissions[RK][r_k] = p
391 390
392 391 # repositories groups
393 392 for perm in default_repo_groups_perms:
394 393 rg_k = perm.UserRepoGroupToPerm.group.group_name
395 394 p = 'group.admin'
396 395 user.permissions[GK][rg_k] = p
397 396 return user
398 397
399 398 #==================================================================
400 399 # set default permissions first for repositories and groups
401 400 #==================================================================
402 401 uid = user.user_id
403 402
404 403 # default global permissions
405 404 default_global_perms = self.sa.query(UserToPerm)\
406 405 .filter(UserToPerm.user_id == default_user_id)
407 406
408 407 for perm in default_global_perms:
409 408 user.permissions[GLOBAL].add(perm.permission.permission_name)
410 409
411 410 # defaults for repositories, taken from default user
412 411 for perm in default_repo_perms:
413 412 r_k = perm.UserRepoToPerm.repository.repo_name
414 413 if perm.Repository.private and not (perm.Repository.user_id == uid):
415 414 # disable defaults for private repos,
416 415 p = 'repository.none'
417 416 elif perm.Repository.user_id == uid:
418 417 # set admin if owner
419 418 p = 'repository.admin'
420 419 else:
421 420 p = perm.Permission.permission_name
422 421
423 422 user.permissions[RK][r_k] = p
424 423
425 424 # defaults for repositories groups taken from default user permission
426 425 # on given group
427 426 for perm in default_repo_groups_perms:
428 427 rg_k = perm.UserRepoGroupToPerm.group.group_name
429 428 p = perm.Permission.permission_name
430 429 user.permissions[GK][rg_k] = p
431 430
432 431 #==================================================================
433 432 # overwrite defaults with user permissions if any found
434 433 #==================================================================
435 434
436 435 # user global permissions
437 436 user_perms = self.sa.query(UserToPerm)\
438 437 .options(joinedload(UserToPerm.permission))\
439 438 .filter(UserToPerm.user_id == uid).all()
440 439
441 440 for perm in user_perms:
442 441 user.permissions[GLOBAL].add(perm.permission.permission_name)
443 442
444 443 # user explicit permissions for repositories
445 444 user_repo_perms = \
446 445 self.sa.query(UserRepoToPerm, Permission, Repository)\
447 446 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
448 447 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
449 448 .filter(UserRepoToPerm.user_id == uid)\
450 449 .all()
451 450
452 451 for perm in user_repo_perms:
453 452 # set admin if owner
454 453 r_k = perm.UserRepoToPerm.repository.repo_name
455 454 if perm.Repository.user_id == uid:
456 455 p = 'repository.admin'
457 456 else:
458 457 p = perm.Permission.permission_name
459 458 user.permissions[RK][r_k] = p
460 459
461 460 # USER GROUP
462 461 #==================================================================
463 462 # check if user is part of user groups for this repository and
464 463 # fill in (or replace with higher) permissions
465 464 #==================================================================
466 465
467 466 # users group global
468 467 user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
469 468 .options(joinedload(UsersGroupToPerm.permission))\
470 469 .join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
471 470 UsersGroupMember.users_group_id))\
472 471 .filter(UsersGroupMember.user_id == uid).all()
473 472
474 473 for perm in user_perms_from_users_groups:
475 474 user.permissions[GLOBAL].add(perm.permission.permission_name)
476 475
477 476 # users group for repositories permissions
478 477 user_repo_perms_from_users_groups = \
479 478 self.sa.query(UsersGroupRepoToPerm, Permission, Repository,)\
480 479 .join((Repository, UsersGroupRepoToPerm.repository_id == Repository.repo_id))\
481 480 .join((Permission, UsersGroupRepoToPerm.permission_id == Permission.permission_id))\
482 481 .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id == UsersGroupMember.users_group_id))\
483 482 .filter(UsersGroupMember.user_id == uid)\
484 483 .all()
485 484
486 485 for perm in user_repo_perms_from_users_groups:
487 486 r_k = perm.UsersGroupRepoToPerm.repository.repo_name
488 487 p = perm.Permission.permission_name
489 488 cur_perm = user.permissions[RK][r_k]
490 489 # overwrite permission only if it's greater than permission
491 490 # given from other sources
492 491 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
493 492 user.permissions[RK][r_k] = p
494 493
495 494 # REPO GROUP
496 495 #==================================================================
497 496 # get access for this user for repos group and override defaults
498 497 #==================================================================
499 498
500 499 # user explicit permissions for repository
501 500 user_repo_groups_perms = \
502 501 self.sa.query(UserRepoGroupToPerm, Permission, RepoGroup)\
503 502 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
504 503 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
505 504 .filter(UserRepoGroupToPerm.user_id == uid)\
506 505 .all()
507 506
508 507 for perm in user_repo_groups_perms:
509 508 rg_k = perm.UserRepoGroupToPerm.group.group_name
510 509 p = perm.Permission.permission_name
511 510 cur_perm = user.permissions[GK][rg_k]
512 511 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
513 512 user.permissions[GK][rg_k] = p
514 513
515 514 # REPO GROUP + USER GROUP
516 515 #==================================================================
517 516 # check if user is part of user groups for this repo group and
518 517 # fill in (or replace with higher) permissions
519 518 #==================================================================
520 519
521 520 # users group for repositories permissions
522 521 user_repo_group_perms_from_users_groups = \
523 522 self.sa.query(UsersGroupRepoGroupToPerm, Permission, RepoGroup)\
524 523 .join((RepoGroup, UsersGroupRepoGroupToPerm.group_id == RepoGroup.group_id))\
525 524 .join((Permission, UsersGroupRepoGroupToPerm.permission_id == Permission.permission_id))\
526 525 .join((UsersGroupMember, UsersGroupRepoGroupToPerm.users_group_id == UsersGroupMember.users_group_id))\
527 526 .filter(UsersGroupMember.user_id == uid)\
528 527 .all()
529 528
530 529 for perm in user_repo_group_perms_from_users_groups:
531 530 g_k = perm.UsersGroupRepoGroupToPerm.group.group_name
532 531 p = perm.Permission.permission_name
533 532 cur_perm = user.permissions[GK][g_k]
534 533 # overwrite permission only if it's greater than permission
535 534 # given from other sources
536 535 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
537 536 user.permissions[GK][g_k] = p
538 537
539 538 return user
540 539
541 540 def has_perm(self, user, perm):
542 541 if not isinstance(perm, Permission):
543 542 raise Exception('perm needs to be an instance of Permission class '
544 543 'got %s instead' % type(perm))
545 544
546 545 user = self._get_user(user)
547 546
548 547 return UserToPerm.query().filter(UserToPerm.user == user)\
549 548 .filter(UserToPerm.permission == perm).scalar() is not None
550 549
551 550 def grant_perm(self, user, perm):
552 551 """
553 552 Grant user global permissions
554 553
555 554 :param user:
556 555 :param perm:
557 556 """
558 557 user = self._get_user(user)
559 558 perm = self._get_perm(perm)
560 559 # if this permission is already granted skip it
561 560 _perm = UserToPerm.query()\
562 561 .filter(UserToPerm.user == user)\
563 562 .filter(UserToPerm.permission == perm)\
564 563 .scalar()
565 564 if _perm:
566 565 return
567 566 new = UserToPerm()
568 567 new.user = user
569 568 new.permission = perm
570 569 self.sa.add(new)
571 570
572 571 def revoke_perm(self, user, perm):
573 572 """
574 573 Revoke users global permissions
575 574
576 575 :param user:
577 576 :param perm:
578 577 """
579 578 user = self._get_user(user)
580 579 perm = self._get_perm(perm)
581 580
582 581 obj = UserToPerm.query()\
583 582 .filter(UserToPerm.user == user)\
584 583 .filter(UserToPerm.permission == perm)\
585 584 .scalar()
586 585 if obj:
587 586 self.sa.delete(obj)
588 587
589 588 def add_extra_email(self, user, email):
590 589 """
591 590 Adds email address to UserEmailMap
592 591
593 592 :param user:
594 593 :param email:
595 594 """
595 from rhodecode.model import forms
596 form = forms.UserExtraEmailForm()()
597 data = form.to_python(dict(email=email))
596 598 user = self._get_user(user)
599
597 600 obj = UserEmailMap()
598 601 obj.user = user
599 obj.email = email
602 obj.email = data['email']
600 603 self.sa.add(obj)
601 604 return obj
602 605
603 606 def delete_extra_email(self, user, email_id):
604 607 """
605 608 Removes email address from UserEmailMap
606 609
607 610 :param user:
608 611 :param email_id:
609 612 """
610 613 user = self._get_user(user)
611 614 obj = UserEmailMap.query().get(email_id)
612 615 if obj:
613 616 self.sa.delete(obj)
@@ -1,592 +1,593
1 1 """
2 2 Set of generic validators
3 3 """
4 4 import os
5 5 import re
6 6 import formencode
7 7 import logging
8 8 from pylons.i18n.translation import _
9 9 from webhelpers.pylonslib.secure_form import authentication_token
10 10
11 11 from formencode.validators import (
12 12 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set
13 13 )
14 14
15 15 from rhodecode.lib.utils import repo_name_slug
16 16 from rhodecode.model.db import RepoGroup, Repository, UsersGroup, User
17 from rhodecode.lib.auth import authenticate
18 17 from rhodecode.lib.exceptions import LdapImportError
19 18 from rhodecode.config.routing import ADMIN_PREFIX
20 19 # silence warnings and pylint
21 20 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set
22 21
23 22 log = logging.getLogger(__name__)
24 23
25 24
26 25 class StateObj(object):
27 26 """
28 27 this is needed to translate the messages using _() in validators
29 28 """
30 29 _ = staticmethod(_)
31 30
32 31
33 32 def M(self, key, state=None, **kwargs):
34 33 """
35 34 returns string from self.message based on given key,
36 35 passed kw params are used to substitute %(named)s params inside
37 36 translated strings
38 37
39 38 :param msg:
40 39 :param state:
41 40 """
42 41 if state is None:
43 42 state = StateObj()
44 43 else:
45 44 state._ = staticmethod(_)
46 45 #inject validator into state object
47 46 return self.message(key, state, **kwargs)
48 47
49 48
50 49 def ValidUsername(edit=False, old_data={}):
51 50 class _validator(formencode.validators.FancyValidator):
52 51 messages = {
53 52 'username_exists': _(u'Username "%(username)s" already exists'),
54 53 'system_invalid_username':
55 54 _(u'Username "%(username)s" is forbidden'),
56 55 'invalid_username':
57 56 _(u'Username may only contain alphanumeric characters '
58 57 'underscores, periods or dashes and must begin with '
59 58 'alphanumeric character')
60 59 }
61 60
62 61 def validate_python(self, value, state):
63 62 if value in ['default', 'new_user']:
64 63 msg = M(self, 'system_invalid_username', state, username=value)
65 64 raise formencode.Invalid(msg, value, state)
66 65 #check if user is unique
67 66 old_un = None
68 67 if edit:
69 68 old_un = User.get(old_data.get('user_id')).username
70 69
71 70 if old_un != value or not edit:
72 71 if User.get_by_username(value, case_insensitive=True):
73 72 msg = M(self, 'username_exists', state, username=value)
74 73 raise formencode.Invalid(msg, value, state)
75 74
76 75 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
77 76 msg = M(self, 'invalid_username', state)
78 77 raise formencode.Invalid(msg, value, state)
79 78 return _validator
80 79
81 80
82 81 def ValidRepoUser():
83 82 class _validator(formencode.validators.FancyValidator):
84 83 messages = {
85 84 'invalid_username': _(u'Username %(username)s is not valid')
86 85 }
87 86
88 87 def validate_python(self, value, state):
89 88 try:
90 89 User.query().filter(User.active == True)\
91 90 .filter(User.username == value).one()
92 91 except Exception:
93 92 msg = M(self, 'invalid_username', state, username=value)
94 93 raise formencode.Invalid(msg, value, state,
95 94 error_dict=dict(username=msg)
96 95 )
97 96
98 97 return _validator
99 98
100 99
101 100 def ValidUsersGroup(edit=False, old_data={}):
102 101 class _validator(formencode.validators.FancyValidator):
103 102 messages = {
104 103 'invalid_group': _(u'Invalid users group name'),
105 104 'group_exist': _(u'Users group "%(usersgroup)s" already exists'),
106 105 'invalid_usersgroup_name':
107 106 _(u'users group name may only contain alphanumeric '
108 107 'characters underscores, periods or dashes and must begin '
109 108 'with alphanumeric character')
110 109 }
111 110
112 111 def validate_python(self, value, state):
113 112 if value in ['default']:
114 113 msg = M(self, 'invalid_group', state)
115 114 raise formencode.Invalid(msg, value, state,
116 115 error_dict=dict(users_group_name=msg)
117 116 )
118 117 #check if group is unique
119 118 old_ugname = None
120 119 if edit:
121 120 old_id = old_data.get('users_group_id')
122 121 old_ugname = UsersGroup.get(old_id).users_group_name
123 122
124 123 if old_ugname != value or not edit:
125 124 is_existing_group = UsersGroup.get_by_group_name(value,
126 125 case_insensitive=True)
127 126 if is_existing_group:
128 127 msg = M(self, 'group_exist', state, usersgroup=value)
129 128 raise formencode.Invalid(msg, value, state,
130 129 error_dict=dict(users_group_name=msg)
131 130 )
132 131
133 132 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
134 133 msg = M(self, 'invalid_usersgroup_name', state)
135 134 raise formencode.Invalid(msg, value, state,
136 135 error_dict=dict(users_group_name=msg)
137 136 )
138 137
139 138 return _validator
140 139
141 140
142 141 def ValidReposGroup(edit=False, old_data={}):
143 142 class _validator(formencode.validators.FancyValidator):
144 143 messages = {
145 144 'group_parent_id': _(u'Cannot assign this group as parent'),
146 145 'group_exists': _(u'Group "%(group_name)s" already exists'),
147 146 'repo_exists':
148 147 _(u'Repository with name "%(group_name)s" already exists')
149 148 }
150 149
151 150 def validate_python(self, value, state):
152 151 # TODO WRITE VALIDATIONS
153 152 group_name = value.get('group_name')
154 153 group_parent_id = value.get('group_parent_id')
155 154
156 155 # slugify repo group just in case :)
157 156 slug = repo_name_slug(group_name)
158 157
159 158 # check for parent of self
160 159 parent_of_self = lambda: (
161 160 old_data['group_id'] == int(group_parent_id)
162 161 if group_parent_id else False
163 162 )
164 163 if edit and parent_of_self():
165 164 msg = M(self, 'group_parent_id', state)
166 165 raise formencode.Invalid(msg, value, state,
167 166 error_dict=dict(group_parent_id=msg)
168 167 )
169 168
170 169 old_gname = None
171 170 if edit:
172 171 old_gname = RepoGroup.get(old_data.get('group_id')).group_name
173 172
174 173 if old_gname != group_name or not edit:
175 174
176 175 # check group
177 176 gr = RepoGroup.query()\
178 177 .filter(RepoGroup.group_name == slug)\
179 178 .filter(RepoGroup.group_parent_id == group_parent_id)\
180 179 .scalar()
181 180
182 181 if gr:
183 182 msg = M(self, 'group_exists', state, group_name=slug)
184 183 raise formencode.Invalid(msg, value, state,
185 184 error_dict=dict(group_name=msg)
186 185 )
187 186
188 187 # check for same repo
189 188 repo = Repository.query()\
190 189 .filter(Repository.repo_name == slug)\
191 190 .scalar()
192 191
193 192 if repo:
194 193 msg = M(self, 'repo_exists', state, group_name=slug)
195 194 raise formencode.Invalid(msg, value, state,
196 195 error_dict=dict(group_name=msg)
197 196 )
198 197
199 198 return _validator
200 199
201 200
202 201 def ValidPassword():
203 202 class _validator(formencode.validators.FancyValidator):
204 203 messages = {
205 204 'invalid_password':
206 205 _(u'Invalid characters (non-ascii) in password')
207 206 }
208 207
209 208 def validate_python(self, value, state):
210 209 try:
211 210 (value or '').decode('ascii')
212 211 except UnicodeError:
213 212 msg = M(self, 'invalid_password', state)
214 213 raise formencode.Invalid(msg, value, state,)
215 214 return _validator
216 215
217 216
218 217 def ValidPasswordsMatch():
219 218 class _validator(formencode.validators.FancyValidator):
220 219 messages = {
221 220 'password_mismatch': _(u'Passwords do not match'),
222 221 }
223 222
224 223 def validate_python(self, value, state):
225 224
226 225 pass_val = value.get('password') or value.get('new_password')
227 226 if pass_val != value['password_confirmation']:
228 227 msg = M(self, 'password_mismatch', state)
229 228 raise formencode.Invalid(msg, value, state,
230 229 error_dict=dict(password_confirmation=msg)
231 230 )
232 231 return _validator
233 232
234 233
235 234 def ValidAuth():
236 235 class _validator(formencode.validators.FancyValidator):
237 236 messages = {
238 237 'invalid_password': _(u'invalid password'),
239 238 'invalid_username': _(u'invalid user name'),
240 239 'disabled_account': _(u'Your account is disabled')
241 240 }
242 241
243 242 def validate_python(self, value, state):
243 from rhodecode.lib.auth import authenticate
244
244 245 password = value['password']
245 246 username = value['username']
246 247
247 248 if not authenticate(username, password):
248 249 user = User.get_by_username(username)
249 250 if user and user.active is False:
250 251 log.warning('user %s is disabled' % username)
251 252 msg = M(self, 'disabled_account', state)
252 253 raise formencode.Invalid(msg, value, state,
253 254 error_dict=dict(username=msg)
254 255 )
255 256 else:
256 257 log.warning('user %s failed to authenticate' % username)
257 258 msg = M(self, 'invalid_username', state)
258 259 msg2 = M(self, 'invalid_password', state)
259 260 raise formencode.Invalid(msg, value, state,
260 261 error_dict=dict(username=msg, password=msg2)
261 262 )
262 263 return _validator
263 264
264 265
265 266 def ValidAuthToken():
266 267 class _validator(formencode.validators.FancyValidator):
267 268 messages = {
268 269 'invalid_token': _(u'Token mismatch')
269 270 }
270 271
271 272 def validate_python(self, value, state):
272 273 if value != authentication_token():
273 274 msg = M(self, 'invalid_token', state)
274 275 raise formencode.Invalid(msg, value, state)
275 276 return _validator
276 277
277 278
278 279 def ValidRepoName(edit=False, old_data={}):
279 280 class _validator(formencode.validators.FancyValidator):
280 281 messages = {
281 282 'invalid_repo_name':
282 283 _(u'Repository name %(repo)s is disallowed'),
283 284 'repository_exists':
284 285 _(u'Repository named %(repo)s already exists'),
285 286 'repository_in_group_exists': _(u'Repository "%(repo)s" already '
286 287 'exists in group "%(group)s"'),
287 288 'same_group_exists': _(u'Repositories group with name "%(repo)s" '
288 289 'already exists')
289 290 }
290 291
291 292 def _to_python(self, value, state):
292 293 repo_name = repo_name_slug(value.get('repo_name', ''))
293 294 repo_group = value.get('repo_group')
294 295 if repo_group:
295 296 gr = RepoGroup.get(repo_group)
296 297 group_path = gr.full_path
297 298 group_name = gr.group_name
298 299 # value needs to be aware of group name in order to check
299 300 # db key This is an actual just the name to store in the
300 301 # database
301 302 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
302 303 else:
303 304 group_name = group_path = ''
304 305 repo_name_full = repo_name
305 306
306 307 value['repo_name'] = repo_name
307 308 value['repo_name_full'] = repo_name_full
308 309 value['group_path'] = group_path
309 310 value['group_name'] = group_name
310 311 return value
311 312
312 313 def validate_python(self, value, state):
313 314
314 315 repo_name = value.get('repo_name')
315 316 repo_name_full = value.get('repo_name_full')
316 317 group_path = value.get('group_path')
317 318 group_name = value.get('group_name')
318 319
319 320 if repo_name in [ADMIN_PREFIX, '']:
320 321 msg = M(self, 'invalid_repo_name', state, repo=repo_name)
321 322 raise formencode.Invalid(msg, value, state,
322 323 error_dict=dict(repo_name=msg)
323 324 )
324 325
325 326 rename = old_data.get('repo_name') != repo_name_full
326 327 create = not edit
327 328 if rename or create:
328 329
329 330 if group_path != '':
330 331 if Repository.get_by_repo_name(repo_name_full):
331 332 msg = M(self, 'repository_in_group_exists', state,
332 333 repo=repo_name, group=group_name)
333 334 raise formencode.Invalid(msg, value, state,
334 335 error_dict=dict(repo_name=msg)
335 336 )
336 337 elif RepoGroup.get_by_group_name(repo_name_full):
337 338 msg = M(self, 'same_group_exists', state,
338 339 repo=repo_name)
339 340 raise formencode.Invalid(msg, value, state,
340 341 error_dict=dict(repo_name=msg)
341 342 )
342 343
343 344 elif Repository.get_by_repo_name(repo_name_full):
344 345 msg = M(self, 'repository_exists', state,
345 346 repo=repo_name)
346 347 raise formencode.Invalid(msg, value, state,
347 348 error_dict=dict(repo_name=msg)
348 349 )
349 350 return value
350 351 return _validator
351 352
352 353
353 354 def ValidForkName(*args, **kwargs):
354 355 return ValidRepoName(*args, **kwargs)
355 356
356 357
357 358 def SlugifyName():
358 359 class _validator(formencode.validators.FancyValidator):
359 360
360 361 def _to_python(self, value, state):
361 362 return repo_name_slug(value)
362 363
363 364 def validate_python(self, value, state):
364 365 pass
365 366
366 367 return _validator
367 368
368 369
369 370 def ValidCloneUri():
370 371 from rhodecode.lib.utils import make_ui
371 372
372 373 def url_handler(repo_type, url, proto, ui=None):
373 374 if repo_type == 'hg':
374 375 from mercurial.httprepo import httprepository, httpsrepository
375 376 if proto == 'https':
376 377 httpsrepository(make_ui('db'), url).capabilities
377 378 elif proto == 'http':
378 379 httprepository(make_ui('db'), url).capabilities
379 380 elif repo_type == 'git':
380 381 #TODO: write a git url validator
381 382 pass
382 383
383 384 class _validator(formencode.validators.FancyValidator):
384 385 messages = {
385 386 'clone_uri': _(u'invalid clone url'),
386 387 'invalid_clone_uri': _(u'Invalid clone url, provide a '
387 388 'valid clone http\s url')
388 389 }
389 390
390 391 def validate_python(self, value, state):
391 392 repo_type = value.get('repo_type')
392 393 url = value.get('clone_uri')
393 394
394 395 if not url:
395 396 pass
396 397 elif url.startswith('https') or url.startswith('http'):
397 398 _type = 'https' if url.startswith('https') else 'http'
398 399 try:
399 400 url_handler(repo_type, url, _type, make_ui('db'))
400 401 except Exception:
401 402 log.exception('Url validation failed')
402 403 msg = M(self, 'clone_uri')
403 404 raise formencode.Invalid(msg, value, state,
404 405 error_dict=dict(clone_uri=msg)
405 406 )
406 407 else:
407 408 msg = M(self, 'invalid_clone_uri', state)
408 409 raise formencode.Invalid(msg, value, state,
409 410 error_dict=dict(clone_uri=msg)
410 411 )
411 412 return _validator
412 413
413 414
414 415 def ValidForkType(old_data={}):
415 416 class _validator(formencode.validators.FancyValidator):
416 417 messages = {
417 418 'invalid_fork_type': _(u'Fork have to be the same type as parent')
418 419 }
419 420
420 421 def validate_python(self, value, state):
421 422 if old_data['repo_type'] != value:
422 423 msg = M(self, 'invalid_fork_type', state)
423 424 raise formencode.Invalid(msg, value, state,
424 425 error_dict=dict(repo_type=msg)
425 426 )
426 427 return _validator
427 428
428 429
429 430 def ValidPerms(type_='repo'):
430 431 if type_ == 'group':
431 432 EMPTY_PERM = 'group.none'
432 433 elif type_ == 'repo':
433 434 EMPTY_PERM = 'repository.none'
434 435
435 436 class _validator(formencode.validators.FancyValidator):
436 437 messages = {
437 438 'perm_new_member_name':
438 439 _(u'This username or users group name is not valid')
439 440 }
440 441
441 442 def to_python(self, value, state):
442 443 perms_update = []
443 444 perms_new = []
444 445 # build a list of permission to update and new permission to create
445 446 for k, v in value.items():
446 447 # means new added member to permissions
447 448 if k.startswith('perm_new_member'):
448 449 new_perm = value.get('perm_new_member', False)
449 450 new_member = value.get('perm_new_member_name', False)
450 451 new_type = value.get('perm_new_member_type')
451 452
452 453 if new_member and new_perm:
453 454 if (new_member, new_perm, new_type) not in perms_new:
454 455 perms_new.append((new_member, new_perm, new_type))
455 456 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
456 457 member = k[7:]
457 458 t = {'u': 'user',
458 459 'g': 'users_group'
459 460 }[k[0]]
460 461 if member == 'default':
461 462 if value.get('private'):
462 463 # set none for default when updating to
463 464 # private repo
464 465 v = EMPTY_PERM
465 466 perms_update.append((member, v, t))
466 467
467 468 value['perms_updates'] = perms_update
468 469 value['perms_new'] = perms_new
469 470
470 471 # update permissions
471 472 for k, v, t in perms_new:
472 473 try:
473 474 if t is 'user':
474 475 self.user_db = User.query()\
475 476 .filter(User.active == True)\
476 477 .filter(User.username == k).one()
477 478 if t is 'users_group':
478 479 self.user_db = UsersGroup.query()\
479 480 .filter(UsersGroup.users_group_active == True)\
480 481 .filter(UsersGroup.users_group_name == k).one()
481 482
482 483 except Exception:
483 484 log.exception('Updated permission failed')
484 485 msg = M(self, 'perm_new_member_type', state)
485 486 raise formencode.Invalid(msg, value, state,
486 487 error_dict=dict(perm_new_member_name=msg)
487 488 )
488 489 return value
489 490 return _validator
490 491
491 492
492 493 def ValidSettings():
493 494 class _validator(formencode.validators.FancyValidator):
494 495 def _to_python(self, value, state):
495 496 # settings form can't edit user
496 497 if 'user' in value:
497 498 del value['user']
498 499 return value
499 500
500 501 def validate_python(self, value, state):
501 502 pass
502 503 return _validator
503 504
504 505
505 506 def ValidPath():
506 507 class _validator(formencode.validators.FancyValidator):
507 508 messages = {
508 509 'invalid_path': _(u'This is not a valid path')
509 510 }
510 511
511 512 def validate_python(self, value, state):
512 513 if not os.path.isdir(value):
513 514 msg = M(self, 'invalid_path', state)
514 515 raise formencode.Invalid(msg, value, state,
515 516 error_dict=dict(paths_root_path=msg)
516 517 )
517 518 return _validator
518 519
519 520
520 521 def UniqSystemEmail(old_data={}):
521 522 class _validator(formencode.validators.FancyValidator):
522 523 messages = {
523 524 'email_taken': _(u'This e-mail address is already taken')
524 525 }
525 526
526 527 def _to_python(self, value, state):
527 528 return value.lower()
528 529
529 530 def validate_python(self, value, state):
530 531 if (old_data.get('email') or '').lower() != value:
531 532 user = User.get_by_email(value, case_insensitive=True)
532 533 if user:
533 534 msg = M(self, 'email_taken', state)
534 535 raise formencode.Invalid(msg, value, state,
535 536 error_dict=dict(email=msg)
536 537 )
537 538 return _validator
538 539
539 540
540 541 def ValidSystemEmail():
541 542 class _validator(formencode.validators.FancyValidator):
542 543 messages = {
543 544 'non_existing_email': _(u'e-mail "%(email)s" does not exist.')
544 545 }
545 546
546 547 def _to_python(self, value, state):
547 548 return value.lower()
548 549
549 550 def validate_python(self, value, state):
550 551 user = User.get_by_email(value, case_insensitive=True)
551 552 if user is None:
552 553 msg = M(self, 'non_existing_email', state, email=value)
553 554 raise formencode.Invalid(msg, value, state,
554 555 error_dict=dict(email=msg)
555 556 )
556 557
557 558 return _validator
558 559
559 560
560 561 def LdapLibValidator():
561 562 class _validator(formencode.validators.FancyValidator):
562 563 messages = {
563 564
564 565 }
565 566
566 567 def validate_python(self, value, state):
567 568 try:
568 569 import ldap
569 570 ldap # pyflakes silence !
570 571 except ImportError:
571 572 raise LdapImportError()
572 573
573 574 return _validator
574 575
575 576
576 577 def AttrLoginValidator():
577 578 class _validator(formencode.validators.FancyValidator):
578 579 messages = {
579 580 'invalid_cn':
580 581 _(u'The LDAP Login attribute of the CN must be specified - '
581 582 'this is the name of the attribute that is equivalent '
582 583 'to "username"')
583 584 }
584 585
585 586 def validate_python(self, value, state):
586 587 if not value or not isinstance(value, (str, unicode)):
587 588 msg = M(self, 'invalid_cn', state)
588 589 raise formencode.Invalid(msg, value, state,
589 590 error_dict=dict(ldap_attr_login=msg)
590 591 )
591 592
592 593 return _validator
General Comments 0
You need to be logged in to leave comments. Login now