From 32ed7d83e822eb4680a0d2ea856d0f6c343000d1 Mon Sep 17 00:00:00 2001 From: Claire Peters Date: Tue, 9 Jun 2026 16:19:03 -0700 Subject: [PATCH] Add is_fasse project attribute and fasse group tagging --- coldfront/config/plugins/ldap_fasrc.py | 1 + .../commands/add_default_project_choices.py | 1 + coldfront/core/project/models.py | 5 +++ coldfront/plugins/ldap/utils.py | 33 +++++++++++++++++++ 4 files changed, 40 insertions(+) diff --git a/coldfront/config/plugins/ldap_fasrc.py b/coldfront/config/plugins/ldap_fasrc.py index 56163603f0..4d7af04464 100644 --- a/coldfront/config/plugins/ldap_fasrc.py +++ b/coldfront/config/plugins/ldap_fasrc.py @@ -21,6 +21,7 @@ AUTH_LDAP_USER_SEARCH_BASE = ENV.str('AUTH_LDAP_USER_SEARCH_BASE') AUTH_LDAP_GROUP_SEARCH_BASE = ENV.str('AUTH_LDAP_GROUP_SEARCH_BASE') CF_LDAP_PROJECT_GROUP_SAM = ENV.str('CF_LDAP_PROJECT_GROUP_SAM', default=None) +CF_LDAP_FASSE_GROUP = ENV.str('CF_LDAP_FASSE_GROUP', default=None) LOGGING['handlers']['ldap_fasrc'] = { diff --git a/coldfront/core/project/management/commands/add_default_project_choices.py b/coldfront/core/project/management/commands/add_default_project_choices.py index ddcaca7271..e209279b6b 100644 --- a/coldfront/core/project/management/commands/add_default_project_choices.py +++ b/coldfront/core/project/management/commands/add_default_project_choices.py @@ -31,6 +31,7 @@ def handle(self, *args, **options): for name, attribute_type, has_usage, is_private in ( ('Starfish Zone', 'Int', False, False), + ('is_fasse', 'Yes/No', False, False), # UBCCR defaults # ('Project ID', 'Text', False, False), # ('Account Number', 'Int', False, True), diff --git a/coldfront/core/project/models.py b/coldfront/core/project/models.py index 9cee740bb2..5ad0c3b414 100644 --- a/coldfront/core/project/models.py +++ b/coldfront/core/project/models.py @@ -143,6 +143,11 @@ def latest_publication(self): def sf_zone(self): return self.get_attribute('Starfish Zone') + @property + def is_fasse(self): + """Return True if the project has an is_fasse attribute set to 'True', False otherwise.""" + return self.get_attribute('is_fasse') == 'True' + @property def needs_review(self): """ diff --git a/coldfront/plugins/ldap/utils.py b/coldfront/plugins/ldap/utils.py index ed85cb08ed..33f8492032 100644 --- a/coldfront/plugins/ldap/utils.py +++ b/coldfront/plugins/ldap/utils.py @@ -25,6 +25,8 @@ from coldfront.core.allocation.models import AllocationUserStatusChoice, AllocationUser from coldfront.core.project.models import ( Project, + ProjectAttribute, + ProjectAttributeType, ProjectStatusChoice, ProjectUserRoleChoice, ProjectUserStatusChoice, @@ -559,6 +561,22 @@ def collect_update_project_status_membership(): ad_conn = LDAPConn() + # Resolve the set of project AD group SAMAccountNames that are MemberOf the fasse group. + CF_LDAP_FASSE_GROUP = import_from_settings('CF_LDAP_FASSE_GROUP', None) + fasse_project_titles = set() + if CF_LDAP_FASSE_GROUP: + fasse_entries = ad_conn.search_groups( + {'sAMAccountName': CF_LDAP_FASSE_GROUP}, attributes=['member'] + ) + if fasse_entries: + fasse_member_dns = fasse_entries[0].get('member', []) + if fasse_member_dns: + fasse_member_groups = ad_conn.search_groups( + {'distinguishedName': fasse_member_dns}, attributes=['sAMAccountName'] + ) + fasse_project_titles = {g['sAMAccountName'][0] for g in fasse_member_groups} + logger.debug('fasse project titles resolved from AD: %s', fasse_project_titles) + proj_membs_mans = {p: ad_conn.return_group_members_manager(p.title) for p in active_projects} proj_membs_mans, _ = cleaned_membership_query(proj_membs_mans) groupusercollections = [ @@ -633,8 +651,23 @@ def collect_update_project_status_membership(): ) logger.info("Reactivating projects: %s", [p.title for p in active_pi_group_projs_statuschange]) active_pi_group_projs_statuschange.update(status=project_active_status) + is_fasse_attr_type = ProjectAttributeType.objects.filter(name='is_fasse').first() + for group in active_pi_groups: + # Update is_fasse ProjectAttribute based on fasse group membership in AD. + if CF_LDAP_FASSE_GROUP and is_fasse_attr_type: + is_fasse = group.name in fasse_project_titles + ProjectAttribute.objects.update_or_create( + project=group.project, + proj_attr_type=is_fasse_attr_type, + defaults={'value': str(is_fasse)}, + ) + else: + logger.warning( + 'Skipping is_fasse ProjectAttribute update. CF_LDAP_FASSE_GROUP or is_fasse ProjectAttributeType not found.' + ) + ad_users_not_added, remove_projuser_names = group.compare_members_projectusers() group_storage_allocations = group.project.allocation_set.filter( resources__resource_type__name='Storage', status__name='Active'