Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,12 @@ Load the ldap groups of the authenticated user. These groups can be used later o

Default: False

##### ldaps_certificate

Path to a certificate to authenticate against LDAPS server, e.g. for self-signed certificate or CAs.

Default: None

#### rights

##### type
Expand Down
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ EXPOSE 5232
CMD ["radicale", "--hosts", "0.0.0.0:5232"]

RUN apk add --no-cache ca-certificates openssl \
&& apk add --no-cache --virtual .build-deps gcc libffi-dev musl-dev \
&& pip install --no-cache-dir "Radicale[bcrypt] @ https://github.com/Kozea/Radicale/archive/${VERSION}.tar.gz" \
&& apk del .build-deps
&& apk add --no-cache --virtual .build-deps gcc libffi-dev musl-dev openldap-dev \
&& pip install --no-cache-dir "Radicale[bcrypt] @ https://github.com/Kozea/Radicale/archive/${VERSION}.tar.gz" \
&& apk del .build-deps
42 changes: 30 additions & 12 deletions radicale/auth/ldap.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,15 @@
ldap_secret The password of the ldap_reader_dn
ldap_filter The search filter to find the user to authenticate by the username
ldap_load_groups If the groups of the authenticated users need to be loaded
ldaps_certificate The path to a certificate to validate ldaps with
"""

import os
import ldap
from radicale import auth, config
from radicale.log import logger


class Auth(auth.BaseAuth):
_ldap_uri: str
_ldap_base: str
Expand All @@ -38,12 +41,19 @@ class Auth(auth.BaseAuth):

def __init__(self, configuration: config.Configuration) -> None:
super().__init__(configuration)
self._ldap_uri = configuration.get("auth", "ldap_uri")
self._ldap_uri = configuration.get("auth", "ldap_uri")
self._ldap_base = configuration.get("auth", "ldap_base")
self._ldap_reader_dn = configuration.get("auth", "ldap_reader_dn")

# Load LDAP reader details via env first if available
self._ldap_reader_dn = os.environ.get("AUTH__LDAP_READER_DN", configuration.get("auth", "ldap_reader_dn"))
self._ldap_secret = os.environ.get("AUTH__LDAP_SECRET", configuration.get("auth", "ldap_secret"))

self._ldap_load_groups = configuration.get("auth", "ldap_load_groups")
self._ldap_secret = configuration.get("auth", "ldap_secret")
self._ldap_filter = configuration.get("auth", "ldap_filter")
self._ldap_filter = configuration.get("auth", "ldap_filter")
self._ldaps_certificate = configuration.get("auth", "ldaps_certificate")
# If a ldaps_certificate is set, configure ldap to use it
if self._ldaps_certificate:
ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, self._ldaps_certificate)

def login(self, login: str, password: str) -> str:
"""Validate credentials.
Expand All @@ -59,30 +69,38 @@ def login(self, login: str, password: str) -> str:
conn.set_option(ldap.OPT_REFERRALS, 0)
conn.simple_bind_s(self._ldap_reader_dn, self._ldap_secret)
"""Search for the dn of user to authenticate"""
res = conn.search_s(self._ldap_base, ldap.SCOPE_SUBTREE, filterstr=self._ldap_filter.format(login), attrlist=['memberOf'])
res = conn.search_s(
self._ldap_base,
ldap.SCOPE_SUBTREE,
filterstr=self._ldap_filter.format(login),
attrlist=["memberOf"],
)
if len(res) == 0:
"""User could not be find"""
"""User could not be found"""
logger.debug("LDAP search returned no results.")
return ""
user_dn = res[0][0]
logger.debug("LDAP Auth user: %s",user_dn)
logger.debug("LDAP Auth user: %s", user_dn)
"""Close ldap connection"""
conn.unbind()
except Exception:
raise RuntimeError("Invalide ldap configuration")
raise RuntimeError("Invalid ldap configuration")

