Added support for Gmail Client Side Encryption

This commit is contained in:
Ross Scroggs
2024-02-21 11:32:00 -08:00
parent d5255615fd
commit 115caf2486
15 changed files with 620 additions and 22 deletions

View File

@@ -10,6 +10,19 @@ Add the `-s` option to the end of the above commands to suppress creating the `g
See [Downloads](https://github.com/taers232c/GAMADV-XTD3/wiki/Downloads) for Windows or other options, including manual installation
### 6.70.01
Added `gmail_cse_incert_dir` and `gmail_cse_inkey_dir` path variables to `gam.cfg` that provide
default values for the `incertdir <FilePath>` and `inkeydir <FilePath>` options in `gam <UserTypeEntity> create csekeypair`.
### 6.70.00
Added support for Gmail Client Side Encryption.
* See: https://github.com/taers232c/GAMADV-XTD3/wiki/Users-Gmail-CSE
This is an initial, minimally tested release; proceed with care and report all issues.
### 6.69.00
Added `use_classroom_owner_access` Boolean variable to `gam.cfg` that controls how GAM gets

View File

@@ -334,7 +334,7 @@ writes the credentials into the file oauth2.txt.
admin@server:/Users/admin/bin/gamadv-xtd3$ rm -f /Users/admin/GAMConfig/oauth2.txt
admin@server:/Users/admin/bin/gamadv-xtd3$ ./gam version
WARNING: Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, Item: oauth2_txt, Value: /Users/admin/GAMConfig/oauth2.txt, Not Found
GAMADV-XTD3 6.69.00 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
GAMADV-XTD3 6.70.00 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
Ross Scroggs <ross.scroggs@gmail.com>
Python 3.12.2 64-bit final
MacOS Sonoma 14.2.1 x86_64
@@ -1002,7 +1002,7 @@ writes the credentials into the file oauth2.txt.
C:\GAMADV-XTD3>del C:\GAMConfig\oauth2.txt
C:\GAMADV-XTD3>gam version
WARNING: Config File: C:\GAMConfig\gam.cfg, Section: DEFAULT, Item: oauth2_txt, Value: C:\GAMConfig\oauth2.txt, Not Found
GAMADV-XTD3 6.69.00 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
GAMADV-XTD3 6.70.00 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
Ross Scroggs <ross.scroggs@gmail.com>
Python 3.12.2 64-bit final
Windows-10-10.0.17134 AMD64

View File

@@ -573,7 +573,7 @@ The `querytime<String> <Time>` value replaces the string `#querytime<String>#` i
The characters following `querytime` can be any combination of lowercase letters and numbers. This is most useful in scripts
where you can specify a relative date without having to change the script.
For example, query for files last modified me than 5 years ago:
For example, query for files last modified more than 5 years ago:
```
querytime5years -5y query "modifiedTime<'#querytime5years#'"
```

197
docs/Users-Gmail-CSE.md Normal file
View File

@@ -0,0 +1,197 @@
# Users - Gmail - Client Side Encryption
- [API documentation](#api-documentation)
- [Notes](#notes)
- [Definitions](#definitions)
- [Create Gmail CSE Identity](#create-gmail-cse-identity)
- [Update Gmail CSE Identity](#update-gmail-cse-identity)
- [Delete Gmail CSE Identity](#delete-gmail-cse-identity)
- [Display Gmail CSE Identities](#display-gmail-cse-identities)
- [Create Gmail CSE Key Pair](#create-gmail-cse-key-pair)
- [Action Gmail CSE Key Pairs](#action-gmail-cse-key-pairs)
- [Display Gmail CSE Key Pairs](#display-gmail-cse-key-pairs)
## API documentation
* https://developers.google.com/gmail/api/reference/rest/v1/users.settings.cse.identities
* https://developers.google.com/gmail/api/reference/rest/v1/users.settings.cse.keypairs
## Notes
This is an initial, minimally tested release; proceed with care and report all issues.
Setting up Client Side Encryption is not for the faint of heart; here is a start.
* https://support.google.com/a/answer/10741897?hl=en&ref_topic=10742486&sjid=10342493441460488213-NA
Do I personally understand what's going on here? No, I just added the API calls to GAM.
Two sets of files are required for Gmail CSE, these two variables in `gam.cfg` control where GAM looks for these files.
You must edit `gam.cfg` to set the paths you currently use.
```
gmail_cse_incert_dir
Directory for the S/MIME certificate files used by Gmail Client Side Encryption.
Default: Blank
gmail_cse_inkey_dir
Directory for the Key Access Control List (KACL) wrapped private key data files used by Gmail Client Side Encryption.
Default: Blank
```
## Definitions
* [`<UserTypeEntity>`](Collections-of-Users)
```
<DomainName> ::= <String>(.<String>)+
<EmailAddress> ::= <String>@<DomainName>
<FilePath> ::= <String>
<Password> ::= <String>
<KeyPairID> ::= <String>
```
## Create Gmail CSE Identity
Creates and configures a client-side encryption identity that's authorized to send mail from the user account.
Google publishes the S/MIME certificate to a shared domain-wide directory so that people within a Google Workspace organization can encrypt and send mail to the identity.
```
gam <UserTypeEntity> create cseidentity <KeyPairID> [kpemail <EmailAddress>]
[formatjson]
```
If `kpemail <EmailAddress>` is not specified, the user's primary email address is used for the identity.
By default, Gam displays the identity as an indented list of keys and values; the following option causes the output to be in JSON format:
* `formatjson` - Display the fields in JSON format.
## Update Gmail CSE Identity
Associates a different key pair with an existing client-side encryption identity. The updated key pair must validate against Google's S/MIME certificate profiles.
```
gam <UserTypeEntity> update cseidentity <KeyPairID> [kpemail <EmailAddress>]
[formatjson]
```
If `kpemail <EmailAddress>` is not specified, the key pair for the user's primary email address is identity updated.
By default, Gam displays the identity as an indented list of keys and values; the following option causes the output to be in JSON format:
* `formatjson` - Display the fields in JSON format.
## Delete Gmail CSE Identity
Deletes a client-side encryption identity. The authenticated user can no longer use the identity to send encrypted messages.
You cannot restore the identity after you delete it. Instead, use the `create cseidentity` to create another identity with the same configuration.
```
gam <UserTypeEntity> delete cseidentity [kpemail <EmailAddress>]
```
If `kpemail <EmailAddress>` is not specified, the identity for the user's primary email address is deleted.
## Display Gmail CSE Identities
### Display a client-side encryption identity configuration.
```
gam <UserTypeEntity> info cseidentity [kpemail <EmailAddress>]
[formatjson]
```
If `kpemail <EmailAddress>` is not specified, the user's primary email address is used for the identity.
### Display all of the client-side encrypted identities for an authenticated user.
```
gam <UserTypeEntity> show cseidentities
[formatjson]
```
By default, Gam displays the identity as an indented list of keys and values; the following option causes the output to be in JSON format:
* `formatjson` - Display the fields in JSON format.
```
gam <UserTypeEntity> print cseidentities [todrive <ToDriveAttribute>*]
[formatjson [quotechar <Character>]]
```
By default, Gam displays the information as columns of fields; the following option causes the output to be in JSON format:
* `formatjson` - Display the fields in JSON format.
By default, when writing CSV files, Gam uses a quote character of double quote `"`. The quote character is used to enclose columns that contain
the quote character itself, the column delimiter (comma by default) and new-line characters. Any quote characters within the column are doubled.
The `quotechar <Character>` option allows you to choose an alternate quote character, single quote for instance, that makes for readable/processable output.
`quotechar` defaults to `gam.cfg/csv_output_quote_char`. When uploading CSV files to Google, double quote `"` should be used.
## Create Gmail CSE Key Pair
Create a CSE Key Pair for the primary address of a user.
```
gam <UserTypeEntity> create csekeypair
[incertdir <FilePath>] [inkeydir <FilePath>]
[addidentity [<Boolean>]] [kpemail <EmailAddress>]
[formatjson|returnidonly]
```
* The S/MIME certificate files for the users are in the `incertdir <FilePath>` folder/directory.
* If this option is not specified, the directory is taken from `gam.cfg/gmail_cse_incert_dir`.
* The files must be named `user@domain.com.p7pem`.
* The certificate contains the public key and its certificate chain. The chain must be in PKCS#7 format and use PEM encoding and ASCII armor.
* The Key Access Control List (KACL) wrapped private key data files are in the `inkeydir <FilePath>` folder/directory.
* If this option is not specified, the directory is taken from `gam.cfg/gmail_cse_inkey_dir`.
* The files must be named `user@domain.com.wrap`.
* The files are in JSON format with two keys:
* `kacls_url` - The URI of the key access control list service that manages the private key.
* `wrapped_private_key` - Opaque data generated and used by the key access control list service.
By default, Gam displays the new key pair as an indented list of keys and values; the following options cause the output to be displayed in alternate forms.
* `formatjson` - Display the fields in JSON format.
* `returnidonly` - Display just the new `<KeyPairID>`.
If 'addidentity` or `addidentity true` is specified, a client-side encryption identity is created with the new key pair.
If `kpemail <EmailAddress>` is not specified, the user's primary email address is used for the identity.
By default, Gam displays the identity as an indented list of keys and values; the following option causes the output to be in JSON format:
* `formatjson` - Display the fields in JSON format.
* `returnidonly` - Display just the new `<KeyPairID>-<EmailAddress>`.
## Action Gmail CSE Key Pairs
### Disable
Turns off a client-side encryption key pair. The authenticated user can no longer use the key pair to decrypt incoming CSE message texts or sign outgoing CSE mail.
```
gam <UserTypeEntity> disable csekeypair <KeyPairID>
[formatjson]
```
By default, Gam displays the disabled key pair as an indented list of keys and values; the following option causes the output to be displayed in alternate forms.
* `formatjson` - Display the fields in JSON format.
### Enable
Turn on a client-side encryption key pair that was turned off. The key pair becomes active again for any associated client-side encryption identities.
```
gam <UserTypeEntity> ensable csekeypair <KeyPairID>
[formatjson]
```
By default, Gam displays the enabled key pair as an indented list of keys and values; the following option causes the output to be displayed in alternate forms.
* `formatjson` - Display the fields in JSON format.
### Obliterate
Delete a client-side encryption key pair permanently and immediately. You can only permanently delete key pairs that have been turned off for more than 30 days.
To turn off a key pair, use `disable csekeypair`.
```
gam <UserTypeEntity> obliterate csekeypair <KeyPairID>
```
Gmail can't restore or decrypt any messages that were encrypted by an obliterated key. Authenticated users and Google Workspace administrators lose access to reading the encrypted messages.
## Display Gmail CSE Key Pairs
### Display an existing client-side encryption key pair.
```
gam <UserTypeEntity> info csekeypair <KeyPairID>
[formatjson]
```
By default, Gam displays the key pairs as an indented list of keys and values; the following option causes the output to be in JSON format:
* `formatjson` - Display the fields in JSON format.
### Display all client-side encryption key pairs for an authenticated user.
```
gam <UserTypeEntity> show csekeypairs
[formatjson]
```
By default, Gam displays the key pairs as an indented list of keys and values; the following option causes the output to be in JSON format:
* `formatjson` - Display the fields in JSON format.
```
gam <UserTypeEntity> print csekeypairs [todrive <ToDriveAttribute>*]
[formatjson [quotechar <Character>]]
```
By default, Gam displays the key pairs as columns of fields; the following option causes the output to be in JSON format:
* `formatjson` - Display the fields in JSON format.
By default, when writing CSV files, Gam uses a quote character of double quote `"`. The quote character is used to enclose columns that contain
the quote character itself, the column delimiter (comma by default) and new-line characters. Any quote characters within the column are doubled.
The `quotechar <Character>` option allows you to choose an alternate quote character, single quote for instance, that makes for readable/processable output.
`quotechar` defaults to `gam.cfg/csv_output_quote_char`. When uploading CSV files to Google, double quote `"` should be used.

View File

@@ -3,7 +3,7 @@
Print the current version of Gam with details
```
gam version
GAMADV-XTD3 6.69.00 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
GAMADV-XTD3 6.70.00 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
Ross Scroggs <ross.scroggs@gmail.com>
Python 3.12.2 64-bit final
MacOS Sonoma 14.2.1 x86_64
@@ -15,7 +15,7 @@ Time: 2023-06-02T21:10:00-07:00
Print the current version of Gam with details and time offset information
```
gam version timeoffset
GAMADV-XTD3 6.69.00 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
GAMADV-XTD3 6.70.00 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
Ross Scroggs <ross.scroggs@gmail.com>
Python 3.12.2 64-bit final
MacOS Sonoma 14.2.1 x86_64
@@ -27,7 +27,7 @@ Your system time differs from www.googleapis.com by less than 1 second
Print the current version of Gam with extended details and SSL information
```
gam version extended
GAMADV-XTD3 6.69.00 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
GAMADV-XTD3 6.70.00 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
Ross Scroggs <ross.scroggs@gmail.com>
Python 3.12.2 64-bit final
MacOS Sonoma 14.2.1 x86_64
@@ -64,7 +64,7 @@ MacOS High Sierra 10.13.6 x86_64
Path: /Users/Admin/bin/gamadv-xtd3
Version Check:
Current: 5.35.08
Latest: 6.69.00
Latest: 6.70.00
echo $?
1
```
@@ -72,7 +72,7 @@ echo $?
Print the current version number without details
```
gam version simple
6.69.00
6.70.00
```
In Linux/MacOS you can do:
```
@@ -82,7 +82,7 @@ echo $VER
Print the current version of Gam and address of this Wiki
```
gam help
GAM 6.69.00 - https://github.com/taers232c/GAMADV-XTD3
GAM 6.70.00 - https://github.com/taers232c/GAMADV-XTD3
Ross Scroggs <ross.scroggs@gmail.com>
Python 3.12.2 64-bit final
MacOS Sonoma 14.2.1 x86_64

View File

@@ -141,6 +141,7 @@ Service Account Access
* [Users - Drive - Shortcuts](Users-Drive-Shortcuts)
* [Users - Drive - Transfer](Users-Drive-Transfer)
* [Users - Forms](Users-Forms)
* [Users - Gmail - Client Side Encryption](Users-Gmail-CSE)
* [Users - Gmail - Delegates](Users-Gmail-Delegates)
* [Users - Gmail - Filters](Users-Gmail-Filters)
* [Users - Gmail - Forwarding](Users-Gmail-Forwarding)

View File

@@ -322,6 +322,12 @@ extra_args
Path to extra_args.txt
Default: Blank
Data file: extra_args.txt
gmail_cse_incert_dir
Directory for the S/MIME certificate files used by Gmail Client Side Encryption.
Default: Blank
gmail_cse_inkey_dir
Directory for the Key Access Control List (KACL) wrapped private key data files used by Gmail Client Side Encryption.
Default: Blank
inter_batch_wait
When processing items in batches, how many seconds should GAM wait between batches
Default: 0

View File

@@ -7126,6 +7126,15 @@ gam <UserTypeEntity> show signature|sig [compact|format|html]
gam <UserTypeEntity> print signature [compact]
[primary|default] [verifyonly] [todrive <ToDriveAttribute>*]
gam <UserTypeEntity> vacation <Boolean> subject <String>
[<VacationMessageContent> (replace <Tag> <UserReplacement>)*]
[html [<Boolean>]] [contactsonly [<Boolean>]] [domainonly [<Boolean>]]
[start|startdate <Date>|Started] [end|enddate <Date>|NotSpecified]
gam <UserTypeEntity> show vacation [compact|format|html] [enabledonly]
gam <UserTypeEntity> print vacation [compact] [enabledonly] [todrive <ToDriveAttribute>*]
# Users - Gmail - S/MIME
gam <UserTypeEntity> create|add smime file <FileName> [password <Password>]
[sendas|sendasemail <EmailAddress>] [default]
gam <UserTypeEntity> update smime default
@@ -7137,12 +7146,37 @@ gam <UserTypeEntity> show smimes
gam <UserTypeEntity> print smimes [todrive <ToDriveAttribute>*]
[primary|default|(sendas|sendasemail <EmailAddress>)]
gam <UserTypeEntity> vacation <Boolean> subject <String>
[<VacationMessageContent> (replace <Tag> <UserReplacement>)*]
[html [<Boolean>]] [contactsonly [<Boolean>]] [domainonly [<Boolean>]]
[start|startdate <Date>|Started] [end|enddate <Date>|NotSpecified]
gam <UserTypeEntity> show vacation [compact|format|html] [enabledonly]
gam <UserTypeEntity> print vacation [compact] [enabledonly] [todrive <ToDriveAttribute>*]
# Users - Gmail Client Side Encryption
gam <UserTypeEntity> create cseidentity <KeyPairID> [kpemail <EmailAddress>]
[formatjson]
gam <UserTypeEntity> update cseidentity <KeyPairID> [kpemail <EmailAddress>]
[formatjson]
gam <UserTypeEntity> delete cseidentity [kpemail <EmailAddress>]
gam <UserTypeEntity> info cseidentity [kpemail <EmailAddress>]
[formatjson]
gam <UserTypeEntity> show cseidentities
[formatjson]
gam <UserTypeEntity> print cseidentities [todrive <ToDriveAttribute>*]
[formatjson [quotechar <Character>]]
gam <UserTypeEntity> create csekeypair
[incertdir <FilePath>] [inkeydir <FilePath>]
[addidentity [<Boolean>]] [kpemail <EmailAddress>]
[formatjson|returnidonly]
gam <UserTypeEntity> disable csekeypair <KeyPairID>
[formatjson]
gam <UserTypeEntity> enable csekeypair <KeyPairID>
[formatjson]
gam <UserTypeEntity> obliterate csekeypair <KeyPairID>
gam <UserTypeEntity> info csekeypair <KeyPairID>
[formatjson]
gam <UserTypeEntity> show csekeypairs
[formatjson]
gam <UserTypeEntity> print csekeypairs [todrive <ToDriveAttribute>*]
[formatjson [quotechar <Character>]]
# Users - Gmail - Settings

View File

@@ -2,7 +2,20 @@
Merged GAM-Team version
6.69.00
6.70.01
Added `gmail_cse_incert_dir` and `gmail_cse_inkey_dir` path variables to `gam.cfg` that provide
default values for the `incertdir <FilePath>` and `inkeydir <FilePath>` options in `gam <UserTypeEntity> create csekeypair`.
6.70.00
Added support for Gmail Client Side Encryption.
* See: https://github.com/taers232c/GAMADV-XTD3/wiki/Users-Gmail-CSE
This is an initial, minimally tested release; proceed with care and report all issues.
c6.69.00
Added `use_classroom_owner_access` Boolean variable to `gam.cfg` that controls how GAM gets
classroom member information and removes students/teachers. Client access does not provide

View File

@@ -2210,7 +2210,7 @@ def getJSON(deleteFields):
jsonData = json.loads(readFile(filename, encoding=encoding))
except (IndexError, KeyError, SyntaxError, TypeError, ValueError) as e:
Cmd.Backup()
usageErrorExit(f'{str(e)}: {filename}')
usageErrorExit(Msg.JSON_ERROR.format(str(e), filename))
for field in deleteFields:
jsonData.pop(field, None)
return jsonData
@@ -3630,6 +3630,8 @@ def SetGlobalVariables():
def _getCfgDirectory(sectionName, itemName):
dirPath = os.path.expanduser(_stripStringQuotes(GM.Globals[GM.PARSER].get(sectionName, itemName)))
if (not dirPath) and (itemName in {GC.GMAIL_CSE_INCERT_DIR, GC.GMAIL_CSE_INKEY_DIR}):
return dirPath
if (not dirPath) or (not os.path.isabs(dirPath)):
if (sectionName != configparser.DEFAULTSECT) and (GM.Globals[GM.PARSER].has_option(sectionName, itemName)):
dirPath = os.path.join(os.path.expanduser(_stripStringQuotes(GM.Globals[GM.PARSER].get(configparser.DEFAULTSECT, itemName))), dirPath)
@@ -3707,6 +3709,8 @@ def SetGlobalVariables():
for itemName, itemEntry in iter(GC.VAR_INFO.items()):
if itemEntry[GC.VAR_TYPE] == GC.TYPE_DIRECTORY:
dirPath = GC.Values[itemName]
if (not dirPath) and (itemName in {GC.GMAIL_CSE_INCERT_DIR, GC.GMAIL_CSE_INKEY_DIR}):
return
if (itemName != GC.CACHE_DIR or not GC.Values[GC.NO_CACHE]) and not os.path.isdir(dirPath):
writeStderr(formatKeyValueList(WARNING_PREFIX,
[Ent.Singular(Ent.CONFIG_FILE), GM.Globals[GM.GAM_CFG_FILE],
@@ -69610,6 +69614,283 @@ def printShowSmimes(users):
if csvPF:
csvPF.writeCSVfile('S/MIME')
def _showCSEItem(result, entityType, keyField, timeObjects, i, count, FJQC):
if FJQC.formatJSON:
printLine(json.dumps(cleanJSON(result, timeObjects=timeObjects), ensure_ascii=False, sort_keys=True))
return
Ind.Increment()
printEntity([entityType, result[keyField]], i, count)
Ind.Increment()
showJSON(None, result, timeObjects=timeObjects)
Ind.Decrement()
Ind.Decrement()
CSE_IDENTITY_TIME_OBJECTS = {}
CSE_KEYPAIR_TIME_OBJECTS = {'disableTime'}
def _printShowCSEItems(users, entityType, keyField, timeObjects):
csvPF = CSVPrintFile(['User', keyField]) if Act.csvFormat() else None
FJQC = FormatJSONQuoteChar(csvPF)
while Cmd.ArgumentsRemaining():
myarg = getArgument()
if csvPF and myarg == 'todrive':
csvPF.GetTodriveParameters()
else:
FJQC.GetFormatJSONQuoteChar(myarg, True)
i, count, users = getEntityArgument(users)
for user in users:
i += 1
user, gmail = buildGAPIServiceObject(API.GMAIL, user, i, count)
if not gmail:
continue
printGettingAllEntityItemsForWhom(entityType, user, i, count)
kvList = [Ent.USER, user, entityType, None]
try:
if entityType == Ent.CSE_IDENTITY:
results = callGAPIpages(gmail.users().settings().cse().identities(), 'list', 'cseIdentities',
throwReasons=GAPI.GMAIL_THROW_REASONS+[GAPI.PERMISSION_DENIED],
userId='me', fields='nextPageToken,cseIdentities')
else:
results = callGAPIpages(gmail.users().settings().cse().keypairs(), 'list', 'cseKeyPairs',
throwReasons=GAPI.GMAIL_THROW_REASONS+[GAPI.PERMISSION_DENIED],
userId='me', fields='nextPageToken,cseKeyPairs')
except GAPI.permissionDenied as e:
entityActionFailedWarning(kvList, str(e), i, count)
continue
except (GAPI.serviceNotAvailable, GAPI.badRequest):
entityServiceNotApplicableWarning(Ent.USER, user, i, count)
continue
jcount = len(results)
if not csvPF:
if not FJQC.formatJSON:
entityPerformActionNumItems([Ent.USER, user], jcount, entityType, i, count)
j = 0
for result in results:
j += 1
_showCSEItem(result, entityType, keyField, timeObjects, j, jcount, FJQC)
else:
for result in results:
row = flattenJSON(result, flattened={'User': user})
if not FJQC.formatJSON:
csvPF.WriteRowTitles(row)
elif csvPF.CheckRowTitles(row):
csvPF.WriteRowNoFilter({'User': user, keyField: result[keyField],
'JSON': json.dumps(cleanJSON(result, timeObjects=timeObjects), ensure_ascii=False, sort_keys=True)})
if csvPF:
csvPF.writeCSVfile(Ent.Plural(entityType))
CSE_IDENTITY_ACTION_FUNCTION_MAP = {
Act.CREATE: 'create',
Act.UPDATE: 'patch',
Act.DELETE: 'delete',
Act.INFO: 'get',
}
# gam <UserTypeEntity> create cseidentity <KeyPairID> [kpemail <EmailAddress>]
# [formatjson]
# gam <UserTypeEntity> update cseidentity <KeyPairID> [kpemail <EmailAddress>]
# [formatjson]
# gam <UserTypeEntity> delete cseidentity [kpemail <EmailAddress>]
# gam <UserTypeEntity> info cseidentity [kpemail <EmailAddress>]
# [formatjson]
def processCSEIdentity(users):
function = CSE_IDENTITY_ACTION_FUNCTION_MAP[Act.Get()]
keyPairId = getString(Cmd.OB_CSE_KEYPAIR_ID) if function in {'create', 'patch'} else None
FJQC = FormatJSONQuoteChar()
kpEmail = None
while Cmd.ArgumentsRemaining():
myarg = getArgument()
if myarg == 'kpemail':
kpEmail = getEmailAddress(noUid=True)
else:
FJQC.GetFormatJSON(myarg)
i, count, users = getEntityArgument(users)
for user in users:
i += 1
user, gmail = buildGAPIServiceObject(API.GMAIL, user, i, count)
if not gmail:
continue
if keyPairId:
identity = {'keyPairId': keyPairId, 'emailAddress': user if not kpEmail else kpEmail}
kwargs = {'body': identity}
if function == 'patch':
kwargs['emailAddress'] = identity['emailAddress']
kvList = [Ent.USER, user, Ent.CSE_IDENTITY, identity['emailAddress'], Ent.CSE_KEYPAIR, keyPairId]
else:
kwargs = {'cseEmailAddress': user if not kpEmail else kpEmail}
kvList = [Ent.USER, user, Ent.CSE_IDENTITY, kwargs['cseEmailAddress']]
try:
result = callGAPI(gmail.users().settings().cse().identities(), function,
throwReasons=GAPI.GMAIL_THROW_REASONS+[GAPI.PERMISSION_DENIED],
userId='me', **kwargs)
if not FJQC.formatJSON:
entityActionPerformed(kvList, i, count)
if function != 'delete':
_showCSEItem(result, Ent.CSE_IDENTITY, 'emailAddress', CSE_IDENTITY_TIME_OBJECTS, i, count, FJQC)
except GAPI.permissionDenied as e:
entityActionFailedWarning(kvList, str(e), i, count)
except (GAPI.serviceNotAvailable, GAPI.badRequest):
entityServiceNotApplicableWarning(Ent.USER, user, i, count)
# gam <UserTypeEntity> show cseidentities
# [formatjson]
# gam <UserTypeEntity> print cseidentities [todrive <ToDriveAttribute>*]
# [formatjson [quotechar <Character>]]
def printShowCSEIdentities(users):
_printShowCSEItems(users, Ent.CSE_IDENTITY, 'emailAddress', CSE_IDENTITY_TIME_OBJECTS)
# gam <UserTypeEntity> create csekeypair [incertdir <FilePath>] [inkeydir <FilePath>]
# [addidentity [<Boolean>]] [kpemail <EmailAddress>]
# [formatjson|returnidonly]
def createCSEKeyPair(users):
def _getFolderPath(myarg):
filepath = os.path.expanduser(getString(Cmd.OB_FILE_PATH))
if not os.path.isdir(filepath):
entityDoesNotExistExit(Ent.DIRECTORY, f'{myarg} {filepath}')
return filepath
FJQC = FormatJSONQuoteChar()
incertdir = GC.Values[GC.GMAIL_CSE_INCERT_DIR]
inkeydir = GC.Values[GC.GMAIL_CSE_INKEY_DIR]
addIdentity = returnIdOnly = False
kpEmail = None
while Cmd.ArgumentsRemaining():
myarg = getArgument()
if myarg == 'incertdir':
incertdir = _getFolderPath(myarg)
elif myarg == 'inkeydir':
inkeydir = _getFolderPath(myarg)
elif myarg == 'addidentity':
addIdentity = getBoolean()
elif myarg == 'kpemail':
kpEmail = getEmailAddress(noUid=True)
elif myarg == 'returnidonly':
returnIdOnly = True
else:
FJQC.GetFormatJSON(myarg)
if not incertdir:
missingArgumentExit('incertdir <FilePath>')
if not inkeydir:
missingArgumentExit('inkeydir <FilePath>')
i, count, users = getEntityArgument(users)
for user in users:
i += 1
user, gmail = buildGAPIServiceObject(API.GMAIL, user, i, count)
if not gmail:
continue
smimeFilename = os.path.join(incertdir, user+'.p7pem')
if not os.path.isfile(smimeFilename):
entityActionNotPerformedWarning([Ent.USER, user, Ent.CSE_KEYPAIR, None],
Msg.FILE_NOT_FOUND.format(smimeFilename), i, count)
continue
smimeData = readFile(smimeFilename, mode='rb', continueOnError=True)
if smimeData is None:
continue
kaclFilename = os.path.join(inkeydir, user+'.wrap')
if not os.path.isfile(kaclFilename):
entityActionNotPerformedWarning([Ent.USER, user, Ent.CSE_KEYPAIR, None],
Msg.FILE_NOT_FOUND.format(kaclFilename), i, count)
continue
jsonData = readFile(kaclFilename, mode='r', encoding=UTF8, continueOnError=True)
if jsonData is None:
continue
try:
keyData = json.loads(jsonData)
key = 'kacls_url'
kaclsUri = keyData[key]
key = 'wrapped_private_key'
kaclsData = keyData[key]
cseKeyPair = {
'pkcs7': smimeData.decode(UTF8),
'privateKeyMetadata': [{'kaclsKeyMetadata': {'kaclsUri': kaclsUri, 'kaclsData': kaclsData}}]
}
except KeyError:
entityActionNotPerformedWarning([Ent.USER, user, Ent.CSE_KEYPAIR, None],
Msg.JSON_KEY_NOT_FOUND.format(key, kaclFilename), i, count)
continue
except (IndexError, SyntaxError, TypeError, ValueError) as e:
entityActionNotPerformedWarning([Ent.USER, user, Ent.CSE_KEYPAIR, None],
Msg.JSON_ERROR.format(str(e), kaclFilename) , i, count)
continue
kvList = [Ent.USER, user, Ent.CSE_KEYPAIR, None]
try:
result = callGAPI(gmail.users().settings().cse().keypairs(), 'create',
throwReasons=GAPI.GMAIL_THROW_REASONS+[GAPI.PERMISSION_DENIED],
userId='me', body=cseKeyPair)
keyPairId = result['keyPairId']
if not returnIdOnly:
kvList[-1] = keyPairId
if not FJQC.formatJSON:
entityActionPerformed(kvList, i, count)
_showCSEItem(result, Ent.CSE_KEYPAIR, 'keyPairId', CSE_KEYPAIR_TIME_OBJECTS, i, count, FJQC)
elif not addIdentity:
writeStdout(f'{keyPairId}\n')
if addIdentity:
identity = {'keyPairId': keyPairId, 'emailAddress': user if not kpEmail else kpEmail}
kvList = [Ent.USER, user, Ent.CSE_IDENTITY, identity['emailAddress'], Ent.CSE_KEYPAIR, keyPairId]
result = callGAPI(gmail.users().settings().cse().identities(), 'create',
throwReasons=GAPI.GMAIL_THROW_REASONS+[GAPI.PERMISSION_DENIED],
userId='me', body=identity)
if not returnIdOnly:
if not FJQC.formatJSON:
entityActionPerformed(kvList, i, count)
_showCSEItem(result, Ent.CSE_IDENTITY, 'emailAddress', CSE_IDENTITY_TIME_OBJECTS, i, count, FJQC)
else:
writeStdout(f'{keyPairId}-{user}\n')
except GAPI.permissionDenied as e:
entityActionFailedWarning(kvList, str(e), i, count)
except (GAPI.serviceNotAvailable, GAPI.badRequest):
entityServiceNotApplicableWarning(Ent.USER, user, i, count)
CSE_KEYPAIR_ACTION_FUNCTION_MAP = {
Act.DISABLE: 'disable',
Act.ENABLE: 'enable',
Act.OBLITERATE: 'obliterate',
Act.INFO: 'get',
}
# gam <UserTypeEntity> disable csekeypair <KeyPairID>
# [formatjson]
# gam <UserTypeEntity> enable csekeypair <KeyPairID>
# [formatjson]
# gam <UserTypeEntity> obliterate csekeypair <KeyPairID>
# gam <UserTypeEntity> info csekeypair <KeyPairID>
# [formatjson]
def processCSEKeyPair(users):
function = CSE_KEYPAIR_ACTION_FUNCTION_MAP[Act.Get()]
keyPairId = getString(Cmd.OB_CSE_KEYPAIR_ID)
FJQC = FormatJSONQuoteChar(formatJSONOnly=True)
i, count, users = getEntityArgument(users)
for user in users:
i += 1
user, gmail = buildGAPIServiceObject(API.GMAIL, user, i, count)
if not gmail:
continue
kvList = [Ent.USER, user, Ent.CSE_KEYPAIR, keyPairId]
try:
result = callGAPI(gmail.users().settings().cse().keypairs(), function,
throwReasons=GAPI.GMAIL_THROW_REASONS+[GAPI.PERMISSION_DENIED],
userId='me', keyPairId=keyPairId)
if function != 'obliterate':
if not FJQC.formatJSON:
entityActionPerformed(kvList, i, count)
_showCSEItem(result, Ent.CSE_KEYPAIR, 'keyPairId', CSE_KEYPAIR_TIME_OBJECTS, i, count, FJQC)
else:
entityActionPerformed(kvList, i, count)
except GAPI.permissionDenied as e:
entityActionFailedWarning(kvList, str(e), i, count)
except (GAPI.serviceNotAvailable, GAPI.badRequest):
entityServiceNotApplicableWarning(Ent.USER, user, i, count)
# gam <UserTypeEntity> show csekeypairs
# [formatjson]
# gam <UserTypeEntity> print csekeypairs [todrive <ToDriveAttribute>*]
# [formatjson [quotechar <Character>]]
def printShowCSEKeyPairs(users):
_printShowCSEItems(users, Ent.CSE_KEYPAIR, 'keyPairId', CSE_KEYPAIR_TIME_OBJECTS)
# gam <UserTypeEntity> signature|sig
# <SignatureContent>
# (replace <Tag> <String>)*
@@ -71044,7 +71325,7 @@ def importTasklist(users):
jsonData = json.loads(readFile(filename, encoding=encoding))
except (IndexError, KeyError, SyntaxError, TypeError, ValueError) as e:
Cmd.Backup()
usageErrorExit(f'{str(e)}: {filename}')
usageErrorExit(Msg.JSON_ERROR.format(str(e), filename))
if jsonData.get('kind', '') != 'tasks#taskLists':
Cmd.Backup()
usageErrorExit(f'{"Not a Tasks takeout JSON file"}: {filename}')
@@ -72417,6 +72698,8 @@ USER_ADD_CREATE_FUNCTIONS = {
Cmd.ARG_CHATSPACE: createChatSpace,
Cmd.ARG_CLASSROOMINVITATION: createClassroomInvitations,
Cmd.ARG_CONTACTDELEGATE: processContactDelegates,
Cmd.ARG_CSEIDENTITY: processCSEIdentity,
Cmd.ARG_CSEKEYPAIR: createCSEKeyPair,
Cmd.ARG_LOOKERSTUDIOPERMISSION: processLookerStudioPermissions,
Cmd.ARG_DELEGATE: processDelegates,
Cmd.ARG_DRIVEFILE: createDriveFile,
@@ -72529,6 +72812,7 @@ USER_COMMANDS_WITH_OBJECTS = {
Cmd.ARG_CHATSPACE: deleteChatSpace,
Cmd.ARG_CLASSROOMINVITATION: deleteClassroomInvitations,
Cmd.ARG_CONTACTDELEGATE: processContactDelegates,
Cmd.ARG_CSEIDENTITY: processCSEIdentity,
Cmd.ARG_LOOKERSTUDIOPERMISSION: processLookerStudioPermissions,
Cmd.ARG_DELEGATE: processDelegates,
Cmd.ARG_DRIVEFILE: deleteDriveFile,
@@ -72568,6 +72852,11 @@ USER_COMMANDS_WITH_OBJECTS = {
Cmd.ARG_WORKINGLOCATION: deleteWorkingLocation,
}
),
'disable':
(Act.DISABLE,
{Cmd.ARG_CSEKEYPAIR: processCSEKeyPair,
}
),
'draft':
(Act.DRAFT,
{Cmd.ARG_MESSAGE: draftMessage,
@@ -72579,6 +72868,11 @@ USER_COMMANDS_WITH_OBJECTS = {
Cmd.ARG_DRIVETRASH: emptyDriveTrash,
}
),
'enable':
(Act.ENABLE,
{Cmd.ARG_CSEKEYPAIR: processCSEKeyPair,
}
),
'export':
(Act.EXPORT,
{Cmd.ARG_MESSAGE: exportMessages,
@@ -72616,6 +72910,8 @@ USER_COMMANDS_WITH_OBJECTS = {
Cmd.ARG_CHATSPACE: infoChatSpace,
Cmd.ARG_CHATSPACEDM: infoChatSpaceDM,
Cmd.ARG_CIGROUPMEMBERS: infoCIGroupMembers,
Cmd.ARG_CSEIDENTITY: processCSEIdentity,
Cmd.ARG_CSEKEYPAIR: processCSEKeyPair,
Cmd.ARG_DRIVEFILE: showFileInfo,
Cmd.ARG_DRIVEFILEACL: infoDriveFileACLs,
Cmd.ARG_DRIVELABEL: infoDriveLabels,
@@ -72657,6 +72953,11 @@ USER_COMMANDS_WITH_OBJECTS = {
Cmd.ARG_TASK: processTasks,
}
),
'obliterate':
(Act.OBLITERATE,
{Cmd.ARG_CSEKEYPAIR: processCSEKeyPair,
}
),
'purge':
(Act.PURGE,
{Cmd.ARG_DRIVEFILE: purgeDriveFile,
@@ -72681,6 +72982,8 @@ USER_COMMANDS_WITH_OBJECTS = {
Cmd.ARG_CLASSROOMINVITATION: printShowClassroomInvitations,
Cmd.ARG_CLASSROOMPROFILE: printShowClassroomProfile,
Cmd.ARG_CONTACTDELEGATE: printShowContactDelegates,
Cmd.ARG_CSEIDENTITY: printShowCSEIdentities,
Cmd.ARG_CSEKEYPAIR: printShowCSEKeyPairs,
Cmd.ARG_LOOKERSTUDIOASSET: printShowLookerStudioAssets,
Cmd.ARG_LOOKERSTUDIOPERMISSION: printShowLookerStudioPermissions,
Cmd.ARG_DELEGATE: printShowDelegates,
@@ -72778,6 +73081,8 @@ USER_COMMANDS_WITH_OBJECTS = {
Cmd.ARG_CLASSROOMPROFILE: printShowClassroomProfile,
Cmd.ARG_CONTACTDELEGATE: printShowContactDelegates,
Cmd.ARG_COUNT: showCountUser,
Cmd.ARG_CSEIDENTITY: printShowCSEIdentities,
Cmd.ARG_CSEKEYPAIR: printShowCSEKeyPairs,
Cmd.ARG_LOOKERSTUDIOASSET: printShowLookerStudioAssets,
Cmd.ARG_LOOKERSTUDIOPERMISSION: printShowLookerStudioPermissions,
Cmd.ARG_DELEGATE: printShowDelegates,
@@ -72902,6 +73207,7 @@ USER_COMMANDS_WITH_OBJECTS = {
Cmd.ARG_CHATMEMBER: deleteUpdateChatMember,
Cmd.ARG_CHATMESSAGE: updateChatMessage,
Cmd.ARG_CHATSPACE: updateChatSpace,
Cmd.ARG_CSEIDENTITY: processCSEIdentity,
Cmd.ARG_LOOKERSTUDIOPERMISSION: processLookerStudioPermissions,
Cmd.ARG_DELEGATE: updateDelegates,
Cmd.ARG_DRIVEFILE: updateDriveFile,
@@ -72982,6 +73288,8 @@ USER_COMMANDS_OBJ_ALIASES = {
Cmd.ARG_CONTACTGROUPS: Cmd.ARG_PEOPLECONTACTGROUP,
Cmd.ARG_CONTACTPHOTO: Cmd.ARG_PEOPLECONTACTPHOTO,
Cmd.ARG_CONTACTPHOTOS: Cmd.ARG_PEOPLECONTACTPHOTO,
Cmd.ARG_CSEIDENTITIES: Cmd.ARG_CSEIDENTITY,
Cmd.ARG_CSEKEYPAIRS: Cmd.ARG_CSEKEYPAIR,
Cmd.ARG_COUNTS: Cmd.ARG_COUNT,
Cmd.ARG_DATASTUDIOASSET: Cmd.ARG_LOOKERSTUDIOASSET,
Cmd.ARG_DATASTUDIOPERMISSION: Cmd.ARG_LOOKERSTUDIOPERMISSION,

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2023 Ross Scroggs All Rights Reserved.
# Copyright (C) 2024 Ross Scroggs All Rights Reserved.
#
# All Rights Reserved.
#
@@ -77,6 +77,7 @@ class GamAction():
MOVE_MERGE = 'movm'
NOACTION = 'noac'
NOACTION_PREVIEW = 'noap'
OBLITERATE = 'obli'
PERFORM = 'perf'
PRE_PROVISIONED_DISABLE ='ppdi'
PRE_PROVISIONED_REENABLE ='ppre'
@@ -193,6 +194,7 @@ class GamAction():
MOVE_MERGE: ['Moved(Merge)', 'Move(Merge)'],
NOACTION: ['No Action', 'No Action'],
NOACTION_PREVIEW: ['No Action (Preview)', 'No Action (Preview)'],
OBLITERATE: ['Obliterated', 'Obliterate'],
PERFORM: ['Action Performed', 'Perform Action'],
PRE_PROVISIONED_DISABLE: ['PreProvisioned Disabled', 'PreProvisioned Disable'],
PRE_PROVISIONED_REENABLE: ['PreProvisioned Reenabled', 'PreProvisioned Reenable'],

View File

@@ -161,6 +161,10 @@ ENABLE_GCLOUD_REAUTH = 'enable_gcloud_reauth'
EVENT_MAX_RESULTS = 'event_max_results'
# Path to extra_args.txt
EXTRA_ARGS = 'extra_args'
# Gmail CSE certificates directory
GMAIL_CSE_INCERT_DIR = 'gmail_cse_incert_dir'
# Gmail CSE KACL wrapped key files
GMAIL_CSE_INKEY_DIR = 'gmail_cse_inkey_dir'
# When processing items in batches, how many seconds should GAM wait between batches
INTER_BATCH_WAIT = 'inter_batch_wait'
# When retrieving lists of licenses from API, how many should be retrieved in each chunk
@@ -284,6 +288,8 @@ TODRIVE_UPLOAD_NODATA = 'todrive_upload_nodata'
TODRIVE_USER = 'todrive_user'
# Update CrOS org unit with orgUnitId
UPDATE_CROS_OU_WITH_ID = 'update_cros_ou_with_id'
# Use course owner for course access
USE_COURSE_OWNER_ACCESS = 'use_course_owner_access'
# Use Project ID as Project Name and App Name
USE_PROJECTID_AS_NAME = 'use_projectid_as_name'
# When retrieving lists of Users from API, how many should be retrieved in each chunk
@@ -359,6 +365,8 @@ Defaults = {
ENABLE_GCLOUD_REAUTH: FALSE,
EVENT_MAX_RESULTS: '250',
EXTRA_ARGS: '',
GMAIL_CSE_INCERT_DIR: '',
GMAIL_CSE_INKEY_DIR: '',
INTER_BATCH_WAIT: '0',
LICENSE_MAX_RESULTS: '100',
LICENSE_SKUS: '',
@@ -420,6 +428,7 @@ Defaults = {
TODRIVE_UPLOAD_NODATA: TRUE,
TODRIVE_USER: '',
UPDATE_CROS_OU_WITH_ID: FALSE,
USE_COURSE_OWNER_ACCESS: FALSE,
USE_PROJECTID_AS_NAME: FALSE,
USER_MAX_RESULTS: '500',
USER_SERVICE_ACCOUNT_ACCESS_ONLY: FALSE,
@@ -514,6 +523,8 @@ VAR_INFO = {
ENABLE_GCLOUD_REAUTH: {VAR_TYPE: TYPE_BOOLEAN},
EVENT_MAX_RESULTS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 2500)},
EXTRA_ARGS: {VAR_TYPE: TYPE_FILE, VAR_SIGFILE: FN_EXTRA_ARGS_TXT, VAR_SFFT: ('', FN_EXTRA_ARGS_TXT), VAR_ACCESS: os.R_OK},
GMAIL_CSE_INCERT_DIR: {VAR_TYPE: TYPE_DIRECTORY},
GMAIL_CSE_INKEY_DIR: {VAR_TYPE: TYPE_DIRECTORY},
INTER_BATCH_WAIT: {VAR_TYPE: TYPE_FLOAT, VAR_LIMITS: (0.0, 60.0)},
LICENSE_MAX_RESULTS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (10, 1000)},
LICENSE_SKUS: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)},
@@ -575,6 +586,7 @@ VAR_INFO = {
TODRIVE_UPLOAD_NODATA: {VAR_TYPE: TYPE_BOOLEAN},
TODRIVE_USER: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)},
UPDATE_CROS_OU_WITH_ID: {VAR_TYPE: TYPE_BOOLEAN},
USE_COURSE_OWNER_ACCESS: {VAR_TYPE: TYPE_BOOLEAN},
USE_PROJECTID_AS_NAME: {VAR_TYPE: TYPE_BOOLEAN},
USER_MAX_RESULTS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 500)},
USER_SERVICE_ACCOUNT_ACCESS_ONLY: {VAR_TYPE: TYPE_BOOLEAN},

View File

@@ -520,6 +520,10 @@ class GamCLArgs():
ARG_CROSES = 'croses'
ARG_CROSACTIVITY = 'crosactivity'
ARG_CROSTELEMETRY = 'crostelemetry'
ARG_CSEIDENTITY = 'cseidentity'
ARG_CSEIDENTITIES = 'cseidentities'
ARG_CSEKEYPAIR = 'csekeypair'
ARG_CSEKEYPAIRS = 'csekeypairs'
ARG_CURRENTPROJECTID = 'currentprojectid'
ARG_CUSTOMER = 'customer'
ARG_DATASTUDIOASSET = 'datastudioasset'
@@ -846,6 +850,7 @@ class GamCLArgs():
OB_COURSE_WORK_STATE_LIST = "CourseWorkStateList"
OB_CROS_DEVICE_ENTITY = 'CrOSDeviceEntity'
OB_CROS_ENTITY = 'CrOSEntity'
OB_CSE_KEYPAIR_ID = 'CSEKeyPairID'
OB_CUSTOMER_ID = 'CustomerID'
OB_CUSTOMER_AUTH_TOKEN = 'CustomerAuthToken'
OB_DEVICE_FILE_ENTITY = 'DeviceFileEntity'

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2023 Ross Scroggs All Rights Reserved.
# Copyright (C) 2024 Ross Scroggs All Rights Reserved.
#
# All Rights Reserved.
#
@@ -150,12 +150,14 @@ class GamEntity():
CRITERIA = 'crit'
CROS_DEVICE = 'cros'
CROS_SERIAL_NUMBER = 'crsn'
CSE_IDENTITY = 'csei'
CSE_KEYPAIR = 'csek'
CUSTOMER_DOMAIN = 'cudo'
CUSTOMER_ID = 'cuid'
DATE = 'date'
DEFAULT_LANGUAGE = 'dfla'
DELEGATE = 'dele'
DELETED_USER = 'del'
DELETED_USER = 'delu'
DELIVERY = 'deli'
DEVICE = 'devi'
DEVICE_FILE = 'devf'
@@ -163,7 +165,7 @@ class GamEntity():
DEVICE_USER = 'devu'
DEVICE_USER_CLIENT_STATE = 'ducs'
DISCOVERY_JSON_FILE = 'disc'
DOCUMENT = 'doc '
DOCUMENT = 'docu'
DOMAIN = 'doma'
DOMAIN_ALIAS = 'doal'
DOMAIN_CONTACT = 'doco'
@@ -483,6 +485,8 @@ class GamEntity():
CRITERIA: ['Criteria', 'Criteria'],
CROS_DEVICE: ['CrOS Devices', 'CrOS Device'],
CROS_SERIAL_NUMBER: ['CrOS Serial Numbers', 'CrOS Serial Numbers'],
CSE_IDENTITY: ['CSE Identities', 'CSE Identity'],
CSE_KEYPAIR: ['CSE KeyPairs', 'CSE KeyPair'],
CUSTOMER_DOMAIN: ['Customer Domains', 'Customer Domain'],
CUSTOMER_ID: ['Customer IDs', 'Customer ID'],
DATE: ['Dates', 'Date'],

View File

@@ -232,6 +232,7 @@ FAILED_PRECONDITION = 'Failed precondition'
FAILED_TO_PARSE_AS_JSON = 'Failed to parse as JSON'
FAILED_TO_PARSE_AS_LIST = 'Failed to parse as list'
FIELD_NOT_FOUND_IN_SCHEMA = 'Field {0} not found in schema {1}'
FILE_NOT_FOUND = 'File {0} not found'
FINISHED = 'Finished'
FILTER_CAN_ONLY_CONTAIN_ONE_CATEGORY_LABEL = 'Filter can only contain one CATEGORY label'
FILTER_CAN_ONLY_CONTAIN_ONE_USER_LABEL = 'Filter can only contain one USER label'
@@ -300,6 +301,8 @@ IS_NOT_UNIQUE = 'Is not unique, {0}: {1}'
IS_REQD_TO_CHG_PWD_NO_DELEGATION = 'Is required to change password at next login. You must change password or clear changepassword flag for delegation.'
IS_SUSPENDED_NO_BACKUPCODES = 'User is suspended. You must unsuspend to process backupcodes'
IS_SUSPENDED_NO_DELEGATION = 'Is suspended. You must unsuspend for delegation.'
JSON_ERROR = 'JSON error "{0}" in file {1}'
JSON_KEY_NOT_FOUND = 'JSON key "{0}" not found in file {1}'
KIOSK_MODE_REQUIRED = ' This command ({0}) requires that the ChromeOS device be in Kiosk mode.'
LESS_THAN_1_SECOND = 'less than 1 second'
LIST_CHROMEOS_INVALID_INPUT_PAGE_TOKEN_RETRY = 'List ChromeOSdevices Invalid Input: pageToken retry'