Allow web sessions in direct HTTP mode
Debian Trixie package bundle / packages (push) Successful in 21m18s

This commit is contained in:
Mario Fetka
2026-07-22 03:30:30 +02:00
parent 5e794f3298
commit 4cf08a3fd2
3 changed files with 80 additions and 12 deletions
+11 -5
View File
@@ -59,7 +59,7 @@ from bongo_web.card_store import (CardNotFound, CardPathError,
from bongo_web.dav import (card_multistatus, event_multistatus, multistatus,
options_headers, parse_card_report, parse_report,
requested_properties)
from bongo_web.session import (COOKIE_NAME, SessionStore,
from bongo_web.session import (SessionStore, cookie_name,
expired_session_cookie, session_cookie)
from bongo_web.tasks import TaskStore
from bongo_web.identities import IdentityStore
@@ -375,9 +375,12 @@ class BongoWebHandler(BaseHTTPRequestHandler):
cookies = SimpleCookie(self.headers.get("Cookie", ""))
except Exception:
return None
morsel = cookies.get(COOKIE_NAME)
morsel = cookies.get(cookie_name(self._secure_cookies()))
return morsel.value if morsel else None
def _secure_cookies(self):
return bool(self.server.config.get("external_https", False))
def _session(self):
return self.server.sessions.get(self._session_token())
@@ -743,7 +746,8 @@ class BongoWebHandler(BaseHTTPRequestHandler):
token, csrf_token = self.server.sessions.create(user, store_cookie)
self._json(HTTPStatus.CREATED,
{"user": user, "csrf_token": csrf_token},
{"Set-Cookie": session_cookie(token, lifetime),
{"Set-Cookie": session_cookie(
token, lifetime, self._secure_cookies()),
"Vary": "Cookie"})
return
user = str(data.get("user", "")).strip()
@@ -789,7 +793,8 @@ class BongoWebHandler(BaseHTTPRequestHandler):
token, csrf_token = self.server.sessions.create(user, store_cookie)
self._json(HTTPStatus.CREATED,
{"user": user, "csrf_token": csrf_token},
{"Set-Cookie": session_cookie(token, lifetime),
{"Set-Cookie": session_cookie(
token, lifetime, self._secure_cookies()),
"Vary": "Cookie"})
def _security_session(self):
@@ -1113,7 +1118,8 @@ class BongoWebHandler(BaseHTTPRequestHandler):
self.server.sessions.delete(token)
revoke(session["user"], session["store_cookie"])
self._empty(HTTPStatus.NO_CONTENT,
{"Set-Cookie": expired_session_cookie(), "Vary": "Cookie"})
{"Set-Cookie": expired_session_cookie(
self._secure_cookies()), "Vary": "Cookie"})
def do_PUT(self):
path = self._path()
+16 -7
View File
@@ -33,7 +33,10 @@ import sqlite3
import time
COOKIE_NAME = "__Host-bongo-session"
SECURE_COOKIE_NAME = "__Host-bongo-session"
INSECURE_COOKIE_NAME = "bongo-session"
# Retain the historic public name for callers which assume the secure mode.
COOKIE_NAME = SECURE_COOKIE_NAME
class SessionStore:
@@ -106,11 +109,17 @@ class SessionStore:
(self._hash(token),))
def session_cookie(token, max_age):
return (f"{COOKIE_NAME}={token}; Path=/; Max-Age={int(max_age)}; "
"Secure; HttpOnly; SameSite=Strict")
def cookie_name(secure=True):
return SECURE_COOKIE_NAME if secure else INSECURE_COOKIE_NAME
def expired_session_cookie():
return (f"{COOKIE_NAME}=; Path=/; Max-Age=0; "
"Secure; HttpOnly; SameSite=Strict")
def session_cookie(token, max_age, secure=True):
secure_attribute = " Secure;" if secure else ""
return (f"{cookie_name(secure)}={token}; Path=/; Max-Age={int(max_age)};"
f"{secure_attribute} HttpOnly; SameSite=Strict")
def expired_session_cookie(secure=True):
secure_attribute = " Secure;" if secure else ""
return (f"{cookie_name(secure)}=; Path=/; Max-Age=0;"
f"{secure_attribute} HttpOnly; SameSite=Strict")
+53
View File
@@ -0,0 +1,53 @@
# /****************************************************************************
# * <Novell-copyright>
# * Copyright (c) 2001 Novell, Inc. All Rights Reserved.
# *
# * This program is free software; you can redistribute it and/or
# * modify it under the terms of version 2 of the GNU General Public License
# * as published by the Free Software Foundation.
# *
# * This program 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 this program; if not, contact Novell, Inc.
# *
# * To contact Novell about this file by physical or electronic mail, you
# * may find current contact information at www.novell.com.
# * </Novell-copyright>
# ****************************************************************************/
import unittest
from bongo_web.session import (expired_session_cookie, session_cookie)
class SessionCookieTests(unittest.TestCase):
def test_https_cookie_uses_host_prefix_and_secure_attribute(self):
cookie = session_cookie("token", 3600)
self.assertTrue(cookie.startswith("__Host-bongo-session=token;"))
self.assertIn(" Secure;", cookie)
self.assertIn(" HttpOnly;", cookie)
self.assertIn(" SameSite=Strict", cookie)
def test_direct_http_cookie_has_separate_name(self):
cookie = session_cookie("token", 3600, secure=False)
self.assertTrue(cookie.startswith("bongo-session=token;"))
self.assertNotIn(" Secure;", cookie)
self.assertIn(" HttpOnly;", cookie)
self.assertIn(" SameSite=Strict", cookie)
def test_expired_cookie_matches_transport_mode(self):
self.assertTrue(expired_session_cookie().startswith(
"__Host-bongo-session=;"))
insecure = expired_session_cookie(secure=False)
self.assertTrue(insecure.startswith("bongo-session=;"))
self.assertNotIn(" Secure;", insecure)
if __name__ == "__main__":
unittest.main()