ソースを参照

Clean LDAP support

Guillaume Ayoub 15 年 前
コミット
12a8e01492
6 ファイル変更98 行追加71 行削除
  1. 15 20
      config
  2. 9 5
      radicale/__init__.py
  3. 61 0
      radicale/acl/LDAP.py
  4. 0 28
      radicale/acl/authLdap.py
  5. 7 11
      radicale/acl/htpasswd.py
  6. 6 7
      radicale/config.py

+ 15 - 20
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]

+ 9 - 5
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."""

+ 61 - 0
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 <http://www.gnu.org/licenses/>.
+
+"""
+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

+ 0 - 28
radicale/acl/authLdap.py

@@ -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

+ 7 - 11
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")]

+ 6 - 7
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()