scim: Upgrade django-scim2; remove request.user monkey patching.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg 2022-11-04 16:26:39 -07:00 committed by Tim Abbott
parent 7e8ebd18b6
commit 55342efd33
5 changed files with 25 additions and 43 deletions

View File

@ -489,8 +489,9 @@ django-phonenumber-field==6.4.0 \
--hash=sha256:72a3e7a3e7493bf2a12c07a3bc77ce89813acc16592bf04d0eee3b5a452097ed \ --hash=sha256:72a3e7a3e7493bf2a12c07a3bc77ce89813acc16592bf04d0eee3b5a452097ed \
--hash=sha256:a31b4f05ac0ff898661516c84940f83adb5cdcf0ae4b9b1d31bb8ad3ff345b58 --hash=sha256:a31b4f05ac0ff898661516c84940f83adb5cdcf0ae4b9b1d31bb8ad3ff345b58
# via django-two-factor-auth # via django-two-factor-auth
django-scim2==0.17.3 \ django-scim2==0.18.0 \
--hash=sha256:6fcff9389e3a6bad1599f8873a5fbf4e0101400cd62beac4d7bf053bd4944447 --hash=sha256:5055099fdbfa55b46488cece7b378225263265ae4acd1669c47b2c286d2cfbb2 \
--hash=sha256:f3353df68b469f494a5c7f53bb487411125a08be77d2ccc2d4c048138895614c
# via -r requirements/common.in # via -r requirements/common.in
django-sendfile2==0.7.0 \ django-sendfile2==0.7.0 \
--hash=sha256:0ee17b4f7ce8cc7159f75fa4e5d62e7795c1217de8f1e52ee6265d4aa46dce03 \ --hash=sha256:0ee17b4f7ce8cc7159f75fa4e5d62e7795c1217de8f1e52ee6265d4aa46dce03 \
@ -1608,7 +1609,6 @@ python-dateutil==2.8.2 \
# -r requirements/common.in # -r requirements/common.in
# arrow # arrow
# botocore # botocore
# django-scim2
# moto # moto
python-debian==0.1.48 \ python-debian==0.1.48 \
--hash=sha256:00edb2c95a9e28e17067c055d17a66e90abf9d7c80cfda93095e7193e5ae58c3 \ --hash=sha256:00edb2c95a9e28e17067c055d17a66e90abf9d7c80cfda93095e7193e5ae58c3 \

View File

@ -331,8 +331,9 @@ django-phonenumber-field==6.4.0 \
--hash=sha256:72a3e7a3e7493bf2a12c07a3bc77ce89813acc16592bf04d0eee3b5a452097ed \ --hash=sha256:72a3e7a3e7493bf2a12c07a3bc77ce89813acc16592bf04d0eee3b5a452097ed \
--hash=sha256:a31b4f05ac0ff898661516c84940f83adb5cdcf0ae4b9b1d31bb8ad3ff345b58 --hash=sha256:a31b4f05ac0ff898661516c84940f83adb5cdcf0ae4b9b1d31bb8ad3ff345b58
# via django-two-factor-auth # via django-two-factor-auth
django-scim2==0.17.3 \ django-scim2==0.18.0 \
--hash=sha256:6fcff9389e3a6bad1599f8873a5fbf4e0101400cd62beac4d7bf053bd4944447 --hash=sha256:5055099fdbfa55b46488cece7b378225263265ae4acd1669c47b2c286d2cfbb2 \
--hash=sha256:f3353df68b469f494a5c7f53bb487411125a08be77d2ccc2d4c048138895614c
# via -r requirements/common.in # via -r requirements/common.in
django-sendfile2==0.7.0 \ django-sendfile2==0.7.0 \
--hash=sha256:0ee17b4f7ce8cc7159f75fa4e5d62e7795c1217de8f1e52ee6265d4aa46dce03 \ --hash=sha256:0ee17b4f7ce8cc7159f75fa4e5d62e7795c1217de8f1e52ee6265d4aa46dce03 \
@ -1102,7 +1103,6 @@ python-dateutil==2.8.2 \
# via # via
# -r requirements/common.in # -r requirements/common.in
# botocore # botocore
# django-scim2
python-gcm==0.4 \ python-gcm==0.4 \
--hash=sha256:511c35fc5ae829f7fc3cbdb45c4ec3fda02f85e4fae039864efe82682ccb9c18 --hash=sha256:511c35fc5ae829f7fc3cbdb45c4ec3fda02f85e4fae039864efe82682ccb9c18
# via -r requirements/common.in # via -r requirements/common.in

