@@ -12,6 +12,7 @@ import (
1212 azauth "github.com/microsoft/kiota-authentication-azure-go"
1313 msgraphsdk "github.com/microsoftgraph/msgraph-sdk-go"
1414 "github.com/microsoftgraph/msgraph-sdk-go/groups"
15+ "github.com/microsoftgraph/msgraph-sdk-go/models"
1516 "github.com/microsoftgraph/msgraph-sdk-go/serviceprincipals"
1617 "github.com/microsoftgraph/msgraph-sdk-go/users"
1718 "github.com/upbound/function-msgraph/input/v1beta1"
@@ -282,8 +283,8 @@ func getCreds(req *fnv1.RunFunctionRequest) (map[string]string, error) {
282283// that interacts with Microsoft Graph API.
283284type GraphQuery struct {}
284285
285- // graphQuery is a concrete implementation that interacts with Microsoft Graph API.
286- func (g * GraphQuery ) graphQuery ( ctx context. Context , azureCreds map [string ]string , in * v1beta1. Input ) (interface {} , error ) {
286+ // createGraphClient initializes a Microsoft Graph client using the provided credentials
287+ func (g * GraphQuery ) createGraphClient ( azureCreds map [string ]string ) (* msgraphsdk. GraphServiceClient , error ) {
287288 tenantID := azureCreds ["tenantId" ]
288289 clientID := azureCreds ["clientId" ]
289290 clientSecret := azureCreds ["clientSecret" ]
@@ -307,7 +308,24 @@ func (g *GraphQuery) graphQuery(ctx context.Context, azureCreds map[string]strin
307308 }
308309
309310 // Initialize Microsoft Graph client
310- client := msgraphsdk .NewGraphServiceClient (adapter )
311+ return msgraphsdk .NewGraphServiceClient (adapter ), nil
312+ }
313+
314+ // validateCustomQuery validates a custom query input
315+ func (g * GraphQuery ) validateCustomQuery (in * v1beta1.Input ) error {
316+ if in .CustomQuery == nil || * in .CustomQuery == "" {
317+ return errors .New ("custom query is empty" )
318+ }
319+ return errors .New ("custom queries not implemented" )
320+ }
321+
322+ // graphQuery is a concrete implementation that interacts with Microsoft Graph API.
323+ func (g * GraphQuery ) graphQuery (ctx context.Context , azureCreds map [string ]string , in * v1beta1.Input ) (interface {}, error ) {
324+ // Create the Microsoft Graph client
325+ client , err := g .createGraphClient (azureCreds )
326+ if err != nil {
327+ return nil , err
328+ }
311329
312330 // Route based on query type
313331 switch in .QueryType {
@@ -320,11 +338,7 @@ func (g *GraphQuery) graphQuery(ctx context.Context, azureCreds map[string]strin
320338 case "ServicePrincipalDetails" :
321339 return g .getServicePrincipalDetails (ctx , client , in )
322340 case "CustomQuery" :
323- if in .CustomQuery == nil || * in .CustomQuery == "" {
324- return nil , errors .New ("custom query is empty" )
325- }
326- // Custom queries not supported yet
327- return nil , errors .New ("custom queries not implemented" )
341+ return nil , g .validateCustomQuery (in )
328342 default :
329343 return nil , errors .Errorf ("unsupported query type: %s" , in .QueryType )
330344 }
@@ -378,124 +392,188 @@ func (g *GraphQuery) validateUsers(ctx context.Context, client *msgraphsdk.Graph
378392 return results , nil
379393}
380394
381- // getGroupMembers retrieves all members of the specified group
382- func (g * GraphQuery ) getGroupMembers (ctx context.Context , client * msgraphsdk.GraphServiceClient , in * v1beta1.Input ) (interface {}, error ) {
383- if in .Group == nil || * in .Group == "" {
384- return nil , errors .New ("no group name provided" )
385- }
386-
387- // First, find the group by displayName
388- filterValue := fmt .Sprintf ("displayName eq '%s'" , * in .Group )
395+ // findGroupByName finds a group by its display name and returns its ID
396+ func (g * GraphQuery ) findGroupByName (ctx context.Context , client * msgraphsdk.GraphServiceClient , groupName string ) (* string , error ) {
397+ // Create filter by displayName
398+ filterValue := fmt .Sprintf ("displayName eq '%s'" , groupName )
389399 groupRequestConfig := & groups.GroupsRequestBuilderGetRequestConfiguration {
390400 QueryParameters : & groups.GroupsRequestBuilderGetQueryParameters {
391401 Filter : & filterValue ,
392402 },
393403 }
394404
405+ // Query for the group
395406 groupResult , err := client .Groups ().Get (ctx , groupRequestConfig )
396407 if err != nil {
397408 return nil , errors .Wrap (err , "failed to find group" )
398409 }
399410
411+ // Verify we found a group
400412 if groupResult .GetValue () == nil || len (groupResult .GetValue ()) == 0 {
401- return nil , errors .Errorf ("group not found: %s" , * in . Group )
413+ return nil , errors .Errorf ("group not found: %s" , groupName )
402414 }
403415
404- // Get the group ID
405- groupID := groupResult .GetValue ()[0 ].GetId ()
416+ // Return the group ID
417+ return groupResult .GetValue ()[0 ].GetId (), nil
418+ }
406419
407- // Now get the members
420+ // fetchGroupMembers fetches all members of a group by group ID
421+ func (g * GraphQuery ) fetchGroupMembers (ctx context.Context , client * msgraphsdk.GraphServiceClient , groupID string , groupName string ) ([]models.DirectoryObjectable , error ) {
422+ // Configure the members request
408423 membersRequestConfig := & groups.ItemMembersRequestBuilderGetRequestConfiguration {
409424 QueryParameters : & groups.ItemMembersRequestBuilderGetQueryParameters {},
410425 }
411426
412- // Use standard fields for group membership
427+ // Select the fields we want
413428 membersRequestConfig .QueryParameters .Select = []string {
414429 "id" , "displayName" , "mail" , "userPrincipalName" ,
415430 "appId" , "description" ,
416431 }
417432
418- // We'll skip expansion for now as it causes API errors
419- // DirectoryObject type doesn't support expansion with 'memberOf'
420- // If we need more data, we'll have to make separate calls for each member
421-
422- // Use the dereferenced groupID (convert *string to string)
423- result , err := client .Groups ().ByGroupId (* groupID ).Members ().Get (ctx , membersRequestConfig )
433+ // Get the members
434+ result , err := client .Groups ().ByGroupId (groupID ).Members ().Get (ctx , membersRequestConfig )
424435 if err != nil {
425- return nil , errors .Wrapf (err , "failed to get members for group %s" , * in . Group )
436+ return nil , errors .Wrapf (err , "failed to get members for group %s" , groupName )
426437 }
427438
428- // No verbose debug logging in production code
429-
430- members := make ([]interface {}, 0 , len (result .GetValue ()))
439+ return result .GetValue (), nil
440+ }
431441
432- for _ , member := range result .GetValue () {
433- // Process member data
442+ // extractDisplayName attempts to extract the display name from a directory object
443+ func (g * GraphQuery ) extractDisplayName (member models.DirectoryObjectable , memberID string ) string {
444+ additionalData := member .GetAdditionalData ()
434445
435- memberType := "unknown"
436- memberMap := map [string ]interface {}{
437- "id" : member .GetId (),
446+ // Try to get from additional data first
447+ if displayNameVal , exists := additionalData ["displayName" ]; exists && displayNameVal != nil {
448+ if displayName , ok := displayNameVal .(string ); ok {
449+ return displayName
438450 }
451+ }
439452
440- // Try to extract displayName more carefully
441- additionalData := member .GetAdditionalData ()
442- if displayNameVal , exists := additionalData ["displayName" ]; exists && displayNameVal != nil {
443- // Debug output removed
444- if displayName , ok := displayNameVal .(string ); ok {
445- memberMap ["displayName" ] = displayName
446- }
447- } else {
448- memberValue := reflect .ValueOf (member )
449- displayNameMethod := memberValue .MethodByName ("GetDisplayName" )
450- if displayNameMethod .IsValid () && displayNameMethod .Type ().NumIn () == 0 {
451- // Debug output removed
452- results := displayNameMethod .Call (nil )
453- if len (results ) > 0 && ! results [0 ].IsNil () {
454- // Check if the result is a *string
455- if displayNamePtr , ok := results [0 ].Interface ().(* string ); ok && displayNamePtr != nil {
456- memberMap ["displayName" ] = * displayNamePtr
457- }
458- }
453+ // Try to use reflection to call GetDisplayName if it exists
454+ memberValue := reflect .ValueOf (member )
455+ displayNameMethod := memberValue .MethodByName ("GetDisplayName" )
456+ if displayNameMethod .IsValid () && displayNameMethod .Type ().NumIn () == 0 {
457+ results := displayNameMethod .Call (nil )
458+ if len (results ) > 0 && ! results [0 ].IsNil () {
459+ // Check if the result is a *string
460+ if displayNamePtr , ok := results [0 ].Interface ().(* string ); ok && displayNamePtr != nil {
461+ return * displayNamePtr
459462 }
463+ }
464+ }
460465
461- // If still no displayName, use a placeholder
462- if _ , hasDisplayName := memberMap ["displayName" ]; ! hasDisplayName {
463- // Debug output removed
464- memberMap ["displayName" ] = fmt .Sprintf ("Member %s" , * member .GetId ())
465- }
466+ // Use fallback display name
467+ return fmt .Sprintf ("Member %s" , memberID )
468+ }
469+
470+ // extractStringProperty safely extracts a string property from additionalData
471+ func (g * GraphQuery ) extractStringProperty (additionalData map [string ]interface {}, key string ) (string , bool ) {
472+ if val , exists := additionalData [key ]; exists && val != nil {
473+ if strVal , ok := val .(string ); ok {
474+ return strVal , true
466475 }
476+ }
477+ return "" , false
478+ }
467479
468- // Extract other properties more safely
469- if odataTypeVal , exists := additionalData ["@odata.type" ]; exists && odataTypeVal != nil {
470- if odataType , ok := odataTypeVal .(string ); ok {
471- switch {
472- case strings .Contains (odataType , "user" ):
473- memberType = "user"
474- // Extract user-specific properties more safely
475- if mailVal , exists := additionalData ["mail" ]; exists && mailVal != nil {
476- if mail , ok := mailVal .(string ); ok {
477- memberMap ["mail" ] = mail
478- }
479- }
480- if upnVal , exists := additionalData ["userPrincipalName" ]; exists && upnVal != nil {
481- if upn , ok := upnVal .(string ); ok {
482- memberMap ["userPrincipalName" ] = upn
483- }
484- }
485- case strings .Contains (odataType , "servicePrincipal" ):
486- memberType = "servicePrincipal"
487- if appIDVal , exists := additionalData ["appId" ]; exists && appIDVal != nil {
488- if appID , ok := appIDVal .(string ); ok {
489- memberMap ["appId" ] = appID
490- }
491- }
492- case strings .Contains (odataType , "group" ):
493- memberType = "group"
494- }
495- memberMap ["type" ] = memberType
496- }
480+ // extractUserProperties extracts user-specific properties from additionalData
481+ func (g * GraphQuery ) extractUserProperties (additionalData map [string ]interface {}, memberMap map [string ]interface {}) {
482+ // Extract mail property
483+ if mail , ok := g .extractStringProperty (additionalData , "mail" ); ok {
484+ memberMap ["mail" ] = mail
485+ }
486+
487+ // Extract userPrincipalName property
488+ if upn , ok := g .extractStringProperty (additionalData , "userPrincipalName" ); ok {
489+ memberMap ["userPrincipalName" ] = upn
490+ }
491+ }
492+
493+ // extractServicePrincipalProperties extracts service principal specific properties
494+ func (g * GraphQuery ) extractServicePrincipalProperties (additionalData map [string ]interface {}, memberMap map [string ]interface {}) {
495+ // Extract appId property
496+ if appID , ok := g .extractStringProperty (additionalData , "appId" ); ok {
497+ memberMap ["appId" ] = appID
498+ }
499+ }
500+
501+ // getMemberType determines the type of member based on odata.type
502+ func (g * GraphQuery ) getMemberType (odataType string ) string {
503+ switch {
504+ case strings .Contains (odataType , "user" ):
505+ return "user"
506+ case strings .Contains (odataType , "servicePrincipal" ):
507+ return "servicePrincipal"
508+ case strings .Contains (odataType , "group" ):
509+ return "group"
510+ default :
511+ return "unknown"
512+ }
513+ }
514+
515+ // extractMemberProperties extracts relevant properties based on the member type
516+ func (g * GraphQuery ) extractMemberProperties (additionalData map [string ]interface {}, memberMap map [string ]interface {}) string {
517+ memberType := "unknown"
518+
519+ // Determine type from @odata.type
520+ odataType , ok := g .extractStringProperty (additionalData , "@odata.type" )
521+ if ! ok {
522+ return memberType
523+ }
524+
525+ // Get the member type
526+ memberType = g .getMemberType (odataType )
527+
528+ // Extract type-specific properties
529+ switch memberType {
530+ case "user" :
531+ g .extractUserProperties (additionalData , memberMap )
532+ case "servicePrincipal" :
533+ g .extractServicePrincipalProperties (additionalData , memberMap )
534+ }
535+
536+ return memberType
537+ }
538+
539+ // getGroupMembers retrieves all members of the specified group
540+ func (g * GraphQuery ) getGroupMembers (ctx context.Context , client * msgraphsdk.GraphServiceClient , in * v1beta1.Input ) (interface {}, error ) {
541+ // Validate input
542+ if in .Group == nil || * in .Group == "" {
543+ return nil , errors .New ("no group name provided" )
544+ }
545+
546+ // Find the group
547+ groupID , err := g .findGroupByName (ctx , client , * in .Group )
548+ if err != nil {
549+ return nil , err
550+ }
551+
552+ // Fetch the members
553+ memberObjects , err := g .fetchGroupMembers (ctx , client , * groupID , * in .Group )
554+ if err != nil {
555+ return nil , err
556+ }
557+
558+ // Process the members
559+ members := make ([]interface {}, 0 , len (memberObjects ))
560+
561+ for _ , member := range memberObjects {
562+ memberID := member .GetId ()
563+ additionalData := member .GetAdditionalData ()
564+
565+ // Create basic member info
566+ memberMap := map [string ]interface {}{
567+ "id" : memberID ,
497568 }
498569
570+ // Extract display name
571+ memberMap ["displayName" ] = g .extractDisplayName (member , * memberID )
572+
573+ // Extract type-specific properties
574+ memberType := g .extractMemberProperties (additionalData , memberMap )
575+ memberMap ["type" ] = memberType
576+
499577 members = append (members , memberMap )
500578 }
501579
0 commit comments