Remove beaker dependency, switch to Pyramid SignedCookieSessionFactory

Replace server-side session backends (redis, file, database, memcached)
with client-side signed cookie sessions. Users will need to
re-authenticate after deploy.

- Rewrite rc_beaker.py to use pyramid.session.SignedCookieSessionFactory
- Gut user_sessions.py to cookie-only stub
- Drop beaker.container from memory_lru_dict.py (pure repoze.lru)
- Migrate ini configs from beaker.session.* to session.* namespace
- Remove beaker==1.13.0 from requirements.txt
- Remove beaker.backends entry points from pyproject.toml
- Preserve backward-compat fallbacks for encryption key resolution
This commit is contained in:
russell@unturf.com 2026-02-19 15:26:24 -05:00
parent ceb23e4d47
commit 4f73837822
20 changed files with 125 additions and 574 deletions

View file

@ -52,7 +52,7 @@ use = egg:gunicorn#main
; allows to set RhodeCode under a prefix in server.
; eg https://server.com/custom_prefix. Enable `filter-with =` option below as well.
; And set your prefix like: `prefix = /custom_prefix`
; be sure to also set beaker.session.cookie_path = /custom_prefix if you need
; be sure to also set session.cookie_path = /custom_prefix if you need
; to make your cookies only work on prefix url
[filter:proxy-prefix]
use = egg:PasteDeploy#prefix
@ -114,7 +114,7 @@ debug_style = true
; encryption key used to encrypt social plugin tokens,
; remote_urls with credentials etc, if not set it defaults to
; `beaker.session.secret`
; `session.secret`
#rhodecode.encrypted_values.secret =
; decryption strict mode (enabled by default). It controls if decryption raises
@ -599,51 +599,19 @@ rc_cache.cache_license.expiration_time = 300
#rc_cache.cache_license.arguments.key_prefix = custom-prefix-
; ##############
; BEAKER SESSION
; ##############
; ######################
; SESSION CONFIGURATION
; ######################
; beaker.session.type is type of storage options for the logged users sessions. Current allowed
; types are file, ext:redis, ext:database, ext:memcached
; Fastest ones are ext:redis and ext:database, DO NOT use memory type for session
#beaker.session.type = file
#beaker.session.data_dir = /var/opt/rhodecode_data/sessions
; Redis based sessions
beaker.session.type = ext:redis
beaker.session.url = redis://redis:6379/2
; DB based session, fast, and allows easy management over logged in users
#beaker.session.type = ext:database
#beaker.session.table_name = db_session
#beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
#beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
#beaker.session.sa.pool_recycle = 3600
#beaker.session.sa.echo = false
beaker.session.key = rhodecode
beaker.session.secret = develop-rc-uytcxaz
beaker.session.lock_dir = /data_ramdisk/lock
; Secure encrypted cookie. Requires AES and AES python libraries
; you must disable beaker.session.secret to use this
#beaker.session.encrypt_key = key_for_encryption
#beaker.session.validate_key = validation_key
; Sets session as invalid (also logging out user) if it haven not been
; accessed for given amount of time in seconds
beaker.session.timeout = 2592000
beaker.session.httponly = true
; Path to use for the cookie. Set to prefix if you use prefix middleware
#beaker.session.cookie_path = /custom_prefix
; Set https secure cookie
beaker.session.secure = false
; default cookie expiration time in seconds, set to `true` to set expire
; at browser close
#beaker.session.cookie_expires = 3600
; Cookie-based signed sessions (no server-side session store needed)
session.secret = develop-rc-uytcxaz
session.key = rhodecode
session.timeout = 2592000
session.httponly = true
session.secure = false
;session.cookie_path = /custom_prefix
;session.domain =
;session.samesite = Lax
; #############################
; SEARCH INDEXING CONFIGURATION

View file

