2020from scim2_models import UniquenessException
2121from scim2_models import User
2222
23+ from .integrations import check_etag
2324from .integrations import delete_record
2425from .integrations import from_scim_user
2526from .integrations import get_record
2829from .integrations import get_schema
2930from .integrations import get_schemas
3031from .integrations import list_records
32+ from .integrations import make_etag
33+ from .integrations import PreconditionFailed
3134from .integrations import save_record
3235from .integrations import service_provider_config
3336from .integrations import to_scim_user
@@ -78,6 +81,14 @@ def scim_uniqueness_error(error):
7881# -- uniqueness-helper-end --
7982
8083
84+ # -- precondition-helper-start --
85+ def scim_precondition_error ():
86+ """Turn ETag mismatches into a SCIM 412 response."""
87+ scim_error = Error (status = 412 , detail = "ETag mismatch" )
88+ return scim_response (scim_error .model_dump_json (), HTTPStatus .PRECONDITION_FAILED )
89+ # -- precondition-helper-end --
90+
91+
8192# -- error-handler-start --
8293def handler404 (request , exception ):
8394 """Turn Django 404 errors into SCIM error responses."""
@@ -99,20 +110,35 @@ def get(self, request, app_record):
99110 except ValidationError as error :
100111 return scim_validation_error (error )
101112
113+ etag = make_etag (app_record )
114+ if_none_match = request .META .get ("HTTP_IF_NONE_MATCH" )
115+ if if_none_match and etag in [t .strip () for t in if_none_match .split ("," )]:
116+ return HttpResponse (status = HTTPStatus .NOT_MODIFIED )
117+
102118 scim_user = to_scim_user (app_record )
103- return scim_response (
119+ resp = scim_response (
104120 scim_user .model_dump_json (
105121 scim_ctx = Context .RESOURCE_QUERY_RESPONSE ,
106122 attributes = req .attributes ,
107123 excluded_attributes = req .excluded_attributes ,
108124 )
109125 )
126+ resp ["ETag" ] = etag
127+ return resp
110128
111129 def delete (self , request , app_record ):
130+ try :
131+ check_etag (app_record , request .META .get ("HTTP_IF_MATCH" ))
132+ except PreconditionFailed :
133+ return scim_precondition_error ()
112134 delete_record (app_record ["id" ])
113135 return scim_response ("" , HTTPStatus .NO_CONTENT )
114136
115137 def put (self , request , app_record ):
138+ try :
139+ check_etag (app_record , request .META .get ("HTTP_IF_MATCH" ))
140+ except PreconditionFailed :
141+ return scim_precondition_error ()
116142 existing_user = to_scim_user (app_record )
117143 try :
118144 replacement = User .model_validate (
@@ -131,13 +157,19 @@ def put(self, request, app_record):
131157 return scim_uniqueness_error (error )
132158
133159 response_user = to_scim_user (updated_record )
134- return scim_response (
160+ resp = scim_response (
135161 response_user .model_dump_json (
136162 scim_ctx = Context .RESOURCE_REPLACEMENT_RESPONSE
137163 )
138164 )
165+ resp ["ETag" ] = make_etag (updated_record )
166+ return resp
139167
140168 def patch (self , request , app_record ):
169+ try :
170+ check_etag (app_record , request .META .get ("HTTP_IF_MATCH" ))
171+ except PreconditionFailed :
172+ return scim_precondition_error ()
141173 try :
142174 patch = PatchOp [User ].model_validate (
143175 json .loads (request .body ),
@@ -155,9 +187,11 @@ def patch(self, request, app_record):
155187 except ValueError as error :
156188 return scim_uniqueness_error (error )
157189
158- return scim_response (
190+ resp = scim_response (
159191 scim_user .model_dump_json (scim_ctx = Context .RESOURCE_PATCH_RESPONSE )
160192 )
193+ resp ["ETag" ] = make_etag (updated_record )
194+ return resp
161195# -- single-resource-end --
162196
163197
@@ -204,10 +238,12 @@ def post(self, request):
204238 return scim_uniqueness_error (error )
205239
206240 response_user = to_scim_user (app_record )
207- return scim_response (
241+ resp = scim_response (
208242 response_user .model_dump_json (scim_ctx = Context .RESOURCE_CREATION_RESPONSE ),
209243 HTTPStatus .CREATED ,
210244 )
245+ resp ["ETag" ] = make_etag (app_record )
246+ return resp
211247
212248
213249urlpatterns = [
0 commit comments