View File

@ -48,4 +48,4 @@ API_FEATURE_LEVEL = 154
# historical commits sharing the same major version, in which case a # historical commits sharing the same major version, in which case a
# minor version bump suffices. # minor version bump suffices.
PROVISION_VERSION = (210, 0) PROVISION_VERSION = (211, 0)

View File

@ -696,29 +696,18 @@ class ZulipCommonMiddleware(CommonMiddleware):
return super().should_redirect_with_slash(request) return super().should_redirect_with_slash(request)
class SCIMClient: def validate_scim_bearer_token(request: HttpRequest) -> bool:
@property
def is_authenticated(self) -> bool:
"""
The purpose of this is to make SCIMClient behave like a UserProfile
when an instance is assigned to request.user - we need it to pass
request.user.is_authenticated verifications.
"""
return True
def validate_scim_bearer_token(request: HttpRequest) -> Optional[SCIMClient]:
""" """
This function verifies the request is allowed to make SCIM requests on this subdomain, This function verifies the request is allowed to make SCIM requests on this subdomain,
by checking the provided bearer token and ensuring it matches a scim client configured by checking the provided bearer token and ensuring it matches a scim client configured
for this subdomain in settings.SCIM_CONFIG. for this subdomain in settings.SCIM_CONFIG.
If successful, returns the corresponding SCIMClient object. Returns None otherwise. Returns True if successful.
""" """
subdomain = get_subdomain(request) subdomain = get_subdomain(request)
scim_config_dict = settings.SCIM_CONFIG.get(subdomain) scim_config_dict = settings.SCIM_CONFIG.get(subdomain)
if not scim_config_dict: if not scim_config_dict:
return None return False
valid_bearer_token = scim_config_dict.get("bearer_token") valid_bearer_token = scim_config_dict.get("bearer_token")
scim_client_name = scim_config_dict.get("scim_client_name") scim_client_name = scim_config_dict.get("scim_client_name")
@ -728,7 +717,7 @@ def validate_scim_bearer_token(request: HttpRequest) -> Optional[SCIMClient]:
assert scim_client_name assert scim_client_name
if request.headers.get("Authorization") != f"Bearer {valid_bearer_token}": if request.headers.get("Authorization") != f"Bearer {valid_bearer_token}":
return None return False
request_notes = RequestNotes.get_notes(request) request_notes = RequestNotes.get_notes(request)
assert request_notes.realm is not None assert request_notes.realm is not None
@ -736,10 +725,7 @@ def validate_scim_bearer_token(request: HttpRequest) -> Optional[SCIMClient]:
f"scim-client:{scim_client_name}:realm:{request_notes.realm.id}" f"scim-client:{scim_client_name}:realm:{request_notes.realm.id}"
) )
# While API authentication code paths are sufficiently high return True
# traffic that we prefer to use a cache, SCIM is much lower
# traffic, and doing a database query is plenty fast.
return SCIMClient()
class ZulipSCIMAuthCheckMiddleware(SCIMAuthCheckMiddleware): class ZulipSCIMAuthCheckMiddleware(SCIMAuthCheckMiddleware):
@ -747,9 +733,17 @@ class ZulipSCIMAuthCheckMiddleware(SCIMAuthCheckMiddleware):
Overridden version of middleware implemented in django-scim2 Overridden version of middleware implemented in django-scim2
(https://github.com/15five/django-scim2/blob/master/src/django_scim/middleware.py) (https://github.com/15five/django-scim2/blob/master/src/django_scim/middleware.py)
to also handle authenticating the client. to also handle authenticating the client.
This doesn't actually function as a regular middleware class that's registered in
settings.MIDDLEWARE, but rather is called inside django-scim2 logic to authenticate
the request when accessing SCIM endpoints.
""" """
def process_request(self, request: HttpRequest) -> Optional[HttpResponse]: def process_request(self, request: HttpRequest) -> Optional[HttpResponse]:
# Defensive assertion to ensure this can't accidentally get called on a request
# to a non-SCIM endpoint.
assert request.path.startswith(self.reverse_url)
# This determines whether this is a SCIM request based on the request's path # This determines whether this is a SCIM request based on the request's path
# and if it is, logs request information, including the body, as well as the response # and if it is, logs request information, including the body, as well as the response
# for debugging purposes to the `django_scim.middleware` logger, at DEBUG level. # for debugging purposes to the `django_scim.middleware` logger, at DEBUG level.
@ -757,24 +751,12 @@ class ZulipSCIMAuthCheckMiddleware(SCIMAuthCheckMiddleware):
if self.should_log_request(request): if self.should_log_request(request):
self.log_request(request) self.log_request(request)
# Here we verify the request is indeed to a SCIM endpoint. That's ensured if not validate_scim_bearer_token(request):
# by comparing the path with self.reverse_url, which is the root SCIM path /scim/b2/. # In case of failed authentication, a response should be returned to
# Of course we don't want to proceed with authenticating the request for SCIM # prevent going further down the codepath (to the SCIM endpoint), since
# if a non-SCIM endpoint is being queried. # this aspect works like regular middleware.
if not request.path.startswith(self.reverse_url):
return None
scim_client = validate_scim_bearer_token(request)
if not scim_client:
response = HttpResponse(status=401) response = HttpResponse(status=401)
response["WWW-Authenticate"] = scim_settings.WWW_AUTHENTICATE_HEADER response["WWW-Authenticate"] = scim_settings.WWW_AUTHENTICATE_HEADER
return response return response
# The client has been successfully authenticated for SCIM on this subdomain,
# so we can assign the corresponding SCIMClient object to request.user - which
# will allow this request to pass request.user.is_authenticated checks from now on,
# to be served by the relevant views implemented in django-scim2.
# Since request.user must be a UserProfile or AnonymousUser, this is a type-unsafe
# workaround to make this monkey-patching work.
request.user = scim_client # type: ignore[assignment] # wrong type
return None return None

View File

@ -176,7 +176,6 @@ MIDDLEWARE = (
"zerver.middleware.LocaleMiddleware", "zerver.middleware.LocaleMiddleware",
"zerver.middleware.HostDomainMiddleware", "zerver.middleware.HostDomainMiddleware",
"django.middleware.csrf.CsrfViewMiddleware", "django.middleware.csrf.CsrfViewMiddleware",
"zerver.middleware.ZulipSCIMAuthCheckMiddleware",
# Make sure 2FA middlewares come after authentication middleware. # Make sure 2FA middlewares come after authentication middleware.
"django_otp.middleware.OTPMiddleware", # Required by two factor auth. "django_otp.middleware.OTPMiddleware", # Required by two factor auth.
"two_factor.middleware.threadlocals.ThreadLocals", # Required by Twilio "two_factor.middleware.threadlocals.ThreadLocals", # Required by Twilio
@ -1235,6 +1234,7 @@ SCIM_SERVICE_PROVIDER = {
"SCHEME": EXTERNAL_URI_SCHEME, "SCHEME": EXTERNAL_URI_SCHEME,
"GET_EXTRA_MODEL_FILTER_KWARGS_GETTER": "zerver.lib.scim.get_extra_model_filter_kwargs_getter", "GET_EXTRA_MODEL_FILTER_KWARGS_GETTER": "zerver.lib.scim.get_extra_model_filter_kwargs_getter",
"BASE_LOCATION_GETTER": "zerver.lib.scim.base_scim_location_getter", "BASE_LOCATION_GETTER": "zerver.lib.scim.base_scim_location_getter",
"AUTH_CHECK_MIDDLEWARE": "zerver.middleware.ZulipSCIMAuthCheckMiddleware",
"AUTHENTICATION_SCHEMES": [ "AUTHENTICATION_SCHEMES": [
{ {
"type": "bearer", "type": "bearer",