@ -46,7 +46,7 @@ use = egg:gunicorn#main
; allows to set RhodeCode under a prefix in server.
; eg https://server.com/custom_prefix. Enable `filter-with =` option below as well.
; And set your prefix like: `prefix = /custom_prefix`
; be sure to also set beaker.session.cookie_path = /custom_prefix if you need
; be sure to also set session.cookie_path = /custom_prefix if you need
; to make your cookies only work on prefix url
[filter:proxy-prefix]
use = egg:PasteDeploy#prefix
@ -76,7 +76,7 @@ use = egg:rhodecode-enterprise-ce
; encryption key used to encrypt social plugin tokens,
; remote_urls with credentials etc, if not set it defaults to
; `beaker.session.secret`
; `session.secret`
#rhodecode.encrypted_values.secret =
; decryption strict mode (enabled by default). It controls if decryption raises
@ -561,51 +561,19 @@ rc_cache.cache_license.expiration_time = 300
#rc_cache.cache_license.arguments.key_prefix = custom-prefix-
; ##############
; BEAKER SESSION
; ##############
; ######################
; SESSION CONFIGURATION
; ######################
; beaker.session.type is type of storage options for the logged users sessions. Current allowed
; types are file, ext:redis, ext:database, ext:memcached
; Fastest ones are ext:redis and ext:database, DO NOT use memory type for session
#beaker.session.type = file
#beaker.session.data_dir = /var/opt/rhodecode_data/sessions
; Redis based sessions
beaker.session.type = ext:redis
beaker.session.url = redis://redis:6379/2
; DB based session, fast, and allows easy management over logged in users
#beaker.session.type = ext:database
#beaker.session.table_name = db_session
#beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
#beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
#beaker.session.sa.pool_recycle = 3600
#beaker.session.sa.echo = false
beaker.session.key = rhodecode
beaker.session.secret = production-rc-uytcxaz
beaker.session.lock_dir = /data_ramdisk/lock
; Secure encrypted cookie. Requires AES and AES python libraries
; you must disable beaker.session.secret to use this
#beaker.session.encrypt_key = key_for_encryption
#beaker.session.validate_key = validation_key
; Sets session as invalid (also logging out user) if it haven not been
; accessed for given amount of time in seconds
beaker.session.timeout = 2592000
beaker.session.httponly = true
; Path to use for the cookie. Set to prefix if you use prefix middleware
#beaker.session.cookie_path = /custom_prefix
; Set https secure cookie
beaker.session.secure = false
; default cookie expiration time in seconds, set to `true` to set expire
; at browser close
#beaker.session.cookie_expires = 3600
; Cookie-based signed sessions (no server-side session store needed)
session.secret = production-rc-uytcxaz
session.key = rhodecode
session.timeout = 2592000
session.httponly = true
session.secure = false
;session.cookie_path = /custom_prefix
;session.domain =
;session.samesite = Lax
; #############################
; SEARCH INDEXING CONFIGURATION

View file

@ -32,9 +32,7 @@ main = "rhodecode.config.middleware:make_pyramid_app"
[project.entry-points."pyramid.pshell_runner"]
ipython = "rhodecode.lib.pyramid_shell:ipython_shell_runner"
[project.entry-points."beaker.backends"]
memorylru_base="rhodecode.lib.memory_lru_dict:MemoryLRUNamespaceManagerBase"
memorylru_debug="rhodecode.lib.memory_lru_dict:MemoryLRUNamespaceManagerDebug"
# beaker.backends entry points removed — beaker dependency eliminated
[project.scripts]

View file

@ -9,7 +9,7 @@ alembic==1.13.1
greenlet==3.3.0
typing_extensions==4.15.0
babel==2.12.1
beaker==1.13.0
# beaker removed — sessions are now cookie-only via pyramid.session.SignedCookieSessionFactory
bleach==6.3.0
webencodings==0.5.1
celery==5.6.2

View file

@ -294,7 +294,7 @@ def cleanup_sessions(request, apiuser, older_then=Optional(60)):
older_than_seconds = 60 * 60 * 24 * older_then
config = system_info.rhodecode_config().get_value()["value"]["config"]
session_model = user_sessions.get_session_handler(config.get("beaker.session.type", "memory"))(config)
session_model = user_sessions.get_session_handler("cookie")(config)
backend = session_model.SESSION_TYPE
try:

