Skip to content

Commit 851e4c3

Browse files
dstuffthugovk
andauthored
Add a configuration option to disable XMLRPC search (#8918)
* Add a configuration option to disable XMLRPC search * Update tests/unit/legacy/api/xmlrpc/test_xmlrpc.py Co-authored-by: Hugo van Kemenade <[email protected]> * fix casing * Change the error warning to reflect the temporary nature * Slight tweak to wording Co-authored-by: Hugo van Kemenade <[email protected]>
1 parent 50af621 commit 851e4c3

File tree

4 files changed

+45
-2
lines changed

4 files changed

+45
-2
lines changed

tests/unit/legacy/api/xmlrpc/test_xmlrpc.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,24 @@ def view(context, request):
114114

115115

116116
class TestSearch:
117+
def test_error_when_disabled(self, pyramid_request, metrics, monkeypatch):
118+
monkeypatch.setattr(
119+
pyramid_request.registry,
120+
"settings",
121+
{"warehouse.xmlrpc.search.enabled": False},
122+
)
123+
with pytest.raises(xmlrpc.XMLRPCWrappedError) as exc:
124+
xmlrpc.search(pyramid_request, {"name": "foo", "summary": ["one", "two"]})
125+
126+
assert exc.value.faultString == (
127+
"RuntimeError: This API has been temporarily disabled due to unmanageable "
128+
"load and will be deprecated in the near future. Please use the Simple or "
129+
"JSON API instead."
130+
)
131+
assert metrics.increment.calls == [
132+
pretend.call("warehouse.xmlrpc.search.deprecated")
133+
]
134+
117135
def test_fails_with_invalid_operator(self, pyramid_request, metrics):
118136
with pytest.raises(xmlrpc.XMLRPCWrappedError) as exc:
119137
xmlrpc.search(pyramid_request, {}, "lol nope")

tests/unit/test_config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ def __init__(self):
230230
"token.two_factor.max_age": 300,
231231
"token.default.max_age": 21600,
232232
"warehouse.xmlrpc.client.ratelimit_string": "3600 per hour",
233+
"warehouse.xmlrpc.search.enabled": True,
233234
}
234235

235236
if environment == config.Environment.development:

warehouse/config.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
# See the License for the specific language governing permissions and
1111
# limitations under the License.
1212

13+
import distutils.util
1314
import enum
1415
import os
1516
import shlex
@@ -183,6 +184,13 @@ def configure(settings=None):
183184
maybe_set(settings, "token.password.secret", "TOKEN_PASSWORD_SECRET")
184185
maybe_set(settings, "token.email.secret", "TOKEN_EMAIL_SECRET")
185186
maybe_set(settings, "token.two_factor.secret", "TOKEN_TWO_FACTOR_SECRET")
187+
maybe_set(
188+
settings,
189+
"warehouse.xmlrpc.search.enabled",
190+
"WAREHOUSE_XMLRPC_SEARCH",
191+
coercer=distutils.util.strtobool,
192+
default=True,
193+
)
186194
maybe_set(settings, "warehouse.xmlrpc.cache.url", "REDIS_URL")
187195
maybe_set(
188196
settings,

warehouse/legacy/api/xmlrpc/views.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,13 +227,29 @@ def exception_view(exc, request):
227227

228228
@xmlrpc_method(method="search")
229229
def search(request, spec: Mapping[str, Union[str, List[str]]], operator: str = "and"):
230+
metrics = request.find_service(IMetricsService, context=None)
231+
232+
# This uses a setting instead of an admin flag to avoid hitting the DB/Elasticsearch
233+
# at all since the broad purpose of this flag is to enable us to control the load to
234+
# our backend servers. This does mean that turning search on or off requires a
235+
# deploy, but it should be infrequent enough to not matter.
236+
if not request.registry.settings.get("warehouse.xmlrpc.search.enabled", True):
237+
metrics.increment("warehouse.xmlrpc.search.deprecated")
238+
raise XMLRPCWrappedError(
239+
RuntimeError(
240+
(
241+
"This API has been temporarily disabled due to unmanageable load "
242+
"and will be deprecated in the near future. Please use the Simple "
243+
"or JSON API instead."
244+
)
245+
)
246+
)
247+
230248
if operator not in {"and", "or"}:
231249
raise XMLRPCWrappedError(
232250
ValueError("Invalid operator, must be one of 'and' or 'or'.")
233251
)
234252

235-
metrics = request.find_service(IMetricsService, context=None)
236-
237253
# Remove any invalid spec fields
238254
spec = {
239255
k: [v] if isinstance(v, str) else v

0 commit comments

Comments
 (0)