瀏覽代碼

Merge pull request #1689 from pbiering/auth-oauth2

migrate oauth2 into upstream
Peter Bieringer 1 年之前
父節點
當前提交
c2def71ce6
共有 9 個文件被更改,包括 93 次插入5 次删除
  1. 4 0
      CHANGELOG.md
  2. 10 1
      DOCUMENTATION.md
  3. 3 0
      config
  4. 2 2
      pyproject.toml
  5. 2 0
      radicale/auth/__init__.py
  6. 66 0
      radicale/auth/oauth2.py
  7. 4 0
      radicale/config.py
  8. 1 1
      setup.cfg.legacy
  9. 1 1
      setup.py.legacy

+ 4 - 0
CHANGELOG.md

@@ -1,5 +1,9 @@
 # Changelog
 
+## 3.4.2.dev
+
+* Add: option [auth] type oauth2 by code migration from https://gitlab.mim-libre.fr/alphabet/radicale_oauth/-/blob/dev/oauth2/
+
 ## 3.4.1
 * Add: option [auth] dovecot_connection_type / dovecot_host / dovecot_port
 * Add: option [auth] type imap by code migration from https://github.com/Unrud/RadicaleIMAP/

+ 10 - 1
DOCUMENTATION.md

@@ -824,7 +824,10 @@ Available backends:
 : Use a Dovecot server to authenticate users.
 
 `imap`
-: Use a IMAP server to authenticate users.
+: Use an IMAP server to authenticate users.
+
+`oauth2`
+: Use an OAuth2 server to authenticate users.
 
 Default: `none`
 
@@ -1019,6 +1022,12 @@ Secure the IMAP connection: tls | starttls | none
 
 Default: `tls`
 
+##### oauth2_token_endpoint
+
+OAuth2 token endpoint URL
+
+Default:
+
 ##### lc_username
 
 Сonvert username to lowercase, must be true for case-insensitive auth

+ 3 - 0
config

@@ -125,6 +125,9 @@
 # Value: tls | starttls | none
 #imap_security = tls
 
+# OAuth2 token endpoint URL
+#oauth2_token_endpoint = <URL>
+
 # Htpasswd filename
 #htpasswd_filename = /etc/radicale/users
 

+ 2 - 2
pyproject.toml

@@ -3,7 +3,7 @@ name = "Radicale"
 # When the version is updated, a new section in the CHANGELOG.md file must be
 # added too.
 readme = "README.md"
-version = "3.4.1"
+version = "3.4.2.dev"
 authors = [{name = "Guillaume Ayoub", email = "guillaume.ayoub@kozea.fr"}, {name = "Unrud", email = "unrud@outlook.com"}, {name = "Peter Bieringer", email = "pb@bieringer.de"}]
 license = {text = "GNU GPL v3"}
 description = "CalDAV and CardDAV Server"
@@ -72,7 +72,7 @@ skip_install = true
 
 [tool.tox.env.mypy]
 deps = ["mypy==1.11.0"]
-commands = [["mypy", "."]]
+commands = [["mypy", "--install-types", "--non-interactive", "."]]
 skip_install = true
 
 

+ 2 - 0
radicale/auth/__init__.py

@@ -42,6 +42,7 @@ INTERNAL_TYPES: Sequence[str] = ("none", "remote_user", "http_x_remote_user",
                                  "htpasswd",
                                  "ldap",
                                  "imap",
+                                 "oauth2",
                                  "dovecot")
 
 CACHE_LOGIN_TYPES: Sequence[str] = (
@@ -49,6 +50,7 @@ CACHE_LOGIN_TYPES: Sequence[str] = (
                                     "ldap",
                                     "htpasswd",
                                     "imap",
+                                    "oauth2",
                                    )
 
 AUTH_SOCKET_FAMILY: Sequence[str] = ("AF_UNIX", "AF_INET", "AF_INET6")

+ 66 - 0
radicale/auth/oauth2.py

@@ -0,0 +1,66 @@
+# This file is part of Radicale Server - Calendar Server
+#
+# Original from https://gitlab.mim-libre.fr/alphabet/radicale_oauth/
+# Copyright © 2021-2022 Bruno Boiget
+# Copyright © 2022-2022 Daniel Dehennin
+#
+# Since migration into upstream
+# Copyright © 2025-2025 Peter Bieringer <pb@bieringer.de>
+#
+# 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/>.
+
+"""
+Authentication backend that checks credentials against an oauth2 server auth endpoint
+"""
+
+import requests
+
+from radicale import auth
+from radicale.log import logger
+
+
+class Auth(auth.BaseAuth):
+    def __init__(self, configuration):
+        super().__init__(configuration)
+        self._endpoint = configuration.get("auth", "oauth2_token_endpoint")
+        if not self._endpoint:
+            logger.error("auth.oauth2_token_endpoint URL missing")
+            raise RuntimeError("OAuth2 token endpoint URL is required")
+        logger.info("auth OAuth2 token endpoint: %s" % (self._endpoint))
+
+    def _login(self, login, password):
+        """Validate credentials.
+        Sends login credentials to oauth token endpoint and checks that a token is returned
+        """
+        try:
+            # authenticate to authentication endpoint and return login if ok, else ""
+            req_params = {
+                "username": login,
+                "password": password,
+                "grant_type": "password",
+                "client_id": "radicale",
+            }
+            req_headers = {"Content-Type": "application/x-www-form-urlencoded"}
+            response = requests.post(
+                self._endpoint, data=req_params, headers=req_headers
+            )
+            if (
+                response.status_code == requests.codes.ok
+                and "access_token" in response.json()
+            ):
+                return login
+        except OSError as e:
+            logger.critical("Failed to authenticate against OAuth2 server %s: %s" % (self._endpoint, e))
+        logger.warning("User failed to authenticate using OAuth2: %r" % login)
+        return ""

+ 4 - 0
radicale/config.py

@@ -307,6 +307,10 @@ DEFAULT_CONFIG_SCHEMA: types.CONFIG_SCHEMA = OrderedDict([
             "value": "tls",
             "help": "Secure the IMAP connection: *tls*|starttls|none",
             "type": imap_security}),
+        ("oauth2_token_endpoint", {
+            "value": "",
+            "help": "OAuth2 token endpoint URL",
+            "type": str}),
         ("strip_domain", {
             "value": "False",
             "help": "strip domain from username",

+ 1 - 1
setup.cfg.legacy

@@ -24,7 +24,7 @@ skip_install = True
 
 [testenv:mypy]
 deps = mypy==1.11.0
-commands = mypy .
+commands = mypy --install-types --non-interactive .
 skip_install = True
 
 [tool:isort]

+ 1 - 1
setup.py.legacy

@@ -20,7 +20,7 @@ from setuptools import find_packages, setup
 
 # When the version is updated, a new section in the CHANGELOG.md file must be
 # added too.
-VERSION = "3.4.1"
+VERSION = "3.4.2.dev"
 
 with open("README.md", encoding="utf-8") as f:
     long_description = f.read()