Clean LDAP support

This commit is contained in:
Guillaume Ayoub 2011-04-25 16:47:42 +02:00
parent 78e52d5cf4
commit 12a8e01492
6 changed files with 98 additions and 71 deletions

35
config
View File

@ -15,9 +15,9 @@ hosts = 0.0.0.0:5232
daemon = False daemon = False
# SSL flag, enable HTTPS protocol # SSL flag, enable HTTPS protocol
ssl = False ssl = False
# SSL certificate path (if needed) # SSL certificate path
certificate = /etc/apache2/ssl/server.crt certificate = /etc/apache2/ssl/server.crt
# SSL private key (if needed) # SSL private key
key = /etc/apache2/ssl/server.key key = /etc/apache2/ssl/server.key
[encoding] [encoding]
@ -28,29 +28,24 @@ stock = utf-8
[acl] [acl]
# Access method # Access method
# Value: None | htpasswd # Value: None | htpasswd | LDAP
type = None type = None
# Personal calendars only available for logged in users (if needed) # Personal calendars only available for logged in users
personal = False personal = False
# Htpasswd filename (if needed) # Htpasswd filename
filename = /etc/radicale/users htpasswd_filename = /etc/radicale/users
# Htpasswd encryption method (if needed) # Htpasswd encryption method
# Value: plain | sha1 | crypt # Value: plain | sha1 | crypt
encryption = crypt htpasswd_encryption = crypt
# LDAP server URL, with protocol and port
[authLdap] ldap_url = ldap://localhost:389/
#LDAP Host # LDAP base path
LDAPServer = 127.0.0.1 ldap_base = ou=users,dc=example,dc=com
#Fields to create a LDAP bind # LDAP login attribute
#Value to add before the user name in a LDAP bind ldap_attribute = uid
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
[storage] [storage]
# Folder for storing local calendars, # Folder for storing local calendars, created if not present
# created if not present
folder = ~/.config/radicale/calendars folder = ~/.config/radicale/calendars
[logging] [logging]

View File

@ -60,26 +60,30 @@ def _check(request, function):
if not request._calendar or not request.server.acl: if not request._calendar or not request.server.acl:
return function(request) 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) authorization = request.headers.get("Authorization", None)
if authorization: if authorization:
challenge = authorization.lstrip("Basic").strip().encode("ascii") challenge = authorization.lstrip("Basic").strip().encode("ascii")
plain = request._decode(base64.b64decode(challenge)) user, password = request._decode(base64.b64decode(challenge)).split(":")
user, password = plain.split(":")
else: else:
user = password = None user = password = None
if request.server.acl.has_right(request._calendar.owner, user, password): if request.server.acl.has_right(request._calendar.owner, user, password):
function(request)
log.LOGGER.info("%s allowed" % request._calendar.owner) log.LOGGER.info("%s allowed" % request._calendar.owner)
return function(request)
else: else:
log.LOGGER.info("%s refused" % request._calendar.owner)
request.send_response(client.UNAUTHORIZED) request.send_response(client.UNAUTHORIZED)
request.send_header( request.send_header(
"WWW-Authenticate", "WWW-Authenticate",
"Basic realm=\"Radicale Server - Password Required\"") "Basic realm=\"Radicale Server - Password Required\"")
request.end_headers() request.end_headers()
log.LOGGER.info("%s refused" % request._calendar.owner)
def _log_request_content(request, function): def _log_request_content(request, function):
"""Log the content of the request and store it in the request object.""" """Log the content of the request and store it in the request object."""

61
radicale/acl/LDAP.py Normal file
View File

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

View File

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

View File

@ -33,6 +33,11 @@ import hashlib
from radicale import config 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): def _plain(hash_value, password):
"""Check if ``hash_value`` and ``password`` match using plain method.""" """Check if ``hash_value`` and ``password`` match using plain method."""
return hash_value == password return hash_value == password
@ -48,7 +53,7 @@ def _crypt(hash_value, password):
def _sha1(hash_value, password): def _sha1(hash_value, password):
"""Check if ``hash_value`` and ``password`` match using sha1 method.""" """Check if ``hash_value`` and ``password`` match using sha1 method."""
hash_value = hash_value.replace("{SHA}", "").encode("ascii") 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 = hashlib.sha1() # pylint: disable=E1101
sha1.update(password) sha1.update(password)
return sha1.digest() == base64.b64decode(hash_value) return sha1.digest() == base64.b64decode(hash_value)
@ -56,18 +61,9 @@ def _sha1(hash_value, password):
def has_right(owner, user, password): def has_right(owner, user, password):
"""Check if ``user``/``password`` couple is valid.""" """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(): for line in open(FILENAME).readlines():
if line.strip(): if line.strip():
login, hash_value = line.strip().split(":") login, hash_value = line.strip().split(":")
if login == user and (not PERSONAL or user == owner): if login == user and (not PERSONAL or user == owner):
return CHECK_PASSWORD(hash_value, password) return locals()["_%s" % ENCRYPTION](hash_value, password)
return False return False
FILENAME = config.get("acl", "filename")
PERSONAL = config.getboolean("acl", "personal")
CHECK_PASSWORD = locals()["_%s" % config.get("acl", "encryption")]

View File

@ -50,17 +50,16 @@ INITIAL_CONFIG = {
"acl": { "acl": {
"type": "None", "type": "None",
"personal": "False", "personal": "False",
"filename": "/etc/radicale/users", "httpasswd_filename": "/etc/radicale/users",
"encryption": "crypt"}, "httpasswd_encryption": "crypt",
"ldap_url": "ldap://localhost:389/",
"ldap_base": "ou=users,dc=example,dc=com",
"ldap_attribute": "uid"},
"storage": { "storage": {
"folder": os.path.expanduser("~/.config/radicale/calendars")}, "folder": os.path.expanduser("~/.config/radicale/calendars")},
"logging": { "logging": {
"config": "/etc/radicale/logging", "config": "/etc/radicale/logging",
"debug": "False"}, "debug": "False"}}
"authLdap": {
"LDAPServer": "127.0.0.1",
"LDAPPrepend": "uid=",
"LDAPAppend": "ou=users,dc=example,dc=com"}}
# Create a ConfigParser and configure it # Create a ConfigParser and configure it
_CONFIG_PARSER = ConfigParser() _CONFIG_PARSER = ConfigParser()