(backend) allow to create a new user in a marketing system

We want to create a new user in a marketing system to create a dedicated
onboarding for each of them. The marketing service is implemented in the
django-lasuite library and it is possible to pick the backend we want
or implement a new one following the documentation on this library.
This commit is contained in:
Manuel Raynaud
2025-12-10 16:30:00 +01:00
committed by GitHub
parent 12cc79b640
commit 8091cbca23
6 changed files with 228 additions and 99 deletions

View File

@@ -11,6 +11,7 @@ and this project adheres to
### Added ### Added
- ⚡️(frontend) export html #1669 - ⚡️(frontend) export html #1669
- ✨(backend) allow to create a new user in a marketing system
### Changed ### Changed

View File

@@ -7,7 +7,7 @@ Here we describe all environment variables that can be set for the docs applicat
These are the environment variables you can set for the `impress-backend` container. These are the environment variables you can set for the `impress-backend` container.
| Option | Description | default | | Option | Description | default |
|-------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------| |-------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------|
| AI_ALLOW_REACH_FROM | Users that can use AI must be this level. options are "public", "authenticated", "restricted" | authenticated | | AI_ALLOW_REACH_FROM | Users that can use AI must be this level. options are "public", "authenticated", "restricted" | authenticated |
| AI_API_KEY | AI key to be used for AI Base url | | | AI_API_KEY | AI key to be used for AI Base url | |
| AI_BASE_URL | OpenAI compatible AI base url | | | AI_BASE_URL | OpenAI compatible AI base url | |
@@ -63,6 +63,8 @@ These are the environment variables you can set for the `impress-backend` contai
| FRONTEND_HOMEPAGE_FEATURE_ENABLED | Frontend feature flag to display the homepage | false | | FRONTEND_HOMEPAGE_FEATURE_ENABLED | Frontend feature flag to display the homepage | false |
| FRONTEND_THEME | Frontend theme to use | | | FRONTEND_THEME | Frontend theme to use | |
| LANGUAGE_CODE | Default language | en-us | | LANGUAGE_CODE | Default language | en-us |
| LASUITE_MARKETING_BACKEND | Backend used when SIGNUP_NEW_USER_TO_MARKETING_EMAIL is True. See https://github.com/suitenumerique/django-lasuite/blob/main/documentation/how-to-use-marketing-backend.md | lasuite.marketing.backends.dummy.DummyBackend |
| LASUITE_MARKETING_PARAMETERS | The parameters to configure LASUITE_MARKETING_BACKEND. See https://github.com/suitenumerique/django-lasuite/blob/main/documentation/how-to-use-marketing-backend.md | {} |
| LOGGING_LEVEL_LOGGERS_APP | Application logging level. options are "DEBUG", "INFO", "WARN", "ERROR", "CRITICAL" | INFO | | LOGGING_LEVEL_LOGGERS_APP | Application logging level. options are "DEBUG", "INFO", "WARN", "ERROR", "CRITICAL" | INFO |
| LOGGING_LEVEL_LOGGERS_ROOT | Default logging level. options are "DEBUG", "INFO", "WARN", "ERROR", "CRITICAL" | INFO | | LOGGING_LEVEL_LOGGERS_ROOT | Default logging level. options are "DEBUG", "INFO", "WARN", "ERROR", "CRITICAL" | INFO |
| LOGIN_REDIRECT_URL | Login redirect url | | | LOGIN_REDIRECT_URL | Login redirect url | |
@@ -95,6 +97,7 @@ These are the environment variables you can set for the `impress-backend` contai
| REDIS_URL | Cache url | redis://redis:6379/1 | | REDIS_URL | Cache url | redis://redis:6379/1 |
| SENTRY_DSN | Sentry host | | | SENTRY_DSN | Sentry host | |
| SESSION_COOKIE_AGE | duration of the cookie session | 60*60*12 | | SESSION_COOKIE_AGE | duration of the cookie session | 60*60*12 |
| SIGNUP_NEW_USER_TO_MARKETING_EMAIL | Register new user to the marketing onboarding. If True, see env LASUITE_MARKETING_* system | False
| SPECTACULAR_SETTINGS_ENABLE_DJANGO_DEPLOY_CHECK | | false | | SPECTACULAR_SETTINGS_ENABLE_DJANGO_DEPLOY_CHECK | | false |
| STORAGES_STATICFILES_BACKEND | | whitenoise.storage.CompressedManifestStaticFilesStorage | | STORAGES_STATICFILES_BACKEND | | whitenoise.storage.CompressedManifestStaticFilesStorage |
| THEME_CUSTOMIZATION_CACHE_TIMEOUT | Cache duration for the customization settings | 86400 | | THEME_CUSTOMIZATION_CACHE_TIMEOUT | Cache duration for the customization settings | 86400 |

