77from ee .api .scim .auth import generate_scim_token
88from ee .api .test .base import APILicensedTest
99from ee .models .rbac .role import RoleMembership
10+ from ee .models .scim_provisioned_user import SCIMProvisionedUser
1011
1112
1213class TestSCIMUsersAPI (APILicensedTest ):
@@ -56,13 +57,27 @@ def test_users_list_filter_exact_match(self):
5657 OrganizationMembership .objects .create (
5758 user = user_a , organization = self .organization , level = OrganizationMembership .Level .MEMBER
5859 )
60+ SCIMProvisionedUser .objects .create (
61+ user = user_a ,
62+ organization_domain = self .domain ,
63+ 64+ identity_provider = SCIMProvisionedUser .IdentityProvider .OTHER ,
65+ active = True ,
66+ )
5967
6068 user_b = User .objects .create_user (
6169 email = "[email protected] " ,
password = None ,
first_name = "Alex" ,
last_name = "Other" ,
is_email_verified = True 6270 )
6371 OrganizationMembership .objects .create (
6472 user = user_b , organization = self .organization , level = OrganizationMembership .Level .MEMBER
6573 )
74+ SCIMProvisionedUser .objects .create (
75+ user = user_b ,
76+ organization_domain = self .domain ,
77+ 78+ identity_provider = SCIMProvisionedUser .IdentityProvider .OTHER ,
79+ active = True ,
80+ )
6681
6782 # Exact match should return only [email protected] 6883 response = self .client .get (
@@ -129,9 +144,9 @@ def test_users_list_filter_unrecognized_returns_empty_list(self):
129144 def test_create_user (self ):
130145 user_data = {
131146 "schemas" : ["urn:ietf:params:scim:schemas:core:2.0:User" ],
132- "userName" : "newuser @example.com" ,
147+ "userName" : "Newuser @example.com" ,
133148 "name" : {"givenName" : "New" , "familyName" : "User" },
134- "emails" : [{"value" : "newuser @example.com" , "primary" : True }],
149+ "emails" : [{"value" : "Newuser @example.com" , "primary" : True }],
135150 "active" : True ,
136151 }
137152
@@ -141,11 +156,11 @@ def test_create_user(self):
141156
142157 assert response .status_code == status .HTTP_201_CREATED
143158 data = response .json ()
144- assert data ["userName" ] == "newuser @example.com"
159+ assert data ["userName" ] == "Newuser @example.com"
145160 assert data ["name" ]["givenName" ] == "New"
146161 assert data ["name" ]["familyName" ] == "User"
147162
148- # Verify user was created
163+ # Verify user was created with lowercase email
149164 user = User .
objects .
get (
email = "[email protected] " )
150165 assert user .first_name == "New"
151166 assert user .last_name == "User"
@@ -155,6 +170,12 @@ def test_create_user(self):
155170 membership = OrganizationMembership .objects .get (user = user , organization = self .organization )
156171 assert membership .level == OrganizationMembership .Level .MEMBER
157172
173+ # Verify SCIM provisioned user record was created
174+ scim_user = SCIMProvisionedUser .objects .get (user = user , organization_domain = self .domain )
175+ assert scim_user .
username == "[email protected] " 176+ assert scim_user .active is True
177+ assert scim_user .identity_provider == SCIMProvisionedUser .IdentityProvider .OTHER
178+
158179 def test_existing_user_is_added_to_org (self ):
159180 # Create user in different org
160181 other_org = Organization .objects .create (name = "Other Org" )
@@ -184,9 +205,11 @@ def test_existing_user_is_added_to_org(self):
184205 assert OrganizationMembership .objects .filter (user = existing_user , organization = self .organization ).exists ()
185206 assert OrganizationMembership .objects .filter (user = existing_user , organization = other_org ).exists ()
186207
187- def test_repeated_post_does_not_create_duplicate_user (self ):
188- # In case the IdP failed to match user by id, it can send POST request to create a new user.
189- # The user should be merged with existing one by email, not create a duplicate.
208+ # Verify SCIM provisioned user record was created for this domain
209+ scim_user = SCIMProvisionedUser .objects .get (user = existing_user , organization_domain = self .domain )
210+ assert scim_user .active is True
211+
212+ def test_repeated_post_returns_409_for_already_provisioned_user (self ):
190213 user_data_first = {
191214 "schemas" : ["urn:ietf:params:scim:schemas:core:2.0:User" ],
192215@@ -202,7 +225,7 @@ def test_repeated_post_does_not_create_duplicate_user(self):
202225 assert response .status_code == status .HTTP_201_CREATED
203226 first_user = User .
objects .
get (
email = "[email protected] " )
204227
205- # IdP sends POST request again with same email
228+ # IdP sends POST request again with same email - should fail with 409
206229 user_data_second = {
207230 "schemas" : ["urn:ietf:params:scim:schemas:core:2.0:User" ],
208231@@ -215,14 +238,14 @@ def test_repeated_post_does_not_create_duplicate_user(self):
215238 f"/scim/v2/{ self .domain .id } /Users" , data = user_data_second , content_type = "application/scim+json"
216239 )
217240
218- assert response .status_code == status .HTTP_201_CREATED
241+ assert response .status_code == status .HTTP_409_CONFLICT
219242
220243 # Should NOT create duplicate user
221244 assert User .
objects .
filter (
email = "[email protected] " ).
count ()
== 1 222245
223- # User should be updated with new data from second POST
246+ # User should NOT be updated (still has first POST data)
224247 first_user .refresh_from_db ()
225- assert first_user .first_name == "Second "
248+ assert first_user .first_name == "First "
226249 assert first_user .last_name == "Time"
227250
228251 # User should have only one membership
@@ -261,6 +284,14 @@ def test_deactivate_user(self):
261284 OrganizationMembership .objects .create (
262285 user = user , organization = self .organization , level = OrganizationMembership .Level .MEMBER
263286 )
287+ # Create SCIM provisioned user record
288+ SCIMProvisionedUser .objects .create (
289+ user = user ,
290+ organization_domain = self .domain ,
291+ 292+ identity_provider = SCIMProvisionedUser .IdentityProvider .OTHER ,
293+ active = True ,
294+ )
264295
265296 patch_data = {
266297 "schemas" : ["urn:ietf:params:scim:api:messages:2.0:PatchOp" ],
@@ -280,13 +311,25 @@ def test_deactivate_user(self):
280311 user .refresh_from_db ()
281312 assert user .is_active is True # User is still active globally
282313
314+ # Verify SCIM provisioned user record still exists but is marked inactive
315+ scim_user = SCIMProvisionedUser .objects .get (user = user , organization_domain = self .domain )
316+ assert scim_user .active is False
317+
283318 def test_delete_user (self ):
284319 user = User .objects .create_user (
285320 email = "[email protected] " ,
password = None ,
first_name = "Delete" ,
is_email_verified = True 286321 )
287322 OrganizationMembership .objects .create (
288323 user = user , organization = self .organization , level = OrganizationMembership .Level .MEMBER
289324 )
325+ # Create SCIM provisioned user record
326+ SCIMProvisionedUser .objects .create (
327+ user = user ,
328+ organization_domain = self .domain ,
329+ 330+ identity_provider = SCIMProvisionedUser .IdentityProvider .OTHER ,
331+ active = True ,
332+ )
290333
291334 response = self .client .delete (f"/scim/v2/{ self .domain .id } /Users/{ user .id } " )
292335
@@ -295,13 +338,24 @@ def test_delete_user(self):
295338 # Verify membership was removed
296339 assert not OrganizationMembership .objects .filter (user = user , organization = self .organization ).exists ()
297340
341+ # Verify SCIM provisioned user record was deleted
342+ assert not SCIMProvisionedUser .objects .filter (user = user , organization_domain = self .domain ).exists ()
343+
298344 def test_put_user (self ):
299345 user = User .objects .create_user (
300346 email = "[email protected] " ,
password = None ,
first_name = "Old" ,
last_name = "Name" ,
is_email_verified = True 301347 )
302348 OrganizationMembership .objects .create (
303349 user = user , organization = self .organization , level = OrganizationMembership .Level .MEMBER
304350 )
351+ # Create SCIM provisioned user record
352+ SCIMProvisionedUser .objects .create (
353+ user = user ,
354+ organization_domain = self .domain ,
355+ 356+ identity_provider = SCIMProvisionedUser .IdentityProvider .OTHER ,
357+ active = True ,
358+ )
305359
306360 put_data = {
307361 "schemas" : ["urn:ietf:params:scim:schemas:core:2.0:User" ],
@@ -321,6 +375,11 @@ def test_put_user(self):
321375 assert user .last_name == "User"
322376 assert user .
email == "[email protected] " 323377
378+ # Verify SCIM provisioned user was updated
379+ scim_user = SCIMProvisionedUser .objects .get (user = user , organization_domain = self .domain )
380+ assert scim_user .
username == "[email protected] " 381+ assert scim_user .active is True
382+
324383 def test_put_user_not_found (self ):
325384 put_data = {
326385 "schemas" : ["urn:ietf:params:scim:schemas:core:2.0:User" ],
0 commit comments