diff --git a/rhodecode/model/forms.py b/rhodecode/model/forms.py --- a/rhodecode/model/forms.py +++ b/rhodecode/model/forms.py @@ -10,46 +10,45 @@ chained_validators [] These val allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present filter_extra_fields False If True, then keys that aren't associated with a validator are removed if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value. -ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already - - +ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already + + = formencode.validators. must equal form name list=[1,2,3,4,5] for SELECT use formencode.All(OneOf(list), Int()) - + """ import os import re import logging +import traceback import formencode from formencode import All from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \ - Email, Bool, StringBoolean + Email, Bool, StringBoolean, Set from pylons.i18n.translation import _ +from webhelpers.pylonslib.secure_form import authentication_token -import rhodecode.lib.helpers as h +from rhodecode.lib.utils import repo_name_slug from rhodecode.lib.auth import authenticate, get_crypt_password from rhodecode.lib.exceptions import LdapImportError -from rhodecode.model import meta from rhodecode.model.user import UserModel from rhodecode.model.repo import RepoModel -from rhodecode.model.db import User +from rhodecode.model.db import User, UsersGroup, Group from rhodecode import BACKENDS -from webhelpers.pylonslib.secure_form import authentication_token - log = logging.getLogger(__name__) #this is needed to translate the messages using _() in validators class State_obj(object): _ = staticmethod(_) -#=============================================================================== +#============================================================================== # VALIDATORS -#=============================================================================== +#============================================================================== class ValidAuthToken(formencode.validators.FancyValidator): messages = {'invalid_token':_('Token mismatch')} @@ -73,20 +72,84 @@ def ValidUsername(edit, old_data): if old_un != value or not edit: if UserModel().get_by_username(value, cache=False, case_insensitive=True): - raise formencode.Invalid(_('This username already exists') , - value, state) - + raise formencode.Invalid(_('This username already ' + 'exists') , value, state) if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None: raise formencode.Invalid(_('Username may only contain ' - 'alphanumeric characters underscores, ' - 'periods or dashes and must begin with ' - 'alphanumeric character'), - value, state) + 'alphanumeric characters ' + 'underscores, periods or dashes ' + 'and must begin with alphanumeric ' + 'character'), value, state) + + return _ValidUsername + + +def ValidUsersGroup(edit, old_data): + + class _ValidUsersGroup(formencode.validators.FancyValidator): + + def validate_python(self, value, state): + if value in ['default']: + raise formencode.Invalid(_('Invalid group name'), value, state) + #check if group is unique + old_ugname = None + if edit: + old_ugname = UsersGroup.get( + old_data.get('users_group_id')).users_group_name + + if old_ugname != value or not edit: + if UsersGroup.get_by_group_name(value, cache=False, + case_insensitive=True): + raise formencode.Invalid(_('This users group ' + 'already exists') , value, + state) + if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None: + raise formencode.Invalid(_('Group name may only contain ' + 'alphanumeric characters ' + 'underscores, periods or dashes ' + 'and must begin with alphanumeric ' + 'character'), value, state) - return _ValidUsername + return _ValidUsersGroup + + +def ValidReposGroup(edit, old_data): + class _ValidReposGroup(formencode.validators.FancyValidator): + + def validate_python(self, value, state): + #TODO WRITE VALIDATIONS + group_name = value.get('group_name') + group_parent_id = int(value.get('group_parent_id') or - 1) + + # slugify repo group just in case :) + slug = repo_name_slug(group_name) + + # check for parent of self + if edit and old_data['group_id'] == group_parent_id: + e_dict = {'group_parent_id':_('Cannot assign this group ' + 'as parent')} + raise formencode.Invalid('', value, state, + error_dict=e_dict) + + old_gname = None + if edit: + old_gname = Group.get( + old_data.get('group_id')).group_name + + if old_gname != group_name or not edit: + # check filesystem + gr = Group.query().filter(Group.group_name == slug)\ + .filter(Group.group_parent_id == group_parent_id).scalar() + + if gr: + e_dict = {'group_name':_('This group already exists')} + raise formencode.Invalid('', value, state, + error_dict=e_dict) + + return _ValidReposGroup class ValidPassword(formencode.validators.FancyValidator): @@ -125,7 +188,7 @@ class ValidPasswordsMatch(formencode.val if value['password'] != value['password_confirmation']: e_dict = {'password_confirmation': - _('Password do not match')} + _('Passwords do not match')} raise formencode.Invalid('', value, state, error_dict=e_dict) class ValidAuth(formencode.validators.FancyValidator): @@ -163,81 +226,165 @@ class ValidAuth(formencode.validators.Fa class ValidRepoUser(formencode.validators.FancyValidator): def to_python(self, value, state): - sa = meta.Session() try: - self.user_db = sa.query(User)\ - .filter(User.active == True)\ + User.query().filter(User.active == True)\ .filter(User.username == value).one() except Exception: raise formencode.Invalid(_('This username is not valid'), value, state) - finally: - meta.Session.remove() - - return self.user_db.user_id + return value def ValidRepoName(edit, old_data): class _ValidRepoName(formencode.validators.FancyValidator): + def to_python(self, value, state): - def to_python(self, value, state): - slug = h.repo_name_slug(value) - if slug in ['_admin']: - raise formencode.Invalid(_('This repository name is disallowed'), - value, state) - if old_data.get('repo_name') != value or not edit: - if RepoModel().get_by_repo_name(slug, cache=False): - raise formencode.Invalid(_('This repository already exists') , - value, state) - return slug + repo_name = value.get('repo_name') + + slug = repo_name_slug(repo_name) + if slug in ['_admin', '']: + e_dict = {'repo_name': _('This repository name is disallowed')} + raise formencode.Invalid('', value, state, error_dict=e_dict) + + + if value.get('repo_group'): + gr = Group.get(value.get('repo_group')) + group_path = gr.full_path + # value needs to be aware of group name in order to check + # db key This is an actuall just the name to store in the + # database + repo_name_full = group_path + Group.url_sep() + repo_name + else: + group_path = '' + repo_name_full = repo_name + + + value['repo_name_full'] = repo_name_full + if old_data.get('repo_name') != repo_name_full or not edit: + + if group_path != '': + if RepoModel().get_by_repo_name(repo_name_full,): + e_dict = {'repo_name':_('This repository already ' + 'exists in group "%s"') % + gr.group_name} + raise formencode.Invalid('', value, state, + error_dict=e_dict) + + else: + if RepoModel().get_by_repo_name(repo_name_full): + e_dict = {'repo_name':_('This repository ' + 'already exists')} + raise formencode.Invalid('', value, state, + error_dict=e_dict) + return value return _ValidRepoName +def ValidForkName(): + class _ValidForkName(formencode.validators.FancyValidator): + def to_python(self, value, state): + return value + return _ValidForkName + + +def SlugifyName(): + class _SlugifyName(formencode.validators.FancyValidator): + + def to_python(self, value, state): + return repo_name_slug(value) + + return _SlugifyName + +def ValidCloneUri(): + from mercurial.httprepo import httprepository, httpsrepository + from rhodecode.lib.utils import make_ui + + class _ValidCloneUri(formencode.validators.FancyValidator): + + def to_python(self, value, state): + if not value: + pass + elif value.startswith('https'): + try: + httpsrepository(make_ui('db'), value).capabilities + except Exception, e: + log.error(traceback.format_exc()) + raise formencode.Invalid(_('invalid clone url'), value, + state) + elif value.startswith('http'): + try: + httprepository(make_ui('db'), value).capabilities + except Exception, e: + log.error(traceback.format_exc()) + raise formencode.Invalid(_('invalid clone url'), value, + state) + else: + raise formencode.Invalid(_('Invalid clone url, provide a ' + 'valid clone http\s url'), value, + state) + return value + + return _ValidCloneUri + def ValidForkType(old_data): class _ValidForkType(formencode.validators.FancyValidator): def to_python(self, value, state): if old_data['repo_type'] != value: - raise formencode.Invalid(_('Fork have to be the same type as original'), - value, state) + raise formencode.Invalid(_('Fork have to be the same ' + 'type as original'), value, state) + return value return _ValidForkType class ValidPerms(formencode.validators.FancyValidator): - messages = {'perm_new_user_name':_('This username is not valid')} + messages = {'perm_new_member_name':_('This username or users group name' + ' is not valid')} def to_python(self, value, state): perms_update = [] perms_new = [] #build a list of permission to update and new permission to create for k, v in value.items(): - if k.startswith('perm_'): - if k.startswith('perm_new_user'): - new_perm = value.get('perm_new_user', False) - new_user = value.get('perm_new_user_name', False) - if new_user and new_perm: - if (new_user, new_perm) not in perms_new: - perms_new.append((new_user, new_perm)) - else: - usr = k[5:] - if usr == 'default': - if value['private']: - #set none for default when updating to private repo - v = 'repository.none' - perms_update.append((usr, v)) + #means new added member to permissions + if k.startswith('perm_new_member'): + new_perm = value.get('perm_new_member', False) + new_member = value.get('perm_new_member_name', False) + new_type = value.get('perm_new_member_type') + + if new_member and new_perm: + if (new_member, new_perm, new_type) not in perms_new: + perms_new.append((new_member, new_perm, new_type)) + elif k.startswith('u_perm_') or k.startswith('g_perm_'): + member = k[7:] + t = {'u':'user', + 'g':'users_group'}[k[0]] + if member == 'default': + if value['private']: + #set none for default when updating to private repo + v = 'repository.none' + perms_update.append((member, v, t)) + value['perms_updates'] = perms_update value['perms_new'] = perms_new - sa = meta.Session - for k, v in perms_new: + + #update permissions + for k, v, t in perms_new: try: - self.user_db = sa.query(User)\ - .filter(User.active == True)\ - .filter(User.username == k).one() + if t is 'user': + self.user_db = User.query()\ + .filter(User.active == True)\ + .filter(User.username == k).one() + if t is 'users_group': + self.user_db = UsersGroup.query()\ + .filter(UsersGroup.users_group_active == True)\ + .filter(UsersGroup.users_group_name == k).one() + except Exception: - msg = self.message('perm_new_user_name', + msg = self.message('perm_new_member_name', state=State_obj) raise formencode.Invalid(msg, value, state, - error_dict={'perm_new_user_name':msg}) + error_dict={'perm_new_member_name':msg}) return value class ValidSettings(formencode.validators.FancyValidator): @@ -263,15 +410,11 @@ def UniqSystemEmail(old_data): def to_python(self, value, state): value = value.lower() if old_data.get('email') != value: - sa = meta.Session() - try: - user = sa.query(User).filter(User.email == value).scalar() - if user: - raise formencode.Invalid(_("This e-mail address is already taken") , - value, state) - finally: - meta.Session.remove() - + user = User.query().filter(User.email == value).scalar() + if user: + raise formencode.Invalid( + _("This e-mail address is already taken"), + value, state) return value return _UniqSystemEmail @@ -279,14 +422,10 @@ def UniqSystemEmail(old_data): class ValidSystemEmail(formencode.validators.FancyValidator): def to_python(self, value, state): value = value.lower() - sa = meta.Session - try: - user = sa.query(User).filter(User.email == value).scalar() - if user is None: - raise formencode.Invalid(_("This e-mail address doesn't exist.") , - value, state) - finally: - meta.Session.remove() + user = User.query().filter(User.email == value).scalar() + if user is None: + raise formencode.Invalid(_("This e-mail address doesn't exist.") , + value, state) return value @@ -300,31 +439,21 @@ class LdapLibValidator(formencode.valida raise LdapImportError return value -def BaseDnValidator(ldap_enable): - class _BaseDnValidator(formencode.validators.FancyValidator): - - def to_python(self, value, state): +class AttrLoginValidator(formencode.validators.FancyValidator): - if not ldap_enable: - return '' - try: - value % {'user':'valid'} + def to_python(self, value, state): - if value.find('%(user)s') == -1: - raise formencode.Invalid(_("You need to specify %(user)s in " - "template for example uid=%(user)s " - ",dc=company...") , - value, state) + if not value or not isinstance(value, (str, unicode)): + raise formencode.Invalid(_("The LDAP Login attribute of the CN " + "must be specified - this is the name " + "of the attribute that is equivalent " + "to 'username'"), + value, state) - except KeyError: - raise formencode.Invalid(_("Wrong template used, only %(user)s " - "is an valid entry") , - value, state) + return value - return value - return _BaseDnValidator #=============================================================================== -# FORMS +# FORMS #=============================================================================== class LoginForm(formencode.Schema): allow_extra_fields = True @@ -334,17 +463,17 @@ class LoginForm(formencode.Schema): min=1, not_empty=True, messages={ - 'empty':_('Please enter a login'), - 'tooShort':_('Enter a value %(min)i characters long or more')} + 'empty':_('Please enter a login'), + 'tooShort':_('Enter a value %(min)i characters long or more')} ) password = UnicodeString( strip=True, - min=6, + min=3, not_empty=True, messages={ - 'empty':_('Please enter a password'), - 'tooShort':_('Enter %(min)i characters or more')} + 'empty':_('Please enter a password'), + 'tooShort':_('Enter %(min)i characters or more')} ) @@ -371,6 +500,41 @@ def UserForm(edit=False, old_data={}): return _UserForm + +def UsersGroupForm(edit=False, old_data={}, available_members=[]): + class _UsersGroupForm(formencode.Schema): + allow_extra_fields = True + filter_extra_fields = True + + users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True), + ValidUsersGroup(edit, old_data)) + + users_group_active = StringBoolean(if_missing=False) + + if edit: + users_group_members = OneOf(available_members, hideList=False, + testValueList=True, + if_missing=None, not_empty=False) + + return _UsersGroupForm + +def ReposGroupForm(edit=False, old_data={}, available_groups=[]): + class _ReposGroupForm(formencode.Schema): + allow_extra_fields = True + filter_extra_fields = True + + group_name = All(UnicodeString(strip=True, min=1, not_empty=True), + SlugifyName()) + group_description = UnicodeString(strip=True, min=1, + not_empty=True) + group_parent_id = OneOf(available_groups, hideList=False, + testValueList=True, + if_missing=None, not_empty=False) + + chained_validators = [ValidReposGroup(edit, old_data)] + + return _ReposGroupForm + def RegisterForm(edit=False, old_data={}): class _RegisterForm(formencode.Schema): allow_extra_fields = True @@ -395,20 +559,27 @@ def PasswordResetForm(): email = All(ValidSystemEmail(), Email(not_empty=True)) return _PasswordResetForm -def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()): +def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(), + repo_groups=[]): class _RepoForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = False repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), - ValidRepoName(edit, old_data)) + SlugifyName()) + clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False), + ValidCloneUri()()) + repo_group = OneOf(repo_groups, hideList=True) + repo_type = OneOf(supported_backends) description = UnicodeString(strip=True, min=1, not_empty=True) private = StringBoolean(if_missing=False) enable_statistics = StringBoolean(if_missing=False) - repo_type = OneOf(supported_backends) + enable_downloads = StringBoolean(if_missing=False) + if edit: - user = All(Int(not_empty=True), ValidRepoUser) + #this is repo owner + user = All(UnicodeString(not_empty=True), ValidRepoUser) - chained_validators = [ValidPerms] + chained_validators = [ValidRepoName(edit, old_data), ValidPerms] return _RepoForm def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()): @@ -416,10 +587,13 @@ def RepoForkForm(edit=False, old_data={} allow_extra_fields = True filter_extra_fields = False fork_name = All(UnicodeString(strip=True, min=1, not_empty=True), - ValidRepoName(edit, old_data)) + SlugifyName()) description = UnicodeString(strip=True, min=1, not_empty=True) private = StringBoolean(if_missing=False) repo_type = All(ValidForkType(old_data), OneOf(supported_backends)) + + chained_validators = [ValidForkName()] + return _RepoForkForm def RepoSettingsForm(edit=False, old_data={}): @@ -427,11 +601,11 @@ def RepoSettingsForm(edit=False, old_dat allow_extra_fields = True filter_extra_fields = False repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), - ValidRepoName(edit, old_data)) + SlugifyName()) description = UnicodeString(strip=True, min=1, not_empty=True) private = StringBoolean(if_missing=False) - chained_validators = [ValidPerms, ValidSettings] + chained_validators = [ValidRepoName(edit, old_data), ValidPerms, ValidSettings] return _RepoForm @@ -441,6 +615,7 @@ def ApplicationSettingsForm(): filter_extra_fields = False rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True) rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True) + rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False) return _ApplicationSettingsForm @@ -470,7 +645,7 @@ def DefaultPermissionsForm(perms_choices return _DefaultPermissionsForm -def LdapSettingsForm(ldap_enable): +def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices): class _LdapSettingsForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = True @@ -478,9 +653,16 @@ def LdapSettingsForm(ldap_enable): ldap_active = StringBoolean(if_missing=False) ldap_host = UnicodeString(strip=True,) ldap_port = Number(strip=True,) - ldap_ldaps = StringBoolean(if_missing=False) + ldap_tls_kind = OneOf(tls_kind_choices) + ldap_tls_reqcert = OneOf(tls_reqcert_choices) ldap_dn_user = UnicodeString(strip=True,) ldap_dn_pass = UnicodeString(strip=True,) - ldap_base_dn = All(BaseDnValidator(ldap_enable), UnicodeString(strip=True,)) + ldap_base_dn = UnicodeString(strip=True,) + ldap_filter = UnicodeString(strip=True,) + ldap_search_scope = OneOf(search_scope_choices) + ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,)) + ldap_attr_firstname = UnicodeString(strip=True,) + ldap_attr_lastname = UnicodeString(strip=True,) + ldap_attr_email = UnicodeString(strip=True,) return _LdapSettingsForm