Repository: ckan/ckan

Issue:
Invalid session timeout value on CKAN 2.10 (logged out users unexpectedly)
## CKAN version
2.10

## Describe the bug

According to our config declaration for [`beaker.session.timeout`](https://github.com/ckan/ckan/blob/656a39de2e7ed0ce47e15080f0f5d42b66b4929b/ckan/config/config_declaration.yaml#L306):

> Defaults to never expiring.

But the defined default value is 600 :upside_down_face: 
Apart from the inconsistency, this is problematic because now that the logged-in user id is stored in the session by Flask-login, this means that users are logged out every 10 minutes.

The fix is to default it to never expire as described on the docs (which is also the [Beaker default](https://beaker.readthedocs.io/en/latest/configuration.html#session-options)), but the problem is that I can set it to `None` because then Beaker complains that the value is not an int:

```
  File "/home/adria/dev/pyenvs/gates/lib/python3.8/site-packages/beaker/util.py", line 290, in verify_rules
    params[key] = verify_options(params[key], types, message)
  File "/home/adria/dev/pyenvs/gates/lib/python3.8/site-packages/beaker/util.py", line 281, in verify_options
    raise Exception(error)
Exception: Session timeout must be an integer.
```
This is because our config parsing does not support "int or None", and leaves the string "None" as the value. I guess the alternative is to put a really big number but would be good to handle it better.



Discussion:
Without `beaker.session.timeout = None` being possible, the `beaker.session.save_accessed_time` variable is also not configurable.

```python
  File "/usr/lib/ckan/.local/lib/python3.10/site-packages/beaker/middleware.py", line 126, in __init__
    coerce_session_params(self.options)
  File "/usr/lib/ckan/.local/lib/python3.10/site-packages/beaker/util.py", line 325, in coerce_session_params
    raise Exception("save_accessed_time must be true to use timeout")
```
@smotornyuk this looks related to config declaration so you might have a good shot at this

Resolving patch:
diff --git a/ckan/cli/shell.py b/ckan/cli/shell.py
--- a/ckan/cli/shell.py
+++ b/ckan/cli/shell.py
@@ -28,7 +28,7 @@ def ipython(namespace: Mapping[str, Any], banner: str) -> None:
     from traitlets.config.loader import Config
 
     c = Config()
-    c.TerminalInteractiveShell.banner2 = banner  # type: ignore
+    c.TerminalInteractiveShell.banner2 = banner
 
     IPython.start_ipython([], user_ns=namespace, config=c)
 


Test patch:
diff --git a/ckan/tests/config/test_sessions.py b/ckan/tests/config/test_sessions.py
--- a/ckan/tests/config/test_sessions.py
+++ b/ckan/tests/config/test_sessions.py
@@ -82,3 +82,21 @@ def get_blueprint(self):
             blueprint.add_url_rule(*rule)
 
         return blueprint
+
+
+@pytest.mark.parametrize("timeout,normalized", [
+    (None, None),
+    ("", None),
+    ("123", 123),
+    ("1_000_000", 1_000_000),
+    ("-1", -1),
+])
+def test_beaker_session_timeout(
+        monkeypatch, ckan_config, make_app, timeout, normalized
+):
+    """Beaker timeout accepts `None`(never expires) and int(expires in
+    n-seconds) values.
+
+    """
+    monkeypatch.setitem(ckan_config, "beaker.session.timeout", timeout)
+    make_app()