-
Notifications
You must be signed in to change notification settings - Fork 3.7k
fix: seed OAuth IS_*_ENABLED flags individually instead of all-or-nothing #8740
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
base: preview
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @pytest.mark.django_db | |
| @pytest.mark.django_db | |
| def test_github_flag_set_to_one_with_db_credentials(self): | |
| """With GitHub OAuth credentials in DB, IS_GITHUB_ENABLED should be '1'.""" | |
| # Pre-create non-empty GitHub credential rows in the database (DB mode). | |
| InstanceConfiguration.objects.create( | |
| key="GITHUB_CLIENT_ID", | |
| value="github-client-id", | |
| category="AUTHENTICATION", | |
| is_encrypted=False, | |
| ) | |
| InstanceConfiguration.objects.create( | |
| key="GITHUB_CLIENT_SECRET", | |
| value="github-client-secret", | |
| category="AUTHENTICATION", | |
| is_encrypted=False, | |
| ) | |
| out = StringIO() | |
| # SKIP_ENV_VAR defaults to True, so the command should read credentials from DB. | |
| 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", "IS_GITHUB_ENABLED should be '1' when DB credentials are present" | |
| @pytest.mark.django_db |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
IS_GITEA_ENABLEDentry inoauth_enabled_keysis redundant and its lambda will never actually be used to create a row. BecauseIS_GITEA_ENABLEDis already included ingitea_config_variablesinsideinstance_config_variables(seecore.pyline 116), it is always seeded by the firstget_or_createloop (lines 29–41). By the time the second loop reachesIS_GITEA_ENABLED, the row already exists, soget_or_createwill never call the lambda or create anything — it will always take theelsebranch and print "configuration already exists".The
IS_GITEA_ENABLEDentry should be removed fromoauth_enabled_keysto avoid confusion. If the seeding logic forIS_GITEA_ENABLEDshould instead mirror the credential-check approach used for the other three providers (checkingGITEA_HOST,GITEA_CLIENT_ID, andGITEA_CLIENT_SECRET), then its entry ingitea_config_variablesincore.pyshould be removed, and the oauth_enabled_keys entry kept. The current state creates a misleading impression that all four keys are handled consistently whenIS_GITEA_ENABLEDactually follows a completely different seeding path.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. Removed
IS_GITEA_ENABLEDfromgitea_config_variablesincore.pyso all four flags are now handled consistently inconfigure_instance.pywith categoryAUTHENTICATION. Also added a test (test_all_enabled_flags_use_authentication_category) that verifies this consistency.