From 8103bba478c05e53e4820f42d06ee0fa896d76b8 Mon Sep 17 00:00:00 2001 From: assakafpix Date: Tue, 15 Jul 2025 08:22:16 +0200 Subject: [PATCH 1/6] feat: Add table googleworkspace_admin_reports_activity Closes (#88) --- docs/index.md | 5 +- .../googleworkspace_admin_reports_activity.md | 184 ++++++++++++++ googleworkspace/plugin.go | 29 +-- googleworkspace/service.go | 27 ++ ..._googleworkspace_admin_reports_activity.go | 235 ++++++++++++++++++ 5 files changed, 464 insertions(+), 16 deletions(-) create mode 100644 docs/tables/googleworkspace_admin_reports_activity.md create mode 100644 googleworkspace/table_googleworkspace_admin_reports_activity.go diff --git a/docs/index.md b/docs/index.md index 204e602..ef3726c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -58,8 +58,8 @@ steampipe plugin install googleworkspace | Item | Description | | :---------- | :-----------| -| APIs | 1. Go to the [Google API Console](https://console.cloud.google.com/apis/dashboard).
2. Select the project that contains your credentials.
3. Click `Enable APIs and Services`.
4. Enable: `Google Calendar API`, `Google Drive API`, `Gmail API`, `Google People API`. -| Credentials | 1. To use **domain-wide delegation**, generate your [service account and credentials](https://developers.google.com/admin-sdk/directory/v1/guides/delegation#create_the_service_account_and_credentials) and [delegate domain-wide authority to your service account](https://developers.google.com/admin-sdk/directory/v1/guides/delegation#delegate_domain-wide_authority_to_your_service_account). Enter the following OAuth 2.0 scopes for the services that the service account can access:
`https://www.googleapis.com/auth/calendar.readonly`,
`https://www.googleapis.com/auth/contacts.readonly`,
`https://www.googleapis.com/auth/contacts.other.readonly`,
`https://www.googleapis.com/auth/directory.readonly`,
`https://www.googleapis.com/auth/drive.readonly`,
`https://www.googleapis.com/auth/gmail.readonly`
2. To use **OAuth client**, configure your [credentials](#authenticate-using-oauth-client). | +| APIs | 1. Go to the [Google API Console](https://console.cloud.google.com/apis/dashboard).
2. Select the project that contains your credentials.
3. Click `Enable APIs and Services`.
4. Enable: `Google Calendar API`, `Google Drive API`, `Gmail API`, `Google People API`, `Google Admin SDK API`. +| Credentials | 1. To use **domain-wide delegation**, generate your [service account and credentials](https://developers.google.com/admin-sdk/directory/v1/guides/delegation#create_the_service_account_and_credentials) and [delegate domain-wide authority to your service account](https://developers.google.com/admin-sdk/directory/v1/guides/delegation#delegate_domain-wide_authority_to_your_service_account). Enter the following OAuth 2.0 scopes for the services that the service account can access:
`https://www.googleapis.com/auth/admin.reports.audit.readonly`
`https://www.googleapis.com/auth/calendar.readonly`,
`https://www.googleapis.com/auth/contacts.readonly`,
`https://www.googleapis.com/auth/contacts.other.readonly`,
`https://www.googleapis.com/auth/directory.readonly`,
`https://www.googleapis.com/auth/drive.readonly`,
`https://www.googleapis.com/auth/gmail.readonly`
2. To use **OAuth client**, configure your [credentials](#authenticate-using-oauth-client). | | Radius | Each connection represents a single Google Workspace account. | | Resolution | 1. Credentials from the JSON file specified by the `credentials` parameter in your Steampipe config.
2. Credentials from the JSON file specified by the `token_path` parameter in your Steampipe config.
3. Credentials from the default json file location (`~/.config/gcloud/application_default_credentials.json`). | @@ -107,6 +107,7 @@ You can use client secret credentials to protect the user's data by only grantin gcloud auth application-default login \ --client-id-file=client_secret.json \ --scopes="\ + https://www.googleapis.com/auth/admin.reports.audit.readonly,\ https://www.googleapis.com/auth/calendar.readonly,\ https://www.googleapis.com/auth/contacts.other.readonly,\ https://www.googleapis.com/auth/contacts.readonly,\ diff --git a/docs/tables/googleworkspace_admin_reports_activity.md b/docs/tables/googleworkspace_admin_reports_activity.md new file mode 100644 index 0000000..0a65cae --- /dev/null +++ b/docs/tables/googleworkspace_admin_reports_activity.md @@ -0,0 +1,184 @@ +--- + +title: "Steampipe Table: googleworkspace_admin_reports_activity - Query Google Workspace Admin Reports Activity using SQL" +description: "Allows users to query the Google Workspace Admin Reports API to retrieve detailed audit activity logs across various Google Workspace applications." +--- + +# Table: googleworkspace_admin_reports_activity - Query Google Workspace Admin Reports Activity using SQL + +The Reports API is a RESTful API you can use to access information about the Google Workspace activities of your users. + +## Table Usage Guide + +The `googleworkspace_admin_reports_activity` table in Steampipe provides a unified interface to query the Google Workspace Admin Reports API. It surfaces detailed audit logs across all Workspace applications (Drive, Calendar, Keep, Admin console, and more). You can use this table to investigate user actions, system events, and security-related activities within your Workspace environment. + +**Important Notes** + +- You must `application_name` in a `where` clause in order to use this table ([List of all applications](https://developers.google.com/workspace/admin/reports/reference/rest/v1/activities/list?hl=fr#applicationname)). +- For improved performance, it is advised that you use the optional qual `time` to limit the result set to a specific time period. +- This table supports optional quals. Queries with optional quals are optimised to use Activity filters. Optional quals are supported for the following columns: + - `actor_email` + - `ip_address` + - `event_name` + +## Examples + +### List all Drive events in the last hour + +Retrieve audit events for Google Drive that occurred in the past hour. + +```sql+postgres +select + time, + actor_email, + event_names, + param->>'value' as file_name, + ip_address, + events +from + googleworkspace_admin_reports_activity as a + cross join lateral jsonb_array_elements(a.events) as evt + cross join lateral jsonb_array_elements(evt->'parameters') as param +where + application_name = 'drive' + and param->>'name' = 'doc_title' + and time > now() - interval '1 hour'; +``` + +```sql+sqlite +select + time, + actor_email, + event_names, + param->>'value' as file_name, + ip_address, + events +from + googleworkspace_admin_reports_activity as a + cross join lateral jsonb_array_elements(a.events) as evt + cross join lateral jsonb_array_elements(evt->'parameters') as param +where + application_name = 'drive' + and param->>'name' = 'doc_title' + and time > datetime('now', '-1 hour'); +``` + +### List all password changes performed by administrators on users + +Show all changes of password performed by administrators on users in the last month. + +```sql+postgres +select + time, + actor_email, + event_names, + param->>'value' as user_email, + ip_address, + events +from + googleworkspace_admin_reports_activity as a + cross join lateral jsonb_array_elements(a.events) as evt + cross join lateral jsonb_array_elements(evt->'parameters') as param +where + application_name = 'admin' + and event_name = 'CHANGE_PASSWORD' + and param->>'name' = 'USER_EMAIL' + and time > now() - interval '1 month'; +``` + +```sql+sqlite +select + time, + actor_email, + event_names, + param->>'value' as user_email, + ip_address, + events +from + googleworkspace_admin_reports_activity as a + cross join lateral jsonb_array_elements(a.events) as evt + cross join lateral jsonb_array_elements(evt->'parameters') as param +where + application_name = 'admin' + and event_name = 'CHANGE_PASSWORD' + and param->>'name' = 'USER_EMAIL' + and time > datetime('now', '-1 month'); +``` + +### Show login failures by specific user + +Show all failed login attempts by a specific user in the last week. + +```sql+postgres +select + time, + event_names, + ip_address +from + googleworkspace_admin_reports_activity +where + application_name = 'login' + and actor_email = 'xxx@xxx.xxx' + and event_name = 'login_failure' + and time > now() - '1 week'::interval; +``` + +```sql+sqlite +select + time, + event_names, + ip_address +from + googleworkspace_admin_reports_activity +where + application_name = 'login' + and actor_email = 'xxx@xxx.xxx' + and event_name = 'login_failure' + and time > datetime('now', '-1 week'); +``` + +### Show all connections from a new device + +Identify all connections from a new device in the last week. + +```sql+postgres +select + time, + actor_email, + event_names, + param1->>'value' as device_id, + param2->>'value' as device_model, + events +from + googleworkspace_admin_reports_activity as a + cross join lateral jsonb_array_elements(a.events) as evt + cross join lateral jsonb_array_elements(evt->'parameters') as param1 + cross join lateral jsonb_array_elements(evt->'parameters') as param2 +where + application_name = 'mobile' + and event_name = 'DEVICE_REGISTER_UNREGISTER_EVENT' + and param1->>'name' = 'DEVICE_ID' + and param2->>'name' = 'DEVICE_MODEL' + and time > now() - interval '1 day'; +``` + +```sql+sqlite +select + time, + actor_email, + event_names, + param1->>'value' as device_id, + param2->>'value' as device_model, + events +from + googleworkspace_admin_reports_activity as a + cross join lateral jsonb_array_elements(a.events) as evt + cross join lateral jsonb_array_elements(evt->'parameters') as param1 + cross join lateral jsonb_array_elements(evt->'parameters') as param2 +where + application_name = 'mobile' + and event_names = 'DEVICE_REGISTER_UNREGISTER_EVENT' + and param1->>'name' = 'DEVICE_ID' + and param2->>'name' = 'DEVICE_MODEL' + and time > datetime('now', '-1 day'); +``` \ No newline at end of file diff --git a/googleworkspace/plugin.go b/googleworkspace/plugin.go index 2e4c2f1..34deaa6 100644 --- a/googleworkspace/plugin.go +++ b/googleworkspace/plugin.go @@ -27,20 +27,21 @@ func Plugin(ctx context.Context) *plugin.Plugin { NewInstance: ConfigInstance, }, TableMap: map[string]*plugin.Table{ - "googleworkspace_calendar": tableGoogleWorkspaceCalendar(ctx), - "googleworkspace_calendar_event": tableGoogleWorkspaceCalendarEvent(ctx), - "googleworkspace_calendar_my_event": tableGoogleWorkspaceCalendarMyEvent(ctx), - "googleworkspace_drive": tableGoogleWorkspaceDrive(ctx), - "googleworkspace_drive_my_file": tableGoogleWorkspaceDriveMyFile(ctx), - "googleworkspace_gmail_draft": tableGoogleWorkspaceGmailDraft(ctx), - "googleworkspace_gmail_message": tableGoogleWorkspaceGmailMessage(ctx), - "googleworkspace_gmail_my_draft": tableGoogleWorkspaceGmailMyDraft(ctx), - "googleworkspace_gmail_my_message": tableGoogleWorkspaceGmailMyMessage(ctx), - "googleworkspace_gmail_my_settings": tableGoogleWorkspaceGmailMySettings(ctx), - "googleworkspace_gmail_settings": tableGoogleWorkspaceGmailSettings(ctx), - "googleworkspace_people_contact": tableGoogleWorkspacePeopleContact(ctx), - "googleworkspace_people_contact_group": tableGoogleWorkspacePeopleContactGroup(ctx), - "googleworkspace_people_directory_people": tableGoogleWorkspacePeopleDirectoryPeople(ctx), + "googleworkspace_admin_reports_activity": tableGoogleworkspaceAdminReportsActivity(ctx), + "googleworkspace_calendar": tableGoogleWorkspaceCalendar(ctx), + "googleworkspace_calendar_event": tableGoogleWorkspaceCalendarEvent(ctx), + "googleworkspace_calendar_my_event": tableGoogleWorkspaceCalendarMyEvent(ctx), + "googleworkspace_drive": tableGoogleWorkspaceDrive(ctx), + "googleworkspace_drive_my_file": tableGoogleWorkspaceDriveMyFile(ctx), + "googleworkspace_gmail_draft": tableGoogleWorkspaceGmailDraft(ctx), + "googleworkspace_gmail_message": tableGoogleWorkspaceGmailMessage(ctx), + "googleworkspace_gmail_my_draft": tableGoogleWorkspaceGmailMyDraft(ctx), + "googleworkspace_gmail_my_message": tableGoogleWorkspaceGmailMyMessage(ctx), + "googleworkspace_gmail_my_settings": tableGoogleWorkspaceGmailMySettings(ctx), + "googleworkspace_gmail_settings": tableGoogleWorkspaceGmailSettings(ctx), + "googleworkspace_people_contact": tableGoogleWorkspacePeopleContact(ctx), + "googleworkspace_people_contact_group": tableGoogleWorkspacePeopleContactGroup(ctx), + "googleworkspace_people_directory_people": tableGoogleWorkspacePeopleDirectoryPeople(ctx), }, } diff --git a/googleworkspace/service.go b/googleworkspace/service.go index 935e440..de1a404 100644 --- a/googleworkspace/service.go +++ b/googleworkspace/service.go @@ -11,6 +11,7 @@ import ( "google.golang.org/api/gmail/v1" "google.golang.org/api/option" "google.golang.org/api/people/v1" + "google.golang.org/api/admin/reports/v1" "github.com/turbot/steampipe-plugin-sdk/v5/plugin" ) @@ -115,6 +116,31 @@ func GmailService(ctx context.Context, d *plugin.QueryData) (*gmail.Service, err return svc, nil } +func ReportsService(ctx context.Context, d *plugin.QueryData) (*admin.Service, error) { + // have we already created and cached the service? + serviceCacheKey := "googleworkspace.reports" + if cached, ok := d.ConnectionManager.Cache.Get(serviceCacheKey); ok { + return cached.(*admin.Service), nil + } + + // so it was not in cache - create service + opts, err := getSessionConfig(ctx, d) + if err != nil { + return nil, err + } + + // Create service + svc, err := admin.NewService(ctx, opts...) + if err != nil { + return nil, err + } + + // cache the service + d.ConnectionManager.Cache.Set(serviceCacheKey, svc) + return svc, nil +} + + func getSessionConfig(ctx context.Context, d *plugin.QueryData) ([]option.ClientOption, error) { opts := []option.ClientOption{} @@ -199,6 +225,7 @@ func getTokenSource(ctx context.Context, d *plugin.QueryData) (oauth2.TokenSourc // Authorize the request config, err := google.JWTConfigFromJSON( []byte(credentialContent), + admin.AdminReportsAuditReadonlyScope, calendar.CalendarReadonlyScope, drive.DriveReadonlyScope, gmail.GmailReadonlyScope, diff --git a/googleworkspace/table_googleworkspace_admin_reports_activity.go b/googleworkspace/table_googleworkspace_admin_reports_activity.go new file mode 100644 index 0000000..36ec117 --- /dev/null +++ b/googleworkspace/table_googleworkspace_admin_reports_activity.go @@ -0,0 +1,235 @@ +package googleworkspace + +import ( + "context" + "fmt" + "time" + + "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" + "github.com/turbot/steampipe-plugin-sdk/v5/query_cache" + "google.golang.org/api/admin/reports/v1" +) + +//// TABLE DEFINITION + +func tableGoogleworkspaceAdminReportsActivity(ctx context.Context) *plugin.Table { + return &plugin.Table{ + Name: "googleworkspace_admin_reports_activity", + Description: "Google Workspace Admin Reports API", + + List: &plugin.ListConfig{ + Hydrate: listGoogleworkspaceAdminReportsActivities, + KeyColumns: plugin.KeyColumnSlice{ + {Name: "application_name", Require: plugin.Required}, + {Name: "time", Require: plugin.Optional, Operators: []string{">", ">=", "<", "<=", "="}}, + {Name: "actor_email", Require: plugin.Optional}, + {Name: "ip_address", Require: plugin.Optional}, + {Name: "event_name", Require: plugin.Optional, CacheMatch: query_cache.CacheMatchExact}, + }, + Tags: map[string]string{"service": "admin", "product": "reports", "action": "activities.list"}, + }, + Columns: []*plugin.Column{ + { + Name: "time", + Description: "Time of occurrence of the activity.", + Type: proto.ColumnType_TIMESTAMP, + Transform: transform.FromField("Id.Time"), + }, + { + Name: "actor_email", + Description: "Email address of the actor.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Actor.Email"), + }, + { + Name: "event_name", + Description: "The name of the event (if queried).", + Type: proto.ColumnType_STRING, + Transform: transform.FromQual("event_name"), + }, + { + Name: "event_names", + Description: "List of event names for this activity.", + Type: proto.ColumnType_JSON, + Transform: transform.FromField("Events").Transform(extractEventNames), + }, + { + Name: "unique_qualifier", + Description: "Unique qualifier ID for this activity.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Id.UniqueQualifier"), + }, + { + Name: "application_name", + Description: "Application name to which the event belongs.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Id.ApplicationName"), + }, + { + Name: "ip_address", + Description: "IP address associated with the activity.", + Type: proto.ColumnType_STRING, + }, + { + Name: "actor_profile_id", + Description: "The unique Google Workspace profile ID of the actor.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Actor.ProfileId"), + }, + { + Name: "customer_id", + Description: "The unique ID of the customer to retrieve data for.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Id.CustomerId"), + }, + { + Name: "events", + Description: "Activity events in the report.", + Type: proto.ColumnType_JSON, + }, + }, + } +} + +//// LIST FUNCTION + +func listGoogleworkspaceAdminReportsActivities(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + service, err := ReportsService(ctx, d) + if err != nil { + plugin.Logger(ctx).Error("googleworkspace_admin_reports_activity.list", "service_error", err) + return nil, err + } + + // Required application_name qualifier + appName := d.EqualsQualString("application_name") + + // Validate application_name + valid := map[string]bool{ + "access_transparency": true, + "admin": true, + "calendar": true, + "chat": true, + "drive": true, + "gcp": true, + "gplus": true, + "groups": true, + "groups_enterprise": true, + "jamboard": true, + "login": true, + "meet": true, + "mobile": true, + "rules": true, + "saml": true, + "token": true, + "user_accounts": true, + "context_aware_access": true, + "chrome": true, + "data_studio": true, + "keep": true, + "vault": true, + "gemini_in_workspace_apps": true, + } + + if !valid[appName] { + return nil, fmt.Errorf("unsupported application_name: %q", appName) + } + + // Setting the maximum number of activities, API can return in a single page + maxResults := int64(1000) + + limit := d.QueryContext.Limit + if d.QueryContext.Limit != nil { + if *limit < maxResults { + maxResults = *limit + } + } + + // Determine userKey: default to "all", or use the actor_email if provided + userKey := "all" + if ae := d.EqualsQualString("actor_email"); ae != "" { + userKey = ae + } + + // Build API call with the chosen category + resp := service.Activities.List(userKey, appName).MaxResults(maxResults) + + if quals := d.Quals["time"]; quals != nil { + var startTime, endTime time.Time + for _, q := range quals.Quals { + if ts := q.Value.GetTimestampValue(); ts != nil { + t := ts.AsTime() + switch q.Operator { + case "=": + startTime, endTime = t, t + case ">": + startTime = t.Add(time.Nanosecond) + case ">=": + startTime = t + case "<": + endTime = t + case "<=": + endTime = t + } + } + } + if !startTime.IsZero() { + resp.StartTime(startTime.Format(time.RFC3339)) + } + if !endTime.IsZero() { + resp.EndTime(endTime.Format(time.RFC3339)) + } + } + + if qual := d.EqualsQualString("ip_address"); qual != "" { + resp = resp.ActorIpAddress(qual) + } + + if qual := d.EqualsQualString("event_name"); qual != "" { + resp = resp.EventName(qual) + } + + if qual := d.EqualsQualString("actor_customer_id"); qual != "" { + resp = resp.CustomerId(qual) + } + + err = resp.Pages(ctx, func(page *admin.Activities) error { + // rate limit + d.WaitForListRateLimit(ctx) + + for _, activity := range page.Items { + d.StreamListItem(ctx, activity) + if d.RowsRemaining(ctx) == 0 { + page.NextPageToken = "" + break + } + } + return nil + }) + if err != nil { + plugin.Logger(ctx).Error("googleworkspace_admin_reports_activity.list", "api_error", err) + return nil, err + } + + return nil, nil +} + +//// TRANSFORM FUNCTIONS + +func extractEventNames(_ context.Context, d *transform.TransformData) (interface{}, error) { + activity, ok := d.HydrateItem.(*admin.Activity) + if !ok { + return nil, nil + } + if activity.Events == nil { + return nil, nil + } + names := []string{} + for _, e := range activity.Events { + if e.Name != "" { + names = append(names, e.Name) + } + } + return names, nil +} \ No newline at end of file From 04e30ac3027d53cf93a3619fcd0e9997dad4bd60 Mon Sep 17 00:00:00 2001 From: ParthaI Date: Tue, 15 Jul 2025 12:01:19 +0530 Subject: [PATCH 2/6] Fixed typo in doc and log message --- docs/tables/googleworkspace_admin_reports_activity.md | 6 ------ googleworkspace/service.go | 1 - .../table_googleworkspace_admin_reports_activity.go | 10 ++++------ 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/docs/tables/googleworkspace_admin_reports_activity.md b/docs/tables/googleworkspace_admin_reports_activity.md index 0a65cae..3e73ef0 100644 --- a/docs/tables/googleworkspace_admin_reports_activity.md +++ b/docs/tables/googleworkspace_admin_reports_activity.md @@ -1,5 +1,4 @@ --- - title: "Steampipe Table: googleworkspace_admin_reports_activity - Query Google Workspace Admin Reports Activity using SQL" description: "Allows users to query the Google Workspace Admin Reports API to retrieve detailed audit activity logs across various Google Workspace applications." --- @@ -13,7 +12,6 @@ The Reports API is a RESTful API you can use to access information about the Goo The `googleworkspace_admin_reports_activity` table in Steampipe provides a unified interface to query the Google Workspace Admin Reports API. It surfaces detailed audit logs across all Workspace applications (Drive, Calendar, Keep, Admin console, and more). You can use this table to investigate user actions, system events, and security-related activities within your Workspace environment. **Important Notes** - - You must `application_name` in a `where` clause in order to use this table ([List of all applications](https://developers.google.com/workspace/admin/reports/reference/rest/v1/activities/list?hl=fr#applicationname)). - For improved performance, it is advised that you use the optional qual `time` to limit the result set to a specific time period. - This table supports optional quals. Queries with optional quals are optimised to use Activity filters. Optional quals are supported for the following columns: @@ -24,7 +22,6 @@ The `googleworkspace_admin_reports_activity` table in Steampipe provides a unifi ## Examples ### List all Drive events in the last hour - Retrieve audit events for Google Drive that occurred in the past hour. ```sql+postgres @@ -64,7 +61,6 @@ where ``` ### List all password changes performed by administrators on users - Show all changes of password performed by administrators on users in the last month. ```sql+postgres @@ -106,7 +102,6 @@ where ``` ### Show login failures by specific user - Show all failed login attempts by a specific user in the last week. ```sql+postgres @@ -138,7 +133,6 @@ where ``` ### Show all connections from a new device - Identify all connections from a new device in the last week. ```sql+postgres diff --git a/googleworkspace/service.go b/googleworkspace/service.go index de1a404..e32bcce 100644 --- a/googleworkspace/service.go +++ b/googleworkspace/service.go @@ -140,7 +140,6 @@ func ReportsService(ctx context.Context, d *plugin.QueryData) (*admin.Service, e return svc, nil } - func getSessionConfig(ctx context.Context, d *plugin.QueryData) ([]option.ClientOption, error) { opts := []option.ClientOption{} diff --git a/googleworkspace/table_googleworkspace_admin_reports_activity.go b/googleworkspace/table_googleworkspace_admin_reports_activity.go index 36ec117..7e9d036 100644 --- a/googleworkspace/table_googleworkspace_admin_reports_activity.go +++ b/googleworkspace/table_googleworkspace_admin_reports_activity.go @@ -98,7 +98,7 @@ func tableGoogleworkspaceAdminReportsActivity(ctx context.Context) *plugin.Table func listGoogleworkspaceAdminReportsActivities(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { service, err := ReportsService(ctx, d) if err != nil { - plugin.Logger(ctx).Error("googleworkspace_admin_reports_activity.list", "service_error", err) + plugin.Logger(ctx).Error("googleworkspace_admin_reports_activity.listGoogleworkspaceAdminReportsActivities", "service_error", err) return nil, err } @@ -190,16 +190,14 @@ func listGoogleworkspaceAdminReportsActivities(ctx context.Context, d *plugin.Qu resp = resp.EventName(qual) } - if qual := d.EqualsQualString("actor_customer_id"); qual != "" { - resp = resp.CustomerId(qual) - } - err = resp.Pages(ctx, func(page *admin.Activities) error { // rate limit d.WaitForListRateLimit(ctx) for _, activity := range page.Items { d.StreamListItem(ctx, activity) + + // Context can be cancelled due to manual cancellation or the limit has been hit if d.RowsRemaining(ctx) == 0 { page.NextPageToken = "" break @@ -208,7 +206,7 @@ func listGoogleworkspaceAdminReportsActivities(ctx context.Context, d *plugin.Qu return nil }) if err != nil { - plugin.Logger(ctx).Error("googleworkspace_admin_reports_activity.list", "api_error", err) + plugin.Logger(ctx).Error("googleworkspace_admin_reports_activity.listGoogleworkspaceAdminReportsActivities", "api_error", err) return nil, err } From 2de44882497fa65a01b5f2cd523cc332f664b5d6 Mon Sep 17 00:00:00 2001 From: ParthaI Date: Tue, 15 Jul 2025 22:19:59 +0530 Subject: [PATCH 3/6] Update the doc --- docs/tables/googleworkspace_admin_reports_activity.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/tables/googleworkspace_admin_reports_activity.md b/docs/tables/googleworkspace_admin_reports_activity.md index 3e73ef0..0dc4c32 100644 --- a/docs/tables/googleworkspace_admin_reports_activity.md +++ b/docs/tables/googleworkspace_admin_reports_activity.md @@ -13,6 +13,7 @@ The `googleworkspace_admin_reports_activity` table in Steampipe provides a unifi **Important Notes** - You must `application_name` in a `where` clause in order to use this table ([List of all applications](https://developers.google.com/workspace/admin/reports/reference/rest/v1/activities/list?hl=fr#applicationname)). +- You must have the `Super Admin` role to use this table. - For improved performance, it is advised that you use the optional qual `time` to limit the result set to a specific time period. - This table supports optional quals. Queries with optional quals are optimised to use Activity filters. Optional quals are supported for the following columns: - `actor_email` From 4e82aa5b159e4a11c5593562816d81af32b31ef6 Mon Sep 17 00:00:00 2001 From: ParthaI Date: Wed, 16 Jul 2025 09:38:47 +0530 Subject: [PATCH 4/6] Update the ip_address column type to ColumnType_IPADDR --- ...able_googleworkspace_admin_reports_activity.go | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/googleworkspace/table_googleworkspace_admin_reports_activity.go b/googleworkspace/table_googleworkspace_admin_reports_activity.go index 7e9d036..a07e38e 100644 --- a/googleworkspace/table_googleworkspace_admin_reports_activity.go +++ b/googleworkspace/table_googleworkspace_admin_reports_activity.go @@ -70,7 +70,7 @@ func tableGoogleworkspaceAdminReportsActivity(ctx context.Context) *plugin.Table { Name: "ip_address", Description: "IP address associated with the activity.", - Type: proto.ColumnType_STRING, + Type: proto.ColumnType_IPADDR, }, { Name: "actor_profile_id", @@ -163,13 +163,9 @@ func listGoogleworkspaceAdminReportsActivities(ctx context.Context, d *plugin.Qu switch q.Operator { case "=": startTime, endTime = t, t - case ">": + case ">", ">=": startTime = t.Add(time.Nanosecond) - case ">=": - startTime = t - case "<": - endTime = t - case "<=": + case "<", "<=": endTime = t } } @@ -182,8 +178,9 @@ func listGoogleworkspaceAdminReportsActivities(ctx context.Context, d *plugin.Qu } } - if qual := d.EqualsQualString("ip_address"); qual != "" { - resp = resp.ActorIpAddress(qual) + if qual := d.EqualsQuals["ip_address"]; qual != nil { + address := qual.GetInetValue().GetAddr() + resp = resp.ActorIpAddress(address) } if qual := d.EqualsQualString("event_name"); qual != "" { From 1fcd03595e02fa30d575a5c9305f33a2336bfaed Mon Sep 17 00:00:00 2001 From: ParthaI Date: Wed, 16 Jul 2025 20:46:41 +0530 Subject: [PATCH 5/6] Update the table naming and doc description for the table --- ....md => googleworkspace_activity_report.md} | 44 ++-- googleworkspace/plugin.go | 30 +-- .../table_googleworkspace_activity_report.go | 230 ++++++++++++++++++ ..._googleworkspace_admin_reports_activity.go | 230 ------------------ 4 files changed, 267 insertions(+), 267 deletions(-) rename docs/tables/{googleworkspace_admin_reports_activity.md => googleworkspace_activity_report.md} (70%) create mode 100644 googleworkspace/table_googleworkspace_activity_report.go delete mode 100644 googleworkspace/table_googleworkspace_admin_reports_activity.go diff --git a/docs/tables/googleworkspace_admin_reports_activity.md b/docs/tables/googleworkspace_activity_report.md similarity index 70% rename from docs/tables/googleworkspace_admin_reports_activity.md rename to docs/tables/googleworkspace_activity_report.md index 0dc4c32..64c0533 100644 --- a/docs/tables/googleworkspace_admin_reports_activity.md +++ b/docs/tables/googleworkspace_activity_report.md @@ -1,15 +1,15 @@ --- -title: "Steampipe Table: googleworkspace_admin_reports_activity - Query Google Workspace Admin Reports Activity using SQL" +title: "Steampipe Table: googleworkspace_activity_report - Query Google Workspace Admin Reports Activity using SQL" description: "Allows users to query the Google Workspace Admin Reports API to retrieve detailed audit activity logs across various Google Workspace applications." --- -# Table: googleworkspace_admin_reports_activity - Query Google Workspace Admin Reports Activity using SQL +# Table: googleworkspace_activity_report - Query Google Workspace Admin Reports Activity using SQL -The Reports API is a RESTful API you can use to access information about the Google Workspace activities of your users. +Google Workspace Activity Report provides visibility into user and administrator activity across your Google Workspace environment. You can query activity data from various Workspace applications—such as Drive, Gmail, and Login—to monitor usage patterns, security events, and administrative actions. ## Table Usage Guide -The `googleworkspace_admin_reports_activity` table in Steampipe provides a unified interface to query the Google Workspace Admin Reports API. It surfaces detailed audit logs across all Workspace applications (Drive, Calendar, Keep, Admin console, and more). You can use this table to investigate user actions, system events, and security-related activities within your Workspace environment. +The `googleworkspace_activity_report` table in Steampipe provides a unified interface to query the Google Workspace Admin Reports API. It surfaces detailed audit logs across all Workspace applications (Drive, Calendar, Keep, Admin console, and more). You can use this table to investigate user actions, system events, and security-related activities within your Workspace environment. **Important Notes** - You must `application_name` in a `where` clause in order to use this table ([List of all applications](https://developers.google.com/workspace/admin/reports/reference/rest/v1/activities/list?hl=fr#applicationname)). @@ -34,12 +34,12 @@ select ip_address, events from - googleworkspace_admin_reports_activity as a - cross join lateral jsonb_array_elements(a.events) as evt - cross join lateral jsonb_array_elements(evt->'parameters') as param + googleworkspace_activity_report as a + cross join lateral jsonb_array_elements(a.events) as evt + cross join lateral jsonb_array_elements(evt->'parameters') as param where - application_name = 'drive' - and param->>'name' = 'doc_title' + application_name = 'drive' + and param->>'name' = 'doc_title' and time > now() - interval '1 hour'; ``` @@ -52,12 +52,12 @@ select ip_address, events from - googleworkspace_admin_reports_activity as a - cross join lateral jsonb_array_elements(a.events) as evt - cross join lateral jsonb_array_elements(evt->'parameters') as param + googleworkspace_activity_report as a + cross join lateral jsonb_array_elements(a.events) as evt + cross join lateral jsonb_array_elements(evt->'parameters') as param where - application_name = 'drive' - and param->>'name' = 'doc_title' + application_name = 'drive' + and param->>'name' = 'doc_title' and time > datetime('now', '-1 hour'); ``` @@ -73,7 +73,7 @@ select ip_address, events from - googleworkspace_admin_reports_activity as a + googleworkspace_activity_report as a cross join lateral jsonb_array_elements(a.events) as evt cross join lateral jsonb_array_elements(evt->'parameters') as param where @@ -92,7 +92,7 @@ select ip_address, events from - googleworkspace_admin_reports_activity as a + googleworkspace_activity_report as a cross join lateral jsonb_array_elements(a.events) as evt cross join lateral jsonb_array_elements(evt->'parameters') as param where @@ -102,7 +102,7 @@ where and time > datetime('now', '-1 month'); ``` -### Show login failures by specific user +### Show login failures by specific user Show all failed login attempts by a specific user in the last week. ```sql+postgres @@ -111,7 +111,7 @@ select event_names, ip_address from - googleworkspace_admin_reports_activity + googleworkspace_activity_report where application_name = 'login' and actor_email = 'xxx@xxx.xxx' @@ -125,7 +125,7 @@ select event_names, ip_address from - googleworkspace_admin_reports_activity + googleworkspace_activity_report where application_name = 'login' and actor_email = 'xxx@xxx.xxx' @@ -145,7 +145,7 @@ select param2->>'value' as device_model, events from - googleworkspace_admin_reports_activity as a + googleworkspace_activity_report as a cross join lateral jsonb_array_elements(a.events) as evt cross join lateral jsonb_array_elements(evt->'parameters') as param1 cross join lateral jsonb_array_elements(evt->'parameters') as param2 @@ -166,7 +166,7 @@ select param2->>'value' as device_model, events from - googleworkspace_admin_reports_activity as a + googleworkspace_activity_report as a cross join lateral jsonb_array_elements(a.events) as evt cross join lateral jsonb_array_elements(evt->'parameters') as param1 cross join lateral jsonb_array_elements(evt->'parameters') as param2 @@ -176,4 +176,4 @@ where and param1->>'name' = 'DEVICE_ID' and param2->>'name' = 'DEVICE_MODEL' and time > datetime('now', '-1 day'); -``` \ No newline at end of file +``` diff --git a/googleworkspace/plugin.go b/googleworkspace/plugin.go index 34deaa6..b098cc8 100644 --- a/googleworkspace/plugin.go +++ b/googleworkspace/plugin.go @@ -27,21 +27,21 @@ func Plugin(ctx context.Context) *plugin.Plugin { NewInstance: ConfigInstance, }, TableMap: map[string]*plugin.Table{ - "googleworkspace_admin_reports_activity": tableGoogleworkspaceAdminReportsActivity(ctx), - "googleworkspace_calendar": tableGoogleWorkspaceCalendar(ctx), - "googleworkspace_calendar_event": tableGoogleWorkspaceCalendarEvent(ctx), - "googleworkspace_calendar_my_event": tableGoogleWorkspaceCalendarMyEvent(ctx), - "googleworkspace_drive": tableGoogleWorkspaceDrive(ctx), - "googleworkspace_drive_my_file": tableGoogleWorkspaceDriveMyFile(ctx), - "googleworkspace_gmail_draft": tableGoogleWorkspaceGmailDraft(ctx), - "googleworkspace_gmail_message": tableGoogleWorkspaceGmailMessage(ctx), - "googleworkspace_gmail_my_draft": tableGoogleWorkspaceGmailMyDraft(ctx), - "googleworkspace_gmail_my_message": tableGoogleWorkspaceGmailMyMessage(ctx), - "googleworkspace_gmail_my_settings": tableGoogleWorkspaceGmailMySettings(ctx), - "googleworkspace_gmail_settings": tableGoogleWorkspaceGmailSettings(ctx), - "googleworkspace_people_contact": tableGoogleWorkspacePeopleContact(ctx), - "googleworkspace_people_contact_group": tableGoogleWorkspacePeopleContactGroup(ctx), - "googleworkspace_people_directory_people": tableGoogleWorkspacePeopleDirectoryPeople(ctx), + "googleworkspace_activity_report": tableGoogleworkspaceActivityReport(ctx), + "googleworkspace_calendar": tableGoogleWorkspaceCalendar(ctx), + "googleworkspace_calendar_event": tableGoogleWorkspaceCalendarEvent(ctx), + "googleworkspace_calendar_my_event": tableGoogleWorkspaceCalendarMyEvent(ctx), + "googleworkspace_drive": tableGoogleWorkspaceDrive(ctx), + "googleworkspace_drive_my_file": tableGoogleWorkspaceDriveMyFile(ctx), + "googleworkspace_gmail_draft": tableGoogleWorkspaceGmailDraft(ctx), + "googleworkspace_gmail_message": tableGoogleWorkspaceGmailMessage(ctx), + "googleworkspace_gmail_my_draft": tableGoogleWorkspaceGmailMyDraft(ctx), + "googleworkspace_gmail_my_message": tableGoogleWorkspaceGmailMyMessage(ctx), + "googleworkspace_gmail_my_settings": tableGoogleWorkspaceGmailMySettings(ctx), + "googleworkspace_gmail_settings": tableGoogleWorkspaceGmailSettings(ctx), + "googleworkspace_people_contact": tableGoogleWorkspacePeopleContact(ctx), + "googleworkspace_people_contact_group": tableGoogleWorkspacePeopleContactGroup(ctx), + "googleworkspace_people_directory_people": tableGoogleWorkspacePeopleDirectoryPeople(ctx), }, } diff --git a/googleworkspace/table_googleworkspace_activity_report.go b/googleworkspace/table_googleworkspace_activity_report.go new file mode 100644 index 0000000..19b08ff --- /dev/null +++ b/googleworkspace/table_googleworkspace_activity_report.go @@ -0,0 +1,230 @@ +package googleworkspace + +import ( + "context" + "fmt" + "time" + + "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin" + "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" + "github.com/turbot/steampipe-plugin-sdk/v5/query_cache" + admin "google.golang.org/api/admin/reports/v1" +) + +//// TABLE DEFINITION + +func tableGoogleworkspaceActivityReport(ctx context.Context) *plugin.Table { + return &plugin.Table{ + Name: "googleworkspace_activity_report", + Description: "Google Workspace Activity Report", + + List: &plugin.ListConfig{ + Hydrate: listGoogleworkspaceAdminReportsActivities, + KeyColumns: plugin.KeyColumnSlice{ + {Name: "application_name", Require: plugin.Required}, + {Name: "time", Require: plugin.Optional, Operators: []string{">", ">=", "<", "<=", "="}}, + {Name: "actor_email", Require: plugin.Optional}, + {Name: "ip_address", Require: plugin.Optional}, + {Name: "event_name", Require: plugin.Optional, CacheMatch: query_cache.CacheMatchExact}, + }, + Tags: map[string]string{"service": "admin", "product": "reports", "action": "activities.list"}, + }, + Columns: []*plugin.Column{ + { + Name: "time", + Description: "Time of occurrence of the activity.", + Type: proto.ColumnType_TIMESTAMP, + Transform: transform.FromField("Id.Time"), + }, + { + Name: "actor_email", + Description: "Email address of the actor.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Actor.Email"), + }, + { + Name: "event_name", + Description: "The name of the event (if queried).", + Type: proto.ColumnType_STRING, + Transform: transform.FromQual("event_name"), + }, + { + Name: "event_names", + Description: "List of event names for this activity.", + Type: proto.ColumnType_JSON, + Transform: transform.FromField("Events").Transform(extractEventNames), + }, + { + Name: "unique_qualifier", + Description: "Unique qualifier ID for this activity.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Id.UniqueQualifier"), + }, + { + Name: "application_name", + Description: "Application name to which the event belongs.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Id.ApplicationName"), + }, + { + Name: "ip_address", + Description: "IP address associated with the activity.", + Type: proto.ColumnType_IPADDR, + }, + { + Name: "actor_profile_id", + Description: "The unique Google Workspace profile ID of the actor.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Actor.ProfileId"), + }, + { + Name: "customer_id", + Description: "The unique ID of the customer to retrieve data for.", + Type: proto.ColumnType_STRING, + Transform: transform.FromField("Id.CustomerId"), + }, + { + Name: "events", + Description: "Activity events in the report.", + Type: proto.ColumnType_JSON, + }, + }, + } +} + +//// LIST FUNCTION + +func listGoogleworkspaceAdminReportsActivities(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + service, err := ReportsService(ctx, d) + if err != nil { + plugin.Logger(ctx).Error("googleworkspace_activity_report.listGoogleworkspaceAdminReportsActivities", "service_error", err) + return nil, err + } + + // Required application_name qualifier + appName := d.EqualsQualString("application_name") + + // Validate application_name + valid := map[string]bool{ + "access_transparency": true, + "admin": true, + "calendar": true, + "chat": true, + "drive": true, + "gcp": true, + "gplus": true, + "groups": true, + "groups_enterprise": true, + "jamboard": true, + "login": true, + "meet": true, + "mobile": true, + "rules": true, + "saml": true, + "token": true, + "user_accounts": true, + "context_aware_access": true, + "chrome": true, + "data_studio": true, + "keep": true, + "vault": true, + "gemini_in_workspace_apps": true, + } + + if !valid[appName] { + return nil, fmt.Errorf("unsupported application_name: %q", appName) + } + + // Setting the maximum number of activities, API can return in a single page + maxResults := int64(1000) + + limit := d.QueryContext.Limit + if d.QueryContext.Limit != nil { + if *limit < maxResults { + maxResults = *limit + } + } + + // Determine userKey: default to "all", or use the actor_email if provided + userKey := "all" + if ae := d.EqualsQualString("actor_email"); ae != "" { + userKey = ae + } + + // Build API call with the chosen category + resp := service.Activities.List(userKey, appName).MaxResults(maxResults) + + if quals := d.Quals["time"]; quals != nil { + var startTime, endTime time.Time + for _, q := range quals.Quals { + if ts := q.Value.GetTimestampValue(); ts != nil { + t := ts.AsTime() + switch q.Operator { + case "=": + startTime, endTime = t, t + case ">", ">=": + startTime = t.Add(time.Nanosecond) + case "<", "<=": + endTime = t + } + } + } + if !startTime.IsZero() { + resp.StartTime(startTime.Format(time.RFC3339)) + } + if !endTime.IsZero() { + resp.EndTime(endTime.Format(time.RFC3339)) + } + } + + if qual := d.EqualsQuals["ip_address"]; qual != nil { + address := qual.GetInetValue().GetAddr() + resp = resp.ActorIpAddress(address) + } + + if qual := d.EqualsQualString("event_name"); qual != "" { + resp = resp.EventName(qual) + } + + err = resp.Pages(ctx, func(page *admin.Activities) error { + // rate limit + d.WaitForListRateLimit(ctx) + + for _, activity := range page.Items { + d.StreamListItem(ctx, activity) + + // Context can be cancelled due to manual cancellation or the limit has been hit + if d.RowsRemaining(ctx) == 0 { + page.NextPageToken = "" + break + } + } + return nil + }) + if err != nil { + plugin.Logger(ctx).Error("googleworkspace_activity_report.listGoogleworkspaceAdminReportsActivities", "api_error", err) + return nil, err + } + + return nil, nil +} + +//// TRANSFORM FUNCTIONS + +func extractEventNames(_ context.Context, d *transform.TransformData) (interface{}, error) { + activity, ok := d.HydrateItem.(*admin.Activity) + if !ok { + return nil, nil + } + if activity.Events == nil { + return nil, nil + } + names := []string{} + for _, e := range activity.Events { + if e.Name != "" { + names = append(names, e.Name) + } + } + return names, nil +} diff --git a/googleworkspace/table_googleworkspace_admin_reports_activity.go b/googleworkspace/table_googleworkspace_admin_reports_activity.go deleted file mode 100644 index a07e38e..0000000 --- a/googleworkspace/table_googleworkspace_admin_reports_activity.go +++ /dev/null @@ -1,230 +0,0 @@ -package googleworkspace - -import ( - "context" - "fmt" - "time" - - "github.com/turbot/steampipe-plugin-sdk/v5/grpc/proto" - "github.com/turbot/steampipe-plugin-sdk/v5/plugin" - "github.com/turbot/steampipe-plugin-sdk/v5/plugin/transform" - "github.com/turbot/steampipe-plugin-sdk/v5/query_cache" - "google.golang.org/api/admin/reports/v1" -) - -//// TABLE DEFINITION - -func tableGoogleworkspaceAdminReportsActivity(ctx context.Context) *plugin.Table { - return &plugin.Table{ - Name: "googleworkspace_admin_reports_activity", - Description: "Google Workspace Admin Reports API", - - List: &plugin.ListConfig{ - Hydrate: listGoogleworkspaceAdminReportsActivities, - KeyColumns: plugin.KeyColumnSlice{ - {Name: "application_name", Require: plugin.Required}, - {Name: "time", Require: plugin.Optional, Operators: []string{">", ">=", "<", "<=", "="}}, - {Name: "actor_email", Require: plugin.Optional}, - {Name: "ip_address", Require: plugin.Optional}, - {Name: "event_name", Require: plugin.Optional, CacheMatch: query_cache.CacheMatchExact}, - }, - Tags: map[string]string{"service": "admin", "product": "reports", "action": "activities.list"}, - }, - Columns: []*plugin.Column{ - { - Name: "time", - Description: "Time of occurrence of the activity.", - Type: proto.ColumnType_TIMESTAMP, - Transform: transform.FromField("Id.Time"), - }, - { - Name: "actor_email", - Description: "Email address of the actor.", - Type: proto.ColumnType_STRING, - Transform: transform.FromField("Actor.Email"), - }, - { - Name: "event_name", - Description: "The name of the event (if queried).", - Type: proto.ColumnType_STRING, - Transform: transform.FromQual("event_name"), - }, - { - Name: "event_names", - Description: "List of event names for this activity.", - Type: proto.ColumnType_JSON, - Transform: transform.FromField("Events").Transform(extractEventNames), - }, - { - Name: "unique_qualifier", - Description: "Unique qualifier ID for this activity.", - Type: proto.ColumnType_STRING, - Transform: transform.FromField("Id.UniqueQualifier"), - }, - { - Name: "application_name", - Description: "Application name to which the event belongs.", - Type: proto.ColumnType_STRING, - Transform: transform.FromField("Id.ApplicationName"), - }, - { - Name: "ip_address", - Description: "IP address associated with the activity.", - Type: proto.ColumnType_IPADDR, - }, - { - Name: "actor_profile_id", - Description: "The unique Google Workspace profile ID of the actor.", - Type: proto.ColumnType_STRING, - Transform: transform.FromField("Actor.ProfileId"), - }, - { - Name: "customer_id", - Description: "The unique ID of the customer to retrieve data for.", - Type: proto.ColumnType_STRING, - Transform: transform.FromField("Id.CustomerId"), - }, - { - Name: "events", - Description: "Activity events in the report.", - Type: proto.ColumnType_JSON, - }, - }, - } -} - -//// LIST FUNCTION - -func listGoogleworkspaceAdminReportsActivities(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { - service, err := ReportsService(ctx, d) - if err != nil { - plugin.Logger(ctx).Error("googleworkspace_admin_reports_activity.listGoogleworkspaceAdminReportsActivities", "service_error", err) - return nil, err - } - - // Required application_name qualifier - appName := d.EqualsQualString("application_name") - - // Validate application_name - valid := map[string]bool{ - "access_transparency": true, - "admin": true, - "calendar": true, - "chat": true, - "drive": true, - "gcp": true, - "gplus": true, - "groups": true, - "groups_enterprise": true, - "jamboard": true, - "login": true, - "meet": true, - "mobile": true, - "rules": true, - "saml": true, - "token": true, - "user_accounts": true, - "context_aware_access": true, - "chrome": true, - "data_studio": true, - "keep": true, - "vault": true, - "gemini_in_workspace_apps": true, - } - - if !valid[appName] { - return nil, fmt.Errorf("unsupported application_name: %q", appName) - } - - // Setting the maximum number of activities, API can return in a single page - maxResults := int64(1000) - - limit := d.QueryContext.Limit - if d.QueryContext.Limit != nil { - if *limit < maxResults { - maxResults = *limit - } - } - - // Determine userKey: default to "all", or use the actor_email if provided - userKey := "all" - if ae := d.EqualsQualString("actor_email"); ae != "" { - userKey = ae - } - - // Build API call with the chosen category - resp := service.Activities.List(userKey, appName).MaxResults(maxResults) - - if quals := d.Quals["time"]; quals != nil { - var startTime, endTime time.Time - for _, q := range quals.Quals { - if ts := q.Value.GetTimestampValue(); ts != nil { - t := ts.AsTime() - switch q.Operator { - case "=": - startTime, endTime = t, t - case ">", ">=": - startTime = t.Add(time.Nanosecond) - case "<", "<=": - endTime = t - } - } - } - if !startTime.IsZero() { - resp.StartTime(startTime.Format(time.RFC3339)) - } - if !endTime.IsZero() { - resp.EndTime(endTime.Format(time.RFC3339)) - } - } - - if qual := d.EqualsQuals["ip_address"]; qual != nil { - address := qual.GetInetValue().GetAddr() - resp = resp.ActorIpAddress(address) - } - - if qual := d.EqualsQualString("event_name"); qual != "" { - resp = resp.EventName(qual) - } - - err = resp.Pages(ctx, func(page *admin.Activities) error { - // rate limit - d.WaitForListRateLimit(ctx) - - for _, activity := range page.Items { - d.StreamListItem(ctx, activity) - - // Context can be cancelled due to manual cancellation or the limit has been hit - if d.RowsRemaining(ctx) == 0 { - page.NextPageToken = "" - break - } - } - return nil - }) - if err != nil { - plugin.Logger(ctx).Error("googleworkspace_admin_reports_activity.listGoogleworkspaceAdminReportsActivities", "api_error", err) - return nil, err - } - - return nil, nil -} - -//// TRANSFORM FUNCTIONS - -func extractEventNames(_ context.Context, d *transform.TransformData) (interface{}, error) { - activity, ok := d.HydrateItem.(*admin.Activity) - if !ok { - return nil, nil - } - if activity.Events == nil { - return nil, nil - } - names := []string{} - for _, e := range activity.Events { - if e.Name != "" { - names = append(names, e.Name) - } - } - return names, nil -} \ No newline at end of file From 93cc1fcd17f01cc54d37f0319debe769fc0454b9 Mon Sep 17 00:00:00 2001 From: ParthaI Date: Fri, 18 Jul 2025 12:50:26 +0530 Subject: [PATCH 6/6] Made changes based on the review comment --- docs/tables/googleworkspace_activity_report.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/tables/googleworkspace_activity_report.md b/docs/tables/googleworkspace_activity_report.md index 64c0533..6c688b7 100644 --- a/docs/tables/googleworkspace_activity_report.md +++ b/docs/tables/googleworkspace_activity_report.md @@ -13,7 +13,7 @@ The `googleworkspace_activity_report` table in Steampipe provides a unified inte **Important Notes** - You must `application_name` in a `where` clause in order to use this table ([List of all applications](https://developers.google.com/workspace/admin/reports/reference/rest/v1/activities/list?hl=fr#applicationname)). -- You must have the `Super Admin` role to use this table. +- You must have the [Admin Reports API scopes access](https://developers.google.com/workspace/admin/reports/auth#scopes) to use this table. - For improved performance, it is advised that you use the optional qual `time` to limit the result set to a specific time period. - This table supports optional quals. Queries with optional quals are optimised to use Activity filters. Optional quals are supported for the following columns: - `actor_email` @@ -22,7 +22,7 @@ The `googleworkspace_activity_report` table in Steampipe provides a unified inte ## Examples -### List all Drive events in the last hour +### List all Google drive events in the last hour Retrieve audit events for Google Drive that occurred in the past hour. ```sql+postgres @@ -114,7 +114,7 @@ from googleworkspace_activity_report where application_name = 'login' - and actor_email = 'xxx@xxx.xxx' + and actor_email = 'john@gmail.com' and event_name = 'login_failure' and time > now() - '1 week'::interval; ``` @@ -128,7 +128,7 @@ from googleworkspace_activity_report where application_name = 'login' - and actor_email = 'xxx@xxx.xxx' + and actor_email = 'john@gmail.com' and event_name = 'login_failure' and time > datetime('now', '-1 week'); ```