Changeset d510103 in trunk


Ignore:
Timestamp:
2023-05-23T18:53:22Z (2 years ago)
Author:
GitHub <noreply@…>
Branches:
master
Children:
839140c, 96670de, f4a099c
Parents:
41131ca (diff), b03db14 (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent.
git-author:
Itamar Turner-Trauring <itamar@…> (2023-05-23 18:53:22)
git-committer:
GitHub <noreply@…> (2023-05-23 18:53:22)
Message:

Merge pull request #1301 from tahoe-lafs/4027-invalid-unicode

Invalid unicode in Authorization header should give better response

Fixes ticket:4027

Files:
1 added
2 edited

Legend:

Unmodified
Added
Removed
  • TabularUnified src/allmydata/storage/http_server.py

    r41131ca rd510103  
    55from __future__ import annotations
    66
    7 from typing import Any, Callable, Union, cast
     7from typing import Any, Callable, Union, cast, Optional
    88from functools import wraps
    99from base64 import b64decode
     
    7676
    7777    If too few secrets were given, or too many, a ``ClientSecretsException`` is
    78     raised.
     78    raised; its text is sent in the HTTP response.
    7979    """
    8080    string_key_to_enum = {e.value: e for e in Secrets}
     
    8585            key = string_key_to_enum[string_key]
    8686            value = b64decode(string_value)
     87            if value == b"":
     88                raise ClientSecretsException(
     89                    "Failed to decode secret {}".format(string_key)
     90                )
    8791            if key in (Secrets.LEASE_CANCEL, Secrets.LEASE_RENEW) and len(value) != 32:
    8892                raise ClientSecretsException("Lease secrets must be 32 bytes long")
     
    9296    if result.keys() != required_secrets:
    9397        raise ClientSecretsException(
    94             "Expected {} secrets, got {}".format(required_secrets, result.keys())
     98            "Expected {} in X-Tahoe-Authorization headers, got {}".format(
     99                [r.value for r in required_secrets], list(result.keys())
     100            )
    95101        )
    96102    return result
     
    117123                try:
    118124                    # Check Authorization header:
     125                    try:
     126                        auth_header = request.requestHeaders.getRawHeaders(
     127                            "Authorization", [""]
     128                        )[0].encode("utf-8")
     129                    except UnicodeError:
     130                        raise _HTTPError(http.BAD_REQUEST, "Bad Authorization header")
    119131                    if not timing_safe_compare(
    120                         request.requestHeaders.getRawHeaders("Authorization", [""])[
    121                             0
    122                         ].encode("utf-8"),
     132                        auth_header,
    123133                        swissnum_auth_header(self._swissnum),
    124134                    ):
    125                         raise _HTTPError(http.UNAUTHORIZED)
     135                        raise _HTTPError(
     136                            http.UNAUTHORIZED, "Wrong Authorization header"
     137                        )
    126138
    127139                    # Check secrets:
     
    131143                    try:
    132144                        secrets = _extract_secrets(authorization, required_secrets)
    133                     except ClientSecretsException:
    134                         raise _HTTPError(http.BAD_REQUEST)
     145                    except ClientSecretsException as e:
     146                        raise _HTTPError(http.BAD_REQUEST, str(e))
    135147
    136148                    # Run the business logic:
     
    273285    """
    274286
    275     def __init__(self, code: int):
     287    def __init__(self, code: int, body: Optional[str] = None):
     288        Exception.__init__(self, (code, body))
    276289        self.code = code
     290        self.body = body
    277291
    278292
     
    500514        """Handle ``_HTTPError`` exceptions."""
    501515        request.setResponseCode(failure.value.code)
    502         return b""
     516        if failure.value.body is not None:
     517            return failure.value.body
     518        else:
     519            return b""
    503520
    504521    @app.handle_errors(CDDLValidationError)
  • TabularUnified src/allmydata/test/test_storage_http.py

    r41131ca rd510103  
    258258    _swissnum = SWISSNUM_FOR_TEST  # Match what the test client is using
    259259
     260    @_authorized_route(_app, {}, "/noop", methods=["GET"])
     261    def noop(self, request, authorization):
     262        return "noop"
     263
    260264    @_authorized_route(_app, {Secrets.UPLOAD}, "/upload_secret", methods=["GET"])
    261265    def validate_upload_secret(self, request, authorization):
     
    341345        self._http_server.clock = self.client._clock
    342346
     347    def test_bad_swissnum_from_client(self) -> None:
     348        """
     349        If the swissnum is invalid, a BAD REQUEST response code is returned.
     350        """
     351        headers = Headers()
     352        # The value is not UTF-8.
     353        headers.addRawHeader("Authorization", b"\x00\xFF\x00\xFF")
     354        response = result_of(
     355            self.client._treq.request(
     356                "GET",
     357                DecodedURL.from_text("http://127.0.0.1/noop"),
     358                headers=headers,
     359            )
     360        )
     361        self.assertEqual(response.code, 400)
     362
     363    def test_bad_secret(self) -> None:
     364        """
     365        If the secret is invalid (not base64), a BAD REQUEST
     366        response code is returned.
     367        """
     368        bad_secret = b"upload-secret []<>"
     369        headers = Headers()
     370        headers.addRawHeader(
     371            "X-Tahoe-Authorization",
     372            bad_secret,
     373        )
     374        response = result_of(
     375            self.client.request(
     376                "GET",
     377                DecodedURL.from_text("http://127.0.0.1/upload_secret"),
     378                headers=headers,
     379            )
     380        )
     381        self.assertEqual(response.code, 400)
     382
    343383    def test_authorization_enforcement(self):
    344384        """
    345385        The requirement for secrets is enforced by the ``_authorized_route``
    346386        decorator; if they are not given, a 400 response code is returned.
     387
     388        Note that this refers to ``X-Tahoe-Authorization``, not the
     389        ``Authorization`` header used for the swissnum.
    347390        """
    348391        # Without secret, get a 400 error.
Note: See TracChangeset for help on using the changeset viewer.