@@ -10,7 +10,6 @@ import {
10
10
} from "../../common/config.js" ;
11
11
import {
12
12
BaseError ,
13
- DecryptionError ,
14
13
EntraFetchError ,
15
14
EntraGroupError ,
16
15
EntraGroupsFromEmailError ,
@@ -574,10 +573,15 @@ export async function isUserInGroup(
574
573
export async function getServicePrincipalOwnedGroups (
575
574
token : string ,
576
575
servicePrincipal : string ,
576
+ includeDynamicGroups : boolean ,
577
577
) : Promise < { id : string ; displayName : string } [ ] > {
578
578
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 } ` ;
581
585
582
586
const response = await fetch ( url , {
583
587
method : "GET" ,
@@ -589,9 +593,26 @@ export async function getServicePrincipalOwnedGroups(
589
593
590
594
if ( response . ok ) {
591
595
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
+ } [ ] ;
593
602
} ;
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 } ) ) ;
595
616
}
596
617
597
618
const errorData = ( await response . json ( ) ) as {
@@ -813,9 +834,11 @@ export async function createM365Group(
813
834
mailEnabled : boolean ;
814
835
securityEnabled : boolean ;
815
836
groupTypes : string [ ] ;
837
+ description : string ;
816
838
817
839
} = {
818
840
displayName : groupName ,
841
+ description : "[Managed by Core API]" ,
819
842
mailNickname : safeMailNickname ,
820
843
mailEnabled : true ,
821
844
securityEnabled : false ,
@@ -898,3 +921,66 @@ export async function createM365Group(
898
921
} ) ;
899
922
}
900
923
}
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