mirror of https://github.com/zulip/zulip.git
scim: Upgrade django-scim2; remove request.user monkey patching.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
parent
7e8ebd18b6
commit
55342efd33
|
@ -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 \
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Loading…
Reference in New Issue