##// END OF EJS Templates
authn: Fix form validation of authentication plugins....
johbo -
r102:1a9dcffb default
parent child Browse files
Show More
@@ -1,607 +1,602 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Authentication modules
22 Authentication modules
23 """
23 """
24
24
25 import logging
25 import logging
26 import time
26 import time
27 import traceback
27 import traceback
28
28
29 from pyramid.threadlocal import get_current_registry
29 from pyramid.threadlocal import get_current_registry
30 from sqlalchemy.ext.hybrid import hybrid_property
30 from sqlalchemy.ext.hybrid import hybrid_property
31
31
32 from rhodecode.authentication.interface import IAuthnPluginRegistry
32 from rhodecode.authentication.interface import IAuthnPluginRegistry
33 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
33 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
34 from rhodecode.lib import caches
34 from rhodecode.lib import caches
35 from rhodecode.lib.auth import PasswordGenerator, _RhodeCodeCryptoBCrypt
35 from rhodecode.lib.auth import PasswordGenerator, _RhodeCodeCryptoBCrypt
36 from rhodecode.lib.utils2 import md5_safe, safe_int
36 from rhodecode.lib.utils2 import md5_safe, safe_int
37 from rhodecode.lib.utils2 import safe_str
37 from rhodecode.lib.utils2 import safe_str
38 from rhodecode.model.db import User
38 from rhodecode.model.db import User
39 from rhodecode.model.meta import Session
39 from rhodecode.model.meta import Session
40 from rhodecode.model.settings import SettingsModel
40 from rhodecode.model.settings import SettingsModel
41 from rhodecode.model.user import UserModel
41 from rhodecode.model.user import UserModel
42 from rhodecode.model.user_group import UserGroupModel
42 from rhodecode.model.user_group import UserGroupModel
43
43
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47 # auth types that authenticate() function can receive
47 # auth types that authenticate() function can receive
48 VCS_TYPE = 'vcs'
48 VCS_TYPE = 'vcs'
49 HTTP_TYPE = 'http'
49 HTTP_TYPE = 'http'
50
50
51
51
52 class LazyFormencode(object):
52 class LazyFormencode(object):
53 def __init__(self, formencode_obj, *args, **kwargs):
53 def __init__(self, formencode_obj, *args, **kwargs):
54 self.formencode_obj = formencode_obj
54 self.formencode_obj = formencode_obj
55 self.args = args
55 self.args = args
56 self.kwargs = kwargs
56 self.kwargs = kwargs
57
57
58 def __call__(self, *args, **kwargs):
58 def __call__(self, *args, **kwargs):
59 from inspect import isfunction
59 from inspect import isfunction
60 formencode_obj = self.formencode_obj
60 formencode_obj = self.formencode_obj
61 if isfunction(formencode_obj):
61 if isfunction(formencode_obj):
62 # case we wrap validators into functions
62 # case we wrap validators into functions
63 formencode_obj = self.formencode_obj(*args, **kwargs)
63 formencode_obj = self.formencode_obj(*args, **kwargs)
64 return formencode_obj(*self.args, **self.kwargs)
64 return formencode_obj(*self.args, **self.kwargs)
65
65
66
66
67 class RhodeCodeAuthPluginBase(object):
67 class RhodeCodeAuthPluginBase(object):
68 # cache the authentication request for N amount of seconds. Some kind
68 # cache the authentication request for N amount of seconds. Some kind
69 # of authentication methods are very heavy and it's very efficient to cache
69 # of authentication methods are very heavy and it's very efficient to cache
70 # the result of a call. If it's set to None (default) cache is off
70 # the result of a call. If it's set to None (default) cache is off
71 AUTH_CACHE_TTL = None
71 AUTH_CACHE_TTL = None
72 AUTH_CACHE = {}
72 AUTH_CACHE = {}
73
73
74 auth_func_attrs = {
74 auth_func_attrs = {
75 "username": "unique username",
75 "username": "unique username",
76 "firstname": "first name",
76 "firstname": "first name",
77 "lastname": "last name",
77 "lastname": "last name",
78 "email": "email address",
78 "email": "email address",
79 "groups": '["list", "of", "groups"]',
79 "groups": '["list", "of", "groups"]',
80 "extern_name": "name in external source of record",
80 "extern_name": "name in external source of record",
81 "extern_type": "type of external source of record",
81 "extern_type": "type of external source of record",
82 "admin": 'True|False defines if user should be RhodeCode super admin',
82 "admin": 'True|False defines if user should be RhodeCode super admin',
83 "active":
83 "active":
84 'True|False defines active state of user internally for RhodeCode',
84 'True|False defines active state of user internally for RhodeCode',
85 "active_from_extern":
85 "active_from_extern":
86 "True|False\None, active state from the external auth, "
86 "True|False\None, active state from the external auth, "
87 "None means use definition from RhodeCode extern_type active value"
87 "None means use definition from RhodeCode extern_type active value"
88 }
88 }
89 # set on authenticate() method and via set_auth_type func.
89 # set on authenticate() method and via set_auth_type func.
90 auth_type = None
90 auth_type = None
91
91
92 # List of setting names to store encrypted. Plugins may override this list
92 # List of setting names to store encrypted. Plugins may override this list
93 # to store settings encrypted.
93 # to store settings encrypted.
94 _settings_encrypted = []
94 _settings_encrypted = []
95
95
96 # Mapping of python to DB settings model types. Plugins may override or
96 # Mapping of python to DB settings model types. Plugins may override or
97 # extend this mapping.
97 # extend this mapping.
98 _settings_type_map = {
98 _settings_type_map = {
99 str: 'str',
99 str: 'str',
100 int: 'int',
100 int: 'int',
101 unicode: 'unicode',
101 unicode: 'unicode',
102 bool: 'bool',
102 bool: 'bool',
103 list: 'list',
103 list: 'list',
104 }
104 }
105
105
106 def __init__(self, plugin_id):
106 def __init__(self, plugin_id):
107 self._plugin_id = plugin_id
107 self._plugin_id = plugin_id
108
108
109 def _get_setting_full_name(self, name):
109 def _get_setting_full_name(self, name):
110 """
110 """
111 Return the full setting name used for storing values in the database.
111 Return the full setting name used for storing values in the database.
112 """
112 """
113 # TODO: johbo: Using the name here is problematic. It would be good to
113 # TODO: johbo: Using the name here is problematic. It would be good to
114 # introduce either new models in the database to hold Plugin and
114 # introduce either new models in the database to hold Plugin and
115 # PluginSetting or to use the plugin id here.
115 # PluginSetting or to use the plugin id here.
116 return 'auth_{}_{}'.format(self.name, name)
116 return 'auth_{}_{}'.format(self.name, name)
117
117
118 def _get_setting_type(self, name, value):
118 def _get_setting_type(self, name, value):
119 """
119 """
120 Get the type as used by the SettingsModel accordingly to type of passed
120 Get the type as used by the SettingsModel accordingly to type of passed
121 value. Optionally the suffix `.encrypted` is appended to instruct
121 value. Optionally the suffix `.encrypted` is appended to instruct
122 SettingsModel to store it encrypted.
122 SettingsModel to store it encrypted.
123 """
123 """
124 type_ = self._settings_type_map.get(type(value), 'unicode')
124 type_ = self._settings_type_map.get(type(value), 'unicode')
125 if name in self._settings_encrypted:
125 if name in self._settings_encrypted:
126 type_ = '{}.encrypted'.format(type_)
126 type_ = '{}.encrypted'.format(type_)
127 return type_
127 return type_
128
128
129 def is_enabled(self):
129 def is_enabled(self):
130 """
130 """
131 Returns true if this plugin is enabled. An enabled plugin can be
131 Returns true if this plugin is enabled. An enabled plugin can be
132 configured in the admin interface but it is not consulted during
132 configured in the admin interface but it is not consulted during
133 authentication.
133 authentication.
134 """
134 """
135 auth_plugins = SettingsModel().get_auth_plugins()
135 auth_plugins = SettingsModel().get_auth_plugins()
136 return self.get_id() in auth_plugins
136 return self.get_id() in auth_plugins
137
137
138 def is_active(self):
138 def is_active(self):
139 """
139 """
140 Returns true if the plugin is activated. An activated plugin is
140 Returns true if the plugin is activated. An activated plugin is
141 consulted during authentication, assumed it is also enabled.
141 consulted during authentication, assumed it is also enabled.
142 """
142 """
143 return self.get_setting_by_name('enabled')
143 return self.get_setting_by_name('enabled')
144
144
145 def get_id(self):
145 def get_id(self):
146 """
146 """
147 Returns the plugin id.
147 Returns the plugin id.
148 """
148 """
149 return self._plugin_id
149 return self._plugin_id
150
150
151 def get_display_name(self):
151 def get_display_name(self):
152 """
152 """
153 Returns a translation string for displaying purposes.
153 Returns a translation string for displaying purposes.
154 """
154 """
155 raise NotImplementedError('Not implemented in base class')
155 raise NotImplementedError('Not implemented in base class')
156
156
157 def get_settings_schema(self):
157 def get_settings_schema(self):
158 """
158 """
159 Returns a colander schema, representing the plugin settings.
159 Returns a colander schema, representing the plugin settings.
160 """
160 """
161 return AuthnPluginSettingsSchemaBase()
161 return AuthnPluginSettingsSchemaBase()
162
162
163 def get_setting_by_name(self, name):
163 def get_setting_by_name(self, name):
164 """
164 """
165 Returns a plugin setting by name.
165 Returns a plugin setting by name.
166 """
166 """
167 full_name = self._get_setting_full_name(name)
167 full_name = self._get_setting_full_name(name)
168 db_setting = SettingsModel().get_setting_by_name(full_name)
168 db_setting = SettingsModel().get_setting_by_name(full_name)
169 return db_setting.app_settings_value if db_setting else None
169 return db_setting.app_settings_value if db_setting else None
170
170
171 def create_or_update_setting(self, name, value):
171 def create_or_update_setting(self, name, value):
172 """
172 """
173 Create or update a setting for this plugin in the persistent storage.
173 Create or update a setting for this plugin in the persistent storage.
174 """
174 """
175 full_name = self._get_setting_full_name(name)
175 full_name = self._get_setting_full_name(name)
176 type_ = self._get_setting_type(name, value)
176 type_ = self._get_setting_type(name, value)
177 db_setting = SettingsModel().create_or_update_setting(
177 db_setting = SettingsModel().create_or_update_setting(
178 full_name, value, type_)
178 full_name, value, type_)
179 return db_setting.app_settings_value
179 return db_setting.app_settings_value
180
180
181 def get_settings(self):
181 def get_settings(self):
182 """
182 """
183 Returns the plugin settings as dictionary.
183 Returns the plugin settings as dictionary.
184 """
184 """
185 settings = {}
185 settings = {}
186 for node in self.get_settings_schema():
186 for node in self.get_settings_schema():
187 settings[node.name] = self.get_setting_by_name(node.name)
187 settings[node.name] = self.get_setting_by_name(node.name)
188 return settings
188 return settings
189
189
190 @property
190 @property
191 def validators(self):
191 def validators(self):
192 """
192 """
193 Exposes RhodeCode validators modules
193 Exposes RhodeCode validators modules
194 """
194 """
195 # this is a hack to overcome issues with pylons threadlocals and
195 # this is a hack to overcome issues with pylons threadlocals and
196 # translator object _() not beein registered properly.
196 # translator object _() not beein registered properly.
197 class LazyCaller(object):
197 class LazyCaller(object):
198 def __init__(self, name):
198 def __init__(self, name):
199 self.validator_name = name
199 self.validator_name = name
200
200
201 def __call__(self, *args, **kwargs):
201 def __call__(self, *args, **kwargs):
202 from rhodecode.model import validators as v
202 from rhodecode.model import validators as v
203 obj = getattr(v, self.validator_name)
203 obj = getattr(v, self.validator_name)
204 # log.debug('Initializing lazy formencode object: %s', obj)
204 # log.debug('Initializing lazy formencode object: %s', obj)
205 return LazyFormencode(obj, *args, **kwargs)
205 return LazyFormencode(obj, *args, **kwargs)
206
206
207 class ProxyGet(object):
207 class ProxyGet(object):
208 def __getattribute__(self, name):
208 def __getattribute__(self, name):
209 return LazyCaller(name)
209 return LazyCaller(name)
210
210
211 return ProxyGet()
211 return ProxyGet()
212
212
213 @hybrid_property
213 @hybrid_property
214 def name(self):
214 def name(self):
215 """
215 """
216 Returns the name of this authentication plugin.
216 Returns the name of this authentication plugin.
217
217
218 :returns: string
218 :returns: string
219 """
219 """
220 raise NotImplementedError("Not implemented in base class")
220 raise NotImplementedError("Not implemented in base class")
221
221
222 @hybrid_property
222 @hybrid_property
223 def is_container_auth(self):
223 def is_container_auth(self):
224 """
224 """
225 Returns bool if this module uses container auth.
225 Returns bool if this module uses container auth.
226
226
227 This property will trigger an automatic call to authenticate on
227 This property will trigger an automatic call to authenticate on
228 a visit to the website or during a push/pull.
228 a visit to the website or during a push/pull.
229
229
230 :returns: bool
230 :returns: bool
231 """
231 """
232 return False
232 return False
233
233
234 @hybrid_property
234 @hybrid_property
235 def allows_creating_users(self):
235 def allows_creating_users(self):
236 """
236 """
237 Defines if Plugin allows users to be created on-the-fly when
237 Defines if Plugin allows users to be created on-the-fly when
238 authentication is called. Controls how external plugins should behave
238 authentication is called. Controls how external plugins should behave
239 in terms if they are allowed to create new users, or not. Base plugins
239 in terms if they are allowed to create new users, or not. Base plugins
240 should not be allowed to, but External ones should be !
240 should not be allowed to, but External ones should be !
241
241
242 :return: bool
242 :return: bool
243 """
243 """
244 return False
244 return False
245
245
246 def set_auth_type(self, auth_type):
246 def set_auth_type(self, auth_type):
247 self.auth_type = auth_type
247 self.auth_type = auth_type
248
248
249 def allows_authentication_from(
249 def allows_authentication_from(
250 self, user, allows_non_existing_user=True,
250 self, user, allows_non_existing_user=True,
251 allowed_auth_plugins=None, allowed_auth_sources=None):
251 allowed_auth_plugins=None, allowed_auth_sources=None):
252 """
252 """
253 Checks if this authentication module should accept a request for
253 Checks if this authentication module should accept a request for
254 the current user.
254 the current user.
255
255
256 :param user: user object fetched using plugin's get_user() method.
256 :param user: user object fetched using plugin's get_user() method.
257 :param allows_non_existing_user: if True, don't allow the
257 :param allows_non_existing_user: if True, don't allow the
258 user to be empty, meaning not existing in our database
258 user to be empty, meaning not existing in our database
259 :param allowed_auth_plugins: if provided, users extern_type will be
259 :param allowed_auth_plugins: if provided, users extern_type will be
260 checked against a list of provided extern types, which are plugin
260 checked against a list of provided extern types, which are plugin
261 auth_names in the end
261 auth_names in the end
262 :param allowed_auth_sources: authentication type allowed,
262 :param allowed_auth_sources: authentication type allowed,
263 `http` or `vcs` default is both.
263 `http` or `vcs` default is both.
264 defines if plugin will accept only http authentication vcs
264 defines if plugin will accept only http authentication vcs
265 authentication(git/hg) or both
265 authentication(git/hg) or both
266 :returns: boolean
266 :returns: boolean
267 """
267 """
268 if not user and not allows_non_existing_user:
268 if not user and not allows_non_existing_user:
269 log.debug('User is empty but plugin does not allow empty users,'
269 log.debug('User is empty but plugin does not allow empty users,'
270 'not allowed to authenticate')
270 'not allowed to authenticate')
271 return False
271 return False
272
272
273 expected_auth_plugins = allowed_auth_plugins or [self.name]
273 expected_auth_plugins = allowed_auth_plugins or [self.name]
274 if user and (user.extern_type and
274 if user and (user.extern_type and
275 user.extern_type not in expected_auth_plugins):
275 user.extern_type not in expected_auth_plugins):
276 log.debug(
276 log.debug(
277 'User `%s` is bound to `%s` auth type. Plugin allows only '
277 'User `%s` is bound to `%s` auth type. Plugin allows only '
278 '%s, skipping', user, user.extern_type, expected_auth_plugins)
278 '%s, skipping', user, user.extern_type, expected_auth_plugins)
279
279
280 return False
280 return False
281
281
282 # by default accept both
282 # by default accept both
283 expected_auth_from = allowed_auth_sources or [HTTP_TYPE, VCS_TYPE]
283 expected_auth_from = allowed_auth_sources or [HTTP_TYPE, VCS_TYPE]
284 if self.auth_type not in expected_auth_from:
284 if self.auth_type not in expected_auth_from:
285 log.debug('Current auth source is %s but plugin only allows %s',
285 log.debug('Current auth source is %s but plugin only allows %s',
286 self.auth_type, expected_auth_from)
286 self.auth_type, expected_auth_from)
287 return False
287 return False
288
288
289 return True
289 return True
290
290
291 def get_user(self, username=None, **kwargs):
291 def get_user(self, username=None, **kwargs):
292 """
292 """
293 Helper method for user fetching in plugins, by default it's using
293 Helper method for user fetching in plugins, by default it's using
294 simple fetch by username, but this method can be custimized in plugins
294 simple fetch by username, but this method can be custimized in plugins
295 eg. container auth plugin to fetch user by environ params
295 eg. container auth plugin to fetch user by environ params
296
296
297 :param username: username if given to fetch from database
297 :param username: username if given to fetch from database
298 :param kwargs: extra arguments needed for user fetching.
298 :param kwargs: extra arguments needed for user fetching.
299 """
299 """
300 user = None
300 user = None
301 log.debug(
301 log.debug(
302 'Trying to fetch user `%s` from RhodeCode database', username)
302 'Trying to fetch user `%s` from RhodeCode database', username)
303 if username:
303 if username:
304 user = User.get_by_username(username)
304 user = User.get_by_username(username)
305 if not user:
305 if not user:
306 log.debug('User not found, fallback to fetch user in '
306 log.debug('User not found, fallback to fetch user in '
307 'case insensitive mode')
307 'case insensitive mode')
308 user = User.get_by_username(username, case_insensitive=True)
308 user = User.get_by_username(username, case_insensitive=True)
309 else:
309 else:
310 log.debug('provided username:`%s` is empty skipping...', username)
310 log.debug('provided username:`%s` is empty skipping...', username)
311 if not user:
311 if not user:
312 log.debug('User `%s` not found in database', username)
312 log.debug('User `%s` not found in database', username)
313 return user
313 return user
314
314
315 def user_activation_state(self):
315 def user_activation_state(self):
316 """
316 """
317 Defines user activation state when creating new users
317 Defines user activation state when creating new users
318
318
319 :returns: boolean
319 :returns: boolean
320 """
320 """
321 raise NotImplementedError("Not implemented in base class")
321 raise NotImplementedError("Not implemented in base class")
322
322
323 def auth(self, userobj, username, passwd, settings, **kwargs):
323 def auth(self, userobj, username, passwd, settings, **kwargs):
324 """
324 """
325 Given a user object (which may be null), username, a plaintext
325 Given a user object (which may be null), username, a plaintext
326 password, and a settings object (containing all the keys needed as
326 password, and a settings object (containing all the keys needed as
327 listed in settings()), authenticate this user's login attempt.
327 listed in settings()), authenticate this user's login attempt.
328
328
329 Return None on failure. On success, return a dictionary of the form:
329 Return None on failure. On success, return a dictionary of the form:
330
330
331 see: RhodeCodeAuthPluginBase.auth_func_attrs
331 see: RhodeCodeAuthPluginBase.auth_func_attrs
332 This is later validated for correctness
332 This is later validated for correctness
333 """
333 """
334 raise NotImplementedError("not implemented in base class")
334 raise NotImplementedError("not implemented in base class")
335
335
336 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
336 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
337 """
337 """
338 Wrapper to call self.auth() that validates call on it
338 Wrapper to call self.auth() that validates call on it
339
339
340 :param userobj: userobj
340 :param userobj: userobj
341 :param username: username
341 :param username: username
342 :param passwd: plaintext password
342 :param passwd: plaintext password
343 :param settings: plugin settings
343 :param settings: plugin settings
344 """
344 """
345 auth = self.auth(userobj, username, passwd, settings, **kwargs)
345 auth = self.auth(userobj, username, passwd, settings, **kwargs)
346 if auth:
346 if auth:
347 # check if hash should be migrated ?
347 # check if hash should be migrated ?
348 new_hash = auth.get('_hash_migrate')
348 new_hash = auth.get('_hash_migrate')
349 if new_hash:
349 if new_hash:
350 self._migrate_hash_to_bcrypt(username, passwd, new_hash)
350 self._migrate_hash_to_bcrypt(username, passwd, new_hash)
351 return self._validate_auth_return(auth)
351 return self._validate_auth_return(auth)
352 return auth
352 return auth
353
353
354 def _migrate_hash_to_bcrypt(self, username, password, new_hash):
354 def _migrate_hash_to_bcrypt(self, username, password, new_hash):
355 new_hash_cypher = _RhodeCodeCryptoBCrypt()
355 new_hash_cypher = _RhodeCodeCryptoBCrypt()
356 # extra checks, so make sure new hash is correct.
356 # extra checks, so make sure new hash is correct.
357 password_encoded = safe_str(password)
357 password_encoded = safe_str(password)
358 if new_hash and new_hash_cypher.hash_check(
358 if new_hash and new_hash_cypher.hash_check(
359 password_encoded, new_hash):
359 password_encoded, new_hash):
360 cur_user = User.get_by_username(username)
360 cur_user = User.get_by_username(username)
361 cur_user.password = new_hash
361 cur_user.password = new_hash
362 Session().add(cur_user)
362 Session().add(cur_user)
363 Session().flush()
363 Session().flush()
364 log.info('Migrated user %s hash to bcrypt', cur_user)
364 log.info('Migrated user %s hash to bcrypt', cur_user)
365
365
366 def _validate_auth_return(self, ret):
366 def _validate_auth_return(self, ret):
367 if not isinstance(ret, dict):
367 if not isinstance(ret, dict):
368 raise Exception('returned value from auth must be a dict')
368 raise Exception('returned value from auth must be a dict')
369 for k in self.auth_func_attrs:
369 for k in self.auth_func_attrs:
370 if k not in ret:
370 if k not in ret:
371 raise Exception('Missing %s attribute from returned data' % k)
371 raise Exception('Missing %s attribute from returned data' % k)
372 return ret
372 return ret
373
373
374
374
375 class RhodeCodeExternalAuthPlugin(RhodeCodeAuthPluginBase):
375 class RhodeCodeExternalAuthPlugin(RhodeCodeAuthPluginBase):
376
376
377 @hybrid_property
377 @hybrid_property
378 def allows_creating_users(self):
378 def allows_creating_users(self):
379 return True
379 return True
380
380
381 def use_fake_password(self):
381 def use_fake_password(self):
382 """
382 """
383 Return a boolean that indicates whether or not we should set the user's
383 Return a boolean that indicates whether or not we should set the user's
384 password to a random value when it is authenticated by this plugin.
384 password to a random value when it is authenticated by this plugin.
385 If your plugin provides authentication, then you will generally
385 If your plugin provides authentication, then you will generally
386 want this.
386 want this.
387
387
388 :returns: boolean
388 :returns: boolean
389 """
389 """
390 raise NotImplementedError("Not implemented in base class")
390 raise NotImplementedError("Not implemented in base class")
391
391
392 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
392 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
393 # at this point _authenticate calls plugin's `auth()` function
393 # at this point _authenticate calls plugin's `auth()` function
394 auth = super(RhodeCodeExternalAuthPlugin, self)._authenticate(
394 auth = super(RhodeCodeExternalAuthPlugin, self)._authenticate(
395 userobj, username, passwd, settings, **kwargs)
395 userobj, username, passwd, settings, **kwargs)
396 if auth:
396 if auth:
397 # maybe plugin will clean the username ?
397 # maybe plugin will clean the username ?
398 # we should use the return value
398 # we should use the return value
399 username = auth['username']
399 username = auth['username']
400
400
401 # if external source tells us that user is not active, we should
401 # if external source tells us that user is not active, we should
402 # skip rest of the process. This can prevent from creating users in
402 # skip rest of the process. This can prevent from creating users in
403 # RhodeCode when using external authentication, but if it's
403 # RhodeCode when using external authentication, but if it's
404 # inactive user we shouldn't create that user anyway
404 # inactive user we shouldn't create that user anyway
405 if auth['active_from_extern'] is False:
405 if auth['active_from_extern'] is False:
406 log.warning(
406 log.warning(
407 "User %s authenticated against %s, but is inactive",
407 "User %s authenticated against %s, but is inactive",
408 username, self.__module__)
408 username, self.__module__)
409 return None
409 return None
410
410
411 cur_user = User.get_by_username(username, case_insensitive=True)
411 cur_user = User.get_by_username(username, case_insensitive=True)
412 is_user_existing = cur_user is not None
412 is_user_existing = cur_user is not None
413
413
414 if is_user_existing:
414 if is_user_existing:
415 log.debug('Syncing user `%s` from '
415 log.debug('Syncing user `%s` from '
416 '`%s` plugin', username, self.name)
416 '`%s` plugin', username, self.name)
417 else:
417 else:
418 log.debug('Creating non existing user `%s` from '
418 log.debug('Creating non existing user `%s` from '
419 '`%s` plugin', username, self.name)
419 '`%s` plugin', username, self.name)
420
420
421 if self.allows_creating_users:
421 if self.allows_creating_users:
422 log.debug('Plugin `%s` allows to '
422 log.debug('Plugin `%s` allows to '
423 'create new users', self.name)
423 'create new users', self.name)
424 else:
424 else:
425 log.debug('Plugin `%s` does not allow to '
425 log.debug('Plugin `%s` does not allow to '
426 'create new users', self.name)
426 'create new users', self.name)
427
427
428 user_parameters = {
428 user_parameters = {
429 'username': username,
429 'username': username,
430 'email': auth["email"],
430 'email': auth["email"],
431 'firstname': auth["firstname"],
431 'firstname': auth["firstname"],
432 'lastname': auth["lastname"],
432 'lastname': auth["lastname"],
433 'active': auth["active"],
433 'active': auth["active"],
434 'admin': auth["admin"],
434 'admin': auth["admin"],
435 'extern_name': auth["extern_name"],
435 'extern_name': auth["extern_name"],
436 'extern_type': self.name,
436 'extern_type': self.name,
437 'plugin': self,
437 'plugin': self,
438 'allow_to_create_user': self.allows_creating_users,
438 'allow_to_create_user': self.allows_creating_users,
439 }
439 }
440
440
441 if not is_user_existing:
441 if not is_user_existing:
442 if self.use_fake_password():
442 if self.use_fake_password():
443 # Randomize the PW because we don't need it, but don't want
443 # Randomize the PW because we don't need it, but don't want
444 # them blank either
444 # them blank either
445 passwd = PasswordGenerator().gen_password(length=16)
445 passwd = PasswordGenerator().gen_password(length=16)
446 user_parameters['password'] = passwd
446 user_parameters['password'] = passwd
447 else:
447 else:
448 # Since the password is required by create_or_update method of
448 # Since the password is required by create_or_update method of
449 # UserModel, we need to set it explicitly.
449 # UserModel, we need to set it explicitly.
450 # The create_or_update method is smart and recognises the
450 # The create_or_update method is smart and recognises the
451 # password hashes as well.
451 # password hashes as well.
452 user_parameters['password'] = cur_user.password
452 user_parameters['password'] = cur_user.password
453
453
454 # we either create or update users, we also pass the flag
454 # we either create or update users, we also pass the flag
455 # that controls if this method can actually do that.
455 # that controls if this method can actually do that.
456 # raises NotAllowedToCreateUserError if it cannot, and we try to.
456 # raises NotAllowedToCreateUserError if it cannot, and we try to.
457 user = UserModel().create_or_update(**user_parameters)
457 user = UserModel().create_or_update(**user_parameters)
458 Session().flush()
458 Session().flush()
459 # enforce user is just in given groups, all of them has to be ones
459 # enforce user is just in given groups, all of them has to be ones
460 # created from plugins. We store this info in _group_data JSON
460 # created from plugins. We store this info in _group_data JSON
461 # field
461 # field
462 try:
462 try:
463 groups = auth['groups'] or []
463 groups = auth['groups'] or []
464 UserGroupModel().enforce_groups(user, groups, self.name)
464 UserGroupModel().enforce_groups(user, groups, self.name)
465 except Exception:
465 except Exception:
466 # for any reason group syncing fails, we should
466 # for any reason group syncing fails, we should
467 # proceed with login
467 # proceed with login
468 log.error(traceback.format_exc())
468 log.error(traceback.format_exc())
469 Session().commit()
469 Session().commit()
470 return auth
470 return auth
471
471
472
472
473 def loadplugin(plugin_id):
473 def loadplugin(plugin_id):
474 """
474 """
475 Loads and returns an instantiated authentication plugin.
475 Loads and returns an instantiated authentication plugin.
476 Returns the RhodeCodeAuthPluginBase subclass on success,
476 Returns the RhodeCodeAuthPluginBase subclass on success,
477 raises exceptions on failure.
477 or None on failure.
478
479 raises:
480 KeyError -- if no plugin available with given name
481 TypeError -- if the RhodeCodeAuthPlugin is not a subclass of
482 ours RhodeCodeAuthPluginBase
483 """
478 """
484 # TODO: Disusing pyramids thread locals to retrieve the registry.
479 # TODO: Disusing pyramids thread locals to retrieve the registry.
485 authn_registry = get_current_registry().getUtility(IAuthnPluginRegistry)
480 authn_registry = get_current_registry().getUtility(IAuthnPluginRegistry)
486 plugin = authn_registry.get_plugin(plugin_id)
481 plugin = authn_registry.get_plugin(plugin_id)
487 if plugin is None:
482 if plugin is None:
488 log.error('Authentication plugin not found: "%s"', plugin_id)
483 log.error('Authentication plugin not found: "%s"', plugin_id)
489 return plugin
484 return plugin
490
485
491
486
492 def get_auth_cache_manager(custom_ttl=None):
487 def get_auth_cache_manager(custom_ttl=None):
493 return caches.get_cache_manager(
488 return caches.get_cache_manager(
494 'auth_plugins', 'rhodecode.authentication', custom_ttl)
489 'auth_plugins', 'rhodecode.authentication', custom_ttl)
495
490
496
491
497 def authenticate(username, password, environ=None, auth_type=None,
492 def authenticate(username, password, environ=None, auth_type=None,
498 skip_missing=False):
493 skip_missing=False):
499 """
494 """
500 Authentication function used for access control,
495 Authentication function used for access control,
501 It tries to authenticate based on enabled authentication modules.
496 It tries to authenticate based on enabled authentication modules.
502
497
503 :param username: username can be empty for container auth
498 :param username: username can be empty for container auth
504 :param password: password can be empty for container auth
499 :param password: password can be empty for container auth
505 :param environ: environ headers passed for container auth
500 :param environ: environ headers passed for container auth
506 :param auth_type: type of authentication, either `HTTP_TYPE` or `VCS_TYPE`
501 :param auth_type: type of authentication, either `HTTP_TYPE` or `VCS_TYPE`
507 :param skip_missing: ignores plugins that are in db but not in environment
502 :param skip_missing: ignores plugins that are in db but not in environment
508 :returns: None if auth failed, plugin_user dict if auth is correct
503 :returns: None if auth failed, plugin_user dict if auth is correct
509 """
504 """
510 if not auth_type or auth_type not in [HTTP_TYPE, VCS_TYPE]:
505 if not auth_type or auth_type not in [HTTP_TYPE, VCS_TYPE]:
511 raise ValueError('auth type must be on of http, vcs got "%s" instead'
506 raise ValueError('auth type must be on of http, vcs got "%s" instead'
512 % auth_type)
507 % auth_type)
513 container_only = environ and not (username and password)
508 container_only = environ and not (username and password)
514
509
515 authn_registry = get_current_registry().getUtility(IAuthnPluginRegistry)
510 authn_registry = get_current_registry().getUtility(IAuthnPluginRegistry)
516 for plugin in authn_registry.get_plugins_for_authentication():
511 for plugin in authn_registry.get_plugins_for_authentication():
517 plugin.set_auth_type(auth_type)
512 plugin.set_auth_type(auth_type)
518 user = plugin.get_user(username)
513 user = plugin.get_user(username)
519 display_user = user.username if user else username
514 display_user = user.username if user else username
520
515
521 if container_only and not plugin.is_container_auth:
516 if container_only and not plugin.is_container_auth:
522 log.debug('Auth type is for container only and plugin `%s` is not '
517 log.debug('Auth type is for container only and plugin `%s` is not '
523 'container plugin, skipping...', plugin.get_id())
518 'container plugin, skipping...', plugin.get_id())
524 continue
519 continue
525
520
526 # load plugin settings from RhodeCode database
521 # load plugin settings from RhodeCode database
527 plugin_settings = plugin.get_settings()
522 plugin_settings = plugin.get_settings()
528 log.debug('Plugin settings:%s', plugin_settings)
523 log.debug('Plugin settings:%s', plugin_settings)
529
524
530 log.debug('Trying authentication using ** %s **', plugin.get_id())
525 log.debug('Trying authentication using ** %s **', plugin.get_id())
531 # use plugin's method of user extraction.
526 # use plugin's method of user extraction.
532 user = plugin.get_user(username, environ=environ,
527 user = plugin.get_user(username, environ=environ,
533 settings=plugin_settings)
528 settings=plugin_settings)
534 display_user = user.username if user else username
529 display_user = user.username if user else username
535 log.debug(
530 log.debug(
536 'Plugin %s extracted user is `%s`', plugin.get_id(), display_user)
531 'Plugin %s extracted user is `%s`', plugin.get_id(), display_user)
537
532
538 if not plugin.allows_authentication_from(user):
533 if not plugin.allows_authentication_from(user):
539 log.debug('Plugin %s does not accept user `%s` for authentication',
534 log.debug('Plugin %s does not accept user `%s` for authentication',
540 plugin.get_id(), display_user)
535 plugin.get_id(), display_user)
541 continue
536 continue
542 else:
537 else:
543 log.debug('Plugin %s accepted user `%s` for authentication',
538 log.debug('Plugin %s accepted user `%s` for authentication',
544 plugin.get_id(), display_user)
539 plugin.get_id(), display_user)
545
540
546 log.info('Authenticating user `%s` using %s plugin',
541 log.info('Authenticating user `%s` using %s plugin',
547 display_user, plugin.get_id())
542 display_user, plugin.get_id())
548
543
549 _cache_ttl = 0
544 _cache_ttl = 0
550
545
551 if isinstance(plugin.AUTH_CACHE_TTL, (int, long)):
546 if isinstance(plugin.AUTH_CACHE_TTL, (int, long)):
552 # plugin cache set inside is more important than the settings value
547 # plugin cache set inside is more important than the settings value
553 _cache_ttl = plugin.AUTH_CACHE_TTL
548 _cache_ttl = plugin.AUTH_CACHE_TTL
554 elif plugin_settings.get('auth_cache_ttl'):
549 elif plugin_settings.get('auth_cache_ttl'):
555 _cache_ttl = safe_int(plugin_settings.get('auth_cache_ttl'), 0)
550 _cache_ttl = safe_int(plugin_settings.get('auth_cache_ttl'), 0)
556
551
557 plugin_cache_active = bool(_cache_ttl and _cache_ttl > 0)
552 plugin_cache_active = bool(_cache_ttl and _cache_ttl > 0)
558
553
559 # get instance of cache manager configured for a namespace
554 # get instance of cache manager configured for a namespace
560 cache_manager = get_auth_cache_manager(custom_ttl=_cache_ttl)
555 cache_manager = get_auth_cache_manager(custom_ttl=_cache_ttl)
561
556
562 log.debug('Cache for plugin `%s` active: %s', plugin.get_id(),
557 log.debug('Cache for plugin `%s` active: %s', plugin.get_id(),
563 plugin_cache_active)
558 plugin_cache_active)
564
559
565 # for environ based password can be empty, but then the validation is
560 # for environ based password can be empty, but then the validation is
566 # on the server that fills in the env data needed for authentication
561 # on the server that fills in the env data needed for authentication
567 _password_hash = md5_safe(plugin.name + username + (password or ''))
562 _password_hash = md5_safe(plugin.name + username + (password or ''))
568
563
569 # _authenticate is a wrapper for .auth() method of plugin.
564 # _authenticate is a wrapper for .auth() method of plugin.
570 # it checks if .auth() sends proper data.
565 # it checks if .auth() sends proper data.
571 # For RhodeCodeExternalAuthPlugin it also maps users to
566 # For RhodeCodeExternalAuthPlugin it also maps users to
572 # Database and maps the attributes returned from .auth()
567 # Database and maps the attributes returned from .auth()
573 # to RhodeCode database. If this function returns data
568 # to RhodeCode database. If this function returns data
574 # then auth is correct.
569 # then auth is correct.
575 start = time.time()
570 start = time.time()
576 log.debug('Running plugin `%s` _authenticate method',
571 log.debug('Running plugin `%s` _authenticate method',
577 plugin.get_id())
572 plugin.get_id())
578
573
579 def auth_func():
574 def auth_func():
580 """
575 """
581 This function is used internally in Cache of Beaker to calculate
576 This function is used internally in Cache of Beaker to calculate
582 Results
577 Results
583 """
578 """
584 return plugin._authenticate(
579 return plugin._authenticate(
585 user, username, password, plugin_settings,
580 user, username, password, plugin_settings,
586 environ=environ or {})
581 environ=environ or {})
587
582
588 if plugin_cache_active:
583 if plugin_cache_active:
589 plugin_user = cache_manager.get(
584 plugin_user = cache_manager.get(
590 _password_hash, createfunc=auth_func)
585 _password_hash, createfunc=auth_func)
591 else:
586 else:
592 plugin_user = auth_func()
587 plugin_user = auth_func()
593
588
594 auth_time = time.time() - start
589 auth_time = time.time() - start
595 log.debug('Authentication for plugin `%s` completed in %.3fs, '
590 log.debug('Authentication for plugin `%s` completed in %.3fs, '
596 'expiration time of fetched cache %.1fs.',
591 'expiration time of fetched cache %.1fs.',
597 plugin.get_id(), auth_time, _cache_ttl)
592 plugin.get_id(), auth_time, _cache_ttl)
598
593
599 log.debug('PLUGIN USER DATA: %s', plugin_user)
594 log.debug('PLUGIN USER DATA: %s', plugin_user)
600
595
601 if plugin_user:
596 if plugin_user:
602 log.debug('Plugin returned proper authentication data')
597 log.debug('Plugin returned proper authentication data')
603 return plugin_user
598 return plugin_user
604 # we failed to Auth because .auth() method didn't return proper user
599 # we failed to Auth because .auth() method didn't return proper user
605 log.debug("User `%s` failed to authenticate against %s",
600 log.debug("User `%s` failed to authenticate against %s",
606 display_user, plugin.get_id())
601 display_user, plugin.get_id())
607 return None
602 return None
@@ -1,1075 +1,1076 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Set of generic validators
22 Set of generic validators
23 """
23 """
24
24
25 import logging
25 import logging
26 import os
26 import os
27 import re
27 import re
28 from collections import defaultdict
28 from collections import defaultdict
29
29
30 import formencode
30 import formencode
31 import ipaddress
31 import ipaddress
32 from formencode.validators import (
32 from formencode.validators import (
33 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
33 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
34 NotEmpty, IPAddress, CIDR, String, FancyValidator
34 NotEmpty, IPAddress, CIDR, String, FancyValidator
35 )
35 )
36 from pylons.i18n.translation import _
36 from pylons.i18n.translation import _
37 from sqlalchemy.sql.expression import true
37 from sqlalchemy.sql.expression import true
38 from sqlalchemy.util import OrderedSet
38 from sqlalchemy.util import OrderedSet
39 from webhelpers.pylonslib.secure_form import authentication_token
39 from webhelpers.pylonslib.secure_form import authentication_token
40
40
41 from rhodecode.config.routing import ADMIN_PREFIX
41 from rhodecode.config.routing import ADMIN_PREFIX
42 from rhodecode.lib.auth import HasRepoGroupPermissionAny, HasPermissionAny
42 from rhodecode.lib.auth import HasRepoGroupPermissionAny, HasPermissionAny
43 from rhodecode.lib.exceptions import LdapImportError
43 from rhodecode.lib.exceptions import LdapImportError
44 from rhodecode.lib.utils import repo_name_slug, make_db_config
44 from rhodecode.lib.utils import repo_name_slug, make_db_config
45 from rhodecode.lib.utils2 import safe_int, str2bool, aslist, md5
45 from rhodecode.lib.utils2 import safe_int, str2bool, aslist, md5
46 from rhodecode.lib.vcs.backends.git.repository import GitRepository
46 from rhodecode.lib.vcs.backends.git.repository import GitRepository
47 from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
47 from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
48 from rhodecode.lib.vcs.backends.svn.repository import SubversionRepository
48 from rhodecode.lib.vcs.backends.svn.repository import SubversionRepository
49 from rhodecode.model.db import (
49 from rhodecode.model.db import (
50 RepoGroup, Repository, UserGroup, User, ChangesetStatus, Gist)
50 RepoGroup, Repository, UserGroup, User, ChangesetStatus, Gist)
51 from rhodecode.model.settings import VcsSettingsModel
51 from rhodecode.model.settings import VcsSettingsModel
52
52
53 # silence warnings and pylint
53 # silence warnings and pylint
54 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
54 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
55 NotEmpty, IPAddress, CIDR, String, FancyValidator
55 NotEmpty, IPAddress, CIDR, String, FancyValidator
56
56
57 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
58
58
59
59
60 class _Missing(object):
60 class _Missing(object):
61 pass
61 pass
62
62
63 Missing = _Missing()
63 Missing = _Missing()
64
64
65
65
66 class StateObj(object):
66 class StateObj(object):
67 """
67 """
68 this is needed to translate the messages using _() in validators
68 this is needed to translate the messages using _() in validators
69 """
69 """
70 _ = staticmethod(_)
70 _ = staticmethod(_)
71
71
72
72
73 def M(self, key, state=None, **kwargs):
73 def M(self, key, state=None, **kwargs):
74 """
74 """
75 returns string from self.message based on given key,
75 returns string from self.message based on given key,
76 passed kw params are used to substitute %(named)s params inside
76 passed kw params are used to substitute %(named)s params inside
77 translated strings
77 translated strings
78
78
79 :param msg:
79 :param msg:
80 :param state:
80 :param state:
81 """
81 """
82 if state is None:
82 if state is None:
83 state = StateObj()
83 state = StateObj()
84 else:
84 else:
85 state._ = staticmethod(_)
85 state._ = staticmethod(_)
86 # inject validator into state object
86 # inject validator into state object
87 return self.message(key, state, **kwargs)
87 return self.message(key, state, **kwargs)
88
88
89
89
90 def UniqueList(convert=None):
90 def UniqueList(convert=None):
91 class _UniqueList(formencode.FancyValidator):
91 class _UniqueList(formencode.FancyValidator):
92 """
92 """
93 Unique List !
93 Unique List !
94 """
94 """
95 messages = {
95 messages = {
96 'empty': _(u'Value cannot be an empty list'),
96 'empty': _(u'Value cannot be an empty list'),
97 'missing_value': _(u'Value cannot be an empty list'),
97 'missing_value': _(u'Value cannot be an empty list'),
98 }
98 }
99
99
100 def _to_python(self, value, state):
100 def _to_python(self, value, state):
101 ret_val = []
101 ret_val = []
102
102
103 def make_unique(value):
103 def make_unique(value):
104 seen = []
104 seen = []
105 return [c for c in value if not (c in seen or seen.append(c))]
105 return [c for c in value if not (c in seen or seen.append(c))]
106
106
107 if isinstance(value, list):
107 if isinstance(value, list):
108 ret_val = make_unique(value)
108 ret_val = make_unique(value)
109 elif isinstance(value, set):
109 elif isinstance(value, set):
110 ret_val = make_unique(list(value))
110 ret_val = make_unique(list(value))
111 elif isinstance(value, tuple):
111 elif isinstance(value, tuple):
112 ret_val = make_unique(list(value))
112 ret_val = make_unique(list(value))
113 elif value is None:
113 elif value is None:
114 ret_val = []
114 ret_val = []
115 else:
115 else:
116 ret_val = [value]
116 ret_val = [value]
117
117
118 if convert:
118 if convert:
119 ret_val = map(convert, ret_val)
119 ret_val = map(convert, ret_val)
120 return ret_val
120 return ret_val
121
121
122 def empty_value(self, value):
122 def empty_value(self, value):
123 return []
123 return []
124
124
125 return _UniqueList
125 return _UniqueList
126
126
127
127
128 def UniqueListFromString():
128 def UniqueListFromString():
129 class _UniqueListFromString(UniqueList()):
129 class _UniqueListFromString(UniqueList()):
130 def _to_python(self, value, state):
130 def _to_python(self, value, state):
131 if isinstance(value, basestring):
131 if isinstance(value, basestring):
132 value = aslist(value, ',')
132 value = aslist(value, ',')
133 return super(_UniqueListFromString, self)._to_python(value, state)
133 return super(_UniqueListFromString, self)._to_python(value, state)
134 return _UniqueListFromString
134 return _UniqueListFromString
135
135
136
136
137 def ValidSvnPattern(section, repo_name=None):
137 def ValidSvnPattern(section, repo_name=None):
138 class _validator(formencode.validators.FancyValidator):
138 class _validator(formencode.validators.FancyValidator):
139 messages = {
139 messages = {
140 'pattern_exists': _(u'Pattern already exists'),
140 'pattern_exists': _(u'Pattern already exists'),
141 }
141 }
142
142
143 def validate_python(self, value, state):
143 def validate_python(self, value, state):
144 if not value:
144 if not value:
145 return
145 return
146 model = VcsSettingsModel(repo=repo_name)
146 model = VcsSettingsModel(repo=repo_name)
147 ui_settings = model.get_svn_patterns(section=section)
147 ui_settings = model.get_svn_patterns(section=section)
148 for entry in ui_settings:
148 for entry in ui_settings:
149 if value == entry.value:
149 if value == entry.value:
150 msg = M(self, 'pattern_exists', state)
150 msg = M(self, 'pattern_exists', state)
151 raise formencode.Invalid(msg, value, state)
151 raise formencode.Invalid(msg, value, state)
152 return _validator
152 return _validator
153
153
154
154
155 def ValidUsername(edit=False, old_data={}):
155 def ValidUsername(edit=False, old_data={}):
156 class _validator(formencode.validators.FancyValidator):
156 class _validator(formencode.validators.FancyValidator):
157 messages = {
157 messages = {
158 'username_exists': _(u'Username "%(username)s" already exists'),
158 'username_exists': _(u'Username "%(username)s" already exists'),
159 'system_invalid_username':
159 'system_invalid_username':
160 _(u'Username "%(username)s" is forbidden'),
160 _(u'Username "%(username)s" is forbidden'),
161 'invalid_username':
161 'invalid_username':
162 _(u'Username may only contain alphanumeric characters '
162 _(u'Username may only contain alphanumeric characters '
163 u'underscores, periods or dashes and must begin with '
163 u'underscores, periods or dashes and must begin with '
164 u'alphanumeric character or underscore')
164 u'alphanumeric character or underscore')
165 }
165 }
166
166
167 def validate_python(self, value, state):
167 def validate_python(self, value, state):
168 if value in ['default', 'new_user']:
168 if value in ['default', 'new_user']:
169 msg = M(self, 'system_invalid_username', state, username=value)
169 msg = M(self, 'system_invalid_username', state, username=value)
170 raise formencode.Invalid(msg, value, state)
170 raise formencode.Invalid(msg, value, state)
171 # check if user is unique
171 # check if user is unique
172 old_un = None
172 old_un = None
173 if edit:
173 if edit:
174 old_un = User.get(old_data.get('user_id')).username
174 old_un = User.get(old_data.get('user_id')).username
175
175
176 if old_un != value or not edit:
176 if old_un != value or not edit:
177 if User.get_by_username(value, case_insensitive=True):
177 if User.get_by_username(value, case_insensitive=True):
178 msg = M(self, 'username_exists', state, username=value)
178 msg = M(self, 'username_exists', state, username=value)
179 raise formencode.Invalid(msg, value, state)
179 raise formencode.Invalid(msg, value, state)
180
180
181 if (re.match(r'^[\w]{1}[\w\-\.]{0,254}$', value)
181 if (re.match(r'^[\w]{1}[\w\-\.]{0,254}$', value)
182 is None):
182 is None):
183 msg = M(self, 'invalid_username', state)
183 msg = M(self, 'invalid_username', state)
184 raise formencode.Invalid(msg, value, state)
184 raise formencode.Invalid(msg, value, state)
185 return _validator
185 return _validator
186
186
187
187
188 def ValidRegex(msg=None):
188 def ValidRegex(msg=None):
189 class _validator(formencode.validators.Regex):
189 class _validator(formencode.validators.Regex):
190 messages = {'invalid': msg or _(u'The input is not valid')}
190 messages = {'invalid': msg or _(u'The input is not valid')}
191 return _validator
191 return _validator
192
192
193
193
194 def ValidRepoUser():
194 def ValidRepoUser():
195 class _validator(formencode.validators.FancyValidator):
195 class _validator(formencode.validators.FancyValidator):
196 messages = {
196 messages = {
197 'invalid_username': _(u'Username %(username)s is not valid')
197 'invalid_username': _(u'Username %(username)s is not valid')
198 }
198 }
199
199
200 def validate_python(self, value, state):
200 def validate_python(self, value, state):
201 try:
201 try:
202 User.query().filter(User.active == true())\
202 User.query().filter(User.active == true())\
203 .filter(User.username == value).one()
203 .filter(User.username == value).one()
204 except Exception:
204 except Exception:
205 msg = M(self, 'invalid_username', state, username=value)
205 msg = M(self, 'invalid_username', state, username=value)
206 raise formencode.Invalid(
206 raise formencode.Invalid(
207 msg, value, state, error_dict={'username': msg}
207 msg, value, state, error_dict={'username': msg}
208 )
208 )
209
209
210 return _validator
210 return _validator
211
211
212
212
213 def ValidUserGroup(edit=False, old_data={}):
213 def ValidUserGroup(edit=False, old_data={}):
214 class _validator(formencode.validators.FancyValidator):
214 class _validator(formencode.validators.FancyValidator):
215 messages = {
215 messages = {
216 'invalid_group': _(u'Invalid user group name'),
216 'invalid_group': _(u'Invalid user group name'),
217 'group_exist': _(u'User group "%(usergroup)s" already exists'),
217 'group_exist': _(u'User group "%(usergroup)s" already exists'),
218 'invalid_usergroup_name':
218 'invalid_usergroup_name':
219 _(u'user group name may only contain alphanumeric '
219 _(u'user group name may only contain alphanumeric '
220 u'characters underscores, periods or dashes and must begin '
220 u'characters underscores, periods or dashes and must begin '
221 u'with alphanumeric character')
221 u'with alphanumeric character')
222 }
222 }
223
223
224 def validate_python(self, value, state):
224 def validate_python(self, value, state):
225 if value in ['default']:
225 if value in ['default']:
226 msg = M(self, 'invalid_group', state)
226 msg = M(self, 'invalid_group', state)
227 raise formencode.Invalid(
227 raise formencode.Invalid(
228 msg, value, state, error_dict={'users_group_name': msg}
228 msg, value, state, error_dict={'users_group_name': msg}
229 )
229 )
230 # check if group is unique
230 # check if group is unique
231 old_ugname = None
231 old_ugname = None
232 if edit:
232 if edit:
233 old_id = old_data.get('users_group_id')
233 old_id = old_data.get('users_group_id')
234 old_ugname = UserGroup.get(old_id).users_group_name
234 old_ugname = UserGroup.get(old_id).users_group_name
235
235
236 if old_ugname != value or not edit:
236 if old_ugname != value or not edit:
237 is_existing_group = UserGroup.get_by_group_name(
237 is_existing_group = UserGroup.get_by_group_name(
238 value, case_insensitive=True)
238 value, case_insensitive=True)
239 if is_existing_group:
239 if is_existing_group:
240 msg = M(self, 'group_exist', state, usergroup=value)
240 msg = M(self, 'group_exist', state, usergroup=value)
241 raise formencode.Invalid(
241 raise formencode.Invalid(
242 msg, value, state, error_dict={'users_group_name': msg}
242 msg, value, state, error_dict={'users_group_name': msg}
243 )
243 )
244
244
245 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
245 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
246 msg = M(self, 'invalid_usergroup_name', state)
246 msg = M(self, 'invalid_usergroup_name', state)
247 raise formencode.Invalid(
247 raise formencode.Invalid(
248 msg, value, state, error_dict={'users_group_name': msg}
248 msg, value, state, error_dict={'users_group_name': msg}
249 )
249 )
250
250
251 return _validator
251 return _validator
252
252
253
253
254 def ValidRepoGroup(edit=False, old_data={}, can_create_in_root=False):
254 def ValidRepoGroup(edit=False, old_data={}, can_create_in_root=False):
255 class _validator(formencode.validators.FancyValidator):
255 class _validator(formencode.validators.FancyValidator):
256 messages = {
256 messages = {
257 'group_parent_id': _(u'Cannot assign this group as parent'),
257 'group_parent_id': _(u'Cannot assign this group as parent'),
258 'group_exists': _(u'Group "%(group_name)s" already exists'),
258 'group_exists': _(u'Group "%(group_name)s" already exists'),
259 'repo_exists': _(u'Repository with name "%(group_name)s" '
259 'repo_exists': _(u'Repository with name "%(group_name)s" '
260 u'already exists'),
260 u'already exists'),
261 'permission_denied': _(u"no permission to store repository group"
261 'permission_denied': _(u"no permission to store repository group"
262 u"in this location"),
262 u"in this location"),
263 'permission_denied_root': _(
263 'permission_denied_root': _(
264 u"no permission to store repository group "
264 u"no permission to store repository group "
265 u"in root location")
265 u"in root location")
266 }
266 }
267
267
268 def _to_python(self, value, state):
268 def _to_python(self, value, state):
269 group_name = repo_name_slug(value.get('group_name', ''))
269 group_name = repo_name_slug(value.get('group_name', ''))
270 group_parent_id = safe_int(value.get('group_parent_id'))
270 group_parent_id = safe_int(value.get('group_parent_id'))
271 gr = RepoGroup.get(group_parent_id)
271 gr = RepoGroup.get(group_parent_id)
272 if gr:
272 if gr:
273 parent_group_path = gr.full_path
273 parent_group_path = gr.full_path
274 # value needs to be aware of group name in order to check
274 # value needs to be aware of group name in order to check
275 # db key This is an actual just the name to store in the
275 # db key This is an actual just the name to store in the
276 # database
276 # database
277 group_name_full = (
277 group_name_full = (
278 parent_group_path + RepoGroup.url_sep() + group_name)
278 parent_group_path + RepoGroup.url_sep() + group_name)
279 else:
279 else:
280 group_name_full = group_name
280 group_name_full = group_name
281
281
282 value['group_name'] = group_name
282 value['group_name'] = group_name
283 value['group_name_full'] = group_name_full
283 value['group_name_full'] = group_name_full
284 value['group_parent_id'] = group_parent_id
284 value['group_parent_id'] = group_parent_id
285 return value
285 return value
286
286
287 def validate_python(self, value, state):
287 def validate_python(self, value, state):
288
288
289 old_group_name = None
289 old_group_name = None
290 group_name = value.get('group_name')
290 group_name = value.get('group_name')
291 group_name_full = value.get('group_name_full')
291 group_name_full = value.get('group_name_full')
292 group_parent_id = safe_int(value.get('group_parent_id'))
292 group_parent_id = safe_int(value.get('group_parent_id'))
293 if group_parent_id == -1:
293 if group_parent_id == -1:
294 group_parent_id = None
294 group_parent_id = None
295
295
296 group_obj = RepoGroup.get(old_data.get('group_id'))
296 group_obj = RepoGroup.get(old_data.get('group_id'))
297 parent_group_changed = False
297 parent_group_changed = False
298 if edit:
298 if edit:
299 old_group_name = group_obj.group_name
299 old_group_name = group_obj.group_name
300 old_group_parent_id = group_obj.group_parent_id
300 old_group_parent_id = group_obj.group_parent_id
301
301
302 if group_parent_id != old_group_parent_id:
302 if group_parent_id != old_group_parent_id:
303 parent_group_changed = True
303 parent_group_changed = True
304
304
305 # TODO: mikhail: the following if statement is not reached
305 # TODO: mikhail: the following if statement is not reached
306 # since group_parent_id's OneOf validation fails before.
306 # since group_parent_id's OneOf validation fails before.
307 # Can be removed.
307 # Can be removed.
308
308
309 # check against setting a parent of self
309 # check against setting a parent of self
310 parent_of_self = (
310 parent_of_self = (
311 old_data['group_id'] == group_parent_id
311 old_data['group_id'] == group_parent_id
312 if group_parent_id else False
312 if group_parent_id else False
313 )
313 )
314 if parent_of_self:
314 if parent_of_self:
315 msg = M(self, 'group_parent_id', state)
315 msg = M(self, 'group_parent_id', state)
316 raise formencode.Invalid(
316 raise formencode.Invalid(
317 msg, value, state, error_dict={'group_parent_id': msg}
317 msg, value, state, error_dict={'group_parent_id': msg}
318 )
318 )
319
319
320 # group we're moving current group inside
320 # group we're moving current group inside
321 child_group = None
321 child_group = None
322 if group_parent_id:
322 if group_parent_id:
323 child_group = RepoGroup.query().filter(
323 child_group = RepoGroup.query().filter(
324 RepoGroup.group_id == group_parent_id).scalar()
324 RepoGroup.group_id == group_parent_id).scalar()
325
325
326 # do a special check that we cannot move a group to one of
326 # do a special check that we cannot move a group to one of
327 # it's children
327 # it's children
328 if edit and child_group:
328 if edit and child_group:
329 parents = [x.group_id for x in child_group.parents]
329 parents = [x.group_id for x in child_group.parents]
330 move_to_children = old_data['group_id'] in parents
330 move_to_children = old_data['group_id'] in parents
331 if move_to_children:
331 if move_to_children:
332 msg = M(self, 'group_parent_id', state)
332 msg = M(self, 'group_parent_id', state)
333 raise formencode.Invalid(
333 raise formencode.Invalid(
334 msg, value, state, error_dict={'group_parent_id': msg})
334 msg, value, state, error_dict={'group_parent_id': msg})
335
335
336 # Check if we have permission to store in the parent.
336 # Check if we have permission to store in the parent.
337 # Only check if the parent group changed.
337 # Only check if the parent group changed.
338 if parent_group_changed:
338 if parent_group_changed:
339 if child_group is None:
339 if child_group is None:
340 if not can_create_in_root:
340 if not can_create_in_root:
341 msg = M(self, 'permission_denied_root', state)
341 msg = M(self, 'permission_denied_root', state)
342 raise formencode.Invalid(
342 raise formencode.Invalid(
343 msg, value, state,
343 msg, value, state,
344 error_dict={'group_parent_id': msg})
344 error_dict={'group_parent_id': msg})
345 else:
345 else:
346 valid = HasRepoGroupPermissionAny('group.admin')
346 valid = HasRepoGroupPermissionAny('group.admin')
347 forbidden = not valid(
347 forbidden = not valid(
348 child_group.group_name, 'can create group validator')
348 child_group.group_name, 'can create group validator')
349 if forbidden:
349 if forbidden:
350 msg = M(self, 'permission_denied', state)
350 msg = M(self, 'permission_denied', state)
351 raise formencode.Invalid(
351 raise formencode.Invalid(
352 msg, value, state,
352 msg, value, state,
353 error_dict={'group_parent_id': msg})
353 error_dict={'group_parent_id': msg})
354
354
355 # if we change the name or it's new group, check for existing names
355 # if we change the name or it's new group, check for existing names
356 # or repositories with the same name
356 # or repositories with the same name
357 if old_group_name != group_name_full or not edit:
357 if old_group_name != group_name_full or not edit:
358 # check group
358 # check group
359 gr = RepoGroup.get_by_group_name(group_name_full)
359 gr = RepoGroup.get_by_group_name(group_name_full)
360 if gr:
360 if gr:
361 msg = M(self, 'group_exists', state, group_name=group_name)
361 msg = M(self, 'group_exists', state, group_name=group_name)
362 raise formencode.Invalid(
362 raise formencode.Invalid(
363 msg, value, state, error_dict={'group_name': msg})
363 msg, value, state, error_dict={'group_name': msg})
364
364
365 # check for same repo
365 # check for same repo
366 repo = Repository.get_by_repo_name(group_name_full)
366 repo = Repository.get_by_repo_name(group_name_full)
367 if repo:
367 if repo:
368 msg = M(self, 'repo_exists', state, group_name=group_name)
368 msg = M(self, 'repo_exists', state, group_name=group_name)
369 raise formencode.Invalid(
369 raise formencode.Invalid(
370 msg, value, state, error_dict={'group_name': msg})
370 msg, value, state, error_dict={'group_name': msg})
371
371
372 return _validator
372 return _validator
373
373
374
374
375 def ValidPassword():
375 def ValidPassword():
376 class _validator(formencode.validators.FancyValidator):
376 class _validator(formencode.validators.FancyValidator):
377 messages = {
377 messages = {
378 'invalid_password':
378 'invalid_password':
379 _(u'Invalid characters (non-ascii) in password')
379 _(u'Invalid characters (non-ascii) in password')
380 }
380 }
381
381
382 def validate_python(self, value, state):
382 def validate_python(self, value, state):
383 try:
383 try:
384 (value or '').decode('ascii')
384 (value or '').decode('ascii')
385 except UnicodeError:
385 except UnicodeError:
386 msg = M(self, 'invalid_password', state)
386 msg = M(self, 'invalid_password', state)
387 raise formencode.Invalid(msg, value, state,)
387 raise formencode.Invalid(msg, value, state,)
388 return _validator
388 return _validator
389
389
390
390
391 def ValidOldPassword(username):
391 def ValidOldPassword(username):
392 class _validator(formencode.validators.FancyValidator):
392 class _validator(formencode.validators.FancyValidator):
393 messages = {
393 messages = {
394 'invalid_password': _(u'Invalid old password')
394 'invalid_password': _(u'Invalid old password')
395 }
395 }
396
396
397 def validate_python(self, value, state):
397 def validate_python(self, value, state):
398 from rhodecode.authentication.base import authenticate, HTTP_TYPE
398 from rhodecode.authentication.base import authenticate, HTTP_TYPE
399 if not authenticate(username, value, '', HTTP_TYPE):
399 if not authenticate(username, value, '', HTTP_TYPE):
400 msg = M(self, 'invalid_password', state)
400 msg = M(self, 'invalid_password', state)
401 raise formencode.Invalid(
401 raise formencode.Invalid(
402 msg, value, state, error_dict={'current_password': msg}
402 msg, value, state, error_dict={'current_password': msg}
403 )
403 )
404 return _validator
404 return _validator
405
405
406
406
407 def ValidPasswordsMatch(
407 def ValidPasswordsMatch(
408 passwd='new_password', passwd_confirmation='password_confirmation'):
408 passwd='new_password', passwd_confirmation='password_confirmation'):
409 class _validator(formencode.validators.FancyValidator):
409 class _validator(formencode.validators.FancyValidator):
410 messages = {
410 messages = {
411 'password_mismatch': _(u'Passwords do not match'),
411 'password_mismatch': _(u'Passwords do not match'),
412 }
412 }
413
413
414 def validate_python(self, value, state):
414 def validate_python(self, value, state):
415
415
416 pass_val = value.get('password') or value.get(passwd)
416 pass_val = value.get('password') or value.get(passwd)
417 if pass_val != value[passwd_confirmation]:
417 if pass_val != value[passwd_confirmation]:
418 msg = M(self, 'password_mismatch', state)
418 msg = M(self, 'password_mismatch', state)
419 raise formencode.Invalid(
419 raise formencode.Invalid(
420 msg, value, state,
420 msg, value, state,
421 error_dict={passwd: msg, passwd_confirmation: msg}
421 error_dict={passwd: msg, passwd_confirmation: msg}
422 )
422 )
423 return _validator
423 return _validator
424
424
425
425
426 def ValidAuth():
426 def ValidAuth():
427 class _validator(formencode.validators.FancyValidator):
427 class _validator(formencode.validators.FancyValidator):
428 messages = {
428 messages = {
429 'invalid_password': _(u'invalid password'),
429 'invalid_password': _(u'invalid password'),
430 'invalid_username': _(u'invalid user name'),
430 'invalid_username': _(u'invalid user name'),
431 'disabled_account': _(u'Your account is disabled')
431 'disabled_account': _(u'Your account is disabled')
432 }
432 }
433
433
434 def validate_python(self, value, state):
434 def validate_python(self, value, state):
435 from rhodecode.authentication.base import authenticate, HTTP_TYPE
435 from rhodecode.authentication.base import authenticate, HTTP_TYPE
436
436
437 password = value['password']
437 password = value['password']
438 username = value['username']
438 username = value['username']
439
439
440 if not authenticate(username, password, '', HTTP_TYPE,
440 if not authenticate(username, password, '', HTTP_TYPE,
441 skip_missing=True):
441 skip_missing=True):
442 user = User.get_by_username(username)
442 user = User.get_by_username(username)
443 if user and not user.active:
443 if user and not user.active:
444 log.warning('user %s is disabled', username)
444 log.warning('user %s is disabled', username)
445 msg = M(self, 'disabled_account', state)
445 msg = M(self, 'disabled_account', state)
446 raise formencode.Invalid(
446 raise formencode.Invalid(
447 msg, value, state, error_dict={'username': msg}
447 msg, value, state, error_dict={'username': msg}
448 )
448 )
449 else:
449 else:
450 log.warning('user `%s` failed to authenticate', username)
450 log.warning('user `%s` failed to authenticate', username)
451 msg = M(self, 'invalid_username', state)
451 msg = M(self, 'invalid_username', state)
452 msg2 = M(self, 'invalid_password', state)
452 msg2 = M(self, 'invalid_password', state)
453 raise formencode.Invalid(
453 raise formencode.Invalid(
454 msg, value, state,
454 msg, value, state,
455 error_dict={'username': msg, 'password': msg2}
455 error_dict={'username': msg, 'password': msg2}
456 )
456 )
457 return _validator
457 return _validator
458
458
459
459
460 def ValidAuthToken():
460 def ValidAuthToken():
461 class _validator(formencode.validators.FancyValidator):
461 class _validator(formencode.validators.FancyValidator):
462 messages = {
462 messages = {
463 'invalid_token': _(u'Token mismatch')
463 'invalid_token': _(u'Token mismatch')
464 }
464 }
465
465
466 def validate_python(self, value, state):
466 def validate_python(self, value, state):
467 if value != authentication_token():
467 if value != authentication_token():
468 msg = M(self, 'invalid_token', state)
468 msg = M(self, 'invalid_token', state)
469 raise formencode.Invalid(msg, value, state)
469 raise formencode.Invalid(msg, value, state)
470 return _validator
470 return _validator
471
471
472
472
473 def ValidRepoName(edit=False, old_data={}):
473 def ValidRepoName(edit=False, old_data={}):
474 class _validator(formencode.validators.FancyValidator):
474 class _validator(formencode.validators.FancyValidator):
475 messages = {
475 messages = {
476 'invalid_repo_name':
476 'invalid_repo_name':
477 _(u'Repository name %(repo)s is disallowed'),
477 _(u'Repository name %(repo)s is disallowed'),
478 # top level
478 # top level
479 'repository_exists': _(u'Repository with name %(repo)s '
479 'repository_exists': _(u'Repository with name %(repo)s '
480 u'already exists'),
480 u'already exists'),
481 'group_exists': _(u'Repository group with name "%(repo)s" '
481 'group_exists': _(u'Repository group with name "%(repo)s" '
482 u'already exists'),
482 u'already exists'),
483 # inside a group
483 # inside a group
484 'repository_in_group_exists': _(u'Repository with name %(repo)s '
484 'repository_in_group_exists': _(u'Repository with name %(repo)s '
485 u'exists in group "%(group)s"'),
485 u'exists in group "%(group)s"'),
486 'group_in_group_exists': _(
486 'group_in_group_exists': _(
487 u'Repository group with name "%(repo)s" '
487 u'Repository group with name "%(repo)s" '
488 u'exists in group "%(group)s"'),
488 u'exists in group "%(group)s"'),
489 }
489 }
490
490
491 def _to_python(self, value, state):
491 def _to_python(self, value, state):
492 repo_name = repo_name_slug(value.get('repo_name', ''))
492 repo_name = repo_name_slug(value.get('repo_name', ''))
493 repo_group = value.get('repo_group')
493 repo_group = value.get('repo_group')
494 if repo_group:
494 if repo_group:
495 gr = RepoGroup.get(repo_group)
495 gr = RepoGroup.get(repo_group)
496 group_path = gr.full_path
496 group_path = gr.full_path
497 group_name = gr.group_name
497 group_name = gr.group_name
498 # value needs to be aware of group name in order to check
498 # value needs to be aware of group name in order to check
499 # db key This is an actual just the name to store in the
499 # db key This is an actual just the name to store in the
500 # database
500 # database
501 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
501 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
502 else:
502 else:
503 group_name = group_path = ''
503 group_name = group_path = ''
504 repo_name_full = repo_name
504 repo_name_full = repo_name
505
505
506 value['repo_name'] = repo_name
506 value['repo_name'] = repo_name
507 value['repo_name_full'] = repo_name_full
507 value['repo_name_full'] = repo_name_full
508 value['group_path'] = group_path
508 value['group_path'] = group_path
509 value['group_name'] = group_name
509 value['group_name'] = group_name
510 return value
510 return value
511
511
512 def validate_python(self, value, state):
512 def validate_python(self, value, state):
513
513
514 repo_name = value.get('repo_name')
514 repo_name = value.get('repo_name')
515 repo_name_full = value.get('repo_name_full')
515 repo_name_full = value.get('repo_name_full')
516 group_path = value.get('group_path')
516 group_path = value.get('group_path')
517 group_name = value.get('group_name')
517 group_name = value.get('group_name')
518
518
519 if repo_name in [ADMIN_PREFIX, '']:
519 if repo_name in [ADMIN_PREFIX, '']:
520 msg = M(self, 'invalid_repo_name', state, repo=repo_name)
520 msg = M(self, 'invalid_repo_name', state, repo=repo_name)
521 raise formencode.Invalid(
521 raise formencode.Invalid(
522 msg, value, state, error_dict={'repo_name': msg})
522 msg, value, state, error_dict={'repo_name': msg})
523
523
524 rename = old_data.get('repo_name') != repo_name_full
524 rename = old_data.get('repo_name') != repo_name_full
525 create = not edit
525 create = not edit
526 if rename or create:
526 if rename or create:
527
527
528 if group_path:
528 if group_path:
529 if Repository.get_by_repo_name(repo_name_full):
529 if Repository.get_by_repo_name(repo_name_full):
530 msg = M(self, 'repository_in_group_exists', state,
530 msg = M(self, 'repository_in_group_exists', state,
531 repo=repo_name, group=group_name)
531 repo=repo_name, group=group_name)
532 raise formencode.Invalid(
532 raise formencode.Invalid(
533 msg, value, state, error_dict={'repo_name': msg})
533 msg, value, state, error_dict={'repo_name': msg})
534 if RepoGroup.get_by_group_name(repo_name_full):
534 if RepoGroup.get_by_group_name(repo_name_full):
535 msg = M(self, 'group_in_group_exists', state,
535 msg = M(self, 'group_in_group_exists', state,
536 repo=repo_name, group=group_name)
536 repo=repo_name, group=group_name)
537 raise formencode.Invalid(
537 raise formencode.Invalid(
538 msg, value, state, error_dict={'repo_name': msg})
538 msg, value, state, error_dict={'repo_name': msg})
539 else:
539 else:
540 if RepoGroup.get_by_group_name(repo_name_full):
540 if RepoGroup.get_by_group_name(repo_name_full):
541 msg = M(self, 'group_exists', state, repo=repo_name)
541 msg = M(self, 'group_exists', state, repo=repo_name)
542 raise formencode.Invalid(
542 raise formencode.Invalid(
543 msg, value, state, error_dict={'repo_name': msg})
543 msg, value, state, error_dict={'repo_name': msg})
544
544
545 if Repository.get_by_repo_name(repo_name_full):
545 if Repository.get_by_repo_name(repo_name_full):
546 msg = M(
546 msg = M(
547 self, 'repository_exists', state, repo=repo_name)
547 self, 'repository_exists', state, repo=repo_name)
548 raise formencode.Invalid(
548 raise formencode.Invalid(
549 msg, value, state, error_dict={'repo_name': msg})
549 msg, value, state, error_dict={'repo_name': msg})
550 return value
550 return value
551 return _validator
551 return _validator
552
552
553
553
554 def ValidForkName(*args, **kwargs):
554 def ValidForkName(*args, **kwargs):
555 return ValidRepoName(*args, **kwargs)
555 return ValidRepoName(*args, **kwargs)
556
556
557
557
558 def SlugifyName():
558 def SlugifyName():
559 class _validator(formencode.validators.FancyValidator):
559 class _validator(formencode.validators.FancyValidator):
560
560
561 def _to_python(self, value, state):
561 def _to_python(self, value, state):
562 return repo_name_slug(value)
562 return repo_name_slug(value)
563
563
564 def validate_python(self, value, state):
564 def validate_python(self, value, state):
565 pass
565 pass
566
566
567 return _validator
567 return _validator
568
568
569
569
570 def ValidCloneUri():
570 def ValidCloneUri():
571 class InvalidCloneUrl(Exception):
571 class InvalidCloneUrl(Exception):
572 allowed_prefixes = ()
572 allowed_prefixes = ()
573
573
574 def url_handler(repo_type, url):
574 def url_handler(repo_type, url):
575 config = make_db_config(clear_session=False)
575 config = make_db_config(clear_session=False)
576 if repo_type == 'hg':
576 if repo_type == 'hg':
577 allowed_prefixes = ('http', 'svn+http', 'git+http')
577 allowed_prefixes = ('http', 'svn+http', 'git+http')
578
578
579 if 'http' in url[:4]:
579 if 'http' in url[:4]:
580 # initially check if it's at least the proper URL
580 # initially check if it's at least the proper URL
581 # or does it pass basic auth
581 # or does it pass basic auth
582 MercurialRepository.check_url(url, config)
582 MercurialRepository.check_url(url, config)
583 elif 'svn+http' in url[:8]: # svn->hg import
583 elif 'svn+http' in url[:8]: # svn->hg import
584 SubversionRepository.check_url(url, config)
584 SubversionRepository.check_url(url, config)
585 elif 'git+http' in url[:8]: # git->hg import
585 elif 'git+http' in url[:8]: # git->hg import
586 raise NotImplementedError()
586 raise NotImplementedError()
587 else:
587 else:
588 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
588 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
589 'Allowed url must start with one of %s'
589 'Allowed url must start with one of %s'
590 % (url, ','.join(allowed_prefixes)))
590 % (url, ','.join(allowed_prefixes)))
591 exc.allowed_prefixes = allowed_prefixes
591 exc.allowed_prefixes = allowed_prefixes
592 raise exc
592 raise exc
593
593
594 elif repo_type == 'git':
594 elif repo_type == 'git':
595 allowed_prefixes = ('http', 'svn+http', 'hg+http')
595 allowed_prefixes = ('http', 'svn+http', 'hg+http')
596 if 'http' in url[:4]:
596 if 'http' in url[:4]:
597 # initially check if it's at least the proper URL
597 # initially check if it's at least the proper URL
598 # or does it pass basic auth
598 # or does it pass basic auth
599 GitRepository.check_url(url, config)
599 GitRepository.check_url(url, config)
600 elif 'svn+http' in url[:8]: # svn->git import
600 elif 'svn+http' in url[:8]: # svn->git import
601 raise NotImplementedError()
601 raise NotImplementedError()
602 elif 'hg+http' in url[:8]: # hg->git import
602 elif 'hg+http' in url[:8]: # hg->git import
603 raise NotImplementedError()
603 raise NotImplementedError()
604 else:
604 else:
605 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
605 exc = InvalidCloneUrl('Clone from URI %s not allowed. '
606 'Allowed url must start with one of %s'
606 'Allowed url must start with one of %s'
607 % (url, ','.join(allowed_prefixes)))
607 % (url, ','.join(allowed_prefixes)))
608 exc.allowed_prefixes = allowed_prefixes
608 exc.allowed_prefixes = allowed_prefixes
609 raise exc
609 raise exc
610
610
611 class _validator(formencode.validators.FancyValidator):
611 class _validator(formencode.validators.FancyValidator):
612 messages = {
612 messages = {
613 'clone_uri': _(u'invalid clone url for %(rtype)s repository'),
613 'clone_uri': _(u'invalid clone url for %(rtype)s repository'),
614 'invalid_clone_uri': _(
614 'invalid_clone_uri': _(
615 u'Invalid clone url, provide a valid clone '
615 u'Invalid clone url, provide a valid clone '
616 u'url starting with one of %(allowed_prefixes)s')
616 u'url starting with one of %(allowed_prefixes)s')
617 }
617 }
618
618
619 def validate_python(self, value, state):
619 def validate_python(self, value, state):
620 repo_type = value.get('repo_type')
620 repo_type = value.get('repo_type')
621 url = value.get('clone_uri')
621 url = value.get('clone_uri')
622
622
623 if url:
623 if url:
624 try:
624 try:
625 url_handler(repo_type, url)
625 url_handler(repo_type, url)
626 except InvalidCloneUrl as e:
626 except InvalidCloneUrl as e:
627 log.warning(e)
627 log.warning(e)
628 msg = M(self, 'invalid_clone_uri', rtype=repo_type,
628 msg = M(self, 'invalid_clone_uri', rtype=repo_type,
629 allowed_prefixes=','.join(e.allowed_prefixes))
629 allowed_prefixes=','.join(e.allowed_prefixes))
630 raise formencode.Invalid(msg, value, state,
630 raise formencode.Invalid(msg, value, state,
631 error_dict={'clone_uri': msg})
631 error_dict={'clone_uri': msg})
632 except Exception:
632 except Exception:
633 log.exception('Url validation failed')
633 log.exception('Url validation failed')
634 msg = M(self, 'clone_uri', rtype=repo_type)
634 msg = M(self, 'clone_uri', rtype=repo_type)
635 raise formencode.Invalid(msg, value, state,
635 raise formencode.Invalid(msg, value, state,
636 error_dict={'clone_uri': msg})
636 error_dict={'clone_uri': msg})
637 return _validator
637 return _validator
638
638
639
639
640 def ValidForkType(old_data={}):
640 def ValidForkType(old_data={}):
641 class _validator(formencode.validators.FancyValidator):
641 class _validator(formencode.validators.FancyValidator):
642 messages = {
642 messages = {
643 'invalid_fork_type': _(u'Fork have to be the same type as parent')
643 'invalid_fork_type': _(u'Fork have to be the same type as parent')
644 }
644 }
645
645
646 def validate_python(self, value, state):
646 def validate_python(self, value, state):
647 if old_data['repo_type'] != value:
647 if old_data['repo_type'] != value:
648 msg = M(self, 'invalid_fork_type', state)
648 msg = M(self, 'invalid_fork_type', state)
649 raise formencode.Invalid(
649 raise formencode.Invalid(
650 msg, value, state, error_dict={'repo_type': msg}
650 msg, value, state, error_dict={'repo_type': msg}
651 )
651 )
652 return _validator
652 return _validator
653
653
654
654
655 def CanWriteGroup(old_data=None):
655 def CanWriteGroup(old_data=None):
656 class _validator(formencode.validators.FancyValidator):
656 class _validator(formencode.validators.FancyValidator):
657 messages = {
657 messages = {
658 'permission_denied': _(
658 'permission_denied': _(
659 u"You do not have the permission "
659 u"You do not have the permission "
660 u"to create repositories in this group."),
660 u"to create repositories in this group."),
661 'permission_denied_root': _(
661 'permission_denied_root': _(
662 u"You do not have the permission to store repositories in "
662 u"You do not have the permission to store repositories in "
663 u"the root location.")
663 u"the root location.")
664 }
664 }
665
665
666 def _to_python(self, value, state):
666 def _to_python(self, value, state):
667 # root location
667 # root location
668 if value in [-1, "-1"]:
668 if value in [-1, "-1"]:
669 return None
669 return None
670 return value
670 return value
671
671
672 def validate_python(self, value, state):
672 def validate_python(self, value, state):
673 gr = RepoGroup.get(value)
673 gr = RepoGroup.get(value)
674 gr_name = gr.group_name if gr else None # None means ROOT location
674 gr_name = gr.group_name if gr else None # None means ROOT location
675 # create repositories with write permission on group is set to true
675 # create repositories with write permission on group is set to true
676 create_on_write = HasPermissionAny(
676 create_on_write = HasPermissionAny(
677 'hg.create.write_on_repogroup.true')()
677 'hg.create.write_on_repogroup.true')()
678 group_admin = HasRepoGroupPermissionAny('group.admin')(
678 group_admin = HasRepoGroupPermissionAny('group.admin')(
679 gr_name, 'can write into group validator')
679 gr_name, 'can write into group validator')
680 group_write = HasRepoGroupPermissionAny('group.write')(
680 group_write = HasRepoGroupPermissionAny('group.write')(
681 gr_name, 'can write into group validator')
681 gr_name, 'can write into group validator')
682 forbidden = not (group_admin or (group_write and create_on_write))
682 forbidden = not (group_admin or (group_write and create_on_write))
683 can_create_repos = HasPermissionAny(
683 can_create_repos = HasPermissionAny(
684 'hg.admin', 'hg.create.repository')
684 'hg.admin', 'hg.create.repository')
685 gid = (old_data['repo_group'].get('group_id')
685 gid = (old_data['repo_group'].get('group_id')
686 if (old_data and 'repo_group' in old_data) else None)
686 if (old_data and 'repo_group' in old_data) else None)
687 value_changed = gid != safe_int(value)
687 value_changed = gid != safe_int(value)
688 new = not old_data
688 new = not old_data
689 # do check if we changed the value, there's a case that someone got
689 # do check if we changed the value, there's a case that someone got
690 # revoked write permissions to a repository, he still created, we
690 # revoked write permissions to a repository, he still created, we
691 # don't need to check permission if he didn't change the value of
691 # don't need to check permission if he didn't change the value of
692 # groups in form box
692 # groups in form box
693 if value_changed or new:
693 if value_changed or new:
694 # parent group need to be existing
694 # parent group need to be existing
695 if gr and forbidden:
695 if gr and forbidden:
696 msg = M(self, 'permission_denied', state)
696 msg = M(self, 'permission_denied', state)
697 raise formencode.Invalid(
697 raise formencode.Invalid(
698 msg, value, state, error_dict={'repo_type': msg}
698 msg, value, state, error_dict={'repo_type': msg}
699 )
699 )
700 # check if we can write to root location !
700 # check if we can write to root location !
701 elif gr is None and not can_create_repos():
701 elif gr is None and not can_create_repos():
702 msg = M(self, 'permission_denied_root', state)
702 msg = M(self, 'permission_denied_root', state)
703 raise formencode.Invalid(
703 raise formencode.Invalid(
704 msg, value, state, error_dict={'repo_type': msg}
704 msg, value, state, error_dict={'repo_type': msg}
705 )
705 )
706
706
707 return _validator
707 return _validator
708
708
709
709
710 def ValidPerms(type_='repo'):
710 def ValidPerms(type_='repo'):
711 if type_ == 'repo_group':
711 if type_ == 'repo_group':
712 EMPTY_PERM = 'group.none'
712 EMPTY_PERM = 'group.none'
713 elif type_ == 'repo':
713 elif type_ == 'repo':
714 EMPTY_PERM = 'repository.none'
714 EMPTY_PERM = 'repository.none'
715 elif type_ == 'user_group':
715 elif type_ == 'user_group':
716 EMPTY_PERM = 'usergroup.none'
716 EMPTY_PERM = 'usergroup.none'
717
717
718 class _validator(formencode.validators.FancyValidator):
718 class _validator(formencode.validators.FancyValidator):
719 messages = {
719 messages = {
720 'perm_new_member_name':
720 'perm_new_member_name':
721 _(u'This username or user group name is not valid')
721 _(u'This username or user group name is not valid')
722 }
722 }
723
723
724 def _to_python(self, value, state):
724 def _to_python(self, value, state):
725 perm_updates = OrderedSet()
725 perm_updates = OrderedSet()
726 perm_additions = OrderedSet()
726 perm_additions = OrderedSet()
727 perm_deletions = OrderedSet()
727 perm_deletions = OrderedSet()
728 # build a list of permission to update/delete and new permission
728 # build a list of permission to update/delete and new permission
729
729
730 # Read the perm_new_member/perm_del_member attributes and group
730 # Read the perm_new_member/perm_del_member attributes and group
731 # them by they IDs
731 # them by they IDs
732 new_perms_group = defaultdict(dict)
732 new_perms_group = defaultdict(dict)
733 del_perms_group = defaultdict(dict)
733 del_perms_group = defaultdict(dict)
734 for k, v in value.copy().iteritems():
734 for k, v in value.copy().iteritems():
735 if k.startswith('perm_del_member'):
735 if k.startswith('perm_del_member'):
736 # delete from org storage so we don't process that later
736 # delete from org storage so we don't process that later
737 del value[k]
737 del value[k]
738 # part is `id`, `type`
738 # part is `id`, `type`
739 _type, part = k.split('perm_del_member_')
739 _type, part = k.split('perm_del_member_')
740 args = part.split('_')
740 args = part.split('_')
741 if len(args) == 2:
741 if len(args) == 2:
742 _key, pos = args
742 _key, pos = args
743 del_perms_group[pos][_key] = v
743 del_perms_group[pos][_key] = v
744 if k.startswith('perm_new_member'):
744 if k.startswith('perm_new_member'):
745 # delete from org storage so we don't process that later
745 # delete from org storage so we don't process that later
746 del value[k]
746 del value[k]
747 # part is `id`, `type`, `perm`
747 # part is `id`, `type`, `perm`
748 _type, part = k.split('perm_new_member_')
748 _type, part = k.split('perm_new_member_')
749 args = part.split('_')
749 args = part.split('_')
750 if len(args) == 2:
750 if len(args) == 2:
751 _key, pos = args
751 _key, pos = args
752 new_perms_group[pos][_key] = v
752 new_perms_group[pos][_key] = v
753
753
754 # store the deletes
754 # store the deletes
755 for k in sorted(del_perms_group.keys()):
755 for k in sorted(del_perms_group.keys()):
756 perm_dict = del_perms_group[k]
756 perm_dict = del_perms_group[k]
757 del_member = perm_dict.get('id')
757 del_member = perm_dict.get('id')
758 del_type = perm_dict.get('type')
758 del_type = perm_dict.get('type')
759 if del_member and del_type:
759 if del_member and del_type:
760 perm_deletions.add((del_member, None, del_type))
760 perm_deletions.add((del_member, None, del_type))
761
761
762 # store additions in order of how they were added in web form
762 # store additions in order of how they were added in web form
763 for k in sorted(new_perms_group.keys()):
763 for k in sorted(new_perms_group.keys()):
764 perm_dict = new_perms_group[k]
764 perm_dict = new_perms_group[k]
765 new_member = perm_dict.get('id')
765 new_member = perm_dict.get('id')
766 new_type = perm_dict.get('type')
766 new_type = perm_dict.get('type')
767 new_perm = perm_dict.get('perm')
767 new_perm = perm_dict.get('perm')
768 if new_member and new_perm and new_type:
768 if new_member and new_perm and new_type:
769 perm_additions.add((new_member, new_perm, new_type))
769 perm_additions.add((new_member, new_perm, new_type))
770
770
771 # get updates of permissions
771 # get updates of permissions
772 # (read the existing radio button states)
772 # (read the existing radio button states)
773 for k, update_value in value.iteritems():
773 for k, update_value in value.iteritems():
774 if k.startswith('u_perm_') or k.startswith('g_perm_'):
774 if k.startswith('u_perm_') or k.startswith('g_perm_'):
775 member = k[7:]
775 member = k[7:]
776 update_type = {'u': 'user',
776 update_type = {'u': 'user',
777 'g': 'users_group'}[k[0]]
777 'g': 'users_group'}[k[0]]
778 if member == User.DEFAULT_USER:
778 if member == User.DEFAULT_USER:
779 if str2bool(value.get('repo_private')):
779 if str2bool(value.get('repo_private')):
780 # set none for default when updating to
780 # set none for default when updating to
781 # private repo protects agains form manipulation
781 # private repo protects agains form manipulation
782 update_value = EMPTY_PERM
782 update_value = EMPTY_PERM
783 perm_updates.add((member, update_value, update_type))
783 perm_updates.add((member, update_value, update_type))
784 # check the deletes
784 # check the deletes
785
785
786 value['perm_additions'] = list(perm_additions)
786 value['perm_additions'] = list(perm_additions)
787 value['perm_updates'] = list(perm_updates)
787 value['perm_updates'] = list(perm_updates)
788 value['perm_deletions'] = list(perm_deletions)
788 value['perm_deletions'] = list(perm_deletions)
789
789
790 # validate users they exist and they are active !
790 # validate users they exist and they are active !
791 for member_id, _perm, member_type in perm_additions:
791 for member_id, _perm, member_type in perm_additions:
792 try:
792 try:
793 if member_type == 'user':
793 if member_type == 'user':
794 self.user_db = User.query()\
794 self.user_db = User.query()\
795 .filter(User.active == true())\
795 .filter(User.active == true())\
796 .filter(User.user_id == member_id).one()
796 .filter(User.user_id == member_id).one()
797 if member_type == 'users_group':
797 if member_type == 'users_group':
798 self.user_db = UserGroup.query()\
798 self.user_db = UserGroup.query()\
799 .filter(UserGroup.users_group_active == true())\
799 .filter(UserGroup.users_group_active == true())\
800 .filter(UserGroup.users_group_id == member_id)\
800 .filter(UserGroup.users_group_id == member_id)\
801 .one()
801 .one()
802
802
803 except Exception:
803 except Exception:
804 log.exception('Updated permission failed: org_exc:')
804 log.exception('Updated permission failed: org_exc:')
805 msg = M(self, 'perm_new_member_type', state)
805 msg = M(self, 'perm_new_member_type', state)
806 raise formencode.Invalid(
806 raise formencode.Invalid(
807 msg, value, state, error_dict={
807 msg, value, state, error_dict={
808 'perm_new_member_name': msg}
808 'perm_new_member_name': msg}
809 )
809 )
810 return value
810 return value
811 return _validator
811 return _validator
812
812
813
813
814 def ValidSettings():
814 def ValidSettings():
815 class _validator(formencode.validators.FancyValidator):
815 class _validator(formencode.validators.FancyValidator):
816 def _to_python(self, value, state):
816 def _to_python(self, value, state):
817 # settings form for users that are not admin
817 # settings form for users that are not admin
818 # can't edit certain parameters, it's extra backup if they mangle
818 # can't edit certain parameters, it's extra backup if they mangle
819 # with forms
819 # with forms
820
820
821 forbidden_params = [
821 forbidden_params = [
822 'user', 'repo_type', 'repo_enable_locking',
822 'user', 'repo_type', 'repo_enable_locking',
823 'repo_enable_downloads', 'repo_enable_statistics'
823 'repo_enable_downloads', 'repo_enable_statistics'
824 ]
824 ]
825
825
826 for param in forbidden_params:
826 for param in forbidden_params:
827 if param in value:
827 if param in value:
828 del value[param]
828 del value[param]
829 return value
829 return value
830
830
831 def validate_python(self, value, state):
831 def validate_python(self, value, state):
832 pass
832 pass
833 return _validator
833 return _validator
834
834
835
835
836 def ValidPath():
836 def ValidPath():
837 class _validator(formencode.validators.FancyValidator):
837 class _validator(formencode.validators.FancyValidator):
838 messages = {
838 messages = {
839 'invalid_path': _(u'This is not a valid path')
839 'invalid_path': _(u'This is not a valid path')
840 }
840 }
841
841
842 def validate_python(self, value, state):
842 def validate_python(self, value, state):
843 if not os.path.isdir(value):
843 if not os.path.isdir(value):
844 msg = M(self, 'invalid_path', state)
844 msg = M(self, 'invalid_path', state)
845 raise formencode.Invalid(
845 raise formencode.Invalid(
846 msg, value, state, error_dict={'paths_root_path': msg}
846 msg, value, state, error_dict={'paths_root_path': msg}
847 )
847 )
848 return _validator
848 return _validator
849
849
850
850
851 def UniqSystemEmail(old_data={}):
851 def UniqSystemEmail(old_data={}):
852 class _validator(formencode.validators.FancyValidator):
852 class _validator(formencode.validators.FancyValidator):
853 messages = {
853 messages = {
854 'email_taken': _(u'This e-mail address is already taken')
854 'email_taken': _(u'This e-mail address is already taken')
855 }
855 }
856
856
857 def _to_python(self, value, state):
857 def _to_python(self, value, state):
858 return value.lower()
858 return value.lower()
859
859
860 def validate_python(self, value, state):
860 def validate_python(self, value, state):
861 if (old_data.get('email') or '').lower() != value:
861 if (old_data.get('email') or '').lower() != value:
862 user = User.get_by_email(value, case_insensitive=True)
862 user = User.get_by_email(value, case_insensitive=True)
863 if user:
863 if user:
864 msg = M(self, 'email_taken', state)
864 msg = M(self, 'email_taken', state)
865 raise formencode.Invalid(
865 raise formencode.Invalid(
866 msg, value, state, error_dict={'email': msg}
866 msg, value, state, error_dict={'email': msg}
867 )
867 )
868 return _validator
868 return _validator
869
869
870
870
871 def ValidSystemEmail():
871 def ValidSystemEmail():
872 class _validator(formencode.validators.FancyValidator):
872 class _validator(formencode.validators.FancyValidator):
873 messages = {
873 messages = {
874 'non_existing_email': _(u'e-mail "%(email)s" does not exist.')
874 'non_existing_email': _(u'e-mail "%(email)s" does not exist.')
875 }
875 }
876
876
877 def _to_python(self, value, state):
877 def _to_python(self, value, state):
878 return value.lower()
878 return value.lower()
879
879
880 def validate_python(self, value, state):
880 def validate_python(self, value, state):
881 user = User.get_by_email(value, case_insensitive=True)
881 user = User.get_by_email(value, case_insensitive=True)
882 if user is None:
882 if user is None:
883 msg = M(self, 'non_existing_email', state, email=value)
883 msg = M(self, 'non_existing_email', state, email=value)
884 raise formencode.Invalid(
884 raise formencode.Invalid(
885 msg, value, state, error_dict={'email': msg}
885 msg, value, state, error_dict={'email': msg}
886 )
886 )
887
887
888 return _validator
888 return _validator
889
889
890
890
891 def NotReviewedRevisions(repo_id):
891 def NotReviewedRevisions(repo_id):
892 class _validator(formencode.validators.FancyValidator):
892 class _validator(formencode.validators.FancyValidator):
893 messages = {
893 messages = {
894 'rev_already_reviewed':
894 'rev_already_reviewed':
895 _(u'Revisions %(revs)s are already part of pull request '
895 _(u'Revisions %(revs)s are already part of pull request '
896 u'or have set status'),
896 u'or have set status'),
897 }
897 }
898
898
899 def validate_python(self, value, state):
899 def validate_python(self, value, state):
900 # check revisions if they are not reviewed, or a part of another
900 # check revisions if they are not reviewed, or a part of another
901 # pull request
901 # pull request
902 statuses = ChangesetStatus.query()\
902 statuses = ChangesetStatus.query()\
903 .filter(ChangesetStatus.revision.in_(value))\
903 .filter(ChangesetStatus.revision.in_(value))\
904 .filter(ChangesetStatus.repo_id == repo_id)\
904 .filter(ChangesetStatus.repo_id == repo_id)\
905 .all()
905 .all()
906
906
907 errors = []
907 errors = []
908 for status in statuses:
908 for status in statuses:
909 if status.pull_request_id:
909 if status.pull_request_id:
910 errors.append(['pull_req', status.revision[:12]])
910 errors.append(['pull_req', status.revision[:12]])
911 elif status.status:
911 elif status.status:
912 errors.append(['status', status.revision[:12]])
912 errors.append(['status', status.revision[:12]])
913
913
914 if errors:
914 if errors:
915 revs = ','.join([x[1] for x in errors])
915 revs = ','.join([x[1] for x in errors])
916 msg = M(self, 'rev_already_reviewed', state, revs=revs)
916 msg = M(self, 'rev_already_reviewed', state, revs=revs)
917 raise formencode.Invalid(
917 raise formencode.Invalid(
918 msg, value, state, error_dict={'revisions': revs})
918 msg, value, state, error_dict={'revisions': revs})
919
919
920 return _validator
920 return _validator
921
921
922
922
923 def ValidIp():
923 def ValidIp():
924 class _validator(CIDR):
924 class _validator(CIDR):
925 messages = {
925 messages = {
926 'badFormat': _(u'Please enter a valid IPv4 or IpV6 address'),
926 'badFormat': _(u'Please enter a valid IPv4 or IpV6 address'),
927 'illegalBits': _(
927 'illegalBits': _(
928 u'The network size (bits) must be within the range '
928 u'The network size (bits) must be within the range '
929 u'of 0-32 (not %(bits)r)'),
929 u'of 0-32 (not %(bits)r)'),
930 }
930 }
931
931
932 # we ovveride the default to_python() call
932 # we ovveride the default to_python() call
933 def to_python(self, value, state):
933 def to_python(self, value, state):
934 v = super(_validator, self).to_python(value, state)
934 v = super(_validator, self).to_python(value, state)
935 v = v.strip()
935 v = v.strip()
936 net = ipaddress.ip_network(address=v, strict=False)
936 net = ipaddress.ip_network(address=v, strict=False)
937 return str(net)
937 return str(net)
938
938
939 def validate_python(self, value, state):
939 def validate_python(self, value, state):
940 try:
940 try:
941 addr = value.strip()
941 addr = value.strip()
942 # this raises an ValueError if address is not IpV4 or IpV6
942 # this raises an ValueError if address is not IpV4 or IpV6
943 ipaddress.ip_network(addr, strict=False)
943 ipaddress.ip_network(addr, strict=False)
944 except ValueError:
944 except ValueError:
945 raise formencode.Invalid(self.message('badFormat', state),
945 raise formencode.Invalid(self.message('badFormat', state),
946 value, state)
946 value, state)
947
947
948 return _validator
948 return _validator
949
949
950
950
951 def FieldKey():
951 def FieldKey():
952 class _validator(formencode.validators.FancyValidator):
952 class _validator(formencode.validators.FancyValidator):
953 messages = {
953 messages = {
954 'badFormat': _(
954 'badFormat': _(
955 u'Key name can only consist of letters, '
955 u'Key name can only consist of letters, '
956 u'underscore, dash or numbers'),
956 u'underscore, dash or numbers'),
957 }
957 }
958
958
959 def validate_python(self, value, state):
959 def validate_python(self, value, state):
960 if not re.match('[a-zA-Z0-9_-]+$', value):
960 if not re.match('[a-zA-Z0-9_-]+$', value):
961 raise formencode.Invalid(self.message('badFormat', state),
961 raise formencode.Invalid(self.message('badFormat', state),
962 value, state)
962 value, state)
963 return _validator
963 return _validator
964
964
965
965
966 def BasePath():
966 def BasePath():
967 class _validator(formencode.validators.FancyValidator):
967 class _validator(formencode.validators.FancyValidator):
968 messages = {
968 messages = {
969 'badPath': _(u'Filename cannot be inside a directory'),
969 'badPath': _(u'Filename cannot be inside a directory'),
970 }
970 }
971
971
972 def _to_python(self, value, state):
972 def _to_python(self, value, state):
973 return value
973 return value
974
974
975 def validate_python(self, value, state):
975 def validate_python(self, value, state):
976 if value != os.path.basename(value):
976 if value != os.path.basename(value):
977 raise formencode.Invalid(self.message('badPath', state),
977 raise formencode.Invalid(self.message('badPath', state),
978 value, state)
978 value, state)
979 return _validator
979 return _validator
980
980
981
981
982 def ValidAuthPlugins():
982 def ValidAuthPlugins():
983 class _validator(formencode.validators.FancyValidator):
983 class _validator(formencode.validators.FancyValidator):
984 messages = {
984 messages = {
985 'import_duplicate': _(
985 'import_duplicate': _(
986 u'Plugins %(loaded)s and %(next_to_load)s '
986 u'Plugins %(loaded)s and %(next_to_load)s '
987 u'both export the same name'),
987 u'both export the same name'),
988 }
988 }
989
989
990 def _to_python(self, value, state):
990 def _to_python(self, value, state):
991 # filter empty values
991 # filter empty values
992 return filter(lambda s: s not in [None, ''], value)
992 return filter(lambda s: s not in [None, ''], value)
993
993
994 def validate_python(self, value, state):
994 def validate_python(self, value, state):
995 from rhodecode.authentication.base import loadplugin
995 from rhodecode.authentication.base import loadplugin
996 module_list = value
996 module_list = value
997 unique_names = {}
997 unique_names = {}
998 try:
998 for module in module_list:
999 for module in module_list:
999 plugin = loadplugin(module)
1000 plugin = loadplugin(module)
1000 if plugin is None:
1001 plugin_name = plugin.name
1001 raise formencode.Invalid(
1002 if plugin_name in unique_names:
1002 _("Can't find plugin with id '{}'".format(module)),
1003 msg = M(self, 'import_duplicate', state,
1003 value, state)
1004 loaded=unique_names[plugin_name],
1004 plugin_name = plugin.name
1005 next_to_load=plugin_name)
1005 if plugin_name in unique_names:
1006 raise formencode.Invalid(msg, value, state)
1006 msg = M(self, 'import_duplicate', state,
1007 unique_names[plugin_name] = plugin
1007 loaded=unique_names[plugin_name],
1008 except (KeyError, AttributeError, TypeError) as e:
1008 next_to_load=plugin_name)
1009 raise formencode.Invalid(str(e), value, state)
1009 raise formencode.Invalid(msg, value, state)
1010 unique_names[plugin_name] = plugin
1010
1011
1011 return _validator
1012 return _validator
1012
1013
1013
1014
1014 def UniqGistId():
1015 def UniqGistId():
1015 class _validator(formencode.validators.FancyValidator):
1016 class _validator(formencode.validators.FancyValidator):
1016 messages = {
1017 messages = {
1017 'gistid_taken': _(u'This gistid is already in use')
1018 'gistid_taken': _(u'This gistid is already in use')
1018 }
1019 }
1019
1020
1020 def _to_python(self, value, state):
1021 def _to_python(self, value, state):
1021 return repo_name_slug(value.lower())
1022 return repo_name_slug(value.lower())
1022
1023
1023 def validate_python(self, value, state):
1024 def validate_python(self, value, state):
1024 existing = Gist.get_by_access_id(value)
1025 existing = Gist.get_by_access_id(value)
1025 if existing:
1026 if existing:
1026 msg = M(self, 'gistid_taken', state)
1027 msg = M(self, 'gistid_taken', state)
1027 raise formencode.Invalid(
1028 raise formencode.Invalid(
1028 msg, value, state, error_dict={'gistid': msg}
1029 msg, value, state, error_dict={'gistid': msg}
1029 )
1030 )
1030
1031
1031 return _validator
1032 return _validator
1032
1033
1033
1034
1034 def ValidPattern():
1035 def ValidPattern():
1035
1036
1036 class _Validator(formencode.validators.FancyValidator):
1037 class _Validator(formencode.validators.FancyValidator):
1037
1038
1038 def _to_python(self, value, state):
1039 def _to_python(self, value, state):
1039 patterns = []
1040 patterns = []
1040
1041
1041 prefix = 'new_pattern'
1042 prefix = 'new_pattern'
1042 for name, v in value.iteritems():
1043 for name, v in value.iteritems():
1043 pattern_name = '_'.join((prefix, 'pattern'))
1044 pattern_name = '_'.join((prefix, 'pattern'))
1044 if name.startswith(pattern_name):
1045 if name.startswith(pattern_name):
1045 new_item_id = name[len(pattern_name)+1:]
1046 new_item_id = name[len(pattern_name)+1:]
1046
1047
1047 def _field(name):
1048 def _field(name):
1048 return '%s_%s_%s' % (prefix, name, new_item_id)
1049 return '%s_%s_%s' % (prefix, name, new_item_id)
1049
1050
1050 values = {
1051 values = {
1051 'issuetracker_pat': value.get(_field('pattern')),
1052 'issuetracker_pat': value.get(_field('pattern')),
1052 'issuetracker_pat': value.get(_field('pattern')),
1053 'issuetracker_pat': value.get(_field('pattern')),
1053 'issuetracker_url': value.get(_field('url')),
1054 'issuetracker_url': value.get(_field('url')),
1054 'issuetracker_pref': value.get(_field('prefix')),
1055 'issuetracker_pref': value.get(_field('prefix')),
1055 'issuetracker_desc': value.get(_field('description'))
1056 'issuetracker_desc': value.get(_field('description'))
1056 }
1057 }
1057 new_uid = md5(values['issuetracker_pat'])
1058 new_uid = md5(values['issuetracker_pat'])
1058
1059
1059 has_required_fields = (
1060 has_required_fields = (
1060 values['issuetracker_pat']
1061 values['issuetracker_pat']
1061 and values['issuetracker_url'])
1062 and values['issuetracker_url'])
1062
1063
1063 if has_required_fields:
1064 if has_required_fields:
1064 settings = [
1065 settings = [
1065 ('_'.join((key, new_uid)), values[key], 'unicode')
1066 ('_'.join((key, new_uid)), values[key], 'unicode')
1066 for key in values]
1067 for key in values]
1067 patterns.append(settings)
1068 patterns.append(settings)
1068
1069
1069 value['patterns'] = patterns
1070 value['patterns'] = patterns
1070 delete_patterns = value.get('uid') or []
1071 delete_patterns = value.get('uid') or []
1071 if not isinstance(delete_patterns, (list, tuple)):
1072 if not isinstance(delete_patterns, (list, tuple)):
1072 delete_patterns = [delete_patterns]
1073 delete_patterns = [delete_patterns]
1073 value['delete_patterns'] = delete_patterns
1074 value['delete_patterns'] = delete_patterns
1074 return value
1075 return value
1075 return _Validator
1076 return _Validator
General Comments 0
You need to be logged in to leave comments. Login now