__init__.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. # This file is part of Radicale - CalDAV and CardDAV server
  2. # Copyright © 2008 Nicolas Kandel
  3. # Copyright © 2008 Pascal Halter
  4. # Copyright © 2008-2017 Guillaume Ayoub
  5. # Copyright © 2017-2022 Unrud <unrud@outlook.com>
  6. # Copyright © 2024-2024 Peter Bieringer <pb@bieringer.de>
  7. #
  8. # This library is free software: you can redistribute it and/or modify
  9. # it under the terms of the GNU General Public License as published by
  10. # the Free Software Foundation, either version 3 of the License, or
  11. # (at your option) any later version.
  12. #
  13. # This library is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. # GNU General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU General Public License
  19. # along with Radicale. If not, see <http://www.gnu.org/licenses/>.
  20. """
  21. Authentication module.
  22. Authentication is based on usernames and passwords. If something more
  23. advanced is needed an external WSGI server or reverse proxy can be used
  24. (see ``remote_user`` or ``http_x_remote_user`` backend).
  25. Take a look at the class ``BaseAuth`` if you want to implement your own.
  26. """
  27. from typing import Sequence, Set, Tuple, Union, final
  28. from radicale import config, types, utils
  29. from radicale.log import logger
  30. INTERNAL_TYPES: Sequence[str] = ("none", "remote_user", "http_x_remote_user",
  31. "denyall",
  32. "htpasswd",
  33. "ldap",
  34. "dovecot")
  35. def load(configuration: "config.Configuration") -> "BaseAuth":
  36. """Load the authentication module chosen in configuration."""
  37. if configuration.get("auth", "type") == "none":
  38. logger.warning("No user authentication is selected: '[auth] type=none' (insecure)")
  39. if configuration.get("auth", "type") == "denyall":
  40. logger.warning("All access is blocked by: '[auth] type=denyall'")
  41. return utils.load_plugin(INTERNAL_TYPES, "auth", "Auth", BaseAuth,
  42. configuration)
  43. class BaseAuth:
  44. _ldap_groups: Set[str] = set([])
  45. _lc_username: bool
  46. _uc_username: bool
  47. _strip_domain: bool
  48. def __init__(self, configuration: "config.Configuration") -> None:
  49. """Initialize BaseAuth.
  50. ``configuration`` see ``radicale.config`` module.
  51. The ``configuration`` must not change during the lifetime of
  52. this object, it is kept as an internal reference.
  53. """
  54. self.configuration = configuration
  55. self._lc_username = configuration.get("auth", "lc_username")
  56. self._uc_username = configuration.get("auth", "uc_username")
  57. self._strip_domain = configuration.get("auth", "strip_domain")
  58. logger.info("auth.strip_domain: %s", self._strip_domain)
  59. logger.info("auth.lc_username: %s", self._lc_username)
  60. logger.info("auth.uc_username: %s", self._uc_username)
  61. if self._lc_username is True and self._uc_username is True:
  62. raise RuntimeError("auth.lc_username and auth.uc_username cannot be enabled together")
  63. def get_external_login(self, environ: types.WSGIEnviron) -> Union[
  64. Tuple[()], Tuple[str, str]]:
  65. """Optionally provide the login and password externally.
  66. ``environ`` a dict with the WSGI environment
  67. If ``()`` is returned, Radicale handles HTTP authentication.
  68. Otherwise, returns a tuple ``(login, password)``. For anonymous users
  69. ``login`` must be ``""``.
  70. """
  71. return ()
  72. def _login(self, login: str, password: str) -> str:
  73. """Check credentials and map login to internal user
  74. ``login`` the login name
  75. ``password`` the password
  76. Returns the username or ``""`` for invalid credentials.
  77. """
  78. raise NotImplementedError
  79. @final
  80. def login(self, login: str, password: str) -> str:
  81. if self._lc_username:
  82. login = login.lower()
  83. if self._uc_username:
  84. login = login.upper()
  85. if self._strip_domain:
  86. login = login.split('@')[0]
  87. return self._login(login, password)