View file

@ -50,7 +50,7 @@ class AdminSessionSettingsView(BaseAppView):
older_than_seconds = 60 * 60 * 24 * c.cleanup_older_days
config = system_info.rhodecode_config().get_value()["value"]["config"]
c.session_model = user_sessions.get_session_handler(config.get("beaker.session.type", "memory"))(config)
c.session_model = user_sessions.get_session_handler("cookie")(config)
c.session_conf = c.session_model.config
c.session_count = c.session_model.get_count()
@ -71,7 +71,7 @@ class AdminSessionSettingsView(BaseAppView):
older_than_seconds = 60 * 60 * 24 * expire_days
config = system_info.rhodecode_config().get_value()["value"]["config"]
session_model = user_sessions.get_session_handler(config.get("beaker.session.type", "memory"))(config)
session_model = user_sessions.get_session_handler("cookie")(config)
try:
session_model.clean_sessions(older_than_seconds=older_than_seconds)

View file

@ -87,7 +87,7 @@ class TestHomeController(TestController):
self.log_user()
response = self.app.get(route_path("home"))
rhodecode_version_hash = calculate_version_hash({"beaker.session.secret": "test-rc-uytcxaz"})
rhodecode_version_hash = calculate_version_hash({"session.secret": "test-rc-uytcxaz"})
response.mustcontain("style.css?ver={0}".format(rhodecode_version_hash))
response.mustcontain("scripts.min.js?ver={0}".format(rhodecode_version_hash))

View file

@ -75,11 +75,7 @@ def sanitize_settings_and_apply_defaults(global_config, settings):
)
log.debug("Using the following Mako template directories: %s", mako_directories)
# NOTE(marcink): fix redis requirement for schema of connection since 3.X
if "beaker.session.type" in settings and settings["beaker.session.type"] == "ext:redis":
raw_url = settings["beaker.session.url"]
if not raw_url.startswith(("redis://", "rediss://", "unix://")):
settings["beaker.session.url"] = "redis://" + raw_url
# NOTE: beaker session redis fixup removed — sessions are now cookie-only
settings_maker.make_setting("__file__", global_config.get("__file__"))
@ -181,9 +177,9 @@ def sanitize_settings_and_apply_defaults(global_config, settings):
settings_maker.make_setting("exception_tracker.send_email", False, parser="bool")
settings_maker.make_setting("exception_tracker.email_prefix", "[RHODECODE ERROR]", default_when_empty=True)
# sessions, ensure file since no-value is memory
settings_maker.make_setting("beaker.session.type", "file")
settings_maker.make_setting("beaker.session.data_dir", jn(default_cache_dir, "session_data"))
# session defaults for signed cookie sessions
settings_maker.make_setting("session.secret", "change-me-in-production")
settings_maker.make_setting("session.timeout", "2592000")
# cache_general
settings_maker.make_setting("rc_cache.cache_general.backend", "dogpile.cache.rc.file_namespace")

View file

@ -284,7 +284,7 @@ class BasicAuth(AuthBasicAuthenticator):
def calculate_version_hash(config):
return sha1(config.get(b"beaker.session.secret", b"") + ascii_bytes(rhodecode.__version__))[:8]
return sha1(config.get(b"session.secret", config.get(b"beaker.session.secret", b"")) + ascii_bytes(rhodecode.__version__))[:8]
def get_current_lang(request):

View file

