diff --git a/CHANGELOG.md b/CHANGELOG.md index 65a3e038fc..018562f8a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to ### Added - ⚡️(frontend) export html #1669 +- ✨(backend) allow to create a new user in a marketing system ### Changed diff --git a/docs/env.md b/docs/env.md index 0b3f9b3bf6..7c7ee85dc3 100644 --- a/docs/env.md +++ b/docs/env.md @@ -6,103 +6,106 @@ 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. -| Option | Description | default | -|-------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------| -| 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_BASE_URL | OpenAI compatible AI base url | | -| AI_FEATURE_ENABLED | Enable AI options | false | -| AI_MODEL | AI Model to use | | -| ALLOW_LOGOUT_GET_METHOD | Allow get logout method | true | -| API_USERS_LIST_LIMIT | Limit on API users | 5 | -| API_USERS_LIST_THROTTLE_RATE_BURST | Throttle rate for api on burst | 30/minute | -| API_USERS_LIST_THROTTLE_RATE_SUSTAINED | Throttle rate for api | 180/hour | -| AWS_S3_ACCESS_KEY_ID | Access id for s3 endpoint | | -| AWS_S3_ENDPOINT_URL | S3 endpoint | | -| AWS_S3_REGION_NAME | Region name for s3 endpoint | | -| AWS_S3_SECRET_ACCESS_KEY | Access key for s3 endpoint | | -| AWS_STORAGE_BUCKET_NAME | Bucket name for s3 endpoint | impress-media-storage | -| CACHES_DEFAULT_TIMEOUT | Cache default timeout | 30 | -| CACHES_KEY_PREFIX | The prefix used to every cache keys. | docs | -| COLLABORATION_API_URL | Collaboration api host | | -| COLLABORATION_SERVER_SECRET | Collaboration api secret | | -| COLLABORATION_WS_NOT_CONNECTED_READY_ONLY | Users not connected to the collaboration server cannot edit | false | -| COLLABORATION_WS_URL | Collaboration websocket url | | -| CONVERSION_API_CONTENT_FIELD | Conversion api content field | content | -| CONVERSION_API_ENDPOINT | Conversion API endpoint | convert | -| CONVERSION_API_SECURE | Require secure conversion api | false | -| CONVERSION_API_TIMEOUT | Conversion api timeout | 30 | -| CRISP_WEBSITE_ID | Crisp website id for support | | -| DB_ENGINE | Engine to use for database connections | django.db.backends.postgresql_psycopg2 | -| DB_HOST | Host of the database | localhost | -| DB_NAME | Name of the database | impress | -| DB_PASSWORD | Password to authenticate with | pass | -| DB_PORT | Port of the database | 5432 | -| DB_USER | User to authenticate with | dinum | -| DJANGO_ALLOWED_HOSTS | Allowed hosts | [] | -| DJANGO_CELERY_BROKER_TRANSPORT_OPTIONS | Celery broker transport options | {} | -| DJANGO_CELERY_BROKER_URL | Celery broker url | redis://redis:6379/0 | -| DJANGO_CORS_ALLOW_ALL_ORIGINS | Allow all CORS origins | false | -| DJANGO_CORS_ALLOWED_ORIGIN_REGEXES | List of origins allowed for CORS using regulair expressions | [] | -| DJANGO_CORS_ALLOWED_ORIGINS | List of origins allowed for CORS | [] | -| DJANGO_CSRF_TRUSTED_ORIGINS | CSRF trusted origins | [] | -| DJANGO_EMAIL_BACKEND | Email backend library | django.core.mail.backends.smtp.EmailBackend | -| DJANGO_EMAIL_BRAND_NAME | Brand name for email | | -| DJANGO_EMAIL_FROM | Email address used as sender | from@example.com | -| DJANGO_EMAIL_HOST | Hostname of email | | -| DJANGO_EMAIL_HOST_PASSWORD | Password to authenticate with on the email host | | -| DJANGO_EMAIL_HOST_USER | User to authenticate with on the email host | | -| DJANGO_EMAIL_LOGO_IMG | Logo for the email | | -| DJANGO_EMAIL_PORT | Port used to connect to email host | | -| DJANGO_EMAIL_USE_SSL | Use ssl for email host connection | false | -| DJANGO_EMAIL_USE_TLS | Use tls for email host connection | false | -| DJANGO_SECRET_KEY | Secret key | | -| DJANGO_SERVER_TO_SERVER_API_TOKENS | | [] | -| DOCUMENT_IMAGE_MAX_SIZE | Maximum size of document in bytes | 10485760 | -| FRONTEND_CSS_URL | To add a external css file to the app | | -| FRONTEND_HOMEPAGE_FEATURE_ENABLED | Frontend feature flag to display the homepage | false | -| FRONTEND_THEME | Frontend theme to use | | -| LANGUAGE_CODE | Default language | en-us | -| 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 | -| LOGIN_REDIRECT_URL | Login redirect url | | -| LOGIN_REDIRECT_URL_FAILURE | Login redirect url on failure | | -| LOGOUT_REDIRECT_URL | Logout redirect url | | -| MALWARE_DETECTION_BACKEND | The malware detection backend use from the django-lasuite package | lasuite.malware_detection.backends.dummy.DummyBackend | -| MALWARE_DETECTION_PARAMETERS | A dict containing all the parameters to initiate the malware detection backend | {"callback_path": "core.malware_detection.malware_detection_callback",} | -| MEDIA_BASE_URL | | | -| NO_WEBSOCKET_CACHE_TIMEOUT | Cache used to store current editor session key when only users without websocket are editing a document | 120 | -| OIDC_ALLOW_DUPLICATE_EMAILS | Allow duplicate emails | false | -| OIDC_AUTH_REQUEST_EXTRA_PARAMS | OIDC extra auth parameters | {} | -| OIDC_CREATE_USER | Create used on OIDC | false | -| OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION | Fallback to email for identification | true | -| OIDC_OP_AUTHORIZATION_ENDPOINT | Authorization endpoint for OIDC | | -| OIDC_OP_JWKS_ENDPOINT | JWKS endpoint for OIDC | | -| OIDC_OP_LOGOUT_ENDPOINT | Logout endpoint for OIDC | | -| OIDC_OP_TOKEN_ENDPOINT | Token endpoint for OIDC | | -| OIDC_OP_USER_ENDPOINT | User endpoint for OIDC | | -| OIDC_REDIRECT_ALLOWED_HOSTS | Allowed hosts for OIDC redirect url | [] | -| OIDC_REDIRECT_REQUIRE_HTTPS | Require https for OIDC redirect url | false | -| OIDC_RP_CLIENT_ID | Client id used for OIDC | impress | -| OIDC_RP_CLIENT_SECRET | Client secret used for OIDC | | -| OIDC_RP_SCOPES | Scopes requested for OIDC | openid email | -| OIDC_RP_SIGN_ALGO | verification algorithm used OIDC tokens | RS256 | -| OIDC_STORE_ID_TOKEN | Store OIDC token | true | -| OIDC_USE_NONCE | Use nonce for OIDC | true | -| OIDC_USERINFO_FULLNAME_FIELDS | OIDC token claims to create full name | ["first_name", "last_name"] | -| OIDC_USERINFO_SHORTNAME_FIELD | OIDC token claims to create shortname | first_name | -| POSTHOG_KEY | Posthog key for analytics | | -| REDIS_URL | Cache url | redis://redis:6379/1 | -| SENTRY_DSN | Sentry host | | -| SESSION_COOKIE_AGE | duration of the cookie session | 60*60*12 | -| SPECTACULAR_SETTINGS_ENABLE_DJANGO_DEPLOY_CHECK | | false | -| STORAGES_STATICFILES_BACKEND | | whitenoise.storage.CompressedManifestStaticFilesStorage | -| THEME_CUSTOMIZATION_CACHE_TIMEOUT | Cache duration for the customization settings | 86400 | -| THEME_CUSTOMIZATION_FILE_PATH | Full path to the file customizing the theme. An example is provided in src/backend/impress/configuration/theme/default.json | BASE_DIR/impress/configuration/theme/default.json | -| TRASHBIN_CUTOFF_DAYS | Trashbin cutoff | 30 | -| USER_OIDC_ESSENTIAL_CLAIMS | Essential claims in OIDC token | [] | -| Y_PROVIDER_API_BASE_URL | Y Provider url | | -| Y_PROVIDER_API_KEY | Y provider API key | | +| Option | Description | default | +|-------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------| +| 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_BASE_URL | OpenAI compatible AI base url | | +| AI_FEATURE_ENABLED | Enable AI options | false | +| AI_MODEL | AI Model to use | | +| ALLOW_LOGOUT_GET_METHOD | Allow get logout method | true | +| API_USERS_LIST_LIMIT | Limit on API users | 5 | +| API_USERS_LIST_THROTTLE_RATE_BURST | Throttle rate for api on burst | 30/minute | +| API_USERS_LIST_THROTTLE_RATE_SUSTAINED | Throttle rate for api | 180/hour | +| AWS_S3_ACCESS_KEY_ID | Access id for s3 endpoint | | +| AWS_S3_ENDPOINT_URL | S3 endpoint | | +| AWS_S3_REGION_NAME | Region name for s3 endpoint | | +| AWS_S3_SECRET_ACCESS_KEY | Access key for s3 endpoint | | +| AWS_STORAGE_BUCKET_NAME | Bucket name for s3 endpoint | impress-media-storage | +| CACHES_DEFAULT_TIMEOUT | Cache default timeout | 30 | +| CACHES_KEY_PREFIX | The prefix used to every cache keys. | docs | +| COLLABORATION_API_URL | Collaboration api host | | +| COLLABORATION_SERVER_SECRET | Collaboration api secret | | +| COLLABORATION_WS_NOT_CONNECTED_READY_ONLY | Users not connected to the collaboration server cannot edit | false | +| COLLABORATION_WS_URL | Collaboration websocket url | | +| CONVERSION_API_CONTENT_FIELD | Conversion api content field | content | +| CONVERSION_API_ENDPOINT | Conversion API endpoint | convert | +| CONVERSION_API_SECURE | Require secure conversion api | false | +| CONVERSION_API_TIMEOUT | Conversion api timeout | 30 | +| CRISP_WEBSITE_ID | Crisp website id for support | | +| DB_ENGINE | Engine to use for database connections | django.db.backends.postgresql_psycopg2 | +| DB_HOST | Host of the database | localhost | +| DB_NAME | Name of the database | impress | +| DB_PASSWORD | Password to authenticate with | pass | +| DB_PORT | Port of the database | 5432 | +| DB_USER | User to authenticate with | dinum | +| DJANGO_ALLOWED_HOSTS | Allowed hosts | [] | +| DJANGO_CELERY_BROKER_TRANSPORT_OPTIONS | Celery broker transport options | {} | +| DJANGO_CELERY_BROKER_URL | Celery broker url | redis://redis:6379/0 | +| DJANGO_CORS_ALLOW_ALL_ORIGINS | Allow all CORS origins | false | +| DJANGO_CORS_ALLOWED_ORIGIN_REGEXES | List of origins allowed for CORS using regulair expressions | [] | +| DJANGO_CORS_ALLOWED_ORIGINS | List of origins allowed for CORS | [] | +| DJANGO_CSRF_TRUSTED_ORIGINS | CSRF trusted origins | [] | +| DJANGO_EMAIL_BACKEND | Email backend library | django.core.mail.backends.smtp.EmailBackend | +| DJANGO_EMAIL_BRAND_NAME | Brand name for email | | +| DJANGO_EMAIL_FROM | Email address used as sender | from@example.com | +| DJANGO_EMAIL_HOST | Hostname of email | | +| DJANGO_EMAIL_HOST_PASSWORD | Password to authenticate with on the email host | | +| DJANGO_EMAIL_HOST_USER | User to authenticate with on the email host | | +| DJANGO_EMAIL_LOGO_IMG | Logo for the email | | +| DJANGO_EMAIL_PORT | Port used to connect to email host | | +| DJANGO_EMAIL_USE_SSL | Use ssl for email host connection | false | +| DJANGO_EMAIL_USE_TLS | Use tls for email host connection | false | +| DJANGO_SECRET_KEY | Secret key | | +| DJANGO_SERVER_TO_SERVER_API_TOKENS | | [] | +| DOCUMENT_IMAGE_MAX_SIZE | Maximum size of document in bytes | 10485760 | +| FRONTEND_CSS_URL | To add a external css file to the app | | +| FRONTEND_HOMEPAGE_FEATURE_ENABLED | Frontend feature flag to display the homepage | false | +| FRONTEND_THEME | Frontend theme to use | | +| 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_ROOT | Default logging level. options are "DEBUG", "INFO", "WARN", "ERROR", "CRITICAL" | INFO | +| LOGIN_REDIRECT_URL | Login redirect url | | +| LOGIN_REDIRECT_URL_FAILURE | Login redirect url on failure | | +| LOGOUT_REDIRECT_URL | Logout redirect url | | +| MALWARE_DETECTION_BACKEND | The malware detection backend use from the django-lasuite package | lasuite.malware_detection.backends.dummy.DummyBackend | +| MALWARE_DETECTION_PARAMETERS | A dict containing all the parameters to initiate the malware detection backend | {"callback_path": "core.malware_detection.malware_detection_callback",} | +| MEDIA_BASE_URL | | | +| NO_WEBSOCKET_CACHE_TIMEOUT | Cache used to store current editor session key when only users without websocket are editing a document | 120 | +| OIDC_ALLOW_DUPLICATE_EMAILS | Allow duplicate emails | false | +| OIDC_AUTH_REQUEST_EXTRA_PARAMS | OIDC extra auth parameters | {} | +| OIDC_CREATE_USER | Create used on OIDC | false | +| OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION | Fallback to email for identification | true | +| OIDC_OP_AUTHORIZATION_ENDPOINT | Authorization endpoint for OIDC | | +| OIDC_OP_JWKS_ENDPOINT | JWKS endpoint for OIDC | | +| OIDC_OP_LOGOUT_ENDPOINT | Logout endpoint for OIDC | | +| OIDC_OP_TOKEN_ENDPOINT | Token endpoint for OIDC | | +| OIDC_OP_USER_ENDPOINT | User endpoint for OIDC | | +| OIDC_REDIRECT_ALLOWED_HOSTS | Allowed hosts for OIDC redirect url | [] | +| OIDC_REDIRECT_REQUIRE_HTTPS | Require https for OIDC redirect url | false | +| OIDC_RP_CLIENT_ID | Client id used for OIDC | impress | +| OIDC_RP_CLIENT_SECRET | Client secret used for OIDC | | +| OIDC_RP_SCOPES | Scopes requested for OIDC | openid email | +| OIDC_RP_SIGN_ALGO | verification algorithm used OIDC tokens | RS256 | +| OIDC_STORE_ID_TOKEN | Store OIDC token | true | +| OIDC_USE_NONCE | Use nonce for OIDC | true | +| OIDC_USERINFO_FULLNAME_FIELDS | OIDC token claims to create full name | ["first_name", "last_name"] | +| OIDC_USERINFO_SHORTNAME_FIELD | OIDC token claims to create shortname | first_name | +| POSTHOG_KEY | Posthog key for analytics | | +| REDIS_URL | Cache url | redis://redis:6379/1 | +| SENTRY_DSN | Sentry host | | +| 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 | +| STORAGES_STATICFILES_BACKEND | | whitenoise.storage.CompressedManifestStaticFilesStorage | +| THEME_CUSTOMIZATION_CACHE_TIMEOUT | Cache duration for the customization settings | 86400 | +| THEME_CUSTOMIZATION_FILE_PATH | Full path to the file customizing the theme. An example is provided in src/backend/impress/configuration/theme/default.json | BASE_DIR/impress/configuration/theme/default.json | +| TRASHBIN_CUTOFF_DAYS | Trashbin cutoff | 30 | +| USER_OIDC_ESSENTIAL_CLAIMS | Essential claims in OIDC token | [] | +| Y_PROVIDER_API_BASE_URL | Y Provider url | | +| Y_PROVIDER_API_KEY | Y provider API key | | ## impress-frontend image diff --git a/src/backend/core/authentication/backends.py b/src/backend/core/authentication/backends.py index 4ea10718b0..898bd12e4a 100644 --- a/src/backend/core/authentication/backends.py +++ b/src/backend/core/authentication/backends.py @@ -6,6 +6,7 @@ from django.conf import settings from django.core.exceptions import SuspiciousOperation +from lasuite.marketing.tasks import create_or_update_contact from lasuite.oidc_login.backends import ( OIDCAuthenticationBackend as LaSuiteOIDCAuthenticationBackend, ) @@ -57,3 +58,22 @@ def get_existing_user(self, sub, email): return self.UserModel.objects.get_user_by_sub_or_email(sub, email) except DuplicateEmailError as 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"]} + ) diff --git a/src/backend/core/tests/authentication/test_backends.py b/src/backend/core/tests/authentication/test_backends.py index b2f1f92f96..94e898e548 100644 --- a/src/backend/core/tests/authentication/test_backends.py +++ b/src/backend/core/tests/authentication/test_backends.py @@ -2,6 +2,7 @@ import random import re +from unittest import mock from django.core.exceptions import SuspiciousOperation from django.test.utils import override_settings @@ -12,7 +13,10 @@ from lasuite.oidc_login.backends import get_oidc_refresh_token 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 pytestmark = pytest.mark.django_db @@ -509,3 +513,79 @@ def verify_token_mocked(*args, **kwargs): assert user is not None assert request.session["oidc_access_token"] == "test-access-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() diff --git a/src/backend/impress/settings.py b/src/backend/impress/settings.py index 2229036c8a..98725edda6 100755 --- a/src/backend/impress/settings.py +++ b/src/backend/impress/settings.py @@ -328,6 +328,7 @@ class Base(Configuration): # OIDC third party "mozilla_django_oidc", "lasuite.malware_detection", + "lasuite.marketing", "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 @property def ENVIRONMENT(self): diff --git a/src/backend/pyproject.toml b/src/backend/pyproject.toml index ff0c5b0249..fc6e9bfaa1 100644 --- a/src/backend/pyproject.toml +++ b/src/backend/pyproject.toml @@ -34,7 +34,7 @@ dependencies = [ "django-countries==8.1.0", "django-csp==4.0", "django-filter==25.2", - "django-lasuite[all]==0.0.18", + "django-lasuite[all]==0.0.22", "django-parler==2.3", "django-redis==6.0.0", "django-storages[s3]==1.14.6",