Clean LDAP support
This commit is contained in:
parent
78e52d5cf4
commit
12a8e01492
35
config
35
config
@ -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]
|
||||||
|
@ -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
61
radicale/acl/LDAP.py
Normal 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
|
@ -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
|
|
@ -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")]
|
|
||||||
|
@ -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()
|
||||||
|
Loading…
Reference in New Issue
Block a user