@ -199,7 +199,7 @@ Encryption signature verification failed.
Please check your value of secret key, and/or encrypted value stored.
Secret key stored inside .ini file:
`rhodecode.encrypted_values.secret` or defaults to
`beaker.session.secret`
`session.secret`
Probably the stored values were encrypted using a different secret then currently set in .ini file
"""

View file

@ -20,7 +20,7 @@
Custom LRU memory manager for debugging purposes. It allows to track the keys
and the state of LRU dict.
inrae.cache is licensed under LRUDict is licensed under ZPL license
repoze.lru is licensed under ZPL license
This software is Copyright (c) Zope Corporation (tm) and
Contributors. All rights reserved.
"""
@ -28,7 +28,6 @@ Contributors. All rights reserved.
import logging
from repoze.lru import LRUCache
from beaker.container import MemoryNamespaceManager, AbstractDictionaryNSManager
from rhodecode.lib.utils2 import safe_str
log = logging.getLogger(__name__)
@ -71,39 +70,3 @@ class LRUDictDebug(LRUDict):
def __getitem__(self, key):
self._report_keys()
return self.get(key)
class MemoryLRUNamespaceManagerBase(MemoryNamespaceManager):
default_max_items = 10000
def _get_factory(self, max_items):
def Factory():
return LRUDict(int(max_items))
return Factory
def __init__(self, namespace, **kwargs):
AbstractDictionaryNSManager.__init__(self, namespace)
if "max_items" in kwargs:
max_items = kwargs["max_items"]
else:
max_items = self.default_max_items
Factory = self._get_factory(max_items)
self.dictionary = MemoryNamespaceManager.namespaces.get(self.namespace, Factory)
class MemoryLRUNamespaceManagerDebug(MemoryLRUNamespaceManagerBase):
"""
A memory namespace manager that return with LRU dicts backend,
special debug for testing
"""
default_max_items = 10000
def _get_factory(self, max_items):
def Factory():
return LRUDictDebug(int(max_items))
return Factory

View file

