diff --git a/GraphClient.go b/GraphClient.go index d60720e..d829bf0 100644 --- a/GraphClient.go +++ b/GraphClient.go @@ -132,6 +132,11 @@ func (g *GraphClient) refreshToken() error { return err } +// GetToken returns a copy the currently token used by this GraphClient instance. +func (g *GraphClient) GetToken() Token { + return g.token +} + // makeGETAPICall performs an API-Call to the msgraph API. func (g *GraphClient) makeGETAPICall(apiCall string, reqParams getRequestParams, v interface{}) error { return g.makeAPICall(apiCall, http.MethodGet, reqParams, nil, v) @@ -356,6 +361,29 @@ func (g *GraphClient) ListGroups(opts ...ListQueryOption) (Groups, error) { return marsh.Groups, err } +// getMemberGroups returns a list of all group IDs the user is a member of. +// You can specify the securityGroupsEnabled parameter to only return security group IDs. +// +// Reference: https://docs.microsoft.com/en-us/graph/api/directoryobject-getmembergroups?view=graph-rest-1.0&tabs=http +func (g *GraphClient) getMemberGroups(identifier string, securityEnabledOnly bool, opts ...GetQueryOption) ([]string, error) { + resource := fmt.Sprintf("/directoryObjects/%v/getMemberGroups", identifier) + var post struct { + SecurityEnabledOnly bool `json:"securityEnabledOnly"` + } + var marsh struct { + Groups []string `json:"value"` + } + post.SecurityEnabledOnly = securityEnabledOnly + bodyBytes, err := json.Marshal(post) + if err != nil { + return marsh.Groups, err + } + body := bytes.NewReader(bodyBytes) + + err = g.makePOSTAPICall(resource, compileGetQueryOptions(opts), body, &marsh) + return marsh.Groups, err +} + // GetUser returns the user object associated to the given user identified by either // the given ID or userPrincipalName // Supports optional OData query parameters https://docs.microsoft.com/en-us/graph/query-parameters diff --git a/Group.go b/Group.go index f6ab86a..96df753 100644 --- a/Group.go +++ b/Group.go @@ -57,6 +57,37 @@ func (g Group) ListMembers(opts ...ListQueryOption) (Users, error) { return marsh.Users, g.graphClient.makeGETAPICall(resource, compileListQueryOptions(opts), &marsh) } +// Get a list of the group's members. A group can have users, devices, organizational contacts, and other groups as members. +// This operation is transitive and returns a flat list of all nested members. +// This method will currently ONLY return User-instances of members +// Supports optional OData query parameters https://docs.microsoft.com/en-us/graph/query-parameters +// +// See https://docs.microsoft.com/en-us/graph/api/group-list-transitivemembers?view=graph-rest-1.0&tabs=http +func (g Group) ListTransitiveMembers(opts ...ListQueryOption) (Users, error) { + if g.graphClient == nil { + return nil, ErrNotGraphClientSourced + } + resource := fmt.Sprintf("/groups/%v/transitiveMembers", g.ID) + + var marsh struct { + Users Users `json:"value"` + } + marsh.Users.setGraphClient(g.graphClient) + return marsh.Users, g.graphClient.makeGETAPICall(resource, compileListQueryOptions(opts), &marsh) +} + +// GetMemberGroupsAsStrings returns a list of all group IDs the user is a member of. +// +// opts ...GetQueryOption - only msgraph.GetWithContext is supported. +// +// Reference: https://docs.microsoft.com/en-us/graph/api/directoryobject-getmembergroups?view=graph-rest-1.0&tabs=http +func (g Group) GetMemberGroupsAsStrings(opts ...GetQueryOption) ([]string, error) { + if g.graphClient == nil { + return nil, ErrNotGraphClientSourced + } + return g.graphClient.getMemberGroups(g.ID, false, opts...) // securityEnabledOnly is not supported for Groups, see documentation / API-reference +} + // UnmarshalJSON implements the json unmarshal to be used by the json-library func (g *Group) UnmarshalJSON(data []byte) error { tmp := struct { diff --git a/Group_test.go b/Group_test.go index dbd6031..14276f0 100644 --- a/Group_test.go +++ b/Group_test.go @@ -82,3 +82,72 @@ func TestGroup_String(t *testing.T) { }) } } + +func TestGroup_ListTransitiveMembers(t *testing.T) { + testGroup := GetTestGroup(t) + + tests := []struct { + name string + g Group + wantErr bool + }{ + { + name: "GraphClient created Group", + g: testGroup, + wantErr: false, + }, { + name: "Not GraphClient created Group", + g: Group{DisplayName: "Test not GraphClient sourced"}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.g.ListTransitiveMembers() + if (err != nil) != tt.wantErr { + t.Errorf("Group.ListTransitiveMembers() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr && len(got) == 0 { + t.Errorf("Group.ListTransitiveMembers() = %v, len(%d), want at least one member of that group", got, len(got)) + } + }) + } +} + +func TestGroup_GetMemberGroupsAsStrings(t *testing.T) { + testGroup := GetTestGroup(t) + + tests := []struct { + name string + g Group + opts []GetQueryOption + wantErr bool + }{ + { + name: "Test group func GetMembershipGroupsAsStrings", + g: testGroup, + wantErr: false, + }, { + name: "Test group func GetMembershipGroupsAsStrings - no securityGroupsEnabeledF", + g: testGroup, + wantErr: false, + }, + { + name: "Group not initialized by GraphClient", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.g.GetMemberGroupsAsStrings(tt.opts...) + if (err != nil) != tt.wantErr { + t.Errorf("Group.GetMemberGroupsAsStrings() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr && len(got) == 0 { + t.Errorf("Group.GetMemberGroupsAsStrings() = %v, len(%d), want at least one value", got, len(got)) + } + }) + } +} diff --git a/User.go b/User.go index a23b92c..5e28d7d 100644 --- a/User.go +++ b/User.go @@ -145,6 +145,19 @@ func (u User) GetFullName() string { return fmt.Sprintf("%v %v", u.GivenName, u.Surname) } +// GetMemberGroupsAsStrings returns a list of all group IDs the user is a member of. +// You can specify the securityGroupsEnabeled parameter to only return security group IDs. +// +// opts ...GetQueryOption - only msgraph.GetWithContext is supported. +// +// Reference: https://docs.microsoft.com/en-us/graph/api/directoryobject-getmembergroups?view=graph-rest-1.0&tabs=http +func (u User) GetMemberGroupsAsStrings(securityGroupsEnabeled bool, opts ...GetQueryOption) ([]string, error) { + if u.graphClient == nil { + return nil, ErrNotGraphClientSourced + } + return u.graphClient.getMemberGroups(u.ID, securityGroupsEnabeled, opts...) +} + // PrettySimpleString returns the User-instance simply formatted for logging purposes: {FullName (email) (activePhone)} func (u User) PrettySimpleString() string { return fmt.Sprintf("{ %v (%v) (%v) }", u.GetFullName(), u.Mail, u.GetActivePhone()) diff --git a/User_test.go b/User_test.go index e13b1fb..1baf34c 100644 --- a/User_test.go +++ b/User_test.go @@ -166,6 +166,49 @@ func TestUser_String(t *testing.T) { }) } +func TestUser_GetMemberGroupsAsStrings(t *testing.T) { + u := GetTestUser(t) + tests := []struct { + name string + u User + securityGroupsEnabeled bool + opt GetQueryOption + wantErr bool + }{ + { + name: "Test user func GetMembershipGroupsAsStrings", + u: u, + securityGroupsEnabeled: true, + opt: GetWithContext(nil), + wantErr: false, + }, { + name: "Test user func GetMembershipGroupsAsStrings - no securityGroupsEnabeledF", + u: u, + securityGroupsEnabeled: false, + opt: GetWithContext(nil), + wantErr: false, + }, + { + name: "User not initialized by GraphClient", + securityGroupsEnabeled: true, + opt: GetWithContext(nil), + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.u.GetMemberGroupsAsStrings(tt.securityGroupsEnabeled, tt.opt) + if (err != nil) != tt.wantErr { + t.Errorf("User.GetMemberGroupsAsStrings() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr && len(got) == 0 { + t.Errorf("User.GetMemberGroupsAsStrings() = %v, len(%d), want at least one value", got, len(got)) + } + }) + } +} + func TestUser_UpdateUser(t *testing.T) { // testing for ErrNotGraphClientSourced notGraphClientSourcedUser := User{ID: "none"}