View File

@@ -6,6 +6,7 @@ import os
from django.conf import settings from django.conf import settings
from django.core.exceptions import SuspiciousOperation from django.core.exceptions import SuspiciousOperation
from lasuite.marketing.tasks import create_or_update_contact
from lasuite.oidc_login.backends import ( from lasuite.oidc_login.backends import (
OIDCAuthenticationBackend as LaSuiteOIDCAuthenticationBackend, OIDCAuthenticationBackend as LaSuiteOIDCAuthenticationBackend,
) )
@@ -57,3 +58,22 @@ class OIDCAuthenticationBackend(LaSuiteOIDCAuthenticationBackend):
return self.UserModel.objects.get_user_by_sub_or_email(sub, email) return self.UserModel.objects.get_user_by_sub_or_email(sub, email)
except DuplicateEmailError as err: except DuplicateEmailError as err:
raise SuspiciousOperation(err.message) from err raise SuspiciousOperation(err.message) from err
def post_get_or_create_user(self, user, claims, is_new_user):
"""
Post-processing after user creation or retrieval.
Args:
user (User): The user instance.
claims (dict): The claims dictionary.
is_new_user (bool): Indicates if the user was newly created.
Returns:
- None
"""
if is_new_user and settings.SIGNUP_NEW_USER_TO_MARKETING_EMAIL:
create_or_update_contact.delay(
email=user.email, attributes={"DOCS_SOURCE": ["SIGNIN"]}
)

View File

@@ -2,6 +2,7 @@
import random import random
import re import re
from unittest import mock
from django.core.exceptions import SuspiciousOperation from django.core.exceptions import SuspiciousOperation
from django.test.utils import override_settings from django.test.utils import override_settings
@@ -12,7 +13,10 @@ from cryptography.fernet import Fernet
from lasuite.oidc_login.backends import get_oidc_refresh_token from lasuite.oidc_login.backends import get_oidc_refresh_token
from core import models from core import models
from core.authentication.backends import OIDCAuthenticationBackend from core.authentication.backends import (
OIDCAuthenticationBackend,
create_or_update_contact,
)
from core.factories import UserFactory from core.factories import UserFactory
pytestmark = pytest.mark.django_db pytestmark = pytest.mark.django_db
@@ -509,3 +513,79 @@ def test_authentication_session_tokens(
assert user is not None assert user is not None
assert request.session["oidc_access_token"] == "test-access-token" assert request.session["oidc_access_token"] == "test-access-token"
assert get_oidc_refresh_token(request.session) == "test-refresh-token" assert get_oidc_refresh_token(request.session) == "test-refresh-token"
def test_authentication_post_get_or_create_user_new_user_to_marketing_email(settings):
"""
New user and SIGNUP_NEW_USER_TO_MARKETING_EMAIL enabled should create a contact
in the marketing backend.
"""
user = UserFactory()
settings.SIGNUP_NEW_USER_TO_MARKETING_EMAIL = True
klass = OIDCAuthenticationBackend()
with mock.patch.object(
create_or_update_contact, "delay"
) as mock_create_or_update_contact:
klass.post_get_or_create_user(user, {}, True)
mock_create_or_update_contact.assert_called_once_with(
email=user.email, attributes={"DOCS_SOURCE": ["SIGNIN"]}
)
def test_authentication_post_get_or_create_user_new_user_to_marketing_email_disabled(
settings,
):
"""
New user and SIGNUP_NEW_USER_TO_MARKETING_EMAIL disabled should not create a contact
in the marketing backend.
"""
user = UserFactory()
settings.SIGNUP_NEW_USER_TO_MARKETING_EMAIL = False
klass = OIDCAuthenticationBackend()
with mock.patch.object(
create_or_update_contact, "delay"
) as mock_create_or_update_contact:
klass.post_get_or_create_user(user, {}, True)
mock_create_or_update_contact.assert_not_called()
def test_authentication_post_get_or_create_user_existing_user_to_marketing_email(
settings,
):
"""
Existing user and SIGNUP_NEW_USER_TO_MARKETING_EMAIL enabled should not create a contact
in the marketing backend.
"""
user = UserFactory()
settings.SIGNUP_NEW_USER_TO_MARKETING_EMAIL = True
klass = OIDCAuthenticationBackend()
with mock.patch.object(
create_or_update_contact, "delay"
) as mock_create_or_update_contact:
klass.post_get_or_create_user(user, {}, False)
mock_create_or_update_contact.assert_not_called()
def test_authentication_post_get_or_create_user_existing_user_to_marketing_email_disabled(
settings,
):
"""
Existing user and SIGNUP_NEW_USER_TO_MARKETING_EMAIL disabled should not create a contact
in the marketing backend.
"""
user = UserFactory()
settings.SIGNUP_NEW_USER_TO_MARKETING_EMAIL = False
klass = OIDCAuthenticationBackend()
with mock.patch.object(
create_or_update_contact, "delay"
) as mock_create_or_update_contact:
klass.post_get_or_create_user(user, {}, False)
mock_create_or_update_contact.assert_not_called()

View File

@@ -328,6 +328,7 @@ class Base(Configuration):
# OIDC third party # OIDC third party
"mozilla_django_oidc", "mozilla_django_oidc",
"lasuite.malware_detection", "lasuite.malware_detection",
"lasuite.marketing",
"csp", "csp",
] ]
@@ -808,6 +809,30 @@ class Base(Configuration):
), ),
} }
# Marketing and communication settings
SIGNUP_NEW_USER_TO_MARKETING_EMAIL = values.BooleanValue(
False,
environ_name="SIGNUP_NEW_USER_TO_MARKETING_EMAIL",
environ_prefix=None,
help_text=(
"When enabled, new users are automatically added to mailing list "
"for product updates, marketing communications, and customized emails. "
),
)
LASUITE_MARKETING = {
"BACKEND": values.Value(
"lasuite.marketing.backends.dummy.DummyBackend",
environ_name="LASUITE_MARKETING_BACKEND",
environ_prefix=None,
),
"PARAMETERS": values.DictValue(
default={},
environ_name="LASUITE_MARKETING_PARAMETERS",
environ_prefix=None,
),
}
# pylint: disable=invalid-name # pylint: disable=invalid-name
@property @property
def ENVIRONMENT(self): def ENVIRONMENT(self):

View File

@@ -34,7 +34,7 @@ dependencies = [
"django-countries==8.1.0", "django-countries==8.1.0",
"django-csp==4.0", "django-csp==4.0",
"django-filter==25.2", "django-filter==25.2",
"django-lasuite[all]==0.0.18", "django-lasuite[all]==0.0.22",
"django-parler==2.3", "django-parler==2.3",
"django-redis==6.0.0", "django-redis==6.0.0",
"django-storages[s3]==1.14.6", "django-storages[s3]==1.14.6",