diff --git a/config b/config index 03e1457..47f0086 100644 --- a/config +++ b/config @@ -15,9 +15,9 @@ hosts = 0.0.0.0:5232 daemon = False # SSL flag, enable HTTPS protocol ssl = False -# SSL certificate path (if needed) +# SSL certificate path certificate = /etc/apache2/ssl/server.crt -# SSL private key (if needed) +# SSL private key key = /etc/apache2/ssl/server.key [encoding] @@ -28,29 +28,24 @@ stock = utf-8 [acl] # Access method -# Value: None | htpasswd +# Value: None | htpasswd | LDAP type = None -# Personal calendars only available for logged in users (if needed) +# Personal calendars only available for logged in users personal = False -# Htpasswd filename (if needed) -filename = /etc/radicale/users -# Htpasswd encryption method (if needed) +# Htpasswd filename +htpasswd_filename = /etc/radicale/users +# Htpasswd encryption method # Value: plain | sha1 | crypt -encryption = crypt - -[authLdap] -#LDAP Host -LDAPServer = 127.0.0.1 -#Fields to create a LDAP bind -#Value to add before the user name in a LDAP bind -LDAPPrepend = uid= -#Value to add after the user name in a LDAP bind -LDAPAppend = ou=users,dc=exmaple,dc=dom -#=> uid=corentin,ou=users,dc=exmaple,dc=dom +htpasswd_encryption = crypt +# LDAP server URL, with protocol and port +ldap_url = ldap://localhost:389/ +# LDAP base path +ldap_base = ou=users,dc=example,dc=com +# LDAP login attribute +ldap_attribute = uid [storage] -# Folder for storing local calendars, -# created if not present +# Folder for storing local calendars, created if not present folder = ~/.config/radicale/calendars [logging] diff --git a/radicale/__init__.py b/radicale/__init__.py index c8a0794..f888d92 100644 --- a/radicale/__init__.py +++ b/radicale/__init__.py @@ -60,26 +60,30 @@ def _check(request, function): if not request._calendar or not request.server.acl: return function(request) - log.LOGGER.info("Checking rights for %s" % request._calendar.owner) + if request._calendar.owner is None and PERSONAL: + # No owner and personal calendars, don't check rights + return function(request) + + log.LOGGER.info( + "Checking rights for calendar owned by %s" % request._calendar.owner) authorization = request.headers.get("Authorization", None) if authorization: challenge = authorization.lstrip("Basic").strip().encode("ascii") - plain = request._decode(base64.b64decode(challenge)) - user, password = plain.split(":") + user, password = request._decode(base64.b64decode(challenge)).split(":") else: user = password = None if request.server.acl.has_right(request._calendar.owner, user, password): - function(request) log.LOGGER.info("%s allowed" % request._calendar.owner) + return function(request) else: + log.LOGGER.info("%s refused" % request._calendar.owner) request.send_response(client.UNAUTHORIZED) request.send_header( "WWW-Authenticate", "Basic realm=\"Radicale Server - Password Required\"") request.end_headers() - log.LOGGER.info("%s refused" % request._calendar.owner) def _log_request_content(request, function): """Log the content of the request and store it in the request object.""" diff --git a/radicale/acl/LDAP.py b/radicale/acl/LDAP.py new file mode 100644 index 0000000..57e7d0a --- /dev/null +++ b/radicale/acl/LDAP.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +# +# This file is part of Radicale Server - Calendar Server +# Copyright © 2011 Corentin Le Bail +# Copyright © 2011 Guillaume Ayoub +# +# This library 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, either version 3 of the License, or +# (at your option) any later version. +# +# This library 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 Radicale. If not, see . + +""" +LDAP ACL. + +Authentication based on the ``python-ldap`` module +(http://www.python-ldap.org/). + +""" + +import ldap +from radicale import config, log + + +BASE = config.get("acl", "ldap_base") +ATTRIBUTE = config.get("acl", "ldap_attribute") +CONNEXION = ldap.initialize(config.get("acl", "ldap_url")) +PERSONAL = config.getboolean("acl", "personal") + + +def has_right(owner, user, password): + """Check if ``user``/``password`` couple is valid.""" + if (user != owner and PERSONAL) or not user: + # User is not owner and personal calendars, or no user given, forbidden + return False + + dn = "%s=%s" % (ATTRIBUTE, ldap.dn.escape_dn_chars(user)) + log.LOGGER.debug("LDAP bind for %s in base %s" % (dn, BASE)) + + users = CONNEXION.search_s(BASE, ldap.SCOPE_ONELEVEL, dn) + if users: + log.LOGGER.debug("User %s found" % user) + try: + CONNEXION.simple_bind_s(users[0][0], password or "") + except ldap.INVALID_CREDENTIALS: + log.LOGGER.debug("Invalid credentials") + else: + log.LOGGER.debug("LDAP bind OK") + return True + else: + log.LOGGER.debug("User %s not found" % user) + + log.LOGGER.debug("LDAP bind failed") + return False diff --git a/radicale/acl/authLdap.py b/radicale/acl/authLdap.py deleted file mode 100644 index c9c67d2..0000000 --- a/radicale/acl/authLdap.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- - -import sys -import ldap -import radicale - -LDAPSERVER = config.get("authLdap", "LDAPServer") -LDAPPREPEND = config.get("authLdap", "LDAPPrepend") -LDAPAPPEND = config.get("authLdap", "LDAPAppend") - -def has_right(owner, user, password): - if user == None: - user="" - if password == None: - password="" - if owner != user: - return False - try: - radicale.log.LOGGER.info("Open LDAP server connexion") - l=ldap.open(LDAPSERVER, 389) - cn="%s%s,%s" % (LDAPPREPEND, user, LDAPAPPEND) - radicale.log.LOGGER.info("LDAP bind with dn: %s" % (cn)) - l.simple_bind_s(cn, password); - radicale.log.LOGGER.info("LDAP bind ok") - return True - except: - radicale.log.LOGGER.info("Nu such credential") - return False diff --git a/radicale/acl/htpasswd.py b/radicale/acl/htpasswd.py index 422b96c..70197d1 100644 --- a/radicale/acl/htpasswd.py +++ b/radicale/acl/htpasswd.py @@ -33,6 +33,11 @@ import hashlib from radicale import config +FILENAME = config.get("acl", "htpasswd_filename") +PERSONAL = config.getboolean("acl", "personal") +ENCRYPTION = config.get("acl", "htpasswd_encryption") + + def _plain(hash_value, password): """Check if ``hash_value`` and ``password`` match using plain method.""" return hash_value == password @@ -48,7 +53,7 @@ def _crypt(hash_value, password): def _sha1(hash_value, password): """Check if ``hash_value`` and ``password`` match using sha1 method.""" hash_value = hash_value.replace("{SHA}", "").encode("ascii") - password = password.encode(config.get("encoding", "stock")) + password = password.encode(config.get("htpasswd_encoding", "stock")) sha1 = hashlib.sha1() # pylint: disable=E1101 sha1.update(password) return sha1.digest() == base64.b64decode(hash_value) @@ -56,18 +61,9 @@ def _sha1(hash_value, password): def has_right(owner, user, password): """Check if ``user``/``password`` couple is valid.""" - if owner is None and PERSONAL: - # No owner and personal calendars, everybody is allowed - return True - for line in open(FILENAME).readlines(): if line.strip(): login, hash_value = line.strip().split(":") if login == user and (not PERSONAL or user == owner): - return CHECK_PASSWORD(hash_value, password) + return locals()["_%s" % ENCRYPTION](hash_value, password) return False - - -FILENAME = config.get("acl", "filename") -PERSONAL = config.getboolean("acl", "personal") -CHECK_PASSWORD = locals()["_%s" % config.get("acl", "encryption")] diff --git a/radicale/config.py b/radicale/config.py index c285ca0..d54a45b 100644 --- a/radicale/config.py +++ b/radicale/config.py @@ -50,17 +50,16 @@ INITIAL_CONFIG = { "acl": { "type": "None", "personal": "False", - "filename": "/etc/radicale/users", - "encryption": "crypt"}, + "httpasswd_filename": "/etc/radicale/users", + "httpasswd_encryption": "crypt", + "ldap_url": "ldap://localhost:389/", + "ldap_base": "ou=users,dc=example,dc=com", + "ldap_attribute": "uid"}, "storage": { "folder": os.path.expanduser("~/.config/radicale/calendars")}, "logging": { "config": "/etc/radicale/logging", - "debug": "False"}, - "authLdap": { - "LDAPServer": "127.0.0.1", - "LDAPPrepend": "uid=", - "LDAPAppend": "ou=users,dc=example,dc=com"}} + "debug": "False"}} # Create a ConfigParser and configure it _CONFIG_PARSER = ConfigParser()