From 88a0af8ba1f703cd3678aaa38fbb865b0a31bff6 Mon Sep 17 00:00:00 2001 From: Unrud Date: Sun, 12 Jan 2020 23:32:28 +0100 Subject: [PATCH] Improve documentation --- radicale/__init__.py | 6 ++-- radicale/__main__.py | 1 + radicale/app/__init__.py | 11 ++++-- radicale/auth/__init__.py | 36 +++---------------- radicale/auth/htpasswd.py | 35 ++++++++++++++++++ radicale/auth/http_x_remote_user.py | 9 +++++ radicale/auth/none.py | 5 +++ radicale/auth/remote_user.py | 8 +++++ radicale/config.py | 37 ++++++++++++++------ radicale/httputils.py | 5 +++ radicale/item/__init__.py | 7 ++++ radicale/log.py | 11 ++++-- radicale/pathutils.py | 5 +++ radicale/rights/__init__.py | 24 +++++-------- radicale/rights/authenticated.py | 5 +++ radicale/rights/from_file.py | 18 ++++++++++ radicale/rights/owner_only.py | 6 ++++ radicale/rights/owner_write.py | 6 ++++ radicale/server.py | 4 ++- radicale/storage/__init__.py | 7 ++-- radicale/storage/multifilesystem/__init__.py | 7 ++++ radicale/web/__init__.py | 7 ++++ radicale/web/internal.py | 12 +++++++ radicale/web/none.py | 5 +++ radicale/xmlutils.py | 6 +--- 25 files changed, 207 insertions(+), 76 deletions(-) diff --git a/radicale/__init__.py b/radicale/__init__.py index 162320b..85f5fbc 100644 --- a/radicale/__init__.py +++ b/radicale/__init__.py @@ -18,9 +18,10 @@ # along with Radicale. If not, see . """ -Radicale WSGI application. +Entry point for external WSGI servers (like uWSGI or Gunicorn). -Can be used with an external WSGI server or the built-in server. +Configuration files can be specified in the environment variable +``RADICALE_CONFIG``. """ @@ -57,6 +58,7 @@ def _init_application(config_path, wsgi_errors): def application(environ, start_response): + """Entry point for external WSGI servers.""" config_path = environ.get("RADICALE_CONFIG", os.environ.get("RADICALE_CONFIG")) if _application is None: diff --git a/radicale/__main__.py b/radicale/__main__.py index fa8a88e..28c064d 100644 --- a/radicale/__main__.py +++ b/radicale/__main__.py @@ -19,6 +19,7 @@ Radicale executable module. This module can be executed from a command line with ``$python -m radicale``. +Uses the built-in WSGI server. """ diff --git a/radicale/app/__init__.py b/radicale/app/__init__.py index a5658d6..f48d9d2 100644 --- a/radicale/app/__init__.py +++ b/radicale/app/__init__.py @@ -20,7 +20,8 @@ """ Radicale WSGI application. -Can be used with an external WSGI server or the built-in server. +Can be used with an external WSGI server (see ``radicale.application()``) or +the built-in server (see ``radicale.server`` module). """ @@ -63,10 +64,14 @@ class Application( ApplicationPropfindMixin, ApplicationProppatchMixin, ApplicationPutMixin, ApplicationReportMixin): - """WSGI application managing collections.""" + """WSGI application.""" def __init__(self, configuration): - """Initialize application.""" + """Initialize application. + + ``configuration`` see ``radicale.config`` module. + + """ super().__init__() self.configuration = configuration self.auth = auth.load(configuration) diff --git a/radicale/auth/__init__.py b/radicale/auth/__init__.py index bb8f85f..6f83083 100644 --- a/radicale/auth/__init__.py +++ b/radicale/auth/__init__.py @@ -18,39 +18,13 @@ # along with Radicale. If not, see . """ -Authentication management. +Authentication module. -Default is htpasswd authentication. +Authentication is based on usernames and passwords. If something more +advanced is needed an external WSGI server or reverse proxy can be used +(see ``remote_user`` or ``http_x_remote_user`` backend). -Apache's htpasswd command (httpd.apache.org/docs/programs/htpasswd.html) -manages a file for storing user credentials. It can encrypt passwords using -different methods, e.g. BCRYPT, MD5-APR1 (a version of MD5 modified for -Apache), SHA1, or by using the system's CRYPT routine. The CRYPT and SHA1 -encryption methods implemented by htpasswd are considered as insecure. MD5-APR1 -provides medium security as of 2015. Only BCRYPT can be considered secure by -current standards. - -MD5-APR1-encrypted credentials can be written by all versions of htpasswd (it -is the default, in fact), whereas BCRYPT requires htpasswd 2.4.x or newer. - -The `is_authenticated(user, password)` function provided by this module -verifies the user-given credentials by parsing the htpasswd credential file -pointed to by the ``htpasswd_filename`` configuration value while assuming -the password encryption method specified via the ``htpasswd_encryption`` -configuration value. - -The following htpasswd password encrpytion methods are supported by Radicale -out-of-the-box: - - - plain-text (created by htpasswd -p...) -- INSECURE - - CRYPT (created by htpasswd -d...) -- INSECURE - - SHA1 (created by htpasswd -s...) -- INSECURE - -When passlib (https://pypi.python.org/pypi/passlib) is importable, the -following significantly more secure schemes are parsable by Radicale: - - - MD5-APR1 (htpasswd -m...) -- htpasswd's default method - - BCRYPT (htpasswd -B...) -- Requires htpasswd 2.4.x +Take a look at the class ``BaseAuth`` if you want to implement your own. """ diff --git a/radicale/auth/htpasswd.py b/radicale/auth/htpasswd.py index d0f1604..92a9a4c 100644 --- a/radicale/auth/htpasswd.py +++ b/radicale/auth/htpasswd.py @@ -17,6 +17,41 @@ # You should have received a copy of the GNU General Public License # along with Radicale. If not, see . +""" +Authentication backend that checks credentials with a htpasswd file. + +Apache's htpasswd command (httpd.apache.org/docs/programs/htpasswd.html) +manages a file for storing user credentials. It can encrypt passwords using +different methods, e.g. BCRYPT, MD5-APR1 (a version of MD5 modified for +Apache), SHA1, or by using the system's CRYPT routine. The CRYPT and SHA1 +encryption methods implemented by htpasswd are considered as insecure. MD5-APR1 +provides medium security as of 2015. Only BCRYPT can be considered secure by +current standards. + +MD5-APR1-encrypted credentials can be written by all versions of htpasswd (it +is the default, in fact), whereas BCRYPT requires htpasswd 2.4.x or newer. + +The `is_authenticated(user, password)` function provided by this module +verifies the user-given credentials by parsing the htpasswd credential file +pointed to by the ``htpasswd_filename`` configuration value while assuming +the password encryption method specified via the ``htpasswd_encryption`` +configuration value. + +The following htpasswd password encrpytion methods are supported by Radicale +out-of-the-box: + + - plain-text (created by htpasswd -p...) -- INSECURE + - CRYPT (created by htpasswd -d...) -- INSECURE + - SHA1 (created by htpasswd -s...) -- INSECURE + +When passlib (https://pypi.python.org/pypi/passlib) is importable, the +following significantly more secure schemes are parsable by Radicale: + + - MD5-APR1 (htpasswd -m...) -- htpasswd's default method + - BCRYPT (htpasswd -B...) -- Requires htpasswd 2.4.x + +""" + import base64 import functools import hashlib diff --git a/radicale/auth/http_x_remote_user.py b/radicale/auth/http_x_remote_user.py index c8f05bb..aa353f2 100644 --- a/radicale/auth/http_x_remote_user.py +++ b/radicale/auth/http_x_remote_user.py @@ -17,6 +17,15 @@ # You should have received a copy of the GNU General Public License # along with Radicale. If not, see . +""" +Authentication backend that takes the username from the +``HTTP_X_REMOTE_USER`` header. + +It's intended for use with a reverse proxy. Be aware as this will be insecure +if the reverse proxy is not configured properly. + +""" + import radicale.auth.none as none diff --git a/radicale/auth/none.py b/radicale/auth/none.py index f1be09e..b785e7e 100644 --- a/radicale/auth/none.py +++ b/radicale/auth/none.py @@ -17,6 +17,11 @@ # You should have received a copy of the GNU General Public License # along with Radicale. If not, see . +""" +A dummy backend that accepts any username and password. + +""" + from radicale import auth diff --git a/radicale/auth/remote_user.py b/radicale/auth/remote_user.py index 2a9d2be..1c2d49a 100644 --- a/radicale/auth/remote_user.py +++ b/radicale/auth/remote_user.py @@ -17,6 +17,14 @@ # You should have received a copy of the GNU General Public License # along with Radicale. If not, see . +""" +Authentication backend that takes the username from the ``REMOTE_USER`` +WSGI environment variable. + +It's intended for use with an external WSGI server. + +""" + import radicale.auth.none as none diff --git a/radicale/config.py b/radicale/config.py index 133afb2..ca27084 100644 --- a/radicale/config.py +++ b/radicale/config.py @@ -18,9 +18,10 @@ # along with Radicale. If not, see . """ -Radicale configuration module. +Configuration module -Give a configparser-like interface to read and write configuration. +Use ``load()`` to obtain an instance of ``Configuration`` for use with +``radicale.app.Application``. """ @@ -254,9 +255,16 @@ def parse_compound_paths(*compound_paths): def load(paths=()): - """Load configuration from files. + """ + Create instance of ``Configuration`` for use with + ``radicale.app.Application``. - ``paths`` a list of the format ``[(PATH, IGNORE_IF_MISSING), ...]``. + ``paths`` a list of configuration files with the format + ``[(PATH, IGNORE_IF_MISSING), ...]``. + If a configuration file is missing and IGNORE_IF_MISSING is set, the + config is set to ``Configuration.SOURCE_MISSING``. + + The configuration can later be changed with ``Configuration.update()``. """ configuration = Configuration(DEFAULT_CONFIG_SCHEMA) @@ -287,6 +295,9 @@ class Configuration: ``schema`` a dict that describes the configuration format. See ``DEFAULT_CONFIG_SCHEMA``. + Use ``load()`` to create an instance for use with + ``radicale.app.Application``. + """ self._schema = schema self._values = {} @@ -304,13 +315,12 @@ class Configuration: """Update the configuration. ``config`` a dict of the format {SECTION: {OPTION: VALUE, ...}, ...}. - Set to ``Configuration.SOURCE_MISSING`` to indicate a missing - configuration source for inspection. + The configuration is checked for errors according to the config schema. - ``source`` a description of the configuration source + ``source`` a description of the configuration source (used in error + messages). - ``internal`` allows updating "_internal" sections and skips the source - during inspection. + ``internal`` allows updating "_internal" sections. """ new_values = {} @@ -407,7 +417,14 @@ class Configuration: return copy def log_config_sources(self): - """Inspect all external config sources and write problems to logger.""" + """ + A helper function that writes a description of all config sources + to logger. + + Configs set to ``Configuration.SOURCE_MISSING`` are described as + missing. + + """ for config, source, _ in self._configs: if config is self.SOURCE_MISSING: logger.info("Skipped missing %s", source) diff --git a/radicale/httputils.py b/radicale/httputils.py index 1b4438c..4142b30 100644 --- a/radicale/httputils.py +++ b/radicale/httputils.py @@ -17,6 +17,11 @@ # You should have received a copy of the GNU General Public License # along with Radicale. If not, see . +""" +Helper functions for HTTP. + +""" + from http import client NOT_ALLOWED = ( diff --git a/radicale/item/__init__.py b/radicale/item/__init__.py index 06270e9..cdda525 100644 --- a/radicale/item/__init__.py +++ b/radicale/item/__init__.py @@ -18,6 +18,11 @@ # You should have received a copy of the GNU General Public License # along with Radicale. If not, see . +""" +Module for address books and calendar entries (see ``Item``). + +""" + import math import sys from hashlib import md5 @@ -257,6 +262,8 @@ def find_tag_and_time_range(vobject_item): class Item: + """Class for address book and calendar entries.""" + def __init__(self, collection_path=None, collection=None, vobject_item=None, href=None, last_modified=None, text=None, etag=None, uid=None, name=None, component_name=None, diff --git a/radicale/log.py b/radicale/log.py index 2fc4f8a..a8b7921 100644 --- a/radicale/log.py +++ b/radicale/log.py @@ -16,10 +16,15 @@ # along with Radicale. If not, see . """ -Radicale logging module. +Functions to set up Python's logging facility for Radicale's WSGI application. -Manage logging from a configuration file. For more information, see: -http://docs.python.org/library/logging.config.html +Log messages are sent to the first available target of: + + - Error stream specified by the WSGI server in wsgi.errors + - systemd-journald + - stderr + +The logger is thread-safe and fork-safe. """ diff --git a/radicale/pathutils.py b/radicale/pathutils.py index 32bd7f8..3d3df42 100644 --- a/radicale/pathutils.py +++ b/radicale/pathutils.py @@ -16,6 +16,11 @@ # You should have received a copy of the GNU General Public License # along with Radicale. If not, see . +""" +Helper functions for working with the file system. + +""" + import contextlib import os import posixpath diff --git a/radicale/rights/__init__.py b/radicale/rights/__init__.py index 2565611..7da4aec 100644 --- a/radicale/rights/__init__.py +++ b/radicale/rights/__init__.py @@ -16,25 +16,17 @@ # along with Radicale. If not, see . """ -Rights backends. +The rights module used to determine if a user can read and/or write +collections and entries. -This module loads the rights backend, according to the rights -configuration. +Permissions: -Default rights are based on a regex-based file whose name is specified in the -config (section "right", key "file"). + - R: read a collection + - r: read an address book or calendar entry + - W: write a collection + - w: read an address book or calendar entry -Authentication login is matched against the "user" key, and collection's path -is matched against the "collection" key. You can use Python's ConfigParser -interpolation values %(login)s and %(path)s. You can also get groups from the -user regex in the collection with {0}, {1}, etc. - -For example, for the "user" key, ".+" means "authenticated user" and ".*" -means "anybody" (including anonymous users). - -Section names are only used for naming the rule. - -Leading or ending slashes are trimmed from collection's path. +Take a look at the class ``BaseRights`` if you want to implement your own. """ diff --git a/radicale/rights/authenticated.py b/radicale/rights/authenticated.py index f41a5c2..4836f3b 100644 --- a/radicale/rights/authenticated.py +++ b/radicale/rights/authenticated.py @@ -15,6 +15,11 @@ # You should have received a copy of the GNU General Public License # along with Radicale. If not, see . +""" +Rights backend that allows authenticated users to read and write all +calendars and address books. + +""" from radicale import pathutils, rights diff --git a/radicale/rights/from_file.py b/radicale/rights/from_file.py index 7243146..6a20381 100644 --- a/radicale/rights/from_file.py +++ b/radicale/rights/from_file.py @@ -15,6 +15,24 @@ # You should have received a copy of the GNU General Public License # along with Radicale. If not, see . +""" +Rights backend based on a regex-based file whose name is specified in the +config (section "right", key "file"). + +Authentication login is matched against the "user" key, and collection's path +is matched against the "collection" key. You can use Python's ConfigParser +interpolation values %(login)s and %(path)s. You can also get groups from the +user regex in the collection with {0}, {1}, etc. + +For example, for the "user" key, ".+" means "authenticated user" and ".*" +means "anybody" (including anonymous users). + +Section names are only used for naming the rule. + +Leading or ending slashes are trimmed from collection's path. + +""" + import configparser import re diff --git a/radicale/rights/owner_only.py b/radicale/rights/owner_only.py index 55ddc4a..d188d0e 100644 --- a/radicale/rights/owner_only.py +++ b/radicale/rights/owner_only.py @@ -15,6 +15,12 @@ # You should have received a copy of the GNU General Public License # along with Radicale. If not, see . +""" +Rights backend that allows authenticated users to read and write their own +calendars and address books. + +""" + import radicale.rights.authenticated as authenticated from radicale import pathutils, rights diff --git a/radicale/rights/owner_write.py b/radicale/rights/owner_write.py index f157150..8f1206e 100644 --- a/radicale/rights/owner_write.py +++ b/radicale/rights/owner_write.py @@ -15,6 +15,12 @@ # You should have received a copy of the GNU General Public License # along with Radicale. If not, see . +""" +Rights backend that allows authenticated users to read all calendars and +address books but only grants write access to their own. + +""" + import radicale.rights.authenticated as authenticated from radicale import pathutils, rights diff --git a/radicale/server.py b/radicale/server.py index 9417551..013a7b2 100644 --- a/radicale/server.py +++ b/radicale/server.py @@ -18,7 +18,9 @@ # along with Radicale. If not, see . """ -Radicale WSGI server. +Built-in WSGI server. + +Uses forking on POSIX to overcome Python's GIL. """ diff --git a/radicale/storage/__init__.py b/radicale/storage/__init__.py index f055a92..473eed7 100644 --- a/radicale/storage/__init__.py +++ b/radicale/storage/__init__.py @@ -17,12 +17,9 @@ # along with Radicale. If not, see . """ -Storage backends. +The storage module that stores calendars and address books. -This module loads the storage backend, according to the storage configuration. - -Default storage uses one folder per collection and one file per collection -entry. +Take a look at the class ``BaseCollection`` if you want to implement your own. """ diff --git a/radicale/storage/multifilesystem/__init__.py b/radicale/storage/multifilesystem/__init__.py index 92a5a21..38b5e71 100644 --- a/radicale/storage/multifilesystem/__init__.py +++ b/radicale/storage/multifilesystem/__init__.py @@ -16,6 +16,13 @@ # You should have received a copy of the GNU General Public License # along with Radicale. If not, see . +""" +Storage backend that stores data in the file system. + +Uses one folder per collection and one file per collection entry. + +""" + import contextlib import os import time diff --git a/radicale/web/__init__.py b/radicale/web/__init__.py index eea7cad..5362695 100644 --- a/radicale/web/__init__.py +++ b/radicale/web/__init__.py @@ -14,6 +14,13 @@ # You should have received a copy of the GNU General Public License # along with Radicale. If not, see . +""" +The web module for the website at ``/.web``. + +Take a look at the class ``BaseWeb`` if you want to implement your own. + +""" + from importlib import import_module from radicale.log import logger diff --git a/radicale/web/internal.py b/radicale/web/internal.py index e5fb073..eb88c42 100644 --- a/radicale/web/internal.py +++ b/radicale/web/internal.py @@ -14,6 +14,18 @@ # You should have received a copy of the GNU General Public License # along with Radicale. If not, see . +""" +The default web backend. + +Features: + + - Create and delete address books and calendars. + - Edit basic metadata of existing address books and calendars. + - Upload address books and calendars from files. + +""" + + import os import posixpath import time diff --git a/radicale/web/none.py b/radicale/web/none.py index e1e8b8c..ab49011 100644 --- a/radicale/web/none.py +++ b/radicale/web/none.py @@ -14,6 +14,11 @@ # You should have received a copy of the GNU General Public License # along with Radicale. If not, see . +""" +A dummy web backend that shows a simple message. + +""" + from http import client from radicale import httputils, pathutils, web diff --git a/radicale/xmlutils.py b/radicale/xmlutils.py index 59e5218..3dca5db 100644 --- a/radicale/xmlutils.py +++ b/radicale/xmlutils.py @@ -18,11 +18,7 @@ # along with Radicale. If not, see . """ -XML and iCal requests manager. - -Note that all these functions need to receive unicode objects for full -iCal requests (PUT) and string objects with charset correctly defined -in them for XML requests (all but PUT). +Helper functions for XML. """