Skip to content

Commit 058f7a2

Browse files
committed
fix: strip whitespace from instance configuration values on save
Trailing whitespace in OAuth credentials (e.g. GITHUB_CLIENT_ID) causes authentication failures. When a user accidentally pastes a value with leading/trailing spaces into the God Mode admin UI, the spaces are persisted to the database as-is. For OAuth client IDs this results in the identity provider rejecting the request (e.g. GitHub returns 404 because the client_id has a URL-encoded space appended). Strip whitespace from all configuration values before persisting them in InstanceConfigurationEndpoint.patch(). Signed-off-by: Mikhail Malyshev <mike.malyshev@gmail.com>
1 parent 6627282 commit 058f7a2

3 files changed

Lines changed: 135 additions & 1 deletion

File tree

apps/api/plane/license/api/views/configuration.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ def patch(self, request):
4545

4646
bulk_configurations = []
4747
for configuration in configurations:
48-
value = request.data.get(configuration.key, configuration.value)
48+
value = str(
49+
request.data.get(configuration.key, configuration.value)
50+
).strip()
4951
if configuration.is_encrypted:
5052
configuration.value = encrypt_data(value)
5153
else:

apps/api/plane/tests/unit/views/__init__.py

Whitespace-only changes.
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# Copyright (c) 2023-present Plane Software, Inc. and contributors
2+
# SPDX-License-Identifier: AGPL-3.0-only
3+
# See the LICENSE file for details.
4+
5+
import pytest
6+
from django.utils import timezone
7+
8+
from rest_framework.test import APIClient
9+
10+
from plane.db.models import User
11+
from plane.license.models import Instance, InstanceAdmin, InstanceConfiguration
12+
13+
14+
@pytest.fixture
15+
def instance_admin(db):
16+
"""Create an instance, an admin user, and link them via InstanceAdmin."""
17+
user = User.objects.create(
18+
email="admin@plane.so",
19+
first_name="Admin",
20+
last_name="User",
21+
)
22+
user.set_password("admin-password")
23+
user.save()
24+
25+
instance = Instance.objects.create(
26+
instance_name="test",
27+
instance_id="test-instance-id",
28+
current_version="1.2.3",
29+
last_checked_at=timezone.now(),
30+
is_setup_done=True,
31+
)
32+
33+
InstanceAdmin.objects.create(
34+
instance=instance,
35+
user=user,
36+
role=20,
37+
)
38+
39+
return user
40+
41+
42+
@pytest.fixture
43+
def admin_client(instance_admin):
44+
"""Return an authenticated API client for the instance admin."""
45+
client = APIClient()
46+
client.force_authenticate(user=instance_admin)
47+
return client
48+
49+
50+
@pytest.mark.unit
51+
class TestInstanceConfigurationWhitespaceTrimming:
52+
"""Test that instance configuration values are trimmed on save."""
53+
54+
@pytest.mark.django_db
55+
def test_patch_strips_trailing_whitespace(self, admin_client):
56+
"""Values with trailing whitespace should be stripped when saved."""
57+
InstanceConfiguration.objects.create(
58+
key="GITHUB_CLIENT_ID",
59+
value="",
60+
category="GITHUB",
61+
is_encrypted=False,
62+
)
63+
64+
response = admin_client.patch(
65+
"/api/instances/configurations/",
66+
{"GITHUB_CLIENT_ID": "Ov23li2Dep2t79q18nxD "},
67+
format="json",
68+
)
69+
70+
assert response.status_code == 200
71+
config = InstanceConfiguration.objects.get(key="GITHUB_CLIENT_ID")
72+
assert config.value == "Ov23li2Dep2t79q18nxD"
73+
74+
@pytest.mark.django_db
75+
def test_patch_strips_leading_whitespace(self, admin_client):
76+
"""Values with leading whitespace should be stripped when saved."""
77+
InstanceConfiguration.objects.create(
78+
key="GITHUB_CLIENT_ID",
79+
value="",
80+
category="GITHUB",
81+
is_encrypted=False,
82+
)
83+
84+
response = admin_client.patch(
85+
"/api/instances/configurations/",
86+
{"GITHUB_CLIENT_ID": " Ov23li2Dep2t79q18nxD"},
87+
format="json",
88+
)
89+
90+
assert response.status_code == 200
91+
config = InstanceConfiguration.objects.get(key="GITHUB_CLIENT_ID")
92+
assert config.value == "Ov23li2Dep2t79q18nxD"
93+
94+
@pytest.mark.django_db
95+
def test_patch_strips_both_sides(self, admin_client):
96+
"""Values with whitespace on both sides should be fully trimmed."""
97+
InstanceConfiguration.objects.create(
98+
key="GOOGLE_CLIENT_ID",
99+
value="",
100+
category="GOOGLE",
101+
is_encrypted=False,
102+
)
103+
104+
response = admin_client.patch(
105+
"/api/instances/configurations/",
106+
{"GOOGLE_CLIENT_ID": " some-client-id "},
107+
format="json",
108+
)
109+
110+
assert response.status_code == 200
111+
config = InstanceConfiguration.objects.get(key="GOOGLE_CLIENT_ID")
112+
assert config.value == "some-client-id"
113+
114+
@pytest.mark.django_db
115+
def test_patch_preserves_clean_values(self, admin_client):
116+
"""Values without whitespace should be saved unchanged."""
117+
InstanceConfiguration.objects.create(
118+
key="GITHUB_CLIENT_ID",
119+
value="",
120+
category="GITHUB",
121+
is_encrypted=False,
122+
)
123+
124+
response = admin_client.patch(
125+
"/api/instances/configurations/",
126+
{"GITHUB_CLIENT_ID": "Ov23li2Dep2t79q18nxD"},
127+
format="json",
128+
)
129+
130+
assert response.status_code == 200
131+
config = InstanceConfiguration.objects.get(key="GITHUB_CLIENT_ID")
132+
assert config.value == "Ov23li2Dep2t79q18nxD"

0 commit comments

Comments
 (0)