Private/Google/Get-GoogleGroupSettings.ps1
|
# PSGuerrilla - Jim Tyler, Microsoft MVP - CC BY 4.0 # https://github.com/jimrtyler/PSGuerrilla | https://creativecommons.org/licenses/by/4.0/ # AI/LLM use: see AI-USAGE.md for required attribution function Get-GoogleGroupSettings { <# .SYNOPSIS Enriches directory groups with their Groups Settings (visibility, join policy, external membership) — the data needed to detect internet-readable and open-join groups. .DESCRIPTION The directory groups.list endpoint returns group identity but NOT the exposure-relevant settings (whoCanViewGroup, whoCanJoin, allowExternalMembers). Those live in the Groups Settings API (https://www.googleapis.com/auth/apps.groups.settings, already in the requested scope set). This collector calls that API once per group and returns a hashtable keyed by group email. Token isolation: the apps.groups.settings scope is requested in its OWN token so a tenant that hasn't delegated it degrades gracefully — the collector returns $null and the dependent Tradecraft checks SKIP, instead of breaking the Google scan. Per-group calls are O(groups); to bound wall-clock on very large tenants the collector caps at MaxGroups (default 1000) and logs when it truncates (never silently). .PARAMETER ServiceAccountKeyPath Path to the service-account JSON key. .PARAMETER AdminEmail Delegated super-admin to impersonate. .PARAMETER Groups The directory groups (each must expose an .email). .PARAMETER MaxGroups Cap on groups inspected (0 = no cap). Default 1000. #> [CmdletBinding()] param( [Parameter(Mandatory)][string]$ServiceAccountKeyPath, [Parameter(Mandatory)][string]$AdminEmail, [object[]]$Groups = @(), [int]$MaxGroups = 1000, [switch]$Quiet ) $scope = 'https://www.googleapis.com/auth/apps.groups.settings' $token = $null try { $token = Get-GoogleAccessToken -ServiceAccountKeyPath $ServiceAccountKeyPath ` -AdminEmail $AdminEmail -Scopes @($scope) } catch { Write-Verbose "Groups Settings API unavailable (scope not delegated): $($_.Exception.Message)" return $null } if (-not $token) { return $null } $list = @($Groups | Where-Object { $_.email -or $_.Email }) $truncated = $false if ($MaxGroups -gt 0 -and $list.Count -gt $MaxGroups) { if (-not $Quiet) { Write-Warning "Get-GoogleGroupSettings: $($list.Count) groups exceeds cap $MaxGroups — inspecting the first $MaxGroups (group-exposure coverage is partial)." } $list = $list[0..($MaxGroups - 1)] $truncated = $true } $result = @{} foreach ($g in $list) { $email = $g.email ?? $g.Email if (-not $email) { continue } try { $s = Invoke-GoogleAdminApi -AccessToken $token ` -Uri "https://www.googleapis.com/groups/v1/groups/$([uri]::EscapeDataString($email))?alt=json" ` -Quiet:$Quiet if ($s) { $result[$email] = [PSCustomObject]@{ email = $email whoCanViewGroup = $s.whoCanViewGroup whoCanJoin = $s.whoCanJoin whoCanPostMessage = $s.whoCanPostMessage whoCanViewMembership = $s.whoCanViewMembership allowExternalMembers = $s.allowExternalMembers } } } catch { Write-Verbose "Group settings unavailable for $email : $($_.Exception.Message)" } } if ($result.Count -gt 0 -and $truncated) { $result['__truncated'] = $true } return $result } |