diff --git a/pkg/accessreview/drivers/hubspot.go b/pkg/accessreview/drivers/hubspot.go index 2edd12366..8325f8184 100644 --- a/pkg/accessreview/drivers/hubspot.go +++ b/pkg/accessreview/drivers/hubspot.go @@ -32,29 +32,39 @@ type HubSpotDriver struct { var _ Driver = (*HubSpotDriver)(nil) -type hubspotRolesResponse struct { - Results []struct { - ID string `json:"id"` - Name string `json:"name"` - } `json:"results"` -} +type ( + hubspotRolesResponse struct { + Results []struct { + ID string `json:"id"` + Name string `json:"name"` + } `json:"results"` + } -type hubspotUsersResponse struct { - Results []struct { - ID string `json:"id"` - Email string `json:"email"` - FirstName string `json:"firstName"` - LastName string `json:"lastName"` - RoleID string `json:"roleId"` - PrimaryTeamID string `json:"primaryTeamId"` - SuperAdmin bool `json:"superAdmin"` - } `json:"results"` - Paging *struct { - Next *struct { - After string `json:"after"` - } `json:"next"` - } `json:"paging"` -} + hubspotUser struct { + ID string `json:"id"` + Email string `json:"email"` + FirstName string `json:"firstName"` + LastName string `json:"lastName"` + RoleID string `json:"roleId"` + RoleIDs []string `json:"roleIds"` + PrimaryTeamID string `json:"primaryTeamId"` + SuperAdmin bool `json:"superAdmin"` + Archived *bool `json:"archived"` + Deactivated *bool `json:"deactivated"` + IsActive *bool `json:"isActive"` + Active *bool `json:"active"` + HSDeactivated *bool `json:"hs_deactivated"` + } + + hubspotUsersResponse struct { + Results []hubspotUser `json:"results"` + Paging *struct { + Next *struct { + After string `json:"after"` + } `json:"next"` + } `json:"paging"` + } +) const ( hubspotUsersEndpoint = "https://api.hubapi.com/settings/v3/users" @@ -83,9 +93,10 @@ func (d *HubSpotDriver) ListAccounts(ctx context.Context) ([]AccountRecord, erro for _, u := range resp.Results { role := "User" + roleID := hubspotRoleID(u) - if roleMap != nil && u.RoleID != "" { - if name, ok := roleMap[u.RoleID]; ok { + if roleMap != nil && roleID != "" { + if name, ok := roleMap[roleID]; ok { role = name } else if u.SuperAdmin { role = "Super Admin" @@ -100,6 +111,7 @@ func (d *HubSpotDriver) ListAccounts(ctx context.Context) ([]AccountRecord, erro Email: u.Email, FullName: fullName, Role: role, + Active: hubspotUserActive(u), IsAdmin: u.SuperAdmin, ExternalID: u.ID, MFAStatus: coredata.MFAStatusUnknown, @@ -107,7 +119,7 @@ func (d *HubSpotDriver) ListAccounts(ctx context.Context) ([]AccountRecord, erro AccountType: coredata.AccessEntryAccountTypeUser, } - if record.Email != "" { + if record.Email != "" || record.ExternalID != "" { records = append(records, record) } } @@ -193,3 +205,39 @@ func (d *HubSpotDriver) fetchRoles(ctx context.Context) (map[string]string, erro return roleMap, nil } + +func hubspotRoleID(user hubspotUser) string { + if user.RoleID != "" { + return user.RoleID + } + + if len(user.RoleIDs) > 0 { + return user.RoleIDs[0] + } + + return "" +} + +func hubspotUserActive(user hubspotUser) *bool { + if user.IsActive != nil { + return new(*user.IsActive) + } + + if user.Active != nil { + return new(*user.Active) + } + + if user.Deactivated != nil { + return new(!*user.Deactivated) + } + + if user.HSDeactivated != nil { + return new(!*user.HSDeactivated) + } + + if user.Archived != nil { + return new(!*user.Archived) + } + + return nil +} diff --git a/pkg/accessreview/drivers/hubspot_test.go b/pkg/accessreview/drivers/hubspot_test.go index c3a68c643..5c16dd9ce 100644 --- a/pkg/accessreview/drivers/hubspot_test.go +++ b/pkg/accessreview/drivers/hubspot_test.go @@ -16,7 +16,10 @@ package drivers import ( "context" + "io" + "net/http" "os" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -39,3 +42,57 @@ func TestHubSpotDriver(t *testing.T) { assert.NotEmpty(t, r.FullName) assert.NotEmpty(t, r.ExternalID) } + +func TestHubSpotDriverArchivedUsers(t *testing.T) { + t.Parallel() + + client := &http.Client{ + Transport: roundTripFunc( + func(req *http.Request) (*http.Response, error) { + switch req.URL.Path { + case "/settings/v3/users/roles": + return hubspotResponse( + http.StatusOK, + `{"results":[{"id":"role-1","name":"Sales Admin"}]}`, + ), nil + case "/settings/v3/users": + return hubspotResponse( + http.StatusOK, + `{"results":[{"id":"user-1","email":"active@example.com","firstName":"Active","lastName":"User","roleIds":["role-1"],"superAdmin":false,"isActive":true},{"id":"user-2","email":"","firstName":"Archived","lastName":"User","superAdmin":false,"archived":true}]}`, + ), nil + default: + return hubspotResponse(http.StatusNotFound, `{"message":"not found"}`), nil + } + }, + ), + } + + driver := NewHubSpotDriver(client) + + records, err := driver.ListAccounts(context.Background()) + require.NoError(t, err) + require.Len(t, records, 2) + + assert.Equal(t, "Sales Admin", records[0].Role) + require.NotNil(t, records[0].Active) + assert.True(t, *records[0].Active) + + assert.Equal(t, "user-2", records[1].ExternalID) + assert.Empty(t, records[1].Email) + require.NotNil(t, records[1].Active) + assert.False(t, *records[1].Active) +} + +type roundTripFunc func(req *http.Request) (*http.Response, error) + +func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { + return f(req) +} + +func hubspotResponse(statusCode int, body string) *http.Response { + return &http.Response{ + StatusCode: statusCode, + Body: io.NopCloser(strings.NewReader(body)), + Header: make(http.Header), + } +}