try:
"""Bind as user to authenticate"""
conn = ldap.initialize(self._ldap_uri)
conn.protocol_version = 3
conn.set_option(ldap.OPT_REFERRALS, 0)
conn.simple_bind_s(user_dn,password)
conn.simple_bind_s(user_dn, password)
tmp = []
if self._ldap_load_groups:
tmp = []
for t in res[0][1]['memberOf']:
tmp.append(t.decode('utf-8').split(',')[0][3:])
for t in res[0][1]["memberOf"]:
tmp.append(t.decode("utf-8").split(",")[0][3:])
self._ldap_groups = set(tmp)
logger.debug("LDAP Auth groups of user: %s",",".join(self._ldap_groups))
logger.debug(
"LDAP Auth groups of user: %s", ",".join(self._ldap_groups)
)
conn.unbind()
return login
except ldap.INVALID_CREDENTIALS:
Expand Down
6 changes: 5 additions & 1 deletion radicale/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,11 @@ def _convert_to_bool(value: Any) -> bool:
"value": "False",
"help": "load the ldap groups of the authenticated user",
"type": bool}),
])),
( "ldaps_certificate", {
"value": None,
"help": "ldap server certificate",
"type": filepath}),
])),
("rights", OrderedDict([
("type", {
"value": "owner_only",
Expand Down
61 changes: 16 additions & 45 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#!/usr/bin/env python3

# This file is part of Radicale - CalDAV and CardDAV server
# Copyright © 2009-2017 Guillaume Ayoub
# Copyright © 2017-2018 Unrud <unrud@outlook.com>
Expand All @@ -17,71 +15,44 @@
# You should have received a copy of the GNU General Public License
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.

"""
Radicale CalDAV and CardDAV server
==================================

The Radicale Project is a CalDAV (calendar) and CardDAV (contact) server. It
aims to be a light solution, easy to use, easy to install, easy to configure.
As a consequence, it requires few software dependances and is pre-configured to
work out-of-the-box.

The Radicale Project runs on most of the UNIX-like platforms (Linux, BSD,
MacOS X) and Windows. It is known to work with Evolution, Lightning, iPhone
and Android clients. It is free and open-source software, released under GPL
version 3.

For further information, please visit the `Radicale Website
<https://radicale.org/>`_.

"""

import os
import sys

from setuptools import find_packages, setup

# When the version is updated, a new section in the CHANGELOG.md file must be
# added too.
VERSION = "master"
WEB_FILES = ["web/internal_data/css/icon.png",

with open("README.md", encoding="utf-8") as f:
long_description = f.read()
web_files = ["web/internal_data/css/icon.png",
"web/internal_data/css/main.css",
"web/internal_data/fn.js",
"web/internal_data/index.html"]

setup_requires = []
if {"pytest", "test", "ptr"}.intersection(sys.argv):
setup_requires.append("pytest-runner")
tests_require = ["pytest-runner", "pytest<7", "pytest-cov", "pytest-flake8",
"pytest-isort", "typeguard", "waitress"]
os.environ["PYTEST_ADDOPTS"] = os.environ.get("PYTEST_ADDOPTS", "")
# Mypy only supports CPython
if sys.implementation.name == "cpython":
tests_require.extend(["pytest-mypy", "types-setuptools"])
os.environ["PYTEST_ADDOPTS"] += " --mypy"
install_requires = ["defusedxml", "passlib", "vobject>=0.9.6",
"python-dateutil>=2.7.3",
"setuptools; python_version<'3.9'"]
bcrypt_requires = ["passlib[bcrypt]", "bcrypt"]
# typeguard requires pytest<7
test_requires = ["pytest<7", "typeguard", "waitress", *bcrypt_requires]
ldap_requires = ["python-ldap>=3.4.3,<3.5.0"]

setup(
name="Radicale",
version=VERSION,
description="CalDAV and CardDAV Server",
long_description=__doc__,
long_description=long_description,
long_description_content_type="text/markdown",
author="Guillaume Ayoub",
author_email="guillaume.ayoub@kozea.fr",
url="https://radicale.org/",
download_url=("https://pypi.python.org/packages/source/R/Radicale/"
"Radicale-%s.tar.gz" % VERSION),
license="GNU GPL v3",
platforms="Any",
packages=find_packages(
exclude=["*.tests", "*.tests.*", "tests.*", "tests"]),
package_data={"radicale": [*WEB_FILES, "py.typed"]},
package_data={"radicale": [*web_files, "py.typed"]},
entry_points={"console_scripts": ["radicale = radicale.__main__:run"]},
install_requires=["defusedxml", "passlib", "vobject>=0.9.6",
"python-dateutil>=2.7.3", "setuptools"],
setup_requires=setup_requires,
tests_require=tests_require,
extras_require={"test": tests_require,
"bcrypt": ["passlib[bcrypt]", "bcrypt"]},
install_requires=install_requires,
extras_require={"test": test_requires, "bcrypt": bcrypt_requires, "ldap": ldap_requires},
keywords=["calendar", "addressbook", "CalDAV", "CardDAV"],
python_requires=">=3.6.0",
classifiers=[
Expand Down