Skip to content

Commit be44301

Browse files
committed
Refacto to adhere to low cyclomatic complexity
Signed-off-by: Yury Tsarev <[email protected]>
1 parent a3653b2 commit be44301

File tree

1 file changed

+167
-89
lines changed

1 file changed

+167
-89
lines changed

fn.go

Lines changed: 167 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
283284
type 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

Comments
 (0)