@ -1,170 +1,73 @@
# Copyright (c) 2010 Agendaless Consulting and Contributors.
# (http://www.agendaless.com), All Rights Reserved
# License: BSD-derived (http://www.repoze.org/LICENSE.txt)
# With Patches from RhodeCode GmBH
# Copyright (C) 2010-2024 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), 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 Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
import os
from beaker import cache
from beaker.session import SessionObject, Session
from beaker.util import coerce_cache_params
from beaker.util import coerce_session_params
from pyramid.interfaces import ISession
from pyramid.session import SignedCookieSessionFactory
from pyramid.settings import asbool
from zope.interface import implementer
from binascii import hexlify
class CustomSession(Session):
pass
def BeakerSessionFactoryConfig(**options):
"""Return a Pyramid session factory using Beaker session settings
supplied directly as ``**options``"""
class PyramidBeakerSessionObject(SessionObject):
_options = options
_cookie_on_exception = _options.pop("cookie_on_exception", True)
_constant_csrf_token = _options.pop("constant_csrf_token", False)
_sa_opts = _options.pop("sa_opts", {})
def __init__(self, request):
self._options["session_class"] = CustomSession
self._options["sa_opts"] = self._sa_opts
SessionObject.__init__(self, request.environ, **self._options)
def session_callback(_request, _response):
exception = getattr(_request, "exception", None)
file_response = getattr(_request, "_file_response", None)
api_call = getattr(_request, "rpc_method", None)
if file_response is not None:
return
if api_call is not None:
return
if exception is not None and not self._cookie_on_exception:
return
if self.accessed():
self.persist()
headers = self.__dict__["_headers"]
if headers.get("set_cookie") and headers.get("cookie_out"):
_response.headerlist.append(("Set-Cookie", headers["cookie_out"]))
request.add_response_callback(session_callback)
# ISession API
@property
def id(self):
# this is as inspected in SessionObject.__init__
if self.__dict__["_params"].get("type") != "cookie":
return self._session().id
return None
@property
def new(self):
return self.last_accessed is None
changed = SessionObject.save
# modifying dictionary methods
@call_save
def clear(self):
return self._session().clear()
@call_save
def update(self, d, **kw):
return self._session().update(d, **kw)
@call_save
def setdefault(self, k, d=None):
return self._session().setdefault(k, d)
@call_save
def pop(self, k, d=None):
return self._session().pop(k, d)
@call_save
def popitem(self):
return self._session().popitem()
__setitem__ = call_save(SessionObject.__setitem__)
__delitem__ = call_save(SessionObject.__delitem__)
# Flash API methods
def flash(self, msg, queue="", allow_duplicate=True):
storage = self.setdefault(f"_f_{queue}", [])
if allow_duplicate or (msg not in storage):
storage.append(msg)
def pop_flash(self, queue=""):
storage = self.pop(f"_f_{queue}", [])
return storage
def peek_flash(self, queue=""):
storage = self.get(f"_f_{queue}", [])
return storage
# CSRF API methods
def new_csrf_token(self):
token = self._constant_csrf_token or hexlify(os.urandom(20)).decode("ascii")
self["_csrft_"] = token
return token
def get_csrf_token(self):
token = self.get("_csrft_", None)
if token is None:
token = self.new_csrf_token()
return token
return implementer(ISession)(PyramidBeakerSessionObject)
def call_save(wrapped):
"""By default, in non-auto-mode beaker badly wants people to
call save even though it should know something has changed when
a mutating method is called. This hack should be removed if
Beaker ever starts to do this by default."""
def save(session, *arg, **kw):
value = wrapped(session, *arg, **kw)
session.save()
return value
save.__doc__ = wrapped.__doc__
return save
def session_factory_from_settings(settings):
"""Return a Pyramid session factory using Beaker session settings
supplied from a Paste configuration file"""
"""Return a Pyramid session factory using signed cookie session settings
supplied from a Paste configuration file."""
prefixes = ("session.", "beaker.session.")
options = {}
# custom gather of our specific sqlalchemy session db configuration we need to translate this into a single entry
# dict because this is how beaker expects that.
sa_opts = {}
# Pull out any config args meant for beaker session. if there are any
for k, v in settings.items():
for prefix in prefixes:
if k.startswith(prefix):
option_name = k[len(prefix) :]
if option_name == "cookie_on_exception":
v = asbool(v)
if option_name.startswith("sa."):
sa_opts[option_name] = v
option_name = k[len(prefix):]
options[option_name] = v
options = coerce_session_params(options)
options["sa_opts"] = sa_opts
return BeakerSessionFactoryConfig(**options)
# Map ini config keys to SignedCookieSessionFactory kwargs
factory_kwargs = {}
factory_kwargs["secret"] = options.get("secret", "")
if "hashalg" in options:
factory_kwargs["hashalg"] = options["hashalg"]
if "timeout" in options:
factory_kwargs["timeout"] = int(options["timeout"])
if "max_age" in options:
factory_kwargs["max_age"] = int(options["max_age"])
if "reissue_time" in options:
factory_kwargs["reissue_time"] = int(options["reissue_time"])
if "key" in options:
factory_kwargs["cookie_name"] = options["key"]
if "secure" in options:
factory_kwargs["secure"] = asbool(options["secure"])
if "httponly" in options:
factory_kwargs["httponly"] = asbool(options["httponly"])
if "domain" in options:
factory_kwargs["domain"] = options["domain"]
if "cookie_path" in options:
factory_kwargs["path"] = options["cookie_path"]
if "samesite" in options:
factory_kwargs["samesite"] = options["samesite"]
return SignedCookieSessionFactory(**factory_kwargs)
def includeme(config):

View file

@ -696,6 +696,7 @@ def rhodecode_server_config():
f"rhodecode_{LicenseModel.LICENSE_DB_KEY}",
"sqlalchemy.db1.url",
"channelstream.secret",
"session.secret",
"beaker.session.secret",
"rhodecode.encrypted_values.secret",
"appenlight.api_key",
@ -751,6 +752,7 @@ def rhodecode_config():
"routes.map",
"sqlalchemy.db1.url",
"channelstream.secret",
"session.secret",
"beaker.session.secret",
"rhodecode.encrypted_values.secret",
"rhodecode_auth_github_consumer_key",

View file

