@@ -10,7 +10,6 @@ import {
1010} from "../../common/config.js" ;
1111import {
1212 BaseError ,
13- DecryptionError ,
1413 EntraFetchError ,
1514 EntraGroupError ,
1615 EntraGroupsFromEmailError ,
@@ -574,10 +573,15 @@ export async function isUserInGroup(
574573export async function getServicePrincipalOwnedGroups (
575574 token : string ,
576575 servicePrincipal : string ,
576+ includeDynamicGroups : boolean ,
577577) : Promise < { id : string ; displayName : string } [ ] > {
578578 try {
579- // Selects only group objects and retrieves just their id and displayName
580- const url = `https://graph.microsoft.com/v1.0/servicePrincipals/${ servicePrincipal } /ownedObjects/microsoft.graph.group?$select=id,displayName` ;
579+ // Include groupTypes in selection to filter dynamic groups if needed
580+ const selectFields = includeDynamicGroups
581+ ? "id,displayName,description"
582+ : "id,displayName,description,groupTypes" ;
583+
584+ const url = `https://graph.microsoft.com/v1.0/servicePrincipals/${ servicePrincipal } /ownedObjects/microsoft.graph.group?$select=${ selectFields } ` ;
581585
582586 const response = await fetch ( url , {
583587 method : "GET" ,
@@ -589,9 +593,26 @@ export async function getServicePrincipalOwnedGroups(
589593
590594 if ( response . ok ) {
591595 const data = ( await response . json ( ) ) as {
592- value : { id : string ; displayName : string } [ ] ;
596+ value : {
597+ id : string ;
598+ displayName : string ;
599+ groupTypes ?: string [ ] ;
600+ description ?: string ;
601+ } [ ] ;
593602 } ;
594- return data . value ;
603+
604+ // Filter out dynamic groups and admin lists if includeDynamicGroups is false
605+ const groups = includeDynamicGroups
606+ ? data . value
607+ : data . value
608+ . filter ( ( group ) => ! group . groupTypes ?. includes ( "DynamicMembership" ) )
609+ . filter (
610+ ( group ) =>
611+ ! group . description ?. startsWith ( "[Managed by Core API]" ) ,
612+ ) ;
613+
614+ // Return only id and displayName (strip groupTypes if it was included)
615+ return groups . map ( ( { id, displayName } ) => ( { id, displayName } ) ) ;
595616 }
596617
597618 const errorData = ( await response . json ( ) ) as {
@@ -813,9 +834,11 @@ export async function createM365Group(
813834 mailEnabled : boolean ;
814835 securityEnabled : boolean ;
815836 groupTypes : string [ ] ;
837+ description : string ;
816838817839 } = {
818840 displayName : groupName ,
841+ description : "[Managed by Core API]" ,
819842 mailNickname : safeMailNickname ,
820843 mailEnabled : true ,
821844 securityEnabled : false ,
@@ -898,3 +921,66 @@ export async function createM365Group(
898921 } ) ;
899922 }
900923}
924+
925+ /**
926+ * Sets the dynamic membership rule for an Entra ID group.
927+ * @param token - Entra ID token authorized to take this action.
928+ * @param groupId - The group ID to update.
929+ * @param membershipRule - The dynamic membership rule expression.
930+ * @throws {EntraGroupError } If setting the membership rule fails.
931+ * @returns {Promise<void> }
932+ */
933+ export async function setGroupMembershipRule (
934+ token : string ,
935+ groupId : string ,
936+ membershipRule : string ,
937+ ) : Promise < void > {
938+ if ( ! validateGroupId ( groupId ) ) {
939+ throw new EntraGroupError ( {
940+ message : "Invalid group ID format" ,
941+ group : groupId ,
942+ } ) ;
943+ }
944+
945+ if ( ! membershipRule || membershipRule . trim ( ) . length === 0 ) {
946+ throw new EntraGroupError ( {
947+ message : "Membership rule cannot be empty" ,
948+ group : groupId ,
949+ } ) ;
950+ }
951+
952+ try {
953+ const url = `https://graph.microsoft.com/v1.0/groups/${ groupId } ` ;
954+ const response = await fetch ( url , {
955+ method : "PATCH" ,
956+ headers : {
957+ Authorization : `Bearer ${ token } ` ,
958+ "Content-Type" : "application/json" ,
959+ } ,
960+ body : JSON . stringify ( {
961+ membershipRule,
962+ membershipRuleProcessingState : "On" ,
963+ groupTypes : [ "DynamicMembership" ] ,
964+ } ) ,
965+ } ) ;
966+
967+ if ( ! response . ok ) {
968+ const errorData = ( await response . json ( ) ) as {
969+ error ?: { message ?: string } ;
970+ } ;
971+ throw new EntraGroupError ( {
972+ message : errorData ?. error ?. message ?? response . statusText ,
973+ group : groupId ,
974+ } ) ;
975+ }
976+ } catch ( error ) {
977+ if ( error instanceof EntraGroupError ) {
978+ throw error ;
979+ }
980+
981+ throw new EntraGroupError ( {
982+ message : error instanceof Error ? error . message : String ( error ) ,
983+ group : groupId ,
984+ } ) ;
985+ }
986+ }
0 commit comments