-
Notifications
You must be signed in to change notification settings - Fork 14
Allow using validate_allocations to validate Keycloak group memberships
#307
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
base: main
Are you sure you want to change the base?
Changes from all commits
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 | ||
|---|---|---|---|---|
|
|
@@ -3,11 +3,13 @@ | |||
| from coldfront_plugin_cloud import attributes | ||||
| from coldfront_plugin_cloud import utils | ||||
| from coldfront_plugin_cloud import tasks | ||||
| from coldfront_plugin_cloud import signals | ||||
|
|
||||
| from django.core.management.base import BaseCommand | ||||
| from coldfront.core.resource.models import Resource | ||||
| from coldfront.core.allocation.models import ( | ||||
| Allocation, | ||||
| AllocationUser, | ||||
| ) | ||||
| from keystoneauth1.exceptions import http | ||||
| from kubernetes.dynamic import exceptions as k8s_exceptions | ||||
|
|
@@ -46,6 +48,66 @@ def check_institution_specific_code(self, allocation, apply): | |||
| utils.set_attribute_on_allocation(allocation, attr, "N/A") | ||||
| logger.warning(f'Attribute "{attr}" added to allocation {alloc_str}') | ||||
|
|
||||
| def validate_keycloak_group_memberships(self, allocation: Allocation, apply: bool): | ||||
| # Fetch or cache Keycloak client | ||||
| kc_client = getattr(self, "_kc_client", None) | ||||
| if kc_client is None: | ||||
| kc_client = tasks.get_kc_client() | ||||
| self._kc_client = kc_client | ||||
|
|
||||
| resource = allocation.resources.first() | ||||
|
Comment on lines
+51
to
+58
|
||||
| group_template = resource.get_attribute( | ||||
| attributes.RESOURCE_KEYCLOAK_GROUP_TEMPLATE | ||||
| ) | ||||
| if not group_template: | ||||
| logger.info( | ||||
| f"Keycloak enabled but no group name template specified for resource {resource.name}. Skipping validation" | ||||
| ) | ||||
| return | ||||
|
|
||||
| allocation_users: list[AllocationUser] = ( | ||||
|
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. We just found a way to make this part of the code reusable in
Can you try to think of a way to plug this into that or something similar? |
||||
| allocation.allocationuser_set.select_related("user").all() | ||||
| ) | ||||
| allocation_usernames: set[str] = set( | ||||
| au.user.username for au in allocation_users | ||||
| ) | ||||
|
|
||||
| group_name = tasks._get_keycloak_group_name(allocation, group_template) | ||||
|
|
||||
| try: | ||||
| group_name = tasks._get_keycloak_group_name(allocation, group_template) | ||||
| except KeyError as e: | ||||
| logger.error( | ||||
| f"Invalid Keycloak group template for allocation {allocation.pk}: missing template variable {e}. Skipping validation" | ||||
| ) | ||||
| return | ||||
|
|
||||
| group_id = kc_client.get_group_id(group_name) | ||||
|
Comment on lines
+75
to
+85
|
||||
| if group_id: | ||||
| group_usernames = set(kc_client.get_group_members(group_id)) | ||||
| else: | ||||
| group_usernames = set() | ||||
|
|
||||
| to_add = [ | ||||
| au for au in allocation_users if au.user.username not in group_usernames | ||||
| ] | ||||
| to_remove = group_usernames - allocation_usernames | ||||
|
|
||||
| for au in to_add: | ||||
| username = au.user.username | ||||
| logger.info(f"Adding user {username} to Keycloak group {group_name}") | ||||
| if apply: | ||||
| tasks.add_user_to_keycloak(au.pk) | ||||
| for username in to_remove: | ||||
|
Comment on lines
+96
to
+101
Contributor
Author
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. |
||||
| logger.info(f"Removing user {username} from Keycloak group {group_name}") | ||||
| if apply: | ||||
| if user_id := kc_client.get_user_id(username): | ||||
| kc_client.remove_user_from_group(user_id, group_id) | ||||
| else: | ||||
| logger.warning( | ||||
| f"User {username} not found in Keycloak, cannot remove from group {group_name}." | ||||
| ) | ||||
|
|
||||
| def handle(self, *args, **options): | ||||
| for resource_name in self.PLUGIN_RESOURCE_NAMES: | ||||
| resource = Resource.objects.filter(resource_type__name=resource_name) | ||||
|
|
@@ -70,6 +132,11 @@ def handle(self, *args, **options): | |||
| ) | ||||
| continue | ||||
|
|
||||
| if signals.is_keycloak_enabled(): | ||||
|
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. I don't like that we're importing |
||||
| self.validate_keycloak_group_memberships( | ||||
| allocation, options["apply"] | ||||
| ) | ||||
|
|
||||
| # Check project exists in remote cluster | ||||
| try: | ||||
| allocator.get_project(project_id) | ||||
|
|
||||
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 is basically what a
cached_propertydoes in an easier to read way.