@@ -92,49 +92,53 @@ via pypi, so You can install it by runni
::
pip install python-ldap
.. note::
python-ldap requires some certain libs on Your system, so before installing
it check that You have at least `openldap`, and `sasl` libraries.
ldap settings are located in admin->ldap section,
Here's a typical ldap setup::
Enable ldap = checked #controls if ldap access is enabled
Host = host.domain.org #actual ldap server to connect
Port = 389 or 689 for ldaps #ldap server ports
Enable LDAPS = unchecked #enable disable ldaps
Account = <account> #access for ldap server(if required)
Password = <password> #password for ldap server(if required)
Base DN = uid=%(user)s,CN=users,DC=host,DC=domain,DC=org
`Account` and `Password` are optional, and used for two-phase ldap
authentication so those are credentials to access Your ldap, if it doesn't
support anonymous search/user lookups.
Base DN must have %(user)s template inside, it's a placer where Your uid used
to login would go, it allows admins to specify not standard schema for uid
variable
If all data are entered correctly, and `python-ldap` is properly installed
Users should be granted to access RhodeCode wit ldap accounts. When
logging at the first time an special ldap account is created inside RhodeCode,
so You can control over permissions even on ldap users. If such user exists
already in RhodeCode database ldap user with the same username would be not
able to access RhodeCode.
If You have problems with ldap access and believe You entered correct
information check out the RhodeCode logs,any error messages sent from
ldap will be saved there.
Nginx virtual host example
--------------------------
Sample config for nginx using proxy::
server {
listen 80;
server_name hg.myserver.com;
access_log /var/log/nginx/rhodecode.access.log;
error_log /var/log/nginx/rhodecode.error.log;
location / {
@@ -34,72 +34,72 @@ 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
ldap_server_type = 'ldap'
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 LdapUsernameError("invalid character in username: ,")
try:
ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, '/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)
login_dn = self.BASE_DN % {'user':uid}
server.simple_bind_s(login_dn, self.LDAP_BIND_PASS)
dn = self.BASE_DN % {'user':uid}
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 LdapUsernameError()
except ldap.INVALID_CREDENTIALS, e:
log.debug("LDAP rejected password for user '%s' (%s)", uid, username)
raise LdapPasswordError()
except ldap.SERVER_DOWN, e:
raise LdapConnectionError("LDAP can't access authentication server")
return properties[0]
@@ -279,48 +279,68 @@ def UniqSystemEmail(old_data):
class ValidSystemEmail(formencode.validators.FancyValidator):
def to_python(self, value, state):
value = value.lower()
sa = meta.Session
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()
return value
class LdapLibValidator(formencode.validators.FancyValidator):
import ldap
except ImportError:
raise LdapImportError
class BaseDnValidator(formencode.validators.FancyValidator):
value % {'user':'valid'}
if value.find('%(user)s') == -1:
raise formencode.Invalid(_("You need to specify %(user)s in "
"template for example uid=%(user)s "
",dc=company...") ,
except KeyError:
raise formencode.Invalid(_("Wrong template used, only %(user)s "
"is an valid entry") ,
#===============================================================================
# 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')}
)
password = UnicodeString(
min=6,
'empty':_('Please enter a password'),
'tooShort':_('Enter %(min)i characters or more')}
@@ -436,27 +456,27 @@ def 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)
default_perm = OneOf(perms_choices)
default_register = OneOf(register_choices)
default_create = OneOf(create_choices)
return _DefaultPermissionsForm
def LdapSettingsForm():
class _LdapSettingsForm(formencode.Schema):
pre_validators = [LdapLibValidator]
ldap_active = StringBoolean(if_missing=False)
ldap_host = UnicodeString(strip=True,)
ldap_port = Number(strip=True,)
ldap_ldaps = StringBoolean(if_missing=False)
ldap_dn_user = UnicodeString(strip=True,)
ldap_dn_pass = UnicodeString(strip=True,)
ldap_base_dn = UnicodeString(strip=True,)
ldap_base_dn = All(BaseDnValidator, UnicodeString(strip=True,))
return _LdapSettingsForm
from rhodecode import get_version
import sys
py_version = sys.version_info
requirements = [
"Pylons>=1.0.0",
"SQLAlchemy>=0.6.5",
"Mako>=0.3.6",
"vcs>=0.1.10",
"pygments>=1.3.0",
"mercurial>=1.7.1",
"whoosh>=1.3.1",
"whoosh==1.3.1",
"celery>=2.1.3",
"py-bcrypt",
"babel",
]
classifiers = ['Development Status :: 4 - Beta',
'Environment :: Web Environment',
'Framework :: Pylons',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python', ]
if sys.version_info < (2, 6):
requirements.append("simplejson")
requirements.append("pysqlite")
#additional files from project that goes somewhere in the filesystem
#relative to sys.prefix
data_files = []
#additional files that goes into package itself
package_data = {'rhodecode': ['i18n/*/LC_MESSAGES/*.mo', ], }
@@ -72,27 +73,31 @@ setup(
install_requires=requirements,
classifiers=classifiers,
setup_requires=["PasteScript>=1.6.3"],
data_files=data_files,
packages=packages,
include_package_data=True,
test_suite='nose.collector',
package_data=package_data,
message_extractors={'rhodecode': [
('**.py', 'python', None),
('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}),
('public/**', 'ignore', None)]},
zip_safe=False,
paster_plugins=['PasteScript', 'Pylons'],
entry_points="""
[paste.app_factory]
main = rhodecode.config.middleware:make_app
[paste.app_install]
main = pylons.util:PylonsInstaller
[paste.global_paster_command]
make-index = rhodecode.lib.indexers:MakeIndex
upgrade-db = rhodecode.lib.utils:UpgradeDb
celeryd=rhodecode.lib.celerypylons.commands:CeleryDaemonCommand
celerybeat=rhodecode.lib.celerypylons.commands:CeleryBeatCommand
camqadm=rhodecode.lib.celerypylons.commands:CAMQPAdminCommand
celeryev=rhodecode.lib.celerypylons.commands:CeleryEventCommand
""",
Status change: