-
Notifications
You must be signed in to change notification settings - Fork 5
Feature/ch-220 - Add Organization Support into Cloudharness from Keycloak and support admin sync #831
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/ch-220 - Add Organization Support into Cloudharness from Keycloak and support admin sync #831
Changes from all commits
09bd7d6
d8145b0
3f3eb43
dac753b
c9e4408
ed6064d
1544f64
0b01b32
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| # Generated by Django 3.2.11 on 2026-01-15 00:00 | ||
|
|
||
| from django.db import migrations, models | ||
| import django.db.models.deletion | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| ('cloudharness_django', '0001_initial'), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.CreateModel( | ||
| name='Organization', | ||
| fields=[ | ||
| ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
| ('name', models.CharField(max_length=256)), | ||
| ('kc_id', models.CharField(db_index=True, max_length=100)), | ||
| ], | ||
| options={ | ||
| 'verbose_name': 'CloudHarness Organization', | ||
| 'verbose_name_plural': 'CloudHarness Organizations', | ||
| }, | ||
| ), | ||
| migrations.CreateModel( | ||
| name='OrganizationMember', | ||
| fields=[ | ||
| ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
| ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='auth.user')), | ||
| ('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cloudharness_django.organization')), | ||
| ], | ||
| options={ | ||
| 'verbose_name': 'CloudHarness Organization Member', | ||
| 'verbose_name_plural': 'CloudHarness Organization Members', | ||
| }, | ||
| ), | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -641,3 +641,53 @@ def user_delete_attribute(self, user_id, attribute_name): | |
| }) | ||
| return True | ||
| return False | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. these additions are unnecessary, as the pattern is to lazily sync groups/organizations together with users (and organizations were coming already) |
||
| @with_refreshtoken | ||
| def get_organizations(self, with_members=False) -> List[dict]: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no much use in typing when returning a List[dict] where a proper type can be used. CloudHarness defines a type for organizations |
||
| """ | ||
| Get all organizations in the realm | ||
|
|
||
| :param with_members: If True, include organization members for each org. Defaults to False | ||
| :return: List of organization representations | ||
| """ | ||
| admin_client = self.get_admin_client() | ||
| try: | ||
| orgs = admin_client.get_organizations() | ||
| if with_members: | ||
| for org in orgs: | ||
| org['members'] = admin_client.get_organization_members(org['id']) | ||
| return orgs | ||
| except Exception as e: | ||
| log.error(f"Error getting organizations: {e}") | ||
| raise | ||
|
|
||
| @with_refreshtoken | ||
| def get_organization_members(self, org_id: str) -> List[User]: | ||
| """ | ||
| Get all members of an organization | ||
|
|
||
| :param org_id: Organization ID | ||
| :return: List of User objects | ||
| """ | ||
| admin_client = self.get_admin_client() | ||
| try: | ||
| members = admin_client.get_organization_members(org_id) | ||
| return [User.from_dict(member) for member in members] | ||
| except Exception as e: | ||
| log.error(f"Error getting members for organization {org_id}: {e}") | ||
| raise | ||
|
|
||
| @with_refreshtoken | ||
| def get_user_organizations(self, user_id: str) -> List[dict]: | ||
| """ | ||
| Get all organizations a user belongs to | ||
|
|
||
| :param user_id: User ID | ||
| :return: List of organization representations | ||
| """ | ||
| admin_client = self.get_admin_client() | ||
| try: | ||
| return admin_client.get_user_organizations(user_id) | ||
| except Exception as e: | ||
| log.error(f"Error getting organizations for user {user_id}: {e}") | ||
| raise | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this
sync_kc_groups()- was removed fromsync_kc_users_groups- so there's no way to sync groups if users are not already assigned to groups. This will ensure groups can be synced in the group admin panelThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need to sync groups that are not assigned to users