@ -16,226 +16,22 @@
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
import os
import re
import time
import datetime
import binascii
import dateutil
import dateutil.relativedelta
import pickle
import base64
from rhodecode.model.db import DbSession, Session
class CleanupCommand(Exception):
pass
class BaseAuthSessions(object):
SESSION_TYPE = None
class CookieAuthSessions:
SESSION_TYPE = "cookie"
NOT_AVAILABLE = "NOT AVAILABLE"
def __init__(self, config):
session_conf = {}
for k, v in list(config.items()):
if k.startswith("beaker.session"):
if k.startswith("session.") or k.startswith("beaker.session"):
session_conf[k] = v
self.config = session_conf
def get_count(self):
raise NotImplementedError
def get_expired_count(self, older_than_seconds=None):
raise NotImplementedError
def clean_sessions(self, older_than_seconds=None):
raise NotImplementedError
def _seconds_to_date(self, seconds):
return datetime.datetime.utcnow() - dateutil.relativedelta.relativedelta(seconds=seconds)
class DbAuthSessions(BaseAuthSessions):
SESSION_TYPE = "ext:database"
def get_count(self):
return DbSession.query().count()
def get_expired_count(self, older_than_seconds=None):
expiry_date = self._seconds_to_date(older_than_seconds)
return DbSession.query().filter(DbSession.accessed < expiry_date).count()
def clean_sessions(self, older_than_seconds=None):
expiry_date = self._seconds_to_date(older_than_seconds)
to_remove = DbSession.query().filter(DbSession.accessed < expiry_date).count()
DbSession.query().filter(DbSession.accessed < expiry_date).delete()
Session().commit()
return to_remove
class FileAuthSessions(BaseAuthSessions):
SESSION_TYPE = "file sessions"
def _get_sessions_dir(self):
data_dir = self.config.get("beaker.session.data_dir")
return data_dir
def _count_on_filesystem(self, path, older_than=0, callback=None):
value = dict(percent=0, used=0, total=0, items=0, callbacks=0, path=path, text="")
items_count = 0
used = 0
callbacks = 0
cur_time = time.time()
for root, dirs, files in os.walk(path):
for f in files:
final_path = os.path.join(root, f)
try:
mtime = os.stat(final_path).st_mtime
if (cur_time - mtime) > older_than:
items_count += 1
if callback:
callback_res = callback(final_path)
callbacks += 1
else:
used += os.path.getsize(final_path)
except OSError:
pass
value.update({"percent": 100, "used": used, "total": used, "items": items_count, "callbacks": callbacks})
return value
def get_count(self):
try:
sessions_dir = self._get_sessions_dir()
items_count = self._count_on_filesystem(sessions_dir)["items"]
except Exception:
items_count = self.NOT_AVAILABLE
return items_count
def get_expired_count(self, older_than_seconds=0):
try:
sessions_dir = self._get_sessions_dir()
items_count = self._count_on_filesystem(sessions_dir, older_than=older_than_seconds)["items"]
except Exception:
items_count = self.NOT_AVAILABLE
return items_count
def clean_sessions(self, older_than_seconds=0):
# find . -mtime +60 -exec rm {} \;
sessions_dir = self._get_sessions_dir()
def remove_item(path):
os.remove(path)
stats = self._count_on_filesystem(sessions_dir, older_than=older_than_seconds, callback=remove_item)
return stats["callbacks"]
class MemcachedAuthSessions(BaseAuthSessions):
SESSION_TYPE = "ext:memcached"
_key_regex = re.compile(r"ITEM (.*_session) \[(.*); (.*)\]")
def _get_client(self):
import memcache
client = memcache.Client([self.config.get("beaker.session.url")])
return client
def _get_telnet_client(self, host, port):
import telnetlib
client = telnetlib.Telnet(host, port, None)
return client
def _run_telnet_cmd(self, client, cmd):
client.write("%s\n" % cmd)
return client.read_until("END")
def key_details(self, client, slab_ids, limit=100):
"""Return a list of tuples containing keys and details"""
cmd = "stats cachedump %s %s"
for slab_id in slab_ids:
yield from self._key_regex.finditer(self._run_telnet_cmd(client, cmd % (slab_id, limit)))
def get_count(self):
client = self._get_client()
count = self.NOT_AVAILABLE
try:
slabs = []
for server, slabs_data in client.get_slabs():
slabs.extend(list(slabs_data.keys()))
host, port = client.servers[0].address
telnet_client = self._get_telnet_client(host, port)
keys = self.key_details(telnet_client, slabs)
count = 0
for _k in keys:
count += 1
except Exception:
return count
return count
def get_expired_count(self, older_than_seconds=None):
return self.NOT_AVAILABLE
def clean_sessions(self, older_than_seconds=None):
raise CleanupCommand("Cleanup for this session type not yet available")
class RedisAuthSessions(BaseAuthSessions):
SESSION_TYPE = "ext:redis"
def _get_client(self):
import redis
args = {"socket_timeout": 60, "decode_responses": False, "url": self.config.get("beaker.session.url")}
client = redis.StrictRedis.from_url(**args)
return client
def get_count(self):
client = self._get_client()
return len(client.keys("beaker_cache:*"))
def get_expired_count(self, older_than_seconds=None):
expiry_date = self._seconds_to_date(older_than_seconds)
return self.NOT_AVAILABLE
def clean_sessions(self, older_than_seconds=None):
client = self._get_client()
expiry_time = time.time() - older_than_seconds
deleted_keys = 0
for key in client.keys("beaker_cache:*"):
data = client.get(key)
if data:
accessed_time = 0
try:
data = base64.b64decode(data)
json_data = pickle.loads(data)
accessed_time = json_data["_accessed_time"]
except binascii.Error:
accessed_time = 0
except pickle.UnpicklingError:
accessed_time = 0
except KeyError:
accessed_time = 0
if accessed_time < expiry_time:
client.delete(key)
deleted_keys += 1
return deleted_keys
class MemoryAuthSessions(BaseAuthSessions):
SESSION_TYPE = "memory"
def get_count(self):
return self.NOT_AVAILABLE
@ -243,19 +39,11 @@ class MemoryAuthSessions(BaseAuthSessions):
return self.NOT_AVAILABLE
def clean_sessions(self, older_than_seconds=None):
raise CleanupCommand("Cleanup for this session type not yet available")
raise CleanupCommand(
"Cookie-based sessions are client-side only. "
"Users will be logged out when their cookie expires."
)
def get_session_handler(session_type):
types = {
"file": FileAuthSessions,
"ext:memcached": MemcachedAuthSessions,
"ext:redis": RedisAuthSessions,
"ext:database": DbAuthSessions,
"memory": MemoryAuthSessions,
}
try:
return types[session_type]
except KeyError:
raise ValueError(f"This type {session_type} is not supported")
return CookieAuthSessions

View file

@ -282,7 +282,7 @@ def engine_from_config(configuration, prefix="sqlalchemy.", **kwargs):
def get_encryption_key(config) -> bytes:
secret = config.get("rhodecode.encrypted_values.secret")
default = config["beaker.session.secret"]
default = config.get("session.secret", config.get("beaker.session.secret", ""))
enc_key = secret or default
return safe_bytes(enc_key)

View file

@ -117,7 +117,7 @@ log = logging.getLogger(__name__)
# =============================================================================
# this is propagated from .ini file rhodecode.encrypted_values.secret or
# beaker.session.secret if first is not set.
# session.secret if first is not set.
# and initialized at environment.py
ENCRYPTION_KEY: bytes = b""

View file

@ -6,7 +6,7 @@
<%
elems = [
(_('Session type'), c.session_model.SESSION_TYPE, ''),
(_('Session expiration period'), '{} seconds'.format(c.session_conf.get('beaker.session.timeout', 0)), ''),
(_('Session expiration period'), '{} seconds'.format(c.session_conf.get('session.timeout', c.session_conf.get('beaker.session.timeout', 0))), ''),
(_('Total sessions'), c.session_count, ''),
(_('Expired sessions ({} days)').format(c.cleanup_older_days ), c.session_expired_count, ''),

View file

@ -60,7 +60,7 @@
%endif
${h.checkbox('remember', value=True, checked=defaults.get('remember'))}
<% timeout = request.registry.settings.get('beaker.session.timeout', '0') %>
<% timeout = request.registry.settings.get('session.timeout', request.registry.settings.get('beaker.session.timeout', '0')) %>
% if timeout == '0':
<% remember_label = _('Remember my indefinitely') %>
% else:

View file

@ -46,7 +46,7 @@ use = egg:gunicorn#main
; allows to set RhodeCode under a prefix in server.
; eg https://server.com/custom_prefix. Enable `filter-with =` option below as well.
; And set your prefix like: `prefix = /custom_prefix`
; be sure to also set beaker.session.cookie_path = /custom_prefix if you need
; be sure to also set session.cookie_path = /custom_prefix if you need
; to make your cookies only work on prefix url
[filter:proxy-prefix]
use = egg:PasteDeploy#prefix
@ -76,7 +76,7 @@ rhodecode.env_expand = false
; encryption key used to encrypt social plugin tokens,
; remote_urls with credentials etc, if not set it defaults to
; `beaker.session.secret`
; `session.secret`
#rhodecode.encrypted_values.secret =
; decryption strict mode (enabled by default). It controls if decryption raises
@ -520,51 +520,16 @@ rc_cache.cache_repo.arguments.filename = %(here)s/.rc-test-data/cache-backend/ca
#rc_cache.cache_repo.arguments.key_prefix = custom-prefix-
; ##############
; BEAKER SESSION
; ##############
; ######################
; SESSION CONFIGURATION
; ######################
; beaker.session.type is type of storage options for the logged users sessions. Current allowed
; types are file, ext:redis, ext:database, ext:memcached
; Fastest ones are ext:redis and ext:database, DO NOT use memory type for session
beaker.session.type = file
beaker.session.data_dir = %(here)s/.rc-test-data/data/sessions
; Redis based sessions
#beaker.session.type = ext:redis
#beaker.session.url = redis://redis:6379/2
; DB based session, fast, and allows easy management over logged in users
#beaker.session.type = ext:database
#beaker.session.table_name = db_session
#beaker.session.sa.url = postgresql://postgres:secret@localhost/rhodecode
#beaker.session.sa.url = mysql://root:secret@127.0.0.1/rhodecode
#beaker.session.sa.pool_recycle = 3600
#beaker.session.sa.echo = false
beaker.session.key = rhodecode
beaker.session.secret = test-rc-uytcxaz
beaker.session.lock_dir = %(here)s/.rc-test-data/data/sessions/lock
; Secure encrypted cookie. Requires AES and AES python libraries
; you must disable beaker.session.secret to use this
#beaker.session.encrypt_key = key_for_encryption
#beaker.session.validate_key = validation_key
; Sets session as invalid (also logging out user) if it haven not been
; accessed for given amount of time in seconds
beaker.session.timeout = 2592000
beaker.session.httponly = true
; Path to use for the cookie. Set to prefix if you use prefix middleware
#beaker.session.cookie_path = /custom_prefix
; Set https secure cookie
beaker.session.secure = false
; default cookie expiration time in seconds, set to `true` to set expire
; at browser close
#beaker.session.cookie_expires = 3600
; Cookie-based signed sessions (no server-side session store needed)
session.secret = test-rc-uytcxaz
session.key = rhodecode
session.timeout = 2592000
session.httponly = true
session.secure = false
; #############################
; SEARCH INDEXING CONFIGURATION

View file

@ -116,8 +116,8 @@ class CustomTestResponse(TestResponse):
"""
from rhodecode.lib.rc_beaker import session_factory_from_settings
session = session_factory_from_settings(self.test_app._pyramid_settings)
return session(self.request)
factory = session_factory_from_settings(self.test_app._pyramid_settings)
return factory(self.request)
class TestRequest(webob.BaseRequest):