diff --git a/provider/configs/credentials.go b/provider/configs/credentials.go index 826824a4..6252b7ee 100644 --- a/provider/configs/credentials.go +++ b/provider/configs/credentials.go @@ -1,5 +1,9 @@ package configs +import "encoding/json" + type IntegrationCredentials struct { - Token string `json:"token"` + AdminEmail string `json:"admin_email"` + CustomerID string `json:"customer_id"` + KeyFile json.RawMessage `json:"key_file"` } diff --git a/provider/describer/directory_settings.go b/provider/describer/directory_settings.go new file mode 100644 index 00000000..b66afe6c --- /dev/null +++ b/provider/describer/directory_settings.go @@ -0,0 +1 @@ +package describer diff --git a/provider/describer/group_members.go b/provider/describer/group_members.go new file mode 100644 index 00000000..f009bbb5 --- /dev/null +++ b/provider/describer/group_members.go @@ -0,0 +1,103 @@ +package describer + +import ( + "context" + "fmt" + "github.com/opengovern/og-describer-googleworkspace/pkg/sdk/models" + "github.com/opengovern/og-describer-googleworkspace/provider/model" + admin "google.golang.org/api/admin/directory/v1" + "sync" +) + +func ListGroupMembers(ctx context.Context, handler *GoogleWorkspaceAPIHandler, stream *models.StreamSender) ([]models.Resource, error) { + var wg sync.WaitGroup + GoogleWorkspaceChan := make(chan models.Resource) + errorChan := make(chan error, 1) // Buffered channel to capture errors + + groups, err := getGroups(ctx, handler) + if err != nil { + return nil, err + } + + go func() { + defer close(GoogleWorkspaceChan) + defer close(errorChan) + for _, group := range groups { + if err = processGroupMembers(ctx, handler, group.Id, GoogleWorkspaceChan, &wg); err != nil { + errorChan <- err // Send error to the error channel + } + } + wg.Wait() + }() + + var values []models.Resource + for { + select { + case value, ok := <-GoogleWorkspaceChan: + if !ok { + return values, nil + } + if stream != nil { + if err = (*stream)(value); err != nil { + return nil, err + } + } else { + values = append(values, value) + } + case err = <-errorChan: + return nil, err + } + } +} + +func processGroupMembers(ctx context.Context, handler *GoogleWorkspaceAPIHandler, groupID string, openaiChan chan<- models.Resource, wg *sync.WaitGroup) error { + var groupMembers []*admin.Member + var groupMembersResp *admin.Members + pageToken := "" + + for { + req := handler.Service.Members.List(groupID).MaxResults(MaxPageResultsGroupMembers) + if pageToken != "" { + req.PageToken(pageToken) + } + + requestFunc := func() (*int, error) { + var e error + groupMembersResp, e = req.Do() + if e != nil { + return nil, fmt.Errorf("request execution failed: %w", e) + } + + groupMembers = append(groupMembers, groupMembersResp.Members...) + return &groupMembersResp.HTTPStatusCode, nil + } + + err := handler.DoRequest(ctx, requestFunc) + if err != nil { + return fmt.Errorf("error during request handling: %w", err) + } + + if groupMembersResp.NextPageToken == "" { + break + } + pageToken = groupMembersResp.NextPageToken + } + + for _, groupMember := range groupMembers { + wg.Add(1) + go func(member *admin.Member) { + defer wg.Done() + value := models.Resource{ + ID: member.Id, + Name: member.Email, + Description: JSONAllFieldsMarshaller{ + Value: model.GroupMemberDescription{ + Member: *member, + }, + }, + } + openaiChan <- value + }(groupMember) + } + return nil +} diff --git a/provider/describer/groups.go b/provider/describer/groups.go new file mode 100644 index 00000000..7d6b7cb8 --- /dev/null +++ b/provider/describer/groups.go @@ -0,0 +1,143 @@ +package describer + +import ( + "context" + "errors" + "fmt" + "github.com/opengovern/og-describer-googleworkspace/pkg/sdk/models" + "github.com/opengovern/og-describer-googleworkspace/provider/model" + admin "google.golang.org/api/admin/directory/v1" + "google.golang.org/api/googleapi" + "net/http" + "sync" +) + +func ListGroups(ctx context.Context, handler *GoogleWorkspaceAPIHandler, stream *models.StreamSender) ([]models.Resource, error) { + var wg sync.WaitGroup + GoogleWorkspaceChan := make(chan models.Resource) + errorChan := make(chan error, 1) // Buffered channel to capture errors + + go func() { + defer close(GoogleWorkspaceChan) + defer close(errorChan) + if err := processGroups(ctx, handler, GoogleWorkspaceChan, &wg); err != nil { + errorChan <- err // Send error to the error channel + } + wg.Wait() + }() + + var values []models.Resource + for { + select { + case value, ok := <-GoogleWorkspaceChan: + if !ok { + return values, nil + } + if stream != nil { + if err := (*stream)(value); err != nil { + return nil, err + } + } else { + values = append(values, value) + } + case err := <-errorChan: + return nil, err + } + } +} + +func GetGroup(ctx context.Context, handler *GoogleWorkspaceAPIHandler, resourceID string) (*models.Resource, error) { + group, err := processGroup(ctx, handler, resourceID) + if err != nil { + return nil, err + } + value := models.Resource{ + ID: group.Id, + Name: group.Name, + Description: JSONAllFieldsMarshaller{ + Value: model.GroupDescription{ + Group: *group, + }, + }, + } + return &value, nil +} + +func processGroups(ctx context.Context, handler *GoogleWorkspaceAPIHandler, openaiChan chan<- models.Resource, wg *sync.WaitGroup) error { + var groups []*admin.Group + var groupsResp *admin.Groups + pageToken := "" + + for { + req := handler.Service.Groups.List().Customer(handler.CustomerID).MaxResults(MaxPageResultsGroups) + if pageToken != "" { + req.PageToken(pageToken) + } + + requestFunc := func() (*int, error) { + var e error + groupsResp, e = req.Do() + if e != nil { + return nil, fmt.Errorf("request execution failed: %w", e) + } + + groups = append(groups, groupsResp.Groups...) + return &groupsResp.HTTPStatusCode, nil + } + + err := handler.DoRequest(ctx, requestFunc) + if err != nil { + return fmt.Errorf("error during request handling: %w", err) + } + + if groupsResp.NextPageToken == "" { + break + } + pageToken = groupsResp.NextPageToken + } + + for _, group := range groups { + wg.Add(1) + go func(group *admin.Group) { + defer wg.Done() + value := models.Resource{ + ID: group.Id, + Name: group.Name, + Description: JSONAllFieldsMarshaller{ + Value: model.GroupDescription{ + Group: *group, + }, + }, + } + openaiChan <- value + }(group) + } + return nil +} + +func processGroup(ctx context.Context, handler *GoogleWorkspaceAPIHandler, resourceID string) (*admin.Group, error) { + var group *admin.Group + var status *int + + req := handler.Service.Groups.Get(resourceID) + + requestFunc := func() (*int, error) { + var e error + group, e = req.Do() + if e != nil { + var apiErr *googleapi.Error + if errors.As(e, &apiErr) { + *status = apiErr.Code + } + return status, fmt.Errorf("request execution failed: %w", e) + } + *status = http.StatusOK + return status, e + } + + err := handler.DoRequest(ctx, requestFunc) + if err != nil { + return nil, fmt.Errorf("error during request handling: %w", err) + } + return group, nil +} diff --git a/provider/describer/org_units.go b/provider/describer/org_units.go new file mode 100644 index 00000000..6c430922 --- /dev/null +++ b/provider/describer/org_units.go @@ -0,0 +1,131 @@ +package describer + +import ( + "context" + "errors" + "fmt" + "github.com/opengovern/og-describer-googleworkspace/pkg/sdk/models" + "github.com/opengovern/og-describer-googleworkspace/provider/model" + admin "google.golang.org/api/admin/directory/v1" + "google.golang.org/api/googleapi" + "net/http" + "sync" +) + +func ListOrgUnits(ctx context.Context, handler *GoogleWorkspaceAPIHandler, stream *models.StreamSender) ([]models.Resource, error) { + var wg sync.WaitGroup + GoogleWorkspaceChan := make(chan models.Resource) + errorChan := make(chan error, 1) // Buffered channel to capture errors + + go func() { + defer close(GoogleWorkspaceChan) + defer close(errorChan) + if err := processOrgUnits(ctx, handler, GoogleWorkspaceChan, &wg); err != nil { + errorChan <- err // Send error to the error channel + } + wg.Wait() + }() + + var values []models.Resource + for { + select { + case value, ok := <-GoogleWorkspaceChan: + if !ok { + return values, nil + } + if stream != nil { + if err := (*stream)(value); err != nil { + return nil, err + } + } else { + values = append(values, value) + } + case err := <-errorChan: + return nil, err + } + } +} + +func GetOrgUnit(ctx context.Context, handler *GoogleWorkspaceAPIHandler, resourceID string) (*models.Resource, error) { + orgUnit, err := processOrgUnit(ctx, handler, resourceID) + if err != nil { + return nil, err + } + value := models.Resource{ + ID: orgUnit.OrgUnitId, + Name: orgUnit.Name, + Description: JSONAllFieldsMarshaller{ + Value: model.OrgUnitDescription{ + OrgUnit: *orgUnit, + }, + }, + } + return &value, nil +} + +func processOrgUnits(ctx context.Context, handler *GoogleWorkspaceAPIHandler, openaiChan chan<- models.Resource, wg *sync.WaitGroup) error { + var orgUnits []*admin.OrgUnit + var orgUnitsResp *admin.OrgUnits + req := handler.Service.Orgunits.List(handler.CustomerID) + + requestFunc := func() (*int, error) { + var e error + orgUnitsResp, e = req.Do() + if e != nil { + return nil, fmt.Errorf("request execution failed: %w", e) + } + + orgUnits = append(orgUnits, orgUnitsResp.OrganizationUnits...) + return &orgUnitsResp.HTTPStatusCode, nil + } + + err := handler.DoRequest(ctx, requestFunc) + if err != nil { + return fmt.Errorf("error during request handling: %w", err) + } + + for _, orgUnit := range orgUnits { + wg.Add(1) + go func(orgUnit *admin.OrgUnit) { + defer wg.Done() + value := models.Resource{ + ID: orgUnit.OrgUnitId, + Name: orgUnit.Name, + Description: JSONAllFieldsMarshaller{ + Value: model.OrgUnitDescription{ + OrgUnit: *orgUnit, + }, + }, + } + openaiChan <- value + }(orgUnit) + } + return nil +} + +func processOrgUnit(ctx context.Context, handler *GoogleWorkspaceAPIHandler, resourceID string) (*admin.OrgUnit, error) { + var orgUnit *admin.OrgUnit + var status *int + + req := handler.Service.Orgunits.Get(handler.CustomerID, resourceID) + + requestFunc := func() (*int, error) { + var e error + orgUnit, e = req.Do() + if e != nil { + var apiErr *googleapi.Error + if errors.As(e, &apiErr) { + *status = apiErr.Code + } + return status, fmt.Errorf("request execution failed: %w", e) + } + *status = http.StatusOK + return status, e + } + + err := handler.DoRequest(ctx, requestFunc) + if err != nil { + return nil, fmt.Errorf("error during request handling: %w", err) + } + return orgUnit, nil +} diff --git a/provider/describer/user_aliases.go b/provider/describer/user_aliases.go new file mode 100644 index 00000000..41c5e97e --- /dev/null +++ b/provider/describer/user_aliases.go @@ -0,0 +1,98 @@ +package describer + +import ( + "context" + "fmt" + "github.com/opengovern/og-describer-googleworkspace/pkg/sdk/models" + "github.com/opengovern/og-describer-googleworkspace/provider/model" + admin "google.golang.org/api/admin/directory/v1" + "sync" +) + +func ListUserAliases(ctx context.Context, handler *GoogleWorkspaceAPIHandler, stream *models.StreamSender) ([]models.Resource, error) { + var wg sync.WaitGroup + GoogleWorkspaceChan := make(chan models.Resource) + errorChan := make(chan error, 1) // Buffered channel to capture errors + + go func() { + defer close(GoogleWorkspaceChan) + defer close(errorChan) + if err := processAliases(ctx, handler, GoogleWorkspaceChan, &wg); err != nil { + errorChan <- err // Send error to the error channel + } + wg.Wait() + }() + + var values []models.Resource + for { + select { + case value, ok := <-GoogleWorkspaceChan: + if !ok { + return values, nil + } + if stream != nil { + if err := (*stream)(value); err != nil { + return nil, err + } + } else { + values = append(values, value) + } + case err := <-errorChan: + return nil, err + } + } +} + +func processAliases(ctx context.Context, handler *GoogleWorkspaceAPIHandler, openaiChan chan<- models.Resource, wg *sync.WaitGroup) error { + var aliasesResp *admin.Aliases + + req := handler.Service.Users.Aliases.List(handler.CustomerID) + + requestFunc := func() (*int, error) { + var e error + aliasesResp, e = req.Do() + if e != nil { + return nil, fmt.Errorf("request execution failed: %w", e) + } + return &aliasesResp.HTTPStatusCode, nil + } + + err := handler.DoRequest(ctx, requestFunc) + if err != nil { + return fmt.Errorf("error during request handling: %w", err) + } + + for _, alias := range aliasesResp.Aliases { + wg.Add(1) + if aliasValue, ok := alias.(admin.UserAlias); ok { + go func(alias admin.UserAlias) { + defer wg.Done() + value := models.Resource{ + ID: alias.Id, + Name: alias.Alias, + Description: JSONAllFieldsMarshaller{ + Value: model.UserAliasDescription{ + UserAlias: aliasValue, + }, + }, + } + openaiChan <- value + }(aliasValue) + } else if aliasValuePtr, ok := alias.(*admin.UserAlias); ok { + go func(alias *admin.UserAlias) { + defer wg.Done() + value := models.Resource{ + ID: alias.Id, + Name: alias.Alias, + Description: JSONAllFieldsMarshaller{ + Value: model.UserAliasDescription{ + UserAlias: aliasValue, + }, + }, + } + openaiChan <- value + }(aliasValuePtr) + } + } + return nil +} diff --git a/provider/describer/users.go b/provider/describer/users.go index ba8ae25e..6a6f3566 100644 --- a/provider/describer/users.go +++ b/provider/describer/users.go @@ -2,12 +2,13 @@ package describer import ( "context" - "encoding/json" + "errors" "fmt" "github.com/opengovern/og-describer-googleworkspace/pkg/sdk/models" "github.com/opengovern/og-describer-googleworkspace/provider/model" + admin "google.golang.org/api/admin/directory/v1" + "google.golang.org/api/googleapi" "net/http" - "net/url" "sync" ) @@ -54,64 +55,58 @@ func GetUser(ctx context.Context, handler *GoogleWorkspaceAPIHandler, resourceID ID: user.Id, Name: user.Name.FullName, Description: JSONAllFieldsMarshaller{ - Value: user, + Value: model.UserDescription{ + User: *user, + }, }, } return &value, nil } func processUsers(ctx context.Context, handler *GoogleWorkspaceAPIHandler, openaiChan chan<- models.Resource, wg *sync.WaitGroup) error { - var users []model.UserDescription - var userListResponse model.UserListResponse - var resp *http.Response - baseURL := "https://admin.googleapis.com/admin/directory/v1/users" + var users []*admin.User + var usersResp *admin.Users pageToken := "" for { - params := url.Values{} - params.Set("pageToken", pageToken) - params.Set("maxResults", "500") - finalURL := fmt.Sprintf("%s?%s", baseURL, params.Encode()) - - req, err := http.NewRequest("GET", finalURL, nil) - if err != nil { - return fmt.Errorf("failed to create request: %w", err) + req := handler.Service.Users.List().Customer(handler.CustomerID).MaxResults(MaxPageResultsUsers) + if pageToken != "" { + req.PageToken(pageToken) } - requestFunc := func(req *http.Request) (*http.Response, error) { + requestFunc := func() (*int, error) { var e error - resp, e = handler.Client.Do(req) + usersResp, e = req.Do() if e != nil { return nil, fmt.Errorf("request execution failed: %w", e) } - defer resp.Body.Close() - if e = json.NewDecoder(resp.Body).Decode(&userListResponse); e != nil { - return nil, fmt.Errorf("failed to decode response: %w", e) - } - users = append(users, userListResponse.Users...) - return resp, nil + users = append(users, usersResp.Users...) + return &usersResp.HTTPStatusCode, nil } - err = handler.DoRequest(ctx, req, requestFunc) + err := handler.DoRequest(ctx, requestFunc) if err != nil { return fmt.Errorf("error during request handling: %w", err) } - if userListResponse.NextPageToken == "" { + if usersResp.NextPageToken == "" { break } - pageToken = userListResponse.NextPageToken + pageToken = usersResp.NextPageToken } + for _, user := range users { wg.Add(1) - go func(user model.UserDescription) { + go func(user *admin.User) { defer wg.Done() value := models.Resource{ ID: user.Id, Name: user.Name.FullName, Description: JSONAllFieldsMarshaller{ - Value: user, + Value: model.UserDescription{ + User: *user, + }, }, } openaiChan <- value @@ -120,34 +115,29 @@ func processUsers(ctx context.Context, handler *GoogleWorkspaceAPIHandler, opena return nil } -func processUser(ctx context.Context, handler *GoogleWorkspaceAPIHandler, resourceID string) (*model.UserDescription, error) { - var user model.UserDescription - var resp *http.Response - baseURL := "https://admin.googleapis.com/admin/directory/v1/users/" +func processUser(ctx context.Context, handler *GoogleWorkspaceAPIHandler, resourceID string) (*admin.User, error) { + var user *admin.User + var status *int - finalURL := fmt.Sprintf("%s%s", baseURL, resourceID) - req, err := http.NewRequest("GET", finalURL, nil) - if err != nil { - return nil, err - } + req := handler.Service.Users.Get(resourceID) - requestFunc := func(req *http.Request) (*http.Response, error) { + requestFunc := func() (*int, error) { var e error - resp, e = handler.Client.Do(req) + user, e = req.Do() if e != nil { - return nil, fmt.Errorf("request execution failed: %w", e) - } - defer resp.Body.Close() - - if e = json.NewDecoder(resp.Body).Decode(&user); e != nil { - return nil, fmt.Errorf("failed to decode response: %w", e) + var apiErr *googleapi.Error + if errors.As(e, &apiErr) { + *status = apiErr.Code + } + return status, fmt.Errorf("request execution failed: %w", e) } - return resp, e + *status = http.StatusOK + return status, e } - err = handler.DoRequest(ctx, req, requestFunc) + err := handler.DoRequest(ctx, requestFunc) if err != nil { return nil, fmt.Errorf("error during request handling: %w", err) } - return &user, nil + return user, nil } diff --git a/provider/describer/utils.go b/provider/describer/utils.go index 258d2251..1c914771 100644 --- a/provider/describer/utils.go +++ b/provider/describer/utils.go @@ -5,24 +5,30 @@ import ( "errors" "fmt" "golang.org/x/time/rate" + admin "google.golang.org/api/admin/directory/v1" "net/http" - "strconv" "time" ) +const ( + MaxPageResultsUsers = 500 + MaxPageResultsGroups = 200 + MaxPageResultsGroupMembers = 200 +) + type GoogleWorkspaceAPIHandler struct { - Client *http.Client - Token string + Service *admin.Service + CustomerID string RateLimiter *rate.Limiter Semaphore chan struct{} MaxRetries int RetryBackoff time.Duration } -func NewGoogleWorkspaceAPIHandler(token string, rateLimit rate.Limit, burst int, maxConcurrency int, maxRetries int, retryBackoff time.Duration) *GoogleWorkspaceAPIHandler { +func NewGoogleWorkspaceAPIHandler(service *admin.Service, customerID string, rateLimit rate.Limit, burst int, maxConcurrency int, maxRetries int, retryBackoff time.Duration) *GoogleWorkspaceAPIHandler { return &GoogleWorkspaceAPIHandler{ - Client: http.DefaultClient, - Token: token, + Service: service, + CustomerID: customerID, RateLimiter: rate.NewLimiter(rateLimit, burst), Semaphore: make(chan struct{}, maxConcurrency), MaxRetries: maxRetries, @@ -30,35 +36,60 @@ func NewGoogleWorkspaceAPIHandler(token string, rateLimit rate.Limit, burst int, } } +func getGroups(ctx context.Context, handler *GoogleWorkspaceAPIHandler) ([]*admin.Group, error) { + var groups []*admin.Group + var groupsResp *admin.Groups + pageToken := "" + + for { + req := handler.Service.Groups.List().Customer(handler.CustomerID).MaxResults(MaxPageResultsGroups) + if pageToken != "" { + req.PageToken(pageToken) + } + + requestFunc := func() (*int, error) { + var e error + groupsResp, e = req.Do() + if e != nil { + return nil, fmt.Errorf("request execution failed: %w", e) + } + + groups = append(groups, groupsResp.Groups...) + return &groupsResp.HTTPStatusCode, nil + } + + err := handler.DoRequest(ctx, requestFunc) + if err != nil { + return nil, fmt.Errorf("error during request handling: %w", err) + } + + if groupsResp.NextPageToken == "" { + break + } + pageToken = groupsResp.NextPageToken + } + + return groups, nil +} + // DoRequest executes the googleWorkspace API request with rate limiting, retries, and concurrency control. -func (h *GoogleWorkspaceAPIHandler) DoRequest(ctx context.Context, req *http.Request, requestFunc func(req *http.Request) (*http.Response, error)) error { +func (h *GoogleWorkspaceAPIHandler) DoRequest(ctx context.Context, requestFunc func() (*int, error)) error { h.Semaphore <- struct{}{} defer func() { <-h.Semaphore }() - var resp *http.Response + var status *int var err error for attempt := 0; attempt <= h.MaxRetries; attempt++ { // Wait based on rate limiter if err = h.RateLimiter.Wait(ctx); err != nil { return err } - // Set request headers - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", h.Token)) // Execute the request function - resp, err = requestFunc(req) + status, err = requestFunc() if err == nil { return nil } // Handle rate limit errors - if resp != nil && resp.StatusCode == http.StatusTooManyRequests { - retryAfter := resp.Header.Get("Retry-After") - if retryAfter != "" { - resetDuration, _ := strconv.Atoi(retryAfter) - if resetDuration > 0 { - time.Sleep(time.Duration(resetDuration)) - continue - } - } + if status != nil && *status == http.StatusTooManyRequests { // Exponential backoff if headers are missing backoff := h.RetryBackoff * (1 << attempt) time.Sleep(backoff) diff --git a/provider/describer_wrapper.go b/provider/describer_wrapper.go index fb0efd92..b6a2dc0a 100755 --- a/provider/describer_wrapper.go +++ b/provider/describer_wrapper.go @@ -2,12 +2,16 @@ package provider import ( "errors" + "fmt" model "github.com/opengovern/og-describer-googleworkspace/pkg/sdk/models" "github.com/opengovern/og-describer-googleworkspace/provider/configs" "github.com/opengovern/og-describer-googleworkspace/provider/describer" "github.com/opengovern/og-util/pkg/describe/enums" "golang.org/x/net/context" + "golang.org/x/oauth2/google" "golang.org/x/time/rate" + admin "google.golang.org/api/admin/directory/v1" + "google.golang.org/api/option" "time" ) @@ -17,12 +21,43 @@ func DescribeListByGoogleWorkspace(describe func(context.Context, *describer.Goo ctx = describer.WithTriggerType(ctx, triggerType) var err error - // Check for the token - if cfg.Token == "" { - return nil, errors.New("token must be configured") + // Check for the keyFile content + if string(cfg.KeyFile) == "" { + return nil, errors.New("key file must be configured") } - googleWorkspaceAPIHandler := describer.NewGoogleWorkspaceAPIHandler(cfg.Token, rate.Every(time.Minute/200), 1, 10, 5, 5*time.Minute) + // Check for the admin email + if string(cfg.AdminEmail) == "" { + return nil, errors.New("admin email must be configured") + } + + // Check for the customer id + if string(cfg.CustomerID) == "" { + return nil, errors.New("customer ID must be configured") + } + + scopes := []string{ + admin.AdminDirectoryUserReadonlyScope, + admin.AdminDirectoryGroupReadonlyScope, + admin.AdminDirectoryDeviceMobileReadonlyScope, + } + + // Create credentials using the service account key + config, err := google.JWTConfigFromJSON(cfg.KeyFile, scopes...) + if err != nil { + return nil, fmt.Errorf("error creating JWT config: %v", err) + } + + // Set the Subject to the specified admin email + config.Subject = cfg.AdminEmail + + // Create the Admin SDK service using the credentials + service, err := admin.NewService(ctx, option.WithTokenSource(config.TokenSource(ctx))) + if err != nil { + return nil, fmt.Errorf("error creating Admin SDK service: %v", err) + } + + googleWorkspaceAPIHandler := describer.NewGoogleWorkspaceAPIHandler(service, cfg.CustomerID, rate.Every(time.Minute/200), 1, 10, 5, 5*time.Minute) // Get values from describer var values []model.Resource @@ -41,12 +76,43 @@ func DescribeSingleByGoogleWorkspace(describe func(context.Context, *describer.G ctx = describer.WithTriggerType(ctx, triggerType) var err error - // Check for the token - if cfg.Token == "" { - return nil, errors.New("token must be configured") + // Check for the keyFile content + if string(cfg.KeyFile) == "" { + return nil, errors.New("key file must be configured") + } + + // Check for the admin email + if string(cfg.AdminEmail) == "" { + return nil, errors.New("admin email must be configured") + } + + // Check for the customer id + if string(cfg.CustomerID) == "" { + return nil, errors.New("customer ID must be configured") + } + + scopes := []string{ + admin.AdminDirectoryUserReadonlyScope, + admin.AdminDirectoryGroupReadonlyScope, + admin.AdminDirectoryDeviceMobileReadonlyScope, + } + + // Create credentials using the service account key + config, err := google.JWTConfigFromJSON(cfg.KeyFile, scopes...) + if err != nil { + return nil, fmt.Errorf("error creating JWT config: %v", err) + } + + // Set the Subject to the specified admin email + config.Subject = cfg.AdminEmail + + // Create the Admin SDK service using the credentials + service, err := admin.NewService(ctx, option.WithTokenSource(config.TokenSource(ctx))) + if err != nil { + return nil, fmt.Errorf("error creating Admin SDK service: %v", err) } - googleWorkspaceAPIHandler := describer.NewGoogleWorkspaceAPIHandler(cfg.Token, rate.Every(time.Minute/200), 1, 10, 5, 5*time.Minute) + googleWorkspaceAPIHandler := describer.NewGoogleWorkspaceAPIHandler(service, cfg.CustomerID, rate.Every(time.Minute/200), 1, 10, 5, 5*time.Minute) // Get value from describer value, err := describe(ctx, googleWorkspaceAPIHandler, resourceID) diff --git a/provider/model/model.go b/provider/model/model.go index b32b272a..125c9059 100755 --- a/provider/model/model.go +++ b/provider/model/model.go @@ -4,63 +4,26 @@ package model -type UserName struct { - DisplayName string `json:"displayName,omitempty"` - FamilyName string `json:"familyName,omitempty"` - FullName string `json:"fullName,omitempty"` - GivenName string `json:"givenName,omitempty"` +import admin "google.golang.org/api/admin/directory/v1" + +type Metadata struct{} + +type UserDescription struct { + admin.User } -type UserListResponse struct { - Users []UserDescription `json:"users"` - NextPageToken string `json:"nextPageToken,omitempty"` +type UserAliasDescription struct { + admin.UserAlias } -type UserDescription struct { - Addresses interface{} `json:"addresses,omitempty"` - AgreedToTerms bool `json:"agreedToTerms,omitempty"` - Aliases []string `json:"aliases,omitempty"` - Archived bool `json:"archived,omitempty"` - ChangePasswordAtNextLogin bool `json:"changePasswordAtNextLogin,omitempty"` - CreationTime string `json:"creationTime,omitempty"` - CustomSchemas map[string][]byte `json:"customSchemas,omitempty"` - CustomerId string `json:"customerId,omitempty"` - DeletionTime string `json:"deletionTime,omitempty"` - Emails interface{} `json:"emails,omitempty"` - Etag string `json:"etag,omitempty"` - ExternalIds interface{} `json:"externalIds,omitempty"` - Gender interface{} `json:"gender,omitempty"` - HashFunction string `json:"hashFunction,omitempty"` - Id string `json:"id,omitempty"` - Ims interface{} `json:"ims,omitempty"` - IncludeInGlobalAddressList bool `json:"includeInGlobalAddressList,omitempty"` - IpWhitelisted bool `json:"ipWhitelisted,omitempty"` - IsAdmin bool `json:"isAdmin,omitempty"` - IsDelegatedAdmin bool `json:"isDelegatedAdmin,omitempty"` - IsEnforcedIn2Sv bool `json:"isEnforcedIn2Sv,omitempty"` - IsEnrolledIn2Sv bool `json:"isEnrolledIn2Sv,omitempty"` - IsMailboxSetup bool `json:"isMailboxSetup,omitempty"` - Keywords interface{} `json:"keywords,omitempty"` - Kind string `json:"kind,omitempty"` - Languages interface{} `json:"languages,omitempty"` - LastLoginTime string `json:"lastLoginTime,omitempty"` - Locations interface{} `json:"locations,omitempty"` - Name *UserName `json:"name,omitempty"` - NonEditableAliases []string `json:"nonEditableAliases,omitempty"` - Notes interface{} `json:"notes,omitempty"` - OrgUnitPath string `json:"orgUnitPath,omitempty"` - Organizations interface{} `json:"organizations,omitempty"` - Password string `json:"password,omitempty"` - Phones interface{} `json:"phones,omitempty"` - PosixAccounts interface{} `json:"posixAccounts,omitempty"` - PrimaryEmail string `json:"primaryEmail,omitempty"` - RecoveryEmail string `json:"recoveryEmail,omitempty"` - RecoveryPhone string `json:"recoveryPhone,omitempty"` - Relations interface{} `json:"relations,omitempty"` - SshPublicKeys interface{} `json:"sshPublicKeys,omitempty"` - Suspended bool `json:"suspended,omitempty"` - SuspensionReason string `json:"suspensionReason,omitempty"` - ThumbnailPhotoEtag string `json:"thumbnailPhotoEtag,omitempty"` - ThumbnailPhotoUrl string `json:"thumbnailPhotoUrl,omitempty"` - Websites interface{} `json:"websites,omitempty"` +type GroupDescription struct { + admin.Group +} + +type GroupMemberDescription struct { + admin.Member +} + +type OrgUnitDescription struct { + admin.OrgUnit }