Skip to content

Commit c2def71

Browse files
authored
Merge pull request #1689 from pbiering/auth-oauth2
migrate oauth2 into upstream
2 parents 780aaa7 + 6f68a64 commit c2def71

File tree

9 files changed

+93
-5
lines changed

9 files changed

+93
-5
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 3.4.2.dev
4+
5+
* Add: option [auth] type oauth2 by code migration from https://gitlab.mim-libre.fr/alphabet/radicale_oauth/-/blob/dev/oauth2/
6+
37
## 3.4.1
48
* Add: option [auth] dovecot_connection_type / dovecot_host / dovecot_port
59
* Add: option [auth] type imap by code migration from https://github.com/Unrud/RadicaleIMAP/

DOCUMENTATION.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -824,7 +824,10 @@ Available backends:
824824
: Use a Dovecot server to authenticate users.
825825

826826
`imap`
827-
: Use a IMAP server to authenticate users.
827+
: Use an IMAP server to authenticate users.
828+
829+
`oauth2`
830+
: Use an OAuth2 server to authenticate users.
828831

829832
Default: `none`
830833

@@ -1019,6 +1022,12 @@ Secure the IMAP connection: tls | starttls | none
10191022

10201023
Default: `tls`
10211024

1025+
##### oauth2_token_endpoint
1026+
1027+
OAuth2 token endpoint URL
1028+
1029+
Default:
1030+
10221031
##### lc_username
10231032

10241033
Сonvert username to lowercase, must be true for case-insensitive auth

config

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@
125125
# Value: tls | starttls | none
126126
#imap_security = tls
127127

128+
# OAuth2 token endpoint URL
129+
#oauth2_token_endpoint = <URL>
130+
128131
# Htpasswd filename
129132
#htpasswd_filename = /etc/radicale/users
130133

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "Radicale"
33
# When the version is updated, a new section in the CHANGELOG.md file must be
44
# added too.
55
readme = "README.md"
6-
version = "3.4.1"
6+
version = "3.4.2.dev"
77
authors = [{name = "Guillaume Ayoub", email = "guillaume.ayoub@kozea.fr"}, {name = "Unrud", email = "unrud@outlook.com"}, {name = "Peter Bieringer", email = "pb@bieringer.de"}]
88
license = {text = "GNU GPL v3"}
99
description = "CalDAV and CardDAV Server"
@@ -72,7 +72,7 @@ skip_install = true
7272

7373
[tool.tox.env.mypy]
7474
deps = ["mypy==1.11.0"]
75-
commands = [["mypy", "."]]
75+
commands = [["mypy", "--install-types", "--non-interactive", "."]]
7676
skip_install = true
7777

7878

radicale/auth/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,15 @@
4242
"htpasswd",
4343
"ldap",
4444
"imap",
45+
"oauth2",
4546
"dovecot")
4647

4748
CACHE_LOGIN_TYPES: Sequence[str] = (
4849
"dovecot",
4950
"ldap",
5051
"htpasswd",
5152
"imap",
53+
"oauth2",
5254
)
5355

5456
AUTH_SOCKET_FAMILY: Sequence[str] = ("AF_UNIX", "AF_INET", "AF_INET6")

radicale/auth/oauth2.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# This file is part of Radicale Server - Calendar Server
2+
#
3+
# Original from https://gitlab.mim-libre.fr/alphabet/radicale_oauth/
4+
# Copyright © 2021-2022 Bruno Boiget
5+
# Copyright © 2022-2022 Daniel Dehennin
6+
#
7+
# Since migration into upstream
8+
# Copyright © 2025-2025 Peter Bieringer <pb@bieringer.de>
9+
#
10+
# This library is free software: you can redistribute it and/or modify
11+
# it under the terms of the GNU General Public License as published by
12+
# the Free Software Foundation, either version 3 of the License, or
13+
# (at your option) any later version.
14+
#
15+
# This library is distributed in the hope that it will be useful,
16+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
# GNU General Public License for more details.
19+
#
20+
# You should have received a copy of the GNU General Public License
21+
# along with Radicale. If not, see <http://www.gnu.org/licenses/>.
22+
23+
"""
24+
Authentication backend that checks credentials against an oauth2 server auth endpoint
25+
"""
26+
27+
import requests
28+
29+
from radicale import auth
30+
from radicale.log import logger
31+
32+
33+
class Auth(auth.BaseAuth):
34+
def __init__(self, configuration):
35+
super().__init__(configuration)
36+
self._endpoint = configuration.get("auth", "oauth2_token_endpoint")
37+
if not self._endpoint:
38+
logger.error("auth.oauth2_token_endpoint URL missing")
39+
raise RuntimeError("OAuth2 token endpoint URL is required")
40+
logger.info("auth OAuth2 token endpoint: %s" % (self._endpoint))
41+
42+
def _login(self, login, password):
43+
"""Validate credentials.
44+
Sends login credentials to oauth token endpoint and checks that a token is returned
45+
"""
46+
try:
47+
# authenticate to authentication endpoint and return login if ok, else ""
48+
req_params = {
49+
"username": login,
50+
"password": password,
51+
"grant_type": "password",
52+
"client_id": "radicale",
53+
}
54+
req_headers = {"Content-Type": "application/x-www-form-urlencoded"}
55+
response = requests.post(
56+
self._endpoint, data=req_params, headers=req_headers
57+
)
58+
if (
59+
response.status_code == requests.codes.ok
60+
and "access_token" in response.json()
61+
):
62+
return login
63+
except OSError as e:
64+
logger.critical("Failed to authenticate against OAuth2 server %s: %s" % (self._endpoint, e))
65+
logger.warning("User failed to authenticate using OAuth2: %r" % login)
66+
return ""

radicale/config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,10 @@ def json_str(value: Any) -> dict:
307307
"value": "tls",
308308
"help": "Secure the IMAP connection: *tls*|starttls|none",
309309
"type": imap_security}),
310+
("oauth2_token_endpoint", {
311+
"value": "",
312+
"help": "OAuth2 token endpoint URL",
313+
"type": str}),
310314
("strip_domain", {
311315
"value": "False",
312316
"help": "strip domain from username",

setup.cfg.legacy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ skip_install = True
2424

2525
[testenv:mypy]
2626
deps = mypy==1.11.0
27-
commands = mypy .
27+
commands = mypy --install-types --non-interactive .
2828
skip_install = True
2929

3030
[tool:isort]

setup.py.legacy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ from setuptools import find_packages, setup
2020

2121
# When the version is updated, a new section in the CHANGELOG.md file must be
2222
# added too.
23-
VERSION = "3.4.1"
23+
VERSION = "3.4.2.dev"
2424

2525
with open("README.md", encoding="utf-8") as f:
2626
long_description = f.read()

0 commit comments

Comments
 (0)