Merge pull request #2145 from maxberger/master

web ui: New setting for browser-based login
This commit is contained in:
Peter Bieringer
2026-05-27 21:31:26 +03:00
committed by GitHub
7 changed files with 51 additions and 6 deletions

View File

@@ -1699,6 +1699,15 @@ Available backends are:
Default: `internal` Default: `internal`
##### prefer_browser_login _(>= 3.7.4)_
Try to use browser based login rather than the built in login in the web UI. This
can be helpful if the login system you are using uses redirects, such as in
an OIDC setup. For this to work properly, you will also have to make sure CORS
headers are configured correctly.s
Default: `False`
#### [logging] #### [logging]
##### level ##### level

3
config
View File

@@ -363,6 +363,9 @@
# Value: none | internal # Value: none | internal
#type = internal #type = internal
# Prefer browser login
#prefer_browser_login = False
[logging] [logging]

View File

@@ -691,7 +691,11 @@ This is an automated message. Please do not reply.""",
"value": "internal", "value": "internal",
"help": "web interface backend", "help": "web interface backend",
"type": str_or_callable, "type": str_or_callable,
"internal": web.INTERNAL_TYPES})])), "internal": web.INTERNAL_TYPES}),
("prefer_browser_login", {
"value": "False",
"help": "prefer browser login",
"type": bool})])),
("logging", OrderedDict([ ("logging", OrderedDict([
("level", { ("level", {
"value": "info", "value": "info",

View File

@@ -31,6 +31,11 @@ class TestBaseWebRequests(BaseTest):
_, answer = self.get("/.web/") _, answer = self.get("/.web/")
assert answer assert answer
self.post("/.web", check=405) self.post("/.web", check=405)
_, answer = self.get("/.web/js/config.js")
assert "export const PREFER_BROWSER_LOGIN = false;" in answer
self.configure({"web": {"prefer_browser_login": "True"}})
_, answer = self.get("/.web/js/config.js")
assert "export const PREFER_BROWSER_LOGIN = true;" in answer
def test_none(self) -> None: def test_none(self) -> None:
self.configure({"web": {"type": "none"}}) self.configure({"web": {"type": "none"}})

View File

@@ -34,7 +34,27 @@ FALLBACK_MIMETYPE = httputils.FALLBACK_MIMETYPE # deprecated
class Web(web.BaseWeb): class Web(web.BaseWeb):
def get(self, environ: types.WSGIEnviron, base_prefix: str, path: str, def get(
user: str, request_info: dict) -> types.WSGIResponse: self,
return httputils.serve_resource("radicale.web", "internal_data", environ: types.WSGIEnviron,
base_prefix, path) base_prefix: str,
path: str,
user: str,
request_info: dict,
) -> types.WSGIResponse:
if path == "/.web/js/config.js":
prefer_browser_login = (
"true"
if self.configuration.get("web", "prefer_browser_login")
else "false"
)
content = (
f"export const PREFER_BROWSER_LOGIN = {prefer_browser_login};\n".encode(
"utf-8"
)
)
headers = {"Content-Type": "application/javascript"}
return 200, headers, content, None
return httputils.serve_resource(
"radicale.web", "internal_data", base_prefix, path
)

View File

@@ -0,0 +1,3 @@
// This file is dynamically overwritten during serving. The contents here are just to enable development an testing.
export const PREFER_BROWSER_LOGIN = false;

View File

@@ -20,6 +20,7 @@
*/ */
import { get_principal } from "../api/api.js"; import { get_principal } from "../api/api.js";
import { PREFER_BROWSER_LOGIN } from "../config.js";
import { ROOT_PATH, SERVER } from "../constants.js"; import { ROOT_PATH, SERVER } from "../constants.js";
import { Collection } from "../models/collection.js"; import { Collection } from "../models/collection.js";
import { extract_title, extractUsernameFromPrincipalCollection } from "../utils/collection_utils.js"; import { extract_title, extractUsernameFromPrincipalCollection } from "../utils/collection_utils.js";
@@ -167,7 +168,7 @@ export class LoginScene {
fetch(SERVER + ROOT_PATH, { fetch(SERVER + ROOT_PATH, {
method: 'PROPFIND', method: 'PROPFIND',
headers: { 'Depth': '0' }, headers: { 'Depth': '0' },
credentials: 'omit' credentials: PREFER_BROWSER_LOGIN ? 'include' : 'omit'
}).then((response) => { }).then((response) => {
if (response.ok) { if (response.ok) {
// Authenticated! Now it's safe to call get_principal // Authenticated! Now it's safe to call get_principal