11from django .contrib .auth .models import User , Group
22from django .db import transaction
33
4- from cloudharness_django .models import Team , Member
4+ from cloudharness_django .models import Team , Member , Organization , OrganizationMember
55from cloudharness_django .services .auth import AuthService , AuthorizationLevel
66from cloudharness import models as ch_models
77
@@ -141,19 +141,23 @@ def sync_kc_user(self, kc_user: ch_models.User, is_superuser=False, delete=False
141141 return user
142142
143143 def sync_kc_user_groups (self , kc_user : ch_models .User ):
144- # Sync the user usergroups and memberships using kc_id for reliable lookups
144+ # Sync the user usergroups (not organizations) using kc_id for reliable lookups
145145 user = get_user_by_kc_id (kc_user .id )
146146
147147 if user is None :
148148 raise ValueError (f"Django user not found for Keycloak user { kc_user .id } " )
149149
150150 user_groups = []
151- for kc_group in [* kc_user .user_groups , * kc_user .organizations ]:
151+ # Only sync user_groups, not organizations (organizations are handled separately)
152+ for kc_group in kc_user .user_groups or []:
152153 group , _ = Group .objects .get_or_create (name = kc_group .name )
153154 user_groups .append (group )
154155 user .groups .set (user_groups )
155156 user .save ()
156157
158+ # Sync organization memberships separately
159+ self .sync_kc_user_organizations (kc_user )
160+
157161 # Ensure the member relationship exists and is correct
158162 try :
159163 member = user .member
@@ -164,6 +168,70 @@ def sync_kc_user_groups(self, kc_user: ch_models.User):
164168 member = Member (user = user , kc_id = kc_user .id )
165169 member .save ()
166170
171+ def sync_kc_organization (self , kc_org : ch_models .Organization ) -> Organization :
172+ """
173+ Sync a single Keycloak organization to Django.
174+ First tries to find by kc_id, then by name (for organizations created without kc_id).
175+ Updates the kc_id if found by name.
176+
177+ :param kc_org: Keycloak organization object
178+ :return: Django Organization instance
179+ """
180+ org = None
181+
182+ # First try to find by kc_id
183+ try :
184+ org = Organization .objects .get (kc_id = kc_org .id )
185+ # Update name if changed
186+ if org .name != kc_org .name :
187+ org .name = kc_org .name
188+ org .save ()
189+ except Organization .DoesNotExist :
190+ # Try to find by name (for organizations created without kc_id)
191+ try :
192+ org = Organization .objects .get (name = kc_org .name , kc_id__isnull = True )
193+ # Update with kc_id from Keycloak
194+ org .kc_id = kc_org .id
195+ org .save ()
196+ except Organization .DoesNotExist :
197+ # Create new organization
198+ org = Organization .objects .create (
199+ kc_id = kc_org .id ,
200+ name = kc_org .name
201+ )
202+ return org
203+
204+ def sync_kc_user_organizations (self , kc_user : ch_models .User ):
205+ """
206+ Sync the user's organization memberships from Keycloak to Django.
207+ Creates Organization records if they don't exist and manages OrganizationMember relationships.
208+
209+ :param kc_user: Keycloak user object with organizations attribute
210+ """
211+ user = get_user_by_kc_id (kc_user .id )
212+
213+ if user is None :
214+ raise ValueError (f"Django user not found for Keycloak user { kc_user .id } " )
215+
216+ # Get current organization memberships
217+ current_org_ids = set ()
218+
219+ # Sync each organization the user belongs to
220+ for kc_org in kc_user .organizations or []:
221+ org = self .sync_kc_organization (kc_org )
222+ current_org_ids .add (org .id )
223+
224+ # Create membership if it doesn't exist
225+ OrganizationMember .objects .get_or_create (
226+ user = user ,
227+ organization = org
228+ )
229+
230+ # Remove memberships that no longer exist in Keycloak
231+ OrganizationMember .objects .filter (user = user ).exclude (
232+ organization_id__in = current_org_ids
233+ ).delete ()
234+
167235 def sync_kc_users_groups (self ):
168236 # cache all admin users to minimize KC rest api calls
169237 all_admin_users = self .auth_client .get_client_role_members (
0 commit comments