@@ -68,97 +68,101 @@ look like this
::
/path/to/python/bin/paster --repo-location=<location for repos> /path/to/rhodecode/production.ini
When using incremental(default) mode whoosh will check last modification date
of each file and add it to reindex if newer file is available. Also indexing
daemon checks for removed files and removes them from index.
Sometime You might want to rebuild index from scratch. You can do that using
the `-f` flag passed to paster command or, in admin panel You can check
`build from scratch` flag.
Setting up LDAP support
-----------------------
RhodeCode starting from version 1.1 supports ldap authentication. In order
to use ldap, You have to install python-ldap package. This package is available
via pypi, so You can install it by running
easy_install python-ldap
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 / {
root /var/www/rhodecode/rhodecode/public/;
if (!-f $request_filename){
proxy_pass http://127.0.0.1:5000;
}
#this is important for https !!!
proxy_set_header X-Url-Scheme $scheme;
include /etc/nginx/proxy.conf;
Here's the proxy.conf. It's tuned so it'll not timeout on long
pushes and also on large pushes::
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Proxy-host $proxy_host;
client_max_body_size 400m;
client_body_buffer_size 128k;
proxy_buffering off;
proxy_connect_timeout 3600;
proxy_send_timeout 3600;
@@ -10,96 +10,96 @@
#
# 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 Nov 17, 2010
@author: marcink
from rhodecode.lib.exceptions import *
import logging
log = logging.getLogger(__name__)
try:
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
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: ,")
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]
@@ -255,96 +255,116 @@ class ValidPath(formencode.validators.Fa
if not os.path.isdir(value):
msg = _('This is not a valid path')
raise formencode.Invalid(msg, value, state,
error_dict={'paths_root_path':msg})
return value
def UniqSystemEmail(old_data):
class _UniqSystemEmail(formencode.validators.FancyValidator):
def to_python(self, value, state):
value = value.lower()
if old_data.get('email') != value:
sa = meta.Session()
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()
return _UniqSystemEmail
class ValidSystemEmail(formencode.validators.FancyValidator):
sa = meta.Session
if user is None:
raise formencode.Invalid(_("This e-mail address doesn't exist.") ,
class LdapLibValidator(formencode.validators.FancyValidator):
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')}
#chained validators have access to all data
chained_validators = [ValidAuth]
def UserForm(edit=False, old_data={}):
class _UserForm(formencode.Schema):
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))
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)
email = All(Email(not_empty=True), UniqSystemEmail(old_data))
chained_validators = [ValidPassword]
return _UserForm
@@ -412,51 +432,51 @@ def RepoSettingsForm(edit=False, old_dat
def ApplicationSettingsForm():
class _ApplicationSettingsForm(formencode.Schema):
filter_extra_fields = False
rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
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))
hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
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)
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', ], }
description = ('Mercurial repository browser/management with '
'build in push/pull server and full text search')
#long description
readme_file = 'README.rst'
changelog_file = 'docs/changelog.rst'
long_description = open(readme_file).read() + '/n/n' + \
open(changelog_file).read()
except IOError, err:
sys.stderr.write("[WARNING] Cannot find file specified as "
"long_description (%s)\n or changelog (%s) skipping that file" \
% (readme_file, changelog_file))
long_description = description
from setuptools import setup, find_packages
from ez_setup import use_setuptools
use_setuptools()
#packages
packages = find_packages(exclude=['ez_setup'])
setup(
name='RhodeCode',
version=get_version(),
description=description,
long_description=long_description,
keywords='rhodiumcode mercurial web hgwebdir gitweb git replacement serving hgweb rhodecode',
license='BSD',
author='Marcin Kuzminski',
author_email='marcin@python-works.com',
url='http://hg.python-works.com',
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: