From ef1fe5ead3d080c6b35cca8970c4df001ba7fd5c Mon Sep 17 00:00:00 2001 From: Tiago Fonseca Date: Mon, 20 Jan 2025 18:16:11 -0300 Subject: [PATCH] fix: Disable invite button when email config is not set --- api/app/utils.py | 19 +++++ api/tests/unit/app/test_unit_app_utils.py | 70 +++++++++++++++++++ api/tests/unit/app/test_unit_app_views.py | 1 + frontend/common/utils/utils.tsx | 2 + .../pages/UsersAndPermissionsPage.tsx | 14 +++- 5 files changed, 103 insertions(+), 3 deletions(-) diff --git a/api/app/utils.py b/api/app/utils.py index 0b3fa4fa72f2..c414a1c79f8e 100644 --- a/api/app/utils.py +++ b/api/app/utils.py @@ -4,6 +4,12 @@ from typing import TypedDict import shortuuid +from app.settings.common import ( + EMAIL_BACKEND, + EMAIL_HOST_USER, + SENDGRID_API_KEY, + AWS_SES_REGION_ENDPOINT, +) UNKNOWN = "unknown" VERSIONS_INFO_FILE_LOCATION = ".versions.json" @@ -12,6 +18,7 @@ class VersionInfo(TypedDict): ci_commit_sha: str image_tag: str + has_email_provider: bool is_enterprise: bool is_saas: bool @@ -29,6 +36,17 @@ def is_saas() -> bool: return pathlib.Path("./SAAS_DEPLOYMENT").exists() +def has_email_provider() -> bool: + match EMAIL_BACKEND: + case "django.core.mail.backends.smtp.EmailBackend": + return EMAIL_HOST_USER is not None + case "sgbackend.SendGridBackend": + return SENDGRID_API_KEY is not None + case "django_ses.SESBackend": + return AWS_SES_REGION_ENDPOINT is not None + case _: + return False + @lru_cache def get_version_info() -> VersionInfo: """Reads the version info baked into src folder of the docker container""" @@ -45,6 +63,7 @@ def get_version_info() -> VersionInfo: version_json = version_json | { "ci_commit_sha": _get_file_contents("./CI_COMMIT_SHA"), "image_tag": image_tag, + "has_email_provider": has_email_provider(), "is_enterprise": is_enterprise(), "is_saas": is_saas(), } diff --git a/api/tests/unit/app/test_unit_app_utils.py b/api/tests/unit/app/test_unit_app_utils.py index 2e5ac6556be7..4d998bf8c64c 100644 --- a/api/tests/unit/app/test_unit_app_utils.py +++ b/api/tests/unit/app/test_unit_app_utils.py @@ -45,6 +45,7 @@ def path_side_effect(file_path: str) -> mocker.MagicMock: assert result == { "ci_commit_sha": "some_sha", "image_tag": "2.66.2", + "has_email_provider": False, "is_enterprise": True, "is_saas": False, "package_versions": {".": "2.66.2"}, @@ -76,6 +77,75 @@ def path_side_effect(file_path: str) -> mocker.MagicMock: assert result == { "ci_commit_sha": "unknown", "image_tag": "unknown", + "has_email_provider": False, "is_enterprise": True, "is_saas": False, } + + +def test_get_version_info_with_email_config_smtp(mocker: MockerFixture) -> None: + + mocker.patch( + "app.utils.EMAIL_BACKEND", "django.core.mail.backends.smtp.EmailBackend" + ) + mocker.patch("app.utils.EMAIL_HOST_USER", "user") + # When + result = get_version_info() + + # Then + assert result == { + "ci_commit_sha": "unknown", + "image_tag": "unknown", + "has_email_provider": True, + "is_enterprise": False, + "is_saas": False, + } + + +def test_get_version_info_with_email_config_sendgrid(mocker: MockerFixture) -> None: + + mocker.patch("app.utils.EMAIL_BACKEND", "sgbackend.SendGridBackend") + mocker.patch("app.utils.SENDGRID_API_KEY", "key") + # When + result = get_version_info() + + # Then + assert result == { + "ci_commit_sha": "unknown", + "image_tag": "unknown", + "has_email_provider": True, + "is_enterprise": False, + "is_saas": False, + } + + +def test_get_version_info_with_email_config_ses(mocker: MockerFixture) -> None: + + mocker.patch("app.utils.EMAIL_BACKEND", "django_ses.SESBackend") + mocker.patch("app.utils.AWS_SES_REGION_ENDPOINT", "endpoint") + # When + result = get_version_info() + + # Then + assert result == { + "ci_commit_sha": "unknown", + "image_tag": "unknown", + "has_email_provider": True, + "is_enterprise": False, + "is_saas": False, + } + + +def test_get_version_info_without_email_config(mocker: MockerFixture) -> None: + + # When + result = get_version_info() + + # Then + assert result == { + "ci_commit_sha": "unknown", + "image_tag": "unknown", + "has_email_provider": False, + "is_enterprise": False, + "is_saas": False, + } diff --git a/api/tests/unit/app/test_unit_app_views.py b/api/tests/unit/app/test_unit_app_views.py index fce9bae82f4f..0b43f8b760fe 100644 --- a/api/tests/unit/app/test_unit_app_views.py +++ b/api/tests/unit/app/test_unit_app_views.py @@ -15,6 +15,7 @@ def test_get_version_info(api_client: APIClient) -> None: assert response.json() == { "ci_commit_sha": "unknown", "image_tag": "unknown", + "has_email_provider": False, "is_enterprise": False, "is_saas": False, } diff --git a/frontend/common/utils/utils.tsx b/frontend/common/utils/utils.tsx index d0caa977df55..f3d67dc0c1fa 100644 --- a/frontend/common/utils/utils.tsx +++ b/frontend/common/utils/utils.tsx @@ -552,6 +552,8 @@ const Utils = Object.assign({}, require('./base/_utils'), { getViewIdentitiesPermission() { return 'VIEW_IDENTITIES' }, + hasEmailProvider: () => + global.flagsmithVersion?.backend?.has_email_provider ?? false, isEnterpriseImage: () => global.flagsmithVersion?.backend.is_enterprise, isMigrating() { const model = ProjectStore.model as null | ProjectType diff --git a/frontend/web/components/pages/UsersAndPermissionsPage.tsx b/frontend/web/components/pages/UsersAndPermissionsPage.tsx index dbe521f29b1c..de419cec6f75 100644 --- a/frontend/web/components/pages/UsersAndPermissionsPage.tsx +++ b/frontend/web/components/pages/UsersAndPermissionsPage.tsx @@ -45,6 +45,7 @@ type UsersAndPermissionsPageType = { } const widths = [300, 200, 80] +const noEmailProvider = `You must configure an email provider before using email invites. Please read our documentation on how to configure an email provider.` type UsersAndPermissionsInnerType = { organisation: Organisation @@ -69,11 +70,11 @@ const UsersAndPermissionsInner: FC = ({ subscriptionMeta, users, }) => { - const paymentsEnabled = Utils.getFlagsmithHasFeature('payments_enabled') const verifySeatsLimit = Utils.getFlagsmithHasFeature( 'verify_seats_limit_for_invite_links', ) + const hasEmailProvider = Utils.hasEmailProvider() const manageUsersPermission = useHasPermission({ id: AccountStore.getOrganisation()?.id, level: 'organisation', @@ -85,6 +86,12 @@ const UsersAndPermissionsInner: FC = ({ permission: 'MANAGE_USER_GROUPS', }) + const hasInvitePermission = + hasEmailProvider && manageUsersPermission.permission + const tooltTipText = !hasEmailProvider + ? noEmailProvider + : Constants.organisationPermissions('Admin') + const roleChanged = (id: number, { value: role }: { value: string }) => { AppActions.updateUserRole(id, role) } @@ -230,10 +237,11 @@ const UsersAndPermissionsInner: FC = ({
Team Members
{Utils.renderWithPermission( - !manageUsersPermission.permission, - Constants.organisationPermissions('Admin'), + hasInvitePermission, + tooltTipText,