@@ -155,15 +155,15 @@ INPUT::
api_key : "<api_key>"
method : "create_user"
args : {
"username" : "<username>",
"password" : "<password>",
"firstname" : "<firstname>",
"lastname" : "<lastname>",
"email" : "<useremail>"
"email" : "<useremail>",
"firstname" : "<firstname> = None",
"lastname" : "<lastname> = None",
"active" : "<bool> = True",
"admin" : "<bool> = False",
"ldap_dn" : "<ldap_dn> = None"
}
OUTPUT::
@@ -32,12 +32,13 @@ news
- improved file filter on files page
- implements #330 api method for listing nodes ar particular revision
- #73 added linking issues in commit messages to chosen issue tracker url
based on user defined regular expression
- added linking of changesets in commit messages
- new compact changelog with expandable commit messages
- firstname and lastname are optional in user creation
fixes
-----
- rewrote dbsession management for atomic operations, and better error handling
- fixed sorting of repo tables
@@ -128,23 +128,23 @@ class ApiController(JSONRPCController):
ldap=user.ldap_dn
)
return result
@HasPermissionAllDecorator('hg.admin')
def create_user(self, apiuser, username, password, firstname,
lastname, email, active=True, admin=False, ldap_dn=None):
def create_user(self, apiuser, username, password, email, firstname=None,
lastname=None, active=True, admin=False, ldap_dn=None):
"""
Create new user or updates current one
:param apiuser:
:param username:
:param password:
:param email:
:param name:
:param lastname:
:param active:
:param admin:
:param ldap_dn:
if User.get_by_username(username):
@@ -126,12 +126,13 @@ def get_crypt_password(password):
return RhodeCodeCrypto.hash_string(password)
def check_password(password, hashed):
return RhodeCodeCrypto.hash_check(password, hashed)
def generate_api_key(str_, salt=None):
Generates API KEY from given string
:param str_:
:param salt:
@@ -234,12 +235,13 @@ def authenticate(username, password):
pass
except (Exception,):
log.error(traceback.format_exc())
return False
def login_container_auth(username):
user = User.get_by_username(username)
if user is None:
user_attrs = {
'name': username,
'lastname': None,
@@ -257,12 +259,13 @@ def login_container_auth(username):
Session.commit()
log.debug('User %s is now logged in by container authentication',
user.username)
return user
def get_container_username(environ, config):
username = None
if str2bool(config.get('container_auth_enabled', False)):
from paste.httpheaders import REMOTE_USER
username = REMOTE_USER(environ)
@@ -275,12 +278,13 @@ def get_container_username(environ, conf
username = username.partition('@')[0]
username = username.rpartition('\\')[2]
log.debug('Received username %s from container', username)
return username
class AuthUser(object):
A simple object that handles all attributes of user in RhodeCode
It does lookup based on API key,given user, or user present in session
Then it fills all required information for such user. It also checks if
@@ -299,12 +303,13 @@ class AuthUser(object):
self.email = ''
self.is_authenticated = False
self.admin = False
self.permissions = {}
self._api_key = api_key
self.propagate_data()
self._instance = None
def propagate_data(self):
user_model = UserModel()
self.anonymous_user = User.get_by_username('default', cache=True)
is_user_loaded = False
@@ -347,36 +352,33 @@ class AuthUser(object):
user_model.fill_perms(self)
@property
def is_admin(self):
return self.admin
def full_contact(self):
return '%s %s <%s>' % (self.name, self.lastname, self.email)
def __repr__(self):
return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
self.is_authenticated)
def set_authenticated(self, authenticated=True):
if self.user_id != self.anonymous_user.user_id:
self.is_authenticated = authenticated
def get_cookie_store(self):
return {'username':self.username,
return {'username': self.username,
'user_id': self.user_id,
'is_authenticated':self.is_authenticated}
'is_authenticated': self.is_authenticated}
@classmethod
def from_cookie_store(cls, cookie_store):
user_id = cookie_store.get('user_id')
username = cookie_store.get('username')
api_key = cookie_store.get('api_key')
return AuthUser(user_id, api_key, username)
def set_available_permissions(config):
This function will propagate pylons globals with all available defined
permission given in db. We don't want to check each time from db for new
permissions since adding a new permission also requires application restart
ie. to decorate new views with the newly created permission
@@ -385,13 +387,13 @@ def set_available_permissions(config):
log.info('getting information about all available permissions')
try:
sa = meta.Session
all_perms = sa.query(Permission).all()
except:
except Exception:
finally:
meta.Session.remove()
config['available_permissions'] = [x.permission_name for x in all_perms]
@@ -296,12 +296,17 @@ class User(Base, BaseModel):
def full_name(self):
return '%s %s' % (self.name, self.lastname)
def full_name_or_username(self):
return ('%s %s' % (self.name, self.lastname)
if (self.name and self.lastname) else self.username)
def short_contact(self):
@@ -351,14 +356,19 @@ class User(Base, BaseModel):
"""Update user lastlogin"""
self.last_login = datetime.datetime.now()
Session.add(self)
log.debug('updated user %s lastlogin', self.username)
def __json__(self):
return dict(email=self.email,
full_name=self.full_name)
return dict(
email=self.email,
full_name=self.full_name,
full_name_or_username=self.full_name_or_username,
short_contact=self.short_contact,
full_contact=self.full_contact
class UserLog(Base, BaseModel):
__tablename__ = 'user_logs'
__table_args__ = {'extend_existing': True}
user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
@@ -48,19 +48,23 @@ class State_obj(object):
#==============================================================================
# VALIDATORS
class ValidAuthToken(formencode.validators.FancyValidator):
messages = {'invalid_token':_('Token mismatch')}
messages = {'invalid_token': _('Token mismatch')}
def validate_python(self, value, state):
if value != authentication_token():
raise formencode.Invalid(self.message('invalid_token', state,
search_number=value), value, state)
raise formencode.Invalid(
self.message('invalid_token',
state, search_number=value),
value,
state
def ValidUsername(edit, old_data):
class _ValidUsername(formencode.validators.FancyValidator):
@@ -74,17 +78,19 @@ def ValidUsername(edit, old_data):
if old_un != value or not edit:
if User.get_by_username(value, case_insensitive=True):
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)
_('Username may only contain alphanumeric characters '
'underscores, periods or dashes and must begin with '
'alphanumeric character'),
return _ValidUsername
def ValidUsersGroup(edit, old_data):
@@ -100,21 +106,23 @@ def ValidUsersGroup(edit, old_data):
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,
'already exists'), value,
state)
raise formencode.Invalid(_('RepoGroup name may only contain '
_('RepoGroup name may only contain alphanumeric characters '
return _ValidUsersGroup
def ValidReposGroup(edit, old_data):
class _ValidReposGroup(formencode.validators.FancyValidator):
@@ -174,38 +182,41 @@ def ValidReposGroup(edit, old_data):
class ValidPassword(formencode.validators.FancyValidator):
def to_python(self, value, state):
if value:
if not value:
return
if value.get('password'):
value['password'] = get_crypt_password(value['password'])
except UnicodeEncodeError:
e_dict = {'password':_('Invalid characters in password')}
raise formencode.Invalid('', value, state, error_dict=e_dict)
e_dict = {'password': _('Invalid characters in password')}
if value.get('password_confirmation'):
value['password_confirmation'] = \
get_crypt_password(value['password_confirmation'])
e_dict = {'password_confirmation':_('Invalid characters in password')}
e_dict = {
'password_confirmation': _('Invalid characters in password')
if value.get('new_password'):
value['new_password'] = \
get_crypt_password(value['new_password'])
e_dict = {'new_password':_('Invalid characters in password')}
e_dict = {'new_password': _('Invalid characters in password')}
return value
class ValidPasswordsMatch(formencode.validators.FancyValidator):
@@ -221,35 +232,39 @@ class ValidAuth(formencode.validators.Fa
'invalid_password':_('invalid password'),
'invalid_login':_('invalid user name'),
'disabled_account':_('Your account is disabled')
# error mapping
e_dict = {'username':messages['invalid_login'],
'password':messages['invalid_password']}
e_dict_disable = {'username':messages['disabled_account']}
e_dict = {'username': messages['invalid_login'],
'password': messages['invalid_password']}
e_dict_disable = {'username': messages['disabled_account']}
password = value['password']
username = value['username']
if authenticate(username, password):
else:
if user and user.active is False:
log.warning('user %s is disabled', username)
raise formencode.Invalid(self.message('disabled_account',
state=State_obj),
value, state,
error_dict=self.e_dict_disable)
self.message('disabled_account',
error_dict=self.e_dict_disable
log.warning('user %s not authenticated', username)
raise formencode.Invalid(self.message('invalid_password',
state=State_obj), value, state,
error_dict=self.e_dict)
self.message('invalid_password',
error_dict=self.e_dict
class ValidRepoUser(formencode.validators.FancyValidator):
@@ -269,47 +284,47 @@ def ValidRepoName(edit, old_data):
slug = repo_name_slug(repo_name)
if slug in [ADMIN_PREFIX, '']:
e_dict = {'repo_name': _('This repository name is disallowed')}
if value.get('repo_group'):
gr = RepoGroup.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 actual just the name to store in the
# database
repo_name_full = group_path + RepoGroup.url_sep() + repo_name
group_path = ''
repo_name_full = repo_name
value['repo_name_full'] = repo_name_full
rename = old_data.get('repo_name') != repo_name_full
create = not edit
if rename or create:
if group_path != '':
if Repository.get_by_repo_name(repo_name_full):
e_dict = {'repo_name':_('This repository already '
'exists in a group "%s"') %
gr.group_name}
'repo_name': _('This repository already exists in '
'a group "%s"') % gr.group_name
raise formencode.Invalid('', value, state,
error_dict=e_dict)
elif RepoGroup.get_by_group_name(repo_name_full):
e_dict = {'repo_name':_('There is a group with this'
' name already "%s"') %
repo_name_full}
'repo_name': _('There is a group with this name '
'already "%s"') % repo_name_full
elif Repository.get_by_repo_name(repo_name_full):
e_dict = {'repo_name':_('This repository '
e_dict = {'repo_name': _('This repository '
'already exists')}
@@ -338,20 +353,20 @@ def ValidCloneUri():
elif value.startswith('https'):
httpsrepository(make_ui('db'), value).capabilities
except Exception, e:
raise formencode.Invalid(_('invalid clone url'), value,
elif value.startswith('http'):
httprepository(make_ui('db'), value).capabilities
raise formencode.Invalid(_('Invalid clone url, provide a '
'valid clone http\s url'), value,
@@ -371,13 +386,13 @@ def ValidForkType(old_data):
return _ValidForkType
class ValidPerms(formencode.validators.FancyValidator):
messages = {'perm_new_member_name':_('This username or users group name'
messages = {'perm_new_member_name': _('This username or users group name'
' is not valid')}
perms_update = []
perms_new = []
#build a list of permission to update and new permission to create
@@ -390,14 +405,15 @@ class ValidPerms(formencode.validators.F
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]]
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))
@@ -416,59 +432,60 @@ class ValidPerms(formencode.validators.F
.filter(UsersGroup.users_group_active == True)\
.filter(UsersGroup.users_group_name == k).one()
msg = self.message('perm_new_member_name',
state=State_obj)
raise formencode.Invalid(msg, value, state,
error_dict={'perm_new_member_name':msg})
msg, value, state, error_dict={'perm_new_member_name': msg}
class ValidSettings(formencode.validators.FancyValidator):
# settings form can't edit user
if value.has_key('user'):
if 'user' in value:
del['value']['user']
class ValidPath(formencode.validators.FancyValidator):
if not os.path.isdir(value):
msg = _('This is not a valid path')
error_dict={'paths_root_path':msg})
error_dict={'paths_root_path': msg})
def UniqSystemEmail(old_data):
class _UniqSystemEmail(formencode.validators.FancyValidator):
value = value.lower()
if old_data.get('email','').lower() != value:
if old_data.get('email', '').lower() != value:
user = User.get_by_email(value, case_insensitive=True)
if user:
_("This e-mail address is already taken"),
value, state)
_("This e-mail address is already taken"), value, state
return _UniqSystemEmail
class ValidSystemEmail(formencode.validators.FancyValidator):
raise formencode.Invalid(_("This e-mail address doesn't exist.") ,
_("This e-mail address doesn't exist."), value, state
class LdapLibValidator(formencode.validators.FancyValidator):
@@ -483,43 +500,44 @@ class LdapLibValidator(formencode.valida
class AttrLoginValidator(formencode.validators.FancyValidator):
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'"),
_("The LDAP Login attribute of the CN must be specified - "
"this is the name of the attribute that is equivalent "
"to 'username'"), value, state
# FORMS
class LoginForm(formencode.Schema):
allow_extra_fields = True
filter_extra_fields = True
username = UnicodeString(
strip=True,
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(
min=3,
'empty':_('Please enter a password'),
'tooShort':_('Enter %(min)i characters or more')}
'empty': _('Please enter a password'),
'tooShort': _('Enter %(min)i characters or more')}
remember = StringBoolean(if_missing=False)
chained_validators = [ValidAuth]
@@ -528,21 +546,23 @@ def UserForm(edit=False, old_data={}):
username = All(UnicodeString(strip=True, min=1, not_empty=True),
ValidUsername(edit, old_data))
if edit:
new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=False))
password_confirmation = All(UnicodeString(strip=True, min=6,
not_empty=False))
admin = StringBoolean(if_missing=False)
password = All(UnicodeString(strip=True, min=6, not_empty=True))
active = StringBoolean(if_missing=False)
name = UnicodeString(strip=True, min=1, not_empty=True)
lastname = UnicodeString(strip=True, min=1, not_empty=True)
name = UnicodeString(strip=True, min=1, not_empty=False)
lastname = UnicodeString(strip=True, min=1, not_empty=False)
email = All(Email(not_empty=True), UniqSystemEmail(old_data))
chained_validators = [ValidPasswordsMatch, ValidPassword]
return _UserForm
@@ -589,27 +609,29 @@ def RegisterForm(edit=False, old_data={}
username = All(ValidUsername(edit, old_data),
UnicodeString(strip=True, min=1, not_empty=True))
password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
return _RegisterForm
def PasswordResetForm():
class _PasswordResetForm(formencode.Schema):
email = All(ValidSystemEmail(), Email(not_empty=True))
return _PasswordResetForm
def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
repo_groups=[]):
class _RepoForm(formencode.Schema):
filter_extra_fields = False
repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
@@ -627,12 +649,13 @@ def RepoForm(edit=False, old_data={}, su
#this is repo owner
user = All(UnicodeString(not_empty=True), ValidRepoUser)
chained_validators = [ValidRepoName(edit, old_data), ValidPerms]
return _RepoForm
def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
class _RepoForkForm(formencode.Schema):
@@ -645,12 +668,13 @@ def RepoForkForm(edit=False, old_data={}
update_after_clone = StringBoolean(if_missing=False)
fork_parent_id = UnicodeString()
chained_validators = [ValidForkName(edit, old_data)]
return _RepoForkForm
def RepoSettingsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
@@ -671,12 +695,13 @@ def ApplicationSettingsForm():
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
def ApplicationUiSettingsForm():
class _ApplicationUiSettingsForm(formencode.Schema):
web_push_ssl = OneOf(['true', 'false'], if_missing='false')
paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
@@ -684,12 +709,13 @@ def ApplicationUiSettingsForm():
hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
return _ApplicationUiSettingsForm
def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
class _DefaultPermissionsForm(formencode.Schema):
overwrite_default = StringBoolean(if_missing=False)
anonymous = OneOf(['True', 'False'], if_missing=False)
@@ -89,13 +89,12 @@ class UserModel(BaseModel):
self.sa.add(new_user)
return new_user
raise
def create_or_update(self, username, password, email, name, lastname,
active=True, admin=False, ldap_dn=None):
Creates a new instance if not found, or updates current one
@@ -133,13 +132,12 @@ class UserModel(BaseModel):
except (DatabaseError,):
def create_for_container_auth(self, username, attrs):
Creates the given user if it's not already in the database
:param attrs:
@@ -228,13 +226,13 @@ class UserModel(BaseModel):
'- Username: %s\n'
'- Full Name: %s\n'
'- Email: %s\n')
body = body % (new_user.username, new_user.full_name,
new_user.email)
edit_url = url('edit_user', id=new_user.user_id, qualified=True)
kw = {'registered_user_url':edit_url}
kw = {'registered_user_url': edit_url}
NotificationModel().create(created_by=new_user, subject=subject,
body=body, recipients=None,
type_=Notification.TYPE_REGISTRATION,
email_kwargs=kw)
@@ -490,13 +488,12 @@ class UserModel(BaseModel):
new = UserToPerm()
new.user = user
new.permission = perm
self.sa.add(new)
def revoke_perm(self, user, perm):
if not isinstance(perm, Permission):
raise Exception('perm needs to be an instance of Permission class '
'got %s instead' % type(perm))
user = self.__get_user(user)
@@ -83,13 +83,13 @@
<div class="field">
<div class="label label-checkbox">
<label for="active">${_('Active')}:</label>
</div>
<div class="checkboxes">
${h.checkbox('active',value=True)}
${h.checkbox('active',value=True,checked='checked')}
<div class="buttons">
${h.submit('save',_('save'),class_="ui-button")}
@@ -109,13 +109,13 @@
${h.end_form()}
%else:
<div class="links_left">
<div class="full_name">${c.rhodecode_user.full_name}</div>
<div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
<div class="email">${c.rhodecode_user.email}</div>
<div class="big_gravatar"><img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,48)}" /></div>
<div class="links_right">
<ol class="links">
<li>${h.link_to(_(u'Home'),h.url('home'))}</li>
Status change: