Skip to content

Commit

Permalink
!deploy v2.21.1 with increased support for non-G Suite Admin users
Browse files Browse the repository at this point in the history
## 2.21.1

* [Issue #131](#131) - _Free/standard Google Account support_
  * Fixed: Handling of scopes in `New-GoogleService` for authentication when a client_secrets.json file is used instead of the typical .p12 key.
  * Updated: Documentation to show how to use an account that is not a G Suite admin or G Suite user at all with PSGSuite
  * Updated: `*-PSGSuiteConfig` commands now store the client_secrets.json string contents directly on the encrypted config once provided either the path or the string contents directly, allowing users to remove any plain text credentials once loaded into the encrypted config.
  * Updated: `Get-GSToken` now uses `New-GoogleService` under the hood, so `client_secrets.json` will work with Contacts API.
  • Loading branch information
scrthq authored Dec 21, 2018
2 parents 3eecc50 + a64713f commit fd498ba
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 115 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Changelog

* [Changelog](#changelog)
* [2.21.1](#2211)
* [2.21.0](#2210)
* [2.20.2](#2202)
* [2.20.1](#2201)
Expand Down Expand Up @@ -65,6 +66,14 @@

***

## 2.21.1

* [Issue #131](https://github.com/scrthq/PSGSuite/issues/131) - _Free/standard Google Account support_
* Fixed: Handling of scopes in `New-GoogleService` for authentication when a client_secrets.json file is used instead of the typical .p12 key.
* Updated: Documentation to show how to use an account that is not a G Suite admin or G Suite user at all with PSGSuite
* Updated: `*-PSGSuiteConfig` commands now store the client_secrets.json string contents directly on the encrypted config once provided either the path or the string contents directly, allowing users to remove any plain text credentials once loaded into the encrypted config.
* Updated: `Get-GSToken` now uses `New-GoogleService` under the hood, so `client_secrets.json` will work with Contacts API.

## 2.21.0

* [PR #130](https://github.com/scrthq/PSGSuite/pull/130) / [Issue #129](https://github.com/scrthq/PSGSuite/issues/129)
Expand Down
41 changes: 4 additions & 37 deletions PSGSuite/PSGSuite.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
RootModule = 'PSGSuite.psm1'

# Version number of this module.
ModuleVersion = '2.21.0'
ModuleVersion = '2.21.1'

# ID used to uniquely identify this module
GUID = '9d751152-e83e-40bb-a6db-4c329092aaec'
Expand All @@ -24,43 +24,10 @@
CompanyName = 'SCRT HQ'

# Copyright statement for this module
Copyright = '(c) SCRT HQ 2016-2018. All rights reserved.'
Copyright = '(c) SCRT HQ 2016-2019. All rights reserved.'

# Description of the functionality provided by this module
Description = '## Summary
Powershell module wrapping Googles .NET SDKs in handy functions. Authentication is supported both with service account P12 keys as well as client_secrets.json to go through OAuth2.
## Prerequisites
In order to use this module, youll need to have the following:
* Powershell 4.0 or higher
* API Access Enabled in the Admin Console under Security
* Service Account key created and downloaded as a P12 key file
* API Client access allowed for the Service Account that will be used towards the API scopes that you intend to utilize
* Domain-Wide Delegation enabled for the service account
## Breaking Changes in 2.0.0
### Functions Removed
Please note that not all functions were ported to PSGSuite 2.0.0 due to restrictions within the .NET SDK and deprecated API calls. Here is the list of functions no longer existing in PSGSuite as of 2.0.0:
* Get-GSToken: no need for this as the keys are being consumed by Googles Auth SDK directly now, which makes Access/Refresh tokens non-existent for P12 Key service accounts and token management is handled automatically
* Revoke-GSToken: same here, no longer needed due to auth service changes
* Start-PSGSuiteConfigWizard: no longer supported as WPF is not compatible outside of Windows
All other functions are either intact or have an alias included to support backwards compatibility in scripts.
## Tips & Tricks
* All functions support pre-acquired Access Tokens (using the AccessToken parameter).
* This is useful if you have a lot of recurring commands that leverage the same admin and scope(s) so you do not overrun the user API call quota, i.e. pulling info for a large set of emails in a user''s inbox.
* If the access token is not pre-acquired, then the P12KeyPath, AppEmail, AdminEmail, CustomerID, and Domain parameters will default to reading from the PSGSuite config file (these can also be named in each function call, if preferred).
* If you plan on using this module on multiple computers or between multiple accounts on the same computer, you will need a new PSGoogle config created for each computer / user account pair.
'
Description = "PSGSuite is a Powershell module wrapping Google's .NET SDKs in handy functions, enabling users perform tasks as large as G Suite SuperAdmins automating the administration of their multi-domain G Suite accounts down to free, Google account users sending Gmail messages or uploading content to Drive from home."

# Minimum version of the Windows PowerShell engine required by this module
PowerShellVersion = '4.0'
Expand Down Expand Up @@ -131,7 +98,7 @@ All other functions are either intact or have an alias included to support backw
Tags = 'GSuite', 'Google', 'Apps', 'API', 'Drive', 'Gmail', 'Admin', 'Automation', 'PSEdition_Core', 'PSEdition_Desktop'

# A URL to the license for this module.
# LicenseUri = ''
LicenseUri = 'https://github.com/scrthq/PSGSuite/blob/master/LICENSE'

# A URL to the main website for this project.
ProjectUri = 'https://github.com/scrthq/PSGSuite'
Expand Down
51 changes: 8 additions & 43 deletions PSGSuite/Public/Authentication/Get-GSToken.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -24,52 +24,17 @@ function Get-GSToken {
[Alias('User')]
[ValidateNotNullOrEmpty()]
[String]
$AdminEmail = $Script:PSGSuite.AdminEmail,
[parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[String]
$AppEmail = $Script:PSGSuite.AppEmail,
[parameter(Mandatory = $false)]
[ValidateNotNullOrEmpty()]
[String]
$P12KeyPath = $Script:PSGSuite.P12KeyPath
$AdminEmail = $Script:PSGSuite.AdminEmail
)
function Invoke-URLEncode ($Object) {
([String]([System.Convert]::ToBase64String($Object))).TrimEnd('=').Replace('+','-').Replace('/','_')
}
$googleCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("$P12KeyPath", "notasecret",[System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable )
$rsaPrivate = $googleCert.PrivateKey
$rsa = New-Object System.Security.Cryptography.RSACryptoServiceProvider
$rsa.ImportParameters($rsaPrivate.ExportParameters($true))
$rawheader = [Ordered]@{
alg = "RS256"
typ = "JWT"
} | ConvertTo-Json -Compress
$header = Invoke-URLEncode ([System.Text.Encoding]::UTF8.GetBytes($rawheader))
[string]$now = Get-Date (Get-Date).ToUniversalTime() -UFormat "%s"
[int]$createDate = $now -replace "(\..*|\,.*)"
[int]$expiryDate = $createDate + 3600
$rawclaims = [Ordered]@{
iss = "$AppEmail"
sub = "$AdminEmail"
scope = "$($Scopes -join " ")"
aud = "https://www.googleapis.com/oauth2/v4/token"
exp = "$expiryDate"
iat = "$createDate"
} | ConvertTo-Json
$claims = Invoke-URLEncode ([System.Text.Encoding]::UTF8.GetBytes($rawclaims))
$toSign = [System.Text.Encoding]::UTF8.GetBytes($header + "." + $claims)
$sig = Invoke-URLEncode ($rsa.SignData($toSign,"SHA256"))
$jwt = $header + "." + $claims + "." + $sig
$fields = [Ordered]@{
grant_type = 'urn:ietf:params:oauth:grant-type:jwt-bearer'
assertion = $jwt
}
try {
Write-Verbose "Acquiring access token..."
$response = Invoke-RestMethod -Uri "https://www.googleapis.com/oauth2/v4/token" -Method Post -Body $fields -ContentType "application/x-www-form-urlencoded" -ErrorAction Stop -Verbose:$false | Select-Object -ExpandProperty access_token
Write-Verbose "Access token acquired!"
return $response
$serviceParams = @{
Scope = $Scopes
ServiceType = 'Google.Apis.Gmail.v1.GmailService'
User = $AdminEmail
}
$service = New-GoogleService @serviceParams
($service.HttpClientInitializer.GetAccessTokenForRequestAsync()).Result
}
catch {
Write-Verbose "Failed to acquire access token!"
Expand Down
20 changes: 17 additions & 3 deletions PSGSuite/Public/Authentication/New-GoogleService.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,27 @@ function New-GoogleService {
}
).FromCertificate($certificate)
}
elseif ($script:PSGSuite.ClientSecretsPath) {
elseif ($script:PSGSuite.ClientSecretsPath -or $script:PSGSuite.ClientSecrets) {
$ClientSecretsScopes = @(
'https://www.google.com/m8/feeds'
'https://mail.google.com'
'https://www.googleapis.com/auth/gmail.settings.basic'
'https://www.googleapis.com/auth/gmail.settings.sharing'
'https://www.googleapis.com/auth/calendar'
'https://www.googleapis.com/auth/drive'
'https://www.googleapis.com/auth/tasks'
'https://www.googleapis.com/auth/tasks.readonly'
)
if (!$script:PSGSuite.ClientSecrets) {
$script:PSGSuite.ClientSecrets = (Get-Content $script:PSGSuite.ClientSecretsPath -Raw)
Set-PSGSuiteConfig -ConfigName $script:PSGSuite.ConfigName -ClientSecretsPath $script:PSGSuite.ClientSecretsPath -Verbose:$false
}
Write-Verbose "Building UserCredentials from ClientSecrets as user '$User'"
$stream = New-Object System.IO.FileStream $script:PSGSuite.ClientSecretsPath,'Open','Read'
$stream = New-Object System.IO.MemoryStream $([System.Text.Encoding]::ASCII.GetBytes(($script:PSGSuite.ClientSecrets))),$null
$credPath = Join-Path (Resolve-Path (Join-Path "~" ".scrthq")) "PSGSuite"
$credential = [Google.Apis.Auth.OAuth2.GoogleWebAuthorizationBroker]::AuthorizeAsync(
[Google.Apis.Auth.OAuth2.GoogleClientSecrets]::Load($stream).Secrets,
[string[]]$Scope,
[string[]]$ClientSecretsScopes,
$User,
[System.Threading.CancellationToken]::None,
[Google.Apis.Util.Store.FileDataStore]::new($credPath,$true)
Expand Down
15 changes: 8 additions & 7 deletions PSGSuite/Public/Configuration/Get-PSGSuiteConfig.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,24 @@ function Get-PSGSuiteConfig {
<#
.SYNOPSIS
Loads the specified PSGSuite config
.DESCRIPTION
Loads the specified PSGSuite config
.PARAMETER ConfigName
The config name to load
.PARAMETER Path
The path of the config to load if non-default.
This can be used to load either a legacy XML config from an older version of PSGSuite or a specific .PSD1 config created with version 2.0.0 or greater
.PARAMETER Scope
The config scope to load
.PARAMETER PassThru
If specified, returns the config after loading it
.EXAMPLE
Get-PSGSuiteConfig personalDomain -PassThru
Expand Down Expand Up @@ -88,6 +88,7 @@ function Get-PSGSuiteConfig {
Select-Object -Property @{l = 'ConfigName';e = {$choice}},
@{l = 'P12KeyPath';e = {Decrypt $_.P12KeyPath}},
@{l = 'ClientSecretsPath';e = {Decrypt $_.ClientSecretsPath}},
@{l = 'ClientSecrets';e = {Decrypt $_.ClientSecrets}},
@{l = 'AppEmail';e = {Decrypt $_.AppEmail}},
@{l = 'AdminEmail';e = {Decrypt $_.AdminEmail}},
@{l = 'CustomerID';e = {Decrypt $_.CustomerID}},
Expand Down Expand Up @@ -125,4 +126,4 @@ function Get-PSGSuiteConfig {
if ($PassThru) {
$decryptedConfig
}
}
}
42 changes: 26 additions & 16 deletions PSGSuite/Public/Configuration/Set-PSGSuiteConfig.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -2,60 +2,63 @@ function Set-PSGSuiteConfig {
<#
.SYNOPSIS
Creates or updates a config
.DESCRIPTION
Creates or updates a config
.PARAMETER ConfigName
The friendly name for the config you are creating or updating
.PARAMETER P12KeyPath
The path to the P12 Key file downloaded from the Google Developer's Console. If both P12KeyPath and ClientSecretsPath are specified, P12KeyPath takes precedence
.PARAMETER ClientSecretsPath
The path to the Client Secrets JSON file downloaded from the Google Developer's Console. Using the ClientSecrets JSON will prompt the user to complete OAuth2 authentication in their browser on the first run and store the retrieved Refresh and Access tokens in the user's home directory. If P12KeyPath is also specified, ClientSecretsPath will be ignored.
.PARAMETER ClientSecrets
The string contents of the Client Secrets JSON file downloaded from the Google Developer's Console. Using the ClientSecrets JSON will prompt the user to complete OAuth2 authentication in their browser on the first run and store the retrieved Refresh and Access tokens in the user's home directory. If P12KeyPath is also specified, ClientSecrets will be ignored.
.PARAMETER AppEmail
The application email from the Google Developer's Console. This typically looks like the following:
[email protected]
.PARAMETER AdminEmail
The email of the Google Admin running the functions. This will typically be your email.
.PARAMETER CustomerID
The Customer ID for your customer. If unknown, you can retrieve it by running Get-GSUser after creating a base config with at least either the P12KeyPath or ClientSecretsPath, the AppEmail and the AdminEmail.
.PARAMETER Domain
The domain that you primarily manage for this CustomerID
.PARAMETER Preference
Some functions allow you to specify whether you are running in the context of the customer or a specific domain in the customer's realm. This allows you to set your preference.
Available values are:
* CustomerID
* Domain
.PARAMETER ServiceAccountClientID
The Service Account's Client ID from the Google Developer's Console. This is optional and is only used as a reference for yourself to prevent needing to check the Developer's Console for the ID when verifying API Client Access.
.PARAMETER Webhook
Web
.PARAMETER Scope
The scope at which you would like to set this config.
Available values are:
* Machine (this would create the config in a location accessible by all users on the machine)
* Enterprise (this would create the config in the Roaming AppData folder for the user or it's *nix equivalent)
* User (this would create the config in the Local AppData folder for the user or it's *nix equivalent)
.PARAMETER SetAsDefaultConfig
If passed, sets the ConfigName as the default config to load on module import
.PARAMETER NoImport
The default behavior when using Set-PSGSuiteConfig is that the new/updated config is imported as active. If -NoImport is passed, this saves the config but retains the previously loaded config as active.
.EXAMPLE
Set-PSGSuiteConfig -ConfigName "personal" -P12KeyPath C:\Keys\PersonalKey.p12 -AppEmail "[email protected]" -AdminEmail "[email protected]" -CustomerID "C83030001" -Domain "domain.com" -Preference CustomerID -ServiceAccountClientID 1175798883298324983498 -SetAsDefaultConfig
Expand Down Expand Up @@ -87,6 +90,9 @@ function Set-PSGSuiteConfig {
$ClientSecretsPath,
[parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
[string]
$ClientSecrets,
[parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
[string]
$AppEmail,
[parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)]
[string]
Expand Down Expand Up @@ -149,7 +155,7 @@ function Set-PSGSuiteConfig {
}
}
Write-Verbose "Setting config name '$ConfigName'"
$configParams = @('P12KeyPath','ClientSecretsPath','AppEmail','AdminEmail','CustomerID','Domain','Preference','ServiceAccountClientID','Webhook','Space')
$configParams = @('P12KeyPath','ClientSecretsPath','ClientSecrets','AppEmail','AdminEmail','CustomerID','Domain','Preference','ServiceAccountClientID','Webhook','Space')
if ($SetAsDefaultConfig -or !$configHash["DefaultConfig"]) {
$configHash["DefaultConfig"] = $ConfigName
}
Expand All @@ -158,6 +164,10 @@ function Set-PSGSuiteConfig {
}
foreach ($key in ($PSBoundParameters.Keys | Where-Object {$configParams -contains $_})) {
switch ($key) {
ClientSecretsPath {
$configHash["$ConfigName"][$key] = (Encrypt $PSBoundParameters[$key])
$configHash["$ConfigName"]['ClientSecrets'] = (Encrypt $(Get-Content $PSBoundParameters[$key] -Raw))
}
Webhook {
if ($configHash["$ConfigName"].Keys -notcontains 'Chat') {
$configHash["$ConfigName"]['Chat'] = @{
Expand Down Expand Up @@ -198,4 +208,4 @@ function Set-PSGSuiteConfig {
Get-PSGSuiteConfig -ConfigName $ConfigName -Verbose:$false
}
}
}
}
13 changes: 7 additions & 6 deletions PSGSuite/Public/Configuration/Switch-PSGSuiteConfig.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@
<#
.SYNOPSIS
Switches the active config
.DESCRIPTION
Switches the active config
.PARAMETER ConfigName
The friendly name of the config you would like to set as active for the session
.PARAMETER Domain
The domain name for the config you would like to set as active for the session
.PARAMETER SetToDefault
If passed, also sets the specified config as the default so it's loaded on the next module import
.EXAMPLE
Switch-PSGSuiteConfig newCustomer
Expand Down Expand Up @@ -78,6 +78,7 @@
Select-Object -Property @{l = 'ConfigName';e = {$choice}},
@{l = 'P12KeyPath';e = {Decrypt $_.P12KeyPath}},
@{l = 'ClientSecretsPath';e = {Decrypt $_.ClientSecretsPath}},
@{l = 'ClientSecrets';e = {Decrypt $_.ClientSecrets}},
@{l = 'AppEmail';e = {Decrypt $_.AppEmail}},
@{l = 'AdminEmail';e = {Decrypt $_.AdminEmail}},
@{l = 'CustomerID';e = {Decrypt $_.CustomerID}},
Expand Down Expand Up @@ -115,4 +116,4 @@
}
}
}
}
}
Loading

0 comments on commit fd498ba

Please sign in to comment.