@@ -6,54 +6,55 @@
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; version 2
# of the License or (at your opinion) any later version of the license.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
"""
Created on April 4, 2010
users controller for pylons
@author: marcink
from formencode import htmlfill
from pylons import request, session, tmpl_context as c, url
from pylons.controllers.util import abort, redirect
from pylons.i18n.translation import _
from rhodecode.lib.exceptions import *
from rhodecode.lib import helpers as h
from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
from rhodecode.lib.base import BaseController, render
from rhodecode.model.db import User, UserLog
from rhodecode.model.db import User
from rhodecode.model.forms import UserForm
from rhodecode.model.user import UserModel, DefaultUserException
from rhodecode.model.user import UserModel
import formencode
import logging
import traceback
log = logging.getLogger(__name__)
class UsersController(BaseController):
"""REST Controller styled on the Atom Publishing Protocol"""
# To properly map this controller, ensure your config/routing.py
# file has a resource setup:
# map.resource('user', 'users')
@LoginRequired()
@HasPermissionAllDecorator('hg.admin')
def __before__(self):
c.admin_user = session.get('admin_user')
c.admin_username = session.get('admin_username')
super(UsersController, self).__before__()
def index(self, format='html'):
"""GET /users: All items in the collection"""
# url('users')
@@ -114,49 +115,49 @@ class UsersController(BaseController):
render('admin/users/user_edit.html'),
defaults=errors.value,
errors=errors.error_dict or {},
prefix_error=False,
encoding="UTF-8")
except Exception:
log.error(traceback.format_exc())
h.flash(_('error occured during update of user %s') \
% form_result.get('username'), category='error')
return redirect(url('users'))
def delete(self, id):
"""DELETE /users/id: Delete an existing item"""
# Forms posted to this method should contain a hidden field:
# <input type="hidden" name="_method" value="DELETE" />
# Or using helpers:
# h.form(url('user', id=ID),
# method='delete')
# url('user', id=ID)
user_model = UserModel()
try:
user_model.delete(id)
h.flash(_('sucessfully deleted user'), category='success')
except DefaultUserException, e:
except (UserOwnsReposException, DefaultUserException), e:
h.flash(str(e), category='warning')
h.flash(_('An error occured during deletion of user'),
category='error')
def show(self, id, format='html'):
"""GET /users/id: Show a specific item"""
def edit(self, id, format='html'):
"""GET /users/id/edit: Form to edit an existing item"""
# url('edit_user', id=ID)
c.user = self.sa.query(User).get(id)
if not c.user:
if c.user.username == 'default':
h.flash(_("You can't edit this user"), category='warning')
defaults = c.user.__dict__
return htmlfill.render(
@@ -3,50 +3,51 @@
# authentication and permission libraries
# Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
from pylons import config, session, url, request
from rhodecode.lib.utils import get_repo_slug
from rhodecode.lib.auth_ldap import AuthLdap, UsernameError, PasswordError
from rhodecode.lib.auth_ldap import AuthLdap
from rhodecode.model import meta
from rhodecode.model.caching_query import FromCache
from rhodecode.model.db import User, RepoToPerm, Repository, Permission, \
UserToPerm
import bcrypt
from decorator import decorator
import random
class PasswordGenerator(object):
"""This is a simple class for generating password from
different sets of characters
usage:
passwd_gen = PasswordGenerator()
#print 8-letter password containing only big and small letters of alphabet
print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
ALPHABETS_NUM = r'''1234567890'''#[0]
ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''#[1]
ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''#[2]
@@ -108,49 +109,49 @@ def authfunc(environ, username, password
#======================================================================
# FALLBACK TO LDAP AUTH IN ENABLE
if ldap_settings.get('ldap_active', False):
kwargs = {
'server':ldap_settings.get('ldap_host', ''),
'base_dn':ldap_settings.get('ldap_base_dn', ''),
'port':ldap_settings.get('ldap_port'),
'bind_dn':ldap_settings.get('ldap_dn_user'),
'bind_pass':ldap_settings.get('ldap_dn_pass'),
'use_ldaps':ldap_settings.get('ldap_ldaps'),
'ldap_version':3,
}
log.debug('Checking for ldap authentication')
aldap = AuthLdap(**kwargs)
res = aldap.authenticate_ldap(username, password)
authenticated = res[1]['uid'][0] == username
if authenticated and user_model.create_ldap(username, password):
log.info('created new ldap user')
return authenticated
except (UsernameError, PasswordError):
except (LdapUsernameError, LdapPasswordError):
return False
except:
class AuthUser(object):
A simple object that handles a mercurial username for authentication
def __init__(self):
self.username = 'None'
self.name = ''
self.lastname = ''
self.email = ''
self.user_id = None
self.is_authenticated = False
self.is_admin = False
self.permissions = {}
def __repr__(self):
return "<AuthUser('id:%s:%s')>" % (self.user_id, self.username)
def set_available_permissions(config):
#==============================================================================
# LDAP
#Name = Just a description for the auth modes page
#Host = DepartmentName.OrganizationName.local/ IP
#Port = 389 default for ldap
#LDAPS = no set True if You need to use ldaps
#Account = DepartmentName\UserName (or UserName@MyDomain depending on AD server)
#Password = <password>
#Base DN = DC=DepartmentName,DC=OrganizationName,DC=local
#!/usr/bin/env python
# encoding: utf-8
# ldap authentication lib
Created on Nov 17, 2010
from rhodecode.lib.exceptions import LdapImportError, UsernameError, \
PasswordError, ConnectionError
import ldap
except ImportError:
pass
class AuthLdap(object):
def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
use_ldaps=False, ldap_version=3):
self.ldap_version = ldap_version
if use_ldaps:
port = port or 689
self.LDAP_USE_LDAPS = use_ldaps
self.LDAP_SERVER_ADDRESS = server
self.LDAP_SERVER_PORT = port
#USE FOR READ ONLY BIND TO LDAP SERVER
self.LDAP_BIND_DN = bind_dn
self.LDAP_BIND_PASS = bind_pass
@@ -40,54 +52,54 @@ class AuthLdap(object):
if self.LDAP_USE_LDAPS:ldap_server_type = ldap_server_type + 's'
self.LDAP_SERVER = "%s://%s:%s" % (ldap_server_type,
self.LDAP_SERVER_ADDRESS,
self.LDAP_SERVER_PORT)
self.BASE_DN = base_dn
self.AUTH_DN = "uid=%s,%s"
def authenticate_ldap(self, username, password):
"""Authenticate a user via LDAP and return his/her LDAP properties.
Raises AuthenticationError if the credentials are rejected, or
EnvironmentError if the LDAP server can't be reached.
:param username: username
:param password: password
from rhodecode.lib.helpers import chop_at
uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS)
dn = self.AUTH_DN % (uid, self.BASE_DN)
log.debug("Authenticating %r at %s", dn, self.LDAP_SERVER)
if "," in username:
raise UsernameError("invalid character in username: ,")
raise LdapUsernameError("invalid character in username: ,")
ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, '/etc/openldap/cacerts')
ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
server = ldap.initialize(self.LDAP_SERVER)
if self.ldap_version == 2:
server.protocol = ldap.VERSION2
else:
server.protocol = ldap.VERSION3
if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
server.simple_bind_s(self.AUTH_DN % (self.LDAP_BIND_DN,
self.BASE_DN),
self.LDAP_BIND_PASS)
server.simple_bind_s(dn, password)
properties = server.search_s(dn, ldap.SCOPE_SUBTREE)
if not properties:
raise ldap.NO_SUCH_OBJECT()
except ldap.NO_SUCH_OBJECT, e:
log.debug("LDAP says no such user '%s' (%s)", uid, username)
raise UsernameError()
raise LdapUsernameError()
except ldap.INVALID_CREDENTIALS, e:
log.debug("LDAP rejected password for user '%s' (%s)", uid, username)
raise PasswordError()
raise LdapPasswordError()
except ldap.SERVER_DOWN, e:
raise ConnectionError("LDAP can't access authentication server")
raise LdapConnectionError("LDAP can't access authentication server")
return properties[0]
@@ -2,28 +2,31 @@
# Custom Exceptions modules
Custom Exceptions modules
class UsernameError(Exception):pass
class PasswordError(Exception):pass
class ConnectionError(Exception):pass
class LdapUsernameError(Exception):pass
class LdapPasswordError(Exception):pass
class LdapConnectionError(Exception):pass
class LdapImportError(Exception):pass
class DefaultUserException(Exception):pass
class UserOwnsReposException(Exception):pass
@@ -27,48 +27,50 @@ class RhodeCodeUi(Base):
ui_id = Column("ui_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
ui_section = Column("ui_section", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
ui_key = Column("ui_key", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
ui_value = Column("ui_value", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
ui_active = Column("ui_active", BOOLEAN(), nullable=True, unique=None, default=True)
class User(Base):
__tablename__ = 'users'
__table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'useexisting':True})
user_id = Column("user_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True)
username = Column("username", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
password = Column("password", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
active = Column("active", BOOLEAN(), nullable=True, unique=None, default=None)
admin = Column("admin", BOOLEAN(), nullable=True, unique=None, default=False)
name = Column("name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
lastname = Column("lastname", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
email = Column("email", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
last_login = Column("last_login", DATETIME(timezone=False), nullable=True, unique=None, default=None)
is_ldap = Column("is_ldap", BOOLEAN(), nullable=False, unique=None, default=False)
user_log = relation('UserLog', cascade='all')
user_perms = relation('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
repositories = relation('Repository')
@LazyProperty
def full_contact(self):
return '%s %s <%s>' % (self.name, self.lastname, self.email)
return "<User('id:%s:%s')>" % (self.user_id, self.username)
def update_lastlogin(self):
"""Update user lastlogin"""
import datetime
session = Session.object_session(self)
self.last_login = datetime.datetime.now()
session.add(self)
session.commit()
log.debug('updated user %s lastlogin', self.username)
session.rollback()
class UserLog(Base):
__tablename__ = 'user_logs'
__table_args__ = {'useexisting':True}
Created on April 9, 2010
Model for users
:author: marcink
from rhodecode.model.meta import Session
class UserModel(object):
self.sa = Session()
def get(self, user_id, cache=False):
user = self.sa.query(User)
if cache:
user = user.options(FromCache("sql_cache_short",
"get_user_%s" % user_id))
return user.get(user_id)
def get_by_username(self, username, cache=False):
user = self.sa.query(User)\
.filter(User.username == username)
"get_user_%s" % username))
return user.scalar()
def create(self, form_data):
@@ -107,89 +108,96 @@ class UserModel(object):
setattr(new_user, k, v)
self.sa.add(new_user)
self.sa.commit()
body = ('New user registration\n'
'username: %s\n'
'email: %s\n')
body = body % (form_data['username'], form_data['email'])
run_task(tasks.send_email, None,
_('[RhodeCode] New User registration'),
body)
self.sa.rollback()
raise
def update(self, user_id, form_data):
new_user = self.get(user_id, cache=False)
if new_user.username == 'default':
raise DefaultUserException(
_("You can't Edit this user since it's"
" crucial for entire application"))
for k, v in form_data.items():
if k == 'new_password' and v != '':
new_user.password = v
def update_my_account(self, user_id, form_data):
if k not in ['admin', 'active']:
def delete(self, user_id):
user = self.get(user_id, cache=False)
if user.username == 'default':
_("You can't remove this user since it's"
if user.repositories:
raise UserOwnsReposException(_('This user still owns %s '
'repositories and cannot be '
'removed. Switch owners or '
'remove those repositories') \
% user.repositories)
self.sa.delete(user)
def reset_password(self, data):
from rhodecode.lib.celerylib import tasks, run_task
run_task(tasks.reset_user_password, data['email'])
def fill_data(self, user):
Fills user data with those from database and log out user if not
present in database
:param user:
if not hasattr(user, 'user_id') or user.user_id is None:
raise Exception('passed in user has to have the user_id attribute')
log.debug('filling auth user data')
Status change: