diff --git a/apps/api/plane/license/management/commands/configure_instance.py b/apps/api/plane/license/management/commands/configure_instance.py index 43026a45543..59fbc3335c6 100644 --- a/apps/api/plane/license/management/commands/configure_instance.py +++ b/apps/api/plane/license/management/commands/configure_instance.py @@ -40,113 +40,55 @@ def handle(self, *args, **options): else: self.stdout.write(self.style.WARNING(f"{obj.key} configuration already exists")) - keys = ["IS_GOOGLE_ENABLED", "IS_GITHUB_ENABLED", "IS_GITLAB_ENABLED", "IS_GITEA_ENABLED"] - if not InstanceConfiguration.objects.filter(key__in=keys).exists(): - for key in keys: - if key == "IS_GOOGLE_ENABLED": - GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET = get_configuration_value( - [ - { - "key": "GOOGLE_CLIENT_ID", - "default": os.environ.get("GOOGLE_CLIENT_ID", ""), - }, - { - "key": "GOOGLE_CLIENT_SECRET", - "default": os.environ.get("GOOGLE_CLIENT_SECRET", "0"), - }, - ] - ) - if bool(GOOGLE_CLIENT_ID) and bool(GOOGLE_CLIENT_SECRET): - value = "1" - else: - value = "0" - InstanceConfiguration.objects.create( - key=key, - value=value, - category="AUTHENTICATION", - is_encrypted=False, - ) - self.stdout.write(self.style.SUCCESS(f"{key} loaded with value from environment variable.")) - if key == "IS_GITHUB_ENABLED": - GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET = get_configuration_value( - [ - { - "key": "GITHUB_CLIENT_ID", - "default": os.environ.get("GITHUB_CLIENT_ID", ""), - }, - { - "key": "GITHUB_CLIENT_SECRET", - "default": os.environ.get("GITHUB_CLIENT_SECRET", "0"), - }, - ] - ) - if bool(GITHUB_CLIENT_ID) and bool(GITHUB_CLIENT_SECRET): - value = "1" - else: - value = "0" - InstanceConfiguration.objects.create( - key="IS_GITHUB_ENABLED", - value=value, - category="AUTHENTICATION", - is_encrypted=False, - ) - self.stdout.write(self.style.SUCCESS(f"{key} loaded with value from environment variable.")) - if key == "IS_GITLAB_ENABLED": - GITLAB_HOST, GITLAB_CLIENT_ID, GITLAB_CLIENT_SECRET = get_configuration_value( - [ - { - "key": "GITLAB_HOST", - "default": os.environ.get("GITLAB_HOST", "https://gitlab.com"), - }, - { - "key": "GITLAB_CLIENT_ID", - "default": os.environ.get("GITLAB_CLIENT_ID", ""), - }, - { - "key": "GITLAB_CLIENT_SECRET", - "default": os.environ.get("GITLAB_CLIENT_SECRET", ""), - }, - ] - ) - if bool(GITLAB_HOST) and bool(GITLAB_CLIENT_ID) and bool(GITLAB_CLIENT_SECRET): - value = "1" - else: - value = "0" - InstanceConfiguration.objects.create( - key="IS_GITLAB_ENABLED", - value=value, - category="AUTHENTICATION", - is_encrypted=False, - ) - self.stdout.write(self.style.SUCCESS(f"{key} loaded with value from environment variable.")) - if key == "IS_GITEA_ENABLED": - GITEA_HOST, GITEA_CLIENT_ID, GITEA_CLIENT_SECRET = get_configuration_value( - [ - { - "key": "GITEA_HOST", - "default": os.environ.get("GITEA_HOST", ""), - }, - { - "key": "GITEA_CLIENT_ID", - "default": os.environ.get("GITEA_CLIENT_ID", ""), - }, - { - "key": "GITEA_CLIENT_SECRET", - "default": os.environ.get("GITEA_CLIENT_SECRET", ""), - }, - ] - ) - if bool(GITEA_HOST) and bool(GITEA_CLIENT_ID) and bool(GITEA_CLIENT_SECRET): - value = "1" - else: - value = "0" - InstanceConfiguration.objects.create( - key="IS_GITEA_ENABLED", - value=value, - category="AUTHENTICATION", - is_encrypted=False, - ) - self.stdout.write(self.style.SUCCESS(f"{key} loaded with value from environment variable.")) - else: - for key in keys: + # Seed IS_*_ENABLED flags individually using get_or_create so that + # each missing key is created independently of the others. + oauth_enabled_keys = { + "IS_GOOGLE_ENABLED": lambda: all( + get_configuration_value( + [ + {"key": "GOOGLE_CLIENT_ID", "default": os.environ.get("GOOGLE_CLIENT_ID", "")}, + {"key": "GOOGLE_CLIENT_SECRET", "default": os.environ.get("GOOGLE_CLIENT_SECRET", "")}, + ] + ) + ), + "IS_GITHUB_ENABLED": lambda: all( + get_configuration_value( + [ + {"key": "GITHUB_CLIENT_ID", "default": os.environ.get("GITHUB_CLIENT_ID", "")}, + {"key": "GITHUB_CLIENT_SECRET", "default": os.environ.get("GITHUB_CLIENT_SECRET", "")}, + ] + ) + ), + "IS_GITLAB_ENABLED": lambda: all( + get_configuration_value( + [ + {"key": "GITLAB_HOST", "default": os.environ.get("GITLAB_HOST", "")}, + {"key": "GITLAB_CLIENT_ID", "default": os.environ.get("GITLAB_CLIENT_ID", "")}, + {"key": "GITLAB_CLIENT_SECRET", "default": os.environ.get("GITLAB_CLIENT_SECRET", "")}, + ] + ) + ), + "IS_GITEA_ENABLED": lambda: all( + get_configuration_value( + [ + {"key": "GITEA_HOST", "default": os.environ.get("GITEA_HOST", "")}, + {"key": "GITEA_CLIENT_ID", "default": os.environ.get("GITEA_CLIENT_ID", "")}, + {"key": "GITEA_CLIENT_SECRET", "default": os.environ.get("GITEA_CLIENT_SECRET", "")}, + ] + ) + ), + } + + for key, check_configured in oauth_enabled_keys.items(): + obj, created = InstanceConfiguration.objects.get_or_create( + key=key, + defaults={ + "value": "1" if check_configured() else "0", + "category": "AUTHENTICATION", + "is_encrypted": False, + }, + ) + if created: + self.stdout.write(self.style.SUCCESS(f"{key} loaded with value from environment variable.")) + else: self.stdout.write(self.style.WARNING(f"{key} configuration already exists")) diff --git a/apps/api/plane/tests/unit/management/__init__.py b/apps/api/plane/tests/unit/management/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/apps/api/plane/tests/unit/management/test_configure_instance.py b/apps/api/plane/tests/unit/management/test_configure_instance.py new file mode 100644 index 00000000000..ca0ea21a48e --- /dev/null +++ b/apps/api/plane/tests/unit/management/test_configure_instance.py @@ -0,0 +1,117 @@ +# Copyright (c) 2023-present Plane Software, Inc. and contributors +# SPDX-License-Identifier: AGPL-3.0-only +# See the LICENSE file for details. + +import pytest +from io import StringIO +from unittest.mock import patch + +from django.core.management import call_command + +from plane.license.models import InstanceConfiguration + + +@pytest.mark.unit +class TestConfigureInstanceOAuthSeeding: + """Test that configure_instance seeds IS_*_ENABLED flags individually.""" + + @pytest.mark.django_db + def test_creates_all_enabled_flags_when_none_exist(self): + """All four IS_*_ENABLED flags should be created on a fresh database.""" + out = StringIO() + with patch.dict("os.environ", {"SECRET_KEY": "test-secret"}): + call_command("configure_instance", stdout=out) + + keys = [ + "IS_GOOGLE_ENABLED", + "IS_GITHUB_ENABLED", + "IS_GITLAB_ENABLED", + "IS_GITEA_ENABLED", + ] + for key in keys: + assert InstanceConfiguration.objects.filter(key=key).exists(), ( + f"{key} should have been created" + ) + + @pytest.mark.django_db + def test_creates_missing_flags_when_some_already_exist(self): + """Missing IS_*_ENABLED flags should be created even if others already exist. + + Each flag uses get_or_create so the presence of one should never + prevent creation of the others. + """ + InstanceConfiguration.objects.create( + key="IS_GITHUB_ENABLED", + value="1", + category="AUTHENTICATION", + is_encrypted=False, + ) + InstanceConfiguration.objects.create( + key="IS_GITEA_ENABLED", + value="0", + category="AUTHENTICATION", + is_encrypted=False, + ) + + out = StringIO() + with patch.dict("os.environ", {"SECRET_KEY": "test-secret"}): + call_command("configure_instance", stdout=out) + + for key in ["IS_GOOGLE_ENABLED", "IS_GITLAB_ENABLED"]: + assert InstanceConfiguration.objects.filter(key=key).exists(), ( + f"{key} should have been created even though other IS_*_ENABLED flags already existed" + ) + + @pytest.mark.django_db + def test_does_not_overwrite_existing_enabled_flags(self): + """Existing IS_*_ENABLED values should not be overwritten on re-run.""" + InstanceConfiguration.objects.create( + key="IS_GITHUB_ENABLED", + value="1", + category="AUTHENTICATION", + is_encrypted=False, + ) + + out = StringIO() + with patch.dict("os.environ", {"SECRET_KEY": "test-secret"}): + call_command("configure_instance", stdout=out) + + config = InstanceConfiguration.objects.get(key="IS_GITHUB_ENABLED") + assert config.value == "1", "Existing IS_GITHUB_ENABLED=1 should not be overwritten" + + @pytest.mark.django_db + def test_enabled_flags_default_to_zero_without_credentials(self): + """Without OAuth env vars, IS_*_ENABLED flags should default to '0'.""" + out = StringIO() + env = { + "SECRET_KEY": "test-secret", + "GITHUB_CLIENT_ID": "", + "GITHUB_CLIENT_SECRET": "", + "GOOGLE_CLIENT_ID": "", + "GOOGLE_CLIENT_SECRET": "", + "GITLAB_HOST": "", + "GITLAB_CLIENT_ID": "", + "GITLAB_CLIENT_SECRET": "", + "GITEA_HOST": "", + "GITEA_CLIENT_ID": "", + "GITEA_CLIENT_SECRET": "", + } + with patch.dict("os.environ", env): + call_command("configure_instance", stdout=out) + + for key in ["IS_GOOGLE_ENABLED", "IS_GITHUB_ENABLED", "IS_GITLAB_ENABLED", "IS_GITEA_ENABLED"]: + config = InstanceConfiguration.objects.get(key=key) + assert config.value == "0", f"{key} should be '0' without credentials" + + @pytest.mark.django_db + def test_all_enabled_flags_use_authentication_category(self): + """All IS_*_ENABLED flags should be created with category AUTHENTICATION.""" + out = StringIO() + with patch.dict("os.environ", {"SECRET_KEY": "test-secret"}): + call_command("configure_instance", stdout=out) + + for key in ["IS_GOOGLE_ENABLED", "IS_GITHUB_ENABLED", "IS_GITLAB_ENABLED", "IS_GITEA_ENABLED"]: + config = InstanceConfiguration.objects.get(key=key) + assert config.category == "AUTHENTICATION", ( + f"{key} should have category AUTHENTICATION, got {config.category}" + ) diff --git a/apps/api/plane/utils/instance_config_variables/core.py b/apps/api/plane/utils/instance_config_variables/core.py index 274c6539af9..0c8bdd54173 100644 --- a/apps/api/plane/utils/instance_config_variables/core.py +++ b/apps/api/plane/utils/instance_config_variables/core.py @@ -112,12 +112,6 @@ ] gitea_config_variables = [ - { - "key": "IS_GITEA_ENABLED", - "value": os.environ.get("IS_GITEA_ENABLED", "0"), - "category": "GITEA", - "is_encrypted": False, - }, { "key": "GITEA_HOST", "value": os.environ.get("GITEA_HOST"),