From bb86610488251923d91ecc9c1e9530fa1a543191 Mon Sep 17 00:00:00 2001 From: jules <130390278+juleslasarte@users.noreply.github.com> Date: Fri, 10 Nov 2023 15:13:47 -0800 Subject: [PATCH] Getlicenseinfo (#4655) Adds a call to get user total count of licenses. Also corrects one test ID which i hope doesn't break E2E tests (although it should if it does, ha) --- #### Does this PR need a docs update or release note? - [ ] :white_check_mark: Yes, it's included - [ ] :clock1: Yes, but in a later PR - [x] :no_entry: No #### Type of change - [x] :sunflower: Feature - [ ] :bug: Bugfix - [ ] :world_map: Documentation - [ ] :robot: Supportability/Tests - [ ] :computer: CI/Deployment - [ ] :broom: Tech Debt/Cleanup #### Issue(s) * # #### Test Plan - [x] :muscle: Manual - [x] :zap: Unit test - [x] :green_heart: E2E --- src/internal/m365/onedrive_test.go | 18 ++++---- src/internal/operations/test/onedrive_test.go | 5 ++- .../tester/tconfig/protected_resources.go | 2 +- src/pkg/services/m365/api/config.go | 4 ++ src/pkg/services/m365/api/users.go | 26 ++++++++--- src/pkg/services/m365/users.go | 26 +++++++++++ src/pkg/services/m365/users_test.go | 45 +++++++++++++++++++ 7 files changed, 109 insertions(+), 17 deletions(-) diff --git a/src/internal/m365/onedrive_test.go b/src/internal/m365/onedrive_test.go index 4b9e86ea2..fb591e1c0 100644 --- a/src/internal/m365/onedrive_test.go +++ b/src/internal/m365/onedrive_test.go @@ -186,15 +186,15 @@ func (suite *SharePointIntegrationSuite) SetupSuite() { si := NewSuiteInfoImpl(suite.T(), ctx, tconfig.M365SiteID(suite.T()), path.SharePointService) // users needed for permissions - user, err := si.controller.AC.Users().GetByID(ctx, si.user) + user, err := si.controller.AC.Users().GetByID(ctx, si.user, api.CallConfig{}) require.NoError(t, err, "fetching user", si.user, clues.ToCore(err)) si.userID = ptr.Val(user.GetId()) - secondaryUser, err := si.controller.AC.Users().GetByID(ctx, si.secondaryUser) + secondaryUser, err := si.controller.AC.Users().GetByID(ctx, si.secondaryUser, api.CallConfig{}) require.NoError(t, err, "fetching user", si.secondaryUser, clues.ToCore(err)) si.secondaryUserID = ptr.Val(secondaryUser.GetId()) - tertiaryUser, err := si.controller.AC.Users().GetByID(ctx, si.tertiaryUser) + tertiaryUser, err := si.controller.AC.Users().GetByID(ctx, si.tertiaryUser, api.CallConfig{}) require.NoError(t, err, "fetching user", si.tertiaryUser, clues.ToCore(err)) si.tertiaryUserID = ptr.Val(tertiaryUser.GetId()) @@ -255,15 +255,15 @@ func (suite *OneDriveIntegrationSuite) SetupSuite() { si := NewSuiteInfoImpl(t, ctx, tconfig.M365UserID(t), path.OneDriveService) - user, err := si.controller.AC.Users().GetByID(ctx, si.user) + user, err := si.controller.AC.Users().GetByID(ctx, si.user, api.CallConfig{}) require.NoError(t, err, "fetching user", si.user, clues.ToCore(err)) si.userID = ptr.Val(user.GetId()) - secondaryUser, err := si.controller.AC.Users().GetByID(ctx, si.secondaryUser) + secondaryUser, err := si.controller.AC.Users().GetByID(ctx, si.secondaryUser, api.CallConfig{}) require.NoError(t, err, "fetching user", si.secondaryUser, clues.ToCore(err)) si.secondaryUserID = ptr.Val(secondaryUser.GetId()) - tertiaryUser, err := si.controller.AC.Users().GetByID(ctx, si.tertiaryUser) + tertiaryUser, err := si.controller.AC.Users().GetByID(ctx, si.tertiaryUser, api.CallConfig{}) require.NoError(t, err, "fetching user", si.tertiaryUser, clues.ToCore(err)) si.tertiaryUserID = ptr.Val(tertiaryUser.GetId()) @@ -319,15 +319,15 @@ func (suite *OneDriveNightlySuite) SetupSuite() { si := NewSuiteInfoImpl(t, ctx, tconfig.M365UserID(t), path.OneDriveService) - user, err := si.controller.AC.Users().GetByID(ctx, si.user) + user, err := si.controller.AC.Users().GetByID(ctx, si.user, api.CallConfig{}) require.NoError(t, err, "fetching user", si.user, clues.ToCore(err)) si.userID = ptr.Val(user.GetId()) - secondaryUser, err := si.controller.AC.Users().GetByID(ctx, si.secondaryUser) + secondaryUser, err := si.controller.AC.Users().GetByID(ctx, si.secondaryUser, api.CallConfig{}) require.NoError(t, err, "fetching user", si.secondaryUser, clues.ToCore(err)) si.secondaryUserID = ptr.Val(secondaryUser.GetId()) - tertiaryUser, err := si.controller.AC.Users().GetByID(ctx, si.tertiaryUser) + tertiaryUser, err := si.controller.AC.Users().GetByID(ctx, si.tertiaryUser, api.CallConfig{}) require.NoError(t, err, "fetching user", si.tertiaryUser, clues.ToCore(err)) si.tertiaryUserID = ptr.Val(tertiaryUser.GetId()) diff --git a/src/internal/operations/test/onedrive_test.go b/src/internal/operations/test/onedrive_test.go index 6cda5b601..16d3a6518 100644 --- a/src/internal/operations/test/onedrive_test.go +++ b/src/internal/operations/test/onedrive_test.go @@ -1139,7 +1139,10 @@ func (suite *OneDriveBackupIntgSuite) TestBackup_Run_oneDriveOwnerMigration() { counter) require.NoError(t, err, clues.ToCore(err)) - userable, err := ctrl.AC.Users().GetByID(ctx, suite.its.user.ID) + userable, err := ctrl.AC.Users().GetByID( + ctx, + suite.its.user.ID, + api.CallConfig{}) require.NoError(t, err, clues.ToCore(err)) uid := ptr.Val(userable.GetId()) diff --git a/src/internal/tester/tconfig/protected_resources.go b/src/internal/tester/tconfig/protected_resources.go index 7b700cf60..9200b024e 100644 --- a/src/internal/tester/tconfig/protected_resources.go +++ b/src/internal/tester/tconfig/protected_resources.go @@ -245,7 +245,7 @@ func UnlicensedM365UserID(t *testing.T) string { cfg, err := ReadTestConfig() require.NoError(t, err, "retrieving unlicensed m365 user id from test configuration: %+v", clues.ToCore(err)) - return strings.ToLower(cfg[TestCfgSecondaryUserID]) + return strings.ToLower(cfg[TestCfgUnlicensedUserID]) } // Teams diff --git a/src/pkg/services/m365/api/config.go b/src/pkg/services/m365/api/config.go index 96d59da2c..a2bc39cdd 100644 --- a/src/pkg/services/m365/api/config.go +++ b/src/pkg/services/m365/api/config.go @@ -86,6 +86,10 @@ func newEventualConsistencyHeaders() *abstractions.RequestHeaders { return headers } +func SelectProps(ss ...string) []string { + return idAnd(ss...) +} + // makes a slice with []string{"id", s...} func idAnd(ss ...string) []string { id := []string{"id"} diff --git a/src/pkg/services/m365/api/users.go b/src/pkg/services/m365/api/users.go index a01eae34f..c7489b423 100644 --- a/src/pkg/services/m365/api/users.go +++ b/src/pkg/services/m365/api/users.go @@ -112,15 +112,29 @@ func (c Users) GetAll( return us, el.Failure() } -// GetByID looks up the user matching the given identifier. The identifier can be either a -// canonical user id or a princpalName. -func (c Users) GetByID(ctx context.Context, identifier string) (models.Userable, error) { +func (c Users) GetByID( + ctx context.Context, + identifier string, + cc CallConfig, +) (models.Userable, error) { var ( resp models.Userable err error ) - resp, err = c.Stable.Client().Users().ByUserId(identifier).Get(ctx, nil) + options := &users.UserItemRequestBuilderGetRequestConfiguration{ + QueryParameters: &users.UserItemRequestBuilderGetQueryParameters{}, + } + + if len(cc.Select) > 0 { + options.QueryParameters.Select = cc.Select + } + + resp, err = c.Stable. + Client(). + Users(). + ByUserId(identifier). + Get(ctx, options) if err != nil { if graph.IsErrResourceLocked(err) { err = clues.Stack(graph.ErrResourceLocked, err) @@ -137,9 +151,9 @@ func (c Users) GetByID(ctx context.Context, identifier string) (models.Userable, func (c Users) GetIDAndName( ctx context.Context, userID string, - _ CallConfig, // not currently supported + cc CallConfig, ) (string, string, error) { - u, err := c.GetByID(ctx, userID) + u, err := c.GetByID(ctx, userID, cc) if err != nil { return "", "", err } diff --git a/src/pkg/services/m365/users.go b/src/pkg/services/m365/users.go index 87794478d..44ba850ac 100644 --- a/src/pkg/services/m365/users.go +++ b/src/pkg/services/m365/users.go @@ -2,6 +2,7 @@ package m365 import ( "context" + "fmt" "github.com/alcionai/clues" "github.com/microsoftgraph/msgraph-sdk-go/models" @@ -104,6 +105,31 @@ func usersNoInfo(ctx context.Context, acct account.Account, errs *fault.Bus) ([] return ret, nil } +func UserAssignedLicenses(ctx context.Context, acct account.Account, userID string) (int, error) { + ac, err := makeAC(ctx, acct, path.UnknownService) + if err != nil { + return 0, clues.Stack(err).WithClues(ctx) + } + + us, err := ac.Users().GetByID( + ctx, + userID, + api.CallConfig{Select: api.SelectProps("assignedLicenses")}) + if err != nil { + return 0, err + } + + if us.GetAssignedLicenses() != nil { + for _, license := range us.GetAssignedLicenses() { + fmt.Println(license.GetSkuId()) + } + + return len(us.GetAssignedLicenses()), nil + } + + return 0, clues.New("user missing assigned licenses") +} + // parseUser extracts information from `models.Userable` we care about func parseUser(item models.Userable) (*UserNoInfo, error) { if item.GetUserPrincipalName() == nil { diff --git a/src/pkg/services/m365/users_test.go b/src/pkg/services/m365/users_test.go index 16270330d..d61fd458a 100644 --- a/src/pkg/services/m365/users_test.go +++ b/src/pkg/services/m365/users_test.go @@ -239,3 +239,48 @@ func (suite *userIntegrationSuite) TestUsers_InvalidCredentials() { }) } } + +func (suite *userIntegrationSuite) TestUserAssignedLicenses() { + t := suite.T() + ctx, flush := tester.NewContext(t) + graph.InitializeConcurrencyLimiter(ctx, true, 4) + + defer flush() + + runs := []struct { + name string + userID string + expect int + expectErr require.ErrorAssertionFunc + }{ + { + name: "user with no licenses", + userID: tconfig.UnlicensedM365UserID(t), + expect: 0, + expectErr: require.NoError, + }, + { + name: "user with licenses", + userID: tconfig.M365UserID(t), + expect: 2, + expectErr: require.NoError, + }, + { + name: "User does not exist", + userID: "fake", + expect: 0, + expectErr: require.Error, + }, + } + + for _, run := range runs { + t.Run(run.name, func(t *testing.T) { + user, err := UserAssignedLicenses( + ctx, + suite.acct, + run.userID) + run.expectErr(t, err, clues.ToCore(err)) + assert.Equal(t, run.expect, user) + }) + } +}