Skip to content

✨ Keep session cookie during 5 days #993

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/backend/core/authentication/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import logging
import os
import time

from django.conf import settings
from django.core.exceptions import SuspiciousOperation
Expand Down Expand Up @@ -35,6 +36,18 @@ class OIDCAuthenticationBackend(LaSuiteOIDCAuthenticationBackend):
in the User and Identity models, and handles signed and/or encrypted UserInfo response.
"""

def store_tokens(self, access_token, id_token):
"""
Extends base method to stores a oidc_token_expiration field in the session
which is used with OIDCRefreshSessionMiddleware, which performs a refresh
token request if oidc_token_expiration expires.
"""
session = self.request.session
token_expiration_delay = self.get_settings("OIDC_TOKEN_EXPIRATION", 60 * 15)
session["oidc_token_expiration"] = time.time() + token_expiration_delay

super().store_tokens(access_token, id_token)

def get_extra_claims(self, user_info):
"""
Return extra claims from user_info.
Expand Down
23 changes: 23 additions & 0 deletions src/backend/core/authentication/middleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""OIDC Refresh Session Middleware for the Impress core app."""

import time

from lasuite.oidc_login.middleware import RefreshOIDCAccessToken


class OIDCRefreshSessionMiddleware(RefreshOIDCAccessToken):
"""
Customizes the process_request method to update the session's
oidc_token_expiration field.
"""

def process_request(self, request):
"""
Run the base process_request method and
update oidc_token_expiration on success.
"""
token_expiration_delay = self.get_settings("OIDC_TOKEN_EXPIRATION")
if super().is_expired(request) and super().process_request(request) is None:
request.session["oidc_token_expiration"] = (
time.time() + token_expiration_delay
)
109 changes: 109 additions & 0 deletions src/backend/core/tests/authentication/test_middleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"""Unit tests for the OIDC Refresh Session Middleware."""

import re
import time
from unittest.mock import MagicMock

import pytest
import responses
from cryptography.fernet import Fernet
from lasuite.oidc_login.backends import (
get_oidc_refresh_token,
store_oidc_refresh_token,
)

from core.authentication.middleware import OIDCRefreshSessionMiddleware
from core.factories import UserFactory

pytestmark = pytest.mark.django_db


@responses.activate
def test_refresh_access_token_when_expired(rf, settings):
"""
Test if the middleware refreshes the access token and updates the oidc_token_expiration field
"""
settings.OIDC_OP_TOKEN_ENDPOINT = "http://oidc.endpoint.test/token"
settings.OIDC_OP_AUTHORIZATION_ENDPOINT = "https://oidc.endpoint.com/authorize"
settings.OIDC_RP_CLIENT_ID = "client_id"
settings.OIDC_RP_CLIENT_SECRET = "client_secret"
settings.OIDC_RP_SCOPES = "openid email"
settings.OIDC_USE_NONCE = True
settings.OIDC_VERIFY_SSL = True
settings.OIDC_TOKEN_USE_BASIC_AUTH = False
settings.OIDC_STORE_ACCESS_TOKEN = True
settings.OIDC_STORE_REFRESH_TOKEN = True
settings.OIDC_STORE_REFRESH_TOKEN_KEY = Fernet.generate_key()
settings.OIDC_TOKEN_EXPIRATION = 100

request = rf.get("/some-url")
request.user = UserFactory()
request.session = {}
request.session["oidc_access_token"] = "old-access-token"
store_oidc_refresh_token(request.session, "old-refresh_token")

now = time.time()
expiration = now - settings.OIDC_TOKEN_EXPIRATION
request.session["oidc_token_expiration"] = expiration

get_response = MagicMock()
refresh_middleware = OIDCRefreshSessionMiddleware(get_response)

responses.add(
responses.POST,
re.compile(settings.OIDC_OP_TOKEN_ENDPOINT),
json={
"access_token": "new-access-token",
"refresh_token": "new-refresh-token",
},
status=200,
)

# pylint: disable=assignment-from-no-return
response = refresh_middleware.process_request(request)

assert response is None
assert request.session["oidc_access_token"] == "new-access-token"
assert request.session["oidc_id_token"] is None
assert (
request.session["oidc_token_expiration"]
> expiration + settings.OIDC_TOKEN_EXPIRATION
)
assert get_oidc_refresh_token(request.session) == "new-refresh-token"


def test_access_token_when_expired(rf, settings):
"""
Test if the middleware doesn't perform a token refresh if token not expired
"""

settings.OIDC_OP_TOKEN_ENDPOINT = "http://oidc.endpoint.test/token"
settings.OIDC_OP_AUTHORIZATION_ENDPOINT = "https://oidc.endpoint.com/authorize"
settings.OIDC_RP_CLIENT_ID = "client_id"
settings.OIDC_RP_CLIENT_SECRET = "client_secret"
settings.OIDC_RP_SCOPES = "openid email"
settings.OIDC_USE_NONCE = True
settings.OIDC_VERIFY_SSL = True
settings.OIDC_TOKEN_USE_BASIC_AUTH = False
settings.OIDC_STORE_ACCESS_TOKEN = True
settings.OIDC_STORE_REFRESH_TOKEN = True
settings.OIDC_STORE_REFRESH_TOKEN_KEY = Fernet.generate_key()
settings.OIDC_TOKEN_EXPIRATION = 100

request = rf.get("/some-url")
request.user = UserFactory()
request.session = {}
request.session["oidc_access_token"] = "access-token"
store_oidc_refresh_token(request.session, "refresh_token")

expiration = time.time() + 120
request.session["oidc_token_expiration"] = expiration

get_response = MagicMock()
refresh_middleware = OIDCRefreshSessionMiddleware(get_response)

# pylint: disable=assignment-from-no-return
response = refresh_middleware.process_request(request)

assert response is None
assert request.session["oidc_token_expiration"] == expiration
17 changes: 13 additions & 4 deletions src/backend/impress/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import sentry_sdk
from configurations import Configuration, values
from cryptography.fernet import Fernet
from sentry_sdk.integrations.django import DjangoIntegration
from sentry_sdk.integrations.logging import ignore_logger

Expand Down Expand Up @@ -283,6 +284,7 @@ class Base(Configuration):
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"core.authentication.middleware.OIDCRefreshSessionMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"dockerflow.django.middleware.DockerflowMiddleware",
]
Expand Down Expand Up @@ -463,7 +465,9 @@ class Base(Configuration):
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default"
SESSION_COOKIE_AGE = values.PositiveIntegerValue(
default=60 * 60 * 12, environ_name="SESSION_COOKIE_AGE", environ_prefix=None
default=60 * 60 * 24 * 5, # 5 days
environ_name="SESSION_COOKIE_AGE",
environ_prefix=None,
)

# OIDC - Authorization Code Flow
Expand Down Expand Up @@ -541,16 +545,21 @@ class Base(Configuration):
default=64, environ_name="OIDC_PKCE_CODE_VERIFIER_SIZE", environ_prefix=None
)
OIDC_STORE_ACCESS_TOKEN = values.BooleanValue(
default=False, environ_name="OIDC_STORE_ACCESS_TOKEN", environ_prefix=None
default=True, environ_name="OIDC_STORE_ACCESS_TOKEN", environ_prefix=None
)
OIDC_STORE_REFRESH_TOKEN = values.BooleanValue(
default=False, environ_name="OIDC_STORE_REFRESH_TOKEN", environ_prefix=None
default=True, environ_name="OIDC_STORE_REFRESH_TOKEN", environ_prefix=None
)
OIDC_STORE_REFRESH_TOKEN_KEY = values.Value(
default=None,
default=Fernet.generate_key(),
environ_name="OIDC_STORE_REFRESH_TOKEN_KEY",
environ_prefix=None,
)
OIDC_TOKEN_EXPIRATION = values.PositiveIntegerValue(
default=60 * 15, # 15 minutes
environ_name="OIDC_TOKEN_EXPIRATION",
environ_prefix=None,
)

# WARNING: Enabling this setting allows multiple user accounts to share the same email
# address. This may cause security issues and is not recommended for production use when
Expand Down
3 changes: 2 additions & 1 deletion src/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,6 @@
"react-dom": "19.1.0",
"typescript": "5.8.3",
"yjs": "13.6.27"
}
},
"packageManager": "[email protected]+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
Loading