mirror of
https://github.com/GAM-team/GAM.git
synced 2026-06-04 22:31:38 +00:00
Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aaf6448563 | ||
|
|
4a696635f5 | ||
|
|
beb14befca | ||
|
|
c91703364d | ||
|
|
597cea17cd | ||
|
|
9585f6c598 | ||
|
|
e356fe3e85 | ||
|
|
55e5b86ec4 | ||
|
|
bf29a56aeb | ||
|
|
07c57d4197 | ||
|
|
146db31cb5 | ||
|
|
14239fcd47 | ||
|
|
8dc6a17295 | ||
|
|
76f9a6c746 | ||
|
|
eb155a5690 | ||
|
|
b78575aa8f | ||
|
|
91a5cd5c69 | ||
|
|
dd3e6420b6 | ||
|
|
d6a65861e0 | ||
|
|
fe77ff3f60 | ||
|
|
326cccd525 | ||
|
|
b41ca0f0be | ||
|
|
02fa092775 | ||
|
|
57860dc5a6 | ||
|
|
e28f2fb8cd | ||
|
|
0423dd4069 | ||
|
|
5e38137916 | ||
|
|
dc90fb9c94 | ||
|
|
2e2575c360 | ||
|
|
2732abbc93 | ||
|
|
e9d5d676a5 | ||
|
|
18bab4044e | ||
|
|
2ccc4a6932 | ||
|
|
d01d02e700 | ||
|
|
56c6f6cabe | ||
|
|
adb1e58937 | ||
|
|
a59c893652 | ||
|
|
93bcd5f43b | ||
|
|
d7453a7841 | ||
|
|
4fe3dc052a | ||
|
|
c88c755785 |
36
.travis.yml
36
.travis.yml
@@ -1,16 +1,16 @@
|
||||
if: tag IS blank
|
||||
os: linux
|
||||
language: python
|
||||
dist: xenial
|
||||
dist: focal
|
||||
|
||||
env:
|
||||
global:
|
||||
- BUILD_PYTHON_VERSION=3.8.5
|
||||
- MIN_PYTHON_VERSION=3.8.5
|
||||
- BUILD_OPENSSL_VERSION=1.1.1g
|
||||
- MIN_OPENSSL_VERSION=1.1.1g
|
||||
- BUILD_PYTHON_VERSION=3.9.0
|
||||
- MIN_PYTHON_VERSION=3.9.0
|
||||
- BUILD_OPENSSL_VERSION=1.1.1h
|
||||
- MIN_OPENSSL_VERSION=1.1.1h
|
||||
- PATCHELF_VERSION=0.11
|
||||
- PYINSTALLER_COMMIT=ad39eb8df209d02636399ffdc44521a97886cf8c
|
||||
- PYINSTALLER_COMMIT=7aa19839c171d898b5cf957739083c4bb901607e
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.cache/pip
|
||||
@@ -61,18 +61,21 @@ jobs:
|
||||
language: python
|
||||
python: 3.7
|
||||
- os: linux
|
||||
name: "Python 3.9 dev Source Testing"
|
||||
name: "Python 3.8 Source Testing"
|
||||
language: python
|
||||
python: 3.9-dev
|
||||
dist: focal
|
||||
python: 3.8
|
||||
- os: linux
|
||||
name: "Python trunk nightly Source Testing"
|
||||
name: "Python 3.10 dev Source Testing"
|
||||
language: python
|
||||
python: nightly
|
||||
- os: linux
|
||||
name: "Python PyPi Source Testing"
|
||||
language: python
|
||||
python: pypy3
|
||||
python: 3.10-dev
|
||||
# - os: linux
|
||||
# name: "Python trunk nightly Source Testing"
|
||||
# language: python
|
||||
# python: nightly
|
||||
# - os: linux
|
||||
# name: "Python PyPi Source Testing"
|
||||
# language: python
|
||||
# python: pypy3
|
||||
- os: osx
|
||||
name: "MacOS 10.13"
|
||||
language: generic
|
||||
@@ -150,6 +153,7 @@ script:
|
||||
done; fi
|
||||
- if [ "$e2e" = true ]; then $gam create user $newuser firstname Travis lastname $jid password random recoveryphone 12125121110 recoveryemail jay0lee@gmail.com travis.jid $jid; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $gam_user sendemail recipient $newuser subject "test message $newbase" message "Travis test message"; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $gam_user sendemail recipient exchange@pdl.jaylee.us subject "test ${tstamp}" message "test message"; fi
|
||||
- if [ "$e2e" = true ]; then $gam create group $newgroup name "Travis $jid group" description "This is a description" isarchived true; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $newuser add license gsuitebusiness; fi
|
||||
- if [ "$e2e" = true ]; then $gam update group $newgroup add owner $gam_user; fi
|
||||
@@ -217,13 +221,13 @@ script:
|
||||
- if [ "$e2e" = true ]; then $gam print devices; fi
|
||||
- if [ "$e2e" = true ]; then export sn="$jid$jid$jid$jid$jid-$(openssl rand -base64 32 | sed 's/[^a-zA-Z0-9]//g')"; fi
|
||||
- if [ "$e2e" = true ]; then $gam create device serialnumber $sn devicetype android; fi
|
||||
- if [ "$e2e" = true ]; then $gam print devices filter "serial:$jid$jid$jid$jid$jid-" | $gam csv - gam delete device id ~name; fi
|
||||
- if [ "$e2e" = true ]; then $gam print cros allfields nolists; fi
|
||||
- if [ "$e2e" = true ]; then $gam report usageparameters customer; fi
|
||||
- if [ "$e2e" = true ]; then $gam report usage customer parameters gmail:num_emails_sent,accounts:num_1day_logins; fi
|
||||
- if [ "$e2e" = true ]; then $gam report customer todrive; fi
|
||||
- if [ "$e2e" = true ]; then $gam report users fields accounts:is_less_secure_apps_access_allowed,gmail:last_imap_time,gmail:last_pop_time filters "accounts:last_login_time>2019-01-01T00:00:00.000Z" todrive; fi
|
||||
- if [ "$e2e" = true ]; then $gam report admin start -3d todrive; fi
|
||||
- if [ "$e2e" = true ]; then $gam print devices nopersonaldevices nodeviceusers filter "serial:$jid$jid$jid$jid$jid-" | $gam csv - gam delete device id ~name; fi
|
||||
- if ([ "$e2e" = true ] && [[ "$TRAVIS_JOB_NAME" != *"Testing" ]]); then
|
||||
for gamfile in gam-$GAMVERSION-*; do
|
||||
fileid=$($gam user $gam_user add drivefile localfile $gamfile drivefilename $GAMVERSION-${TRAVIS_COMMIT:0:7}-$gamfile parentid 1N2zbO33qzUQFsGM49-m9AQC1ijzd_ru1 returnidonly);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
GAM is a command line tool for Google G Suite Administrators to manage domain and user settings quickly and easily. [](https://travis-ci.org/jay0lee/GAM)
|
||||
GAM is a command line tool for Google Workspace (fka G Suite) Administrators to manage domain and user settings quickly and easily. [](https://travis-ci.org/jay0lee/GAM)
|
||||
# Quick Start
|
||||
## Linux / MacOS
|
||||
Open a terminal and run:
|
||||
|
||||
@@ -67,7 +67,9 @@ If an item contains spaces, it should be surrounded by ".
|
||||
gpresentation|
|
||||
gscript|
|
||||
gsite|
|
||||
gsheet|gspreadsheet
|
||||
gsheet|gspreadsheet|
|
||||
gshortcut|
|
||||
g3pshortcut
|
||||
<ProductID> ::=
|
||||
Google-Apps|
|
||||
Google-Chrome-Device-Management|
|
||||
@@ -91,14 +93,19 @@ If an item contains spaces, it should be surrounded by ".
|
||||
gams|postini|gsuitegams|gsuitepostini|gsuitemessagesecurity|Google-Apps-For-Postini|
|
||||
gal|gsl|lite|gsuitelite|Google-Apps-Lite|
|
||||
gau|gsb|unlimited|gsuitebusiness|Google-Apps-Unlimited|
|
||||
gae|gse|enterprise|gsuiteenterprise|1010020020|
|
||||
wsentplus|workspaceenterpriseplus|gae|gse|enterprise|gsuiteenterprise|1010020020|
|
||||
wsbizplus|workspacebusinessplus|1010020025|
|
||||
wsentstan|workspaceenterprisestandard|'1010020026|
|
||||
wsbizstart|workspacebusinessstarter|1010020027|
|
||||
wsbizstan|workspacebusinessstandard|1010020028|
|
||||
gsefe|e4e|gsuiteenterpriseeducation|1010310002|
|
||||
gsefes|e4es|gsuiteenterpriseeducationstudent|1010310003|
|
||||
gsbau|businessarchived|gsuitebusinessarchived|
|
||||
gseau|enterprisearchived|gsuiteenterprisearchived|
|
||||
chrome|cdm|googlechromedevicemanagement|Google-Chrome-Device-Management|
|
||||
coordinate|googlecoordinate|Google-Coordinate|
|
||||
d4e|driveenterprise|drive4enterprise|
|
||||
wsess|workspaceesentials|gsuiteessentials|essentials|d4e|driveenterprise|drive4enterprise|1010060001|
|
||||
wsentess|workspaceenterpriseessentials|1010060003|
|
||||
drive20gb|20gb|googledrivestorage20gb|Google-Drive-storage-20GB|
|
||||
drive50gb|50gb|googledrivestorage50gb|Google-Drive-storage-50GB|
|
||||
drive200gb|200gb|googledrivestorage200gb|Google-Drive-storage-200GB|
|
||||
@@ -158,6 +165,9 @@ If an item contains spaces, it should be surrounded by ".
|
||||
<CourseState> ::= active|archived|provisioned|declined
|
||||
<CrOSID> ::= <String>
|
||||
<CustomerID> ::= <String>
|
||||
<DeviceID> ::= devices/<String>
|
||||
<DeviceType> ::= android|chrome_os|google_sync|ios|linux|mac_os|windows
|
||||
<DeviceUserID> ::= devices/<String>/deviceUsers/<String>
|
||||
<DomainAlias> ::= <String>
|
||||
<DriveFileACLRole> ::= commenter|contentmanager|editor|fileorganizer|organizer|owner|reader|writer
|
||||
<DriveFileID> ::= <String>
|
||||
@@ -305,6 +315,7 @@ If an item contains spaces, it should be surrounded by ".
|
||||
appdatacontents|
|
||||
cancomment|
|
||||
canreadrevisions|
|
||||
contentrestrictions|
|
||||
copyable|
|
||||
copyrequireswriterpermission|
|
||||
createddate|createdtime|
|
||||
@@ -678,16 +689,22 @@ Specify a collection of Users by directly specifying them or by specifiying item
|
||||
(localfile <FileName>)|
|
||||
(convert)|(ocr)|(ocrlanguage <Language>)|
|
||||
(restricted|restrict)|(starred|star)|(trashed|trash)|(viewed|view)|
|
||||
(contentrestrictions readonly false)|
|
||||
(contentrestrictions readonly true [reason <String>])|
|
||||
copyrequireswriterpermission|
|
||||
(lastviewedbyme <Time>)|(modifieddate|modifiedtime <Time>)|(description <String>)|(mimetype <MimeType>)|
|
||||
(parentid <DriveFolderID>)|(parentname <DriveFolderName>)|(anyownerparentname <DriveFolderName>)|writerscantshare
|
||||
(parentid <DriveFolderID>)|(parentname <DriveFolderName>)|(anyownerparentname <DriveFolderName>)|writerscantshare|
|
||||
(shortcut <DriveFileID>)
|
||||
<DriveFileUpdateAttributes> ::=
|
||||
(localfile <FileName>)|
|
||||
(convert)|(ocr)|(ocrlanguage <Language>)|
|
||||
(restricted|restrict <Boolean>)|(starred|star <Boolean>)|(trashed|trash <Boolean>)|(viewed|view <Boolean>)|
|
||||
(contentrestrictions readonly false)|
|
||||
(contentrestrictions readonly true [reason <String>])|
|
||||
(copyrequireswriterpermission <Boolean>)|
|
||||
(lastviewedbyme <Time>)|(modifieddate <Time>)|(description <String>)|(mimetype <MimeType>)|
|
||||
(parentid <DriveFolderID>)|(parentname <DriveFolderName>)|(anyownerparentname <DriveFolderName>)|writerscantshare
|
||||
(parentid <DriveFolderID>)|(parentname <DriveFolderName>)|(anyownerparentname <DriveFolderName>)|writerscantshare|
|
||||
(shortcut <DriveFileID>)
|
||||
<GroupSettingsAttribute> ::=
|
||||
(allowexternalmembers <Boolean>)|
|
||||
(allowwebposting <Boolean>)|
|
||||
@@ -841,7 +858,7 @@ gam show projects [<EmailAddress>] [all|gam|<ProjectID>|(filter <String>)]
|
||||
gam print projects [<EmailAddress>] [all|gam|<ProjectID>|(filter <String>)] [todrive]
|
||||
|
||||
gam rotate sakey|sakeys [retain_none|retain_existing|replace_current]
|
||||
[(algorithm KEY_ALG_RSA_1024|KEY_ALG_RSA_2048)|(localkeysize 1024|2048|4096)]
|
||||
[(algorithm KEY_ALG_RSA_1024|KEY_ALG_RSA_2048)|(localkeysize 1024|2048|4096)]
|
||||
gam delete sakey|sakeys <ServiceAccountKeyList>+ [doit]
|
||||
gam show sakey|sakeys [all|system|user]
|
||||
|
||||
@@ -1024,7 +1041,10 @@ The following attributes are equivalent:
|
||||
(colorindex|colorid <EventColorIndex>)
|
||||
(description <String>)|
|
||||
(end (allday <Date>)|<Time>)|
|
||||
(guestscaninviteothers <Boolean>)|
|
||||
guestscantinviteothers|
|
||||
(guestscanmodify <Boolean>)|
|
||||
(guestscanseeothers <Boolean>)|
|
||||
guestscantseeothers|
|
||||
hangoutsmeet|
|
||||
(location <String>)|
|
||||
@@ -1127,6 +1147,34 @@ The listlimit <Number> argument limits the number of recent users, time ranges a
|
||||
The start <Date> and end <Date> arguments filter the time ranges.
|
||||
Delimiter defaults to comma.
|
||||
|
||||
<DeviceID> ::= devices/<String>
|
||||
<DeviceType> ::= android|chrome_os|google_sync|ios|linux|mac_os|windows
|
||||
<DeviceUserID> ::= devices/<String>/deviceUsers/<String>
|
||||
<DeviceOrderbyFieldName> ::=
|
||||
createtime|devicetype|lastsynctime|model|osversion|serialnumber
|
||||
|
||||
gam create device serialnumber <String> devicetype <DeviceType> [assetid <String>]
|
||||
gam info device [id] <DeviceID>
|
||||
gam delete device [id] <DeviceID>
|
||||
gam cancelwipe device [id] <DeviceID>
|
||||
gam wipe device [id] <DeviceID>
|
||||
gam approve deviceuser [id] <DeviceUserID>
|
||||
gam block deviceuser [id] <DeviceUserID>
|
||||
gam delete deviceuser [id] <DeviceUserID>
|
||||
gam cancelwipe deviceuser [id] <DeviceUserID>
|
||||
gam wipe deviceuser [id] <DeviceUserID>
|
||||
gam print devices [todrive] [filter|query <QueryDevice>]
|
||||
[orderby <DeviceOrderByFieldName> [ascending|descending]]
|
||||
[company|personal|nocompanydevices|nopersonaldevices]
|
||||
[nodeviceusers]
|
||||
gam sync devices [filter|query <QueryDevice>]
|
||||
csvfile <FileName>
|
||||
(devicetype_column <String>)|(static_devicetype <DeviceType>)
|
||||
serialnumber_column <String>
|
||||
[assettag_column <String>]
|
||||
[unassigned_missing_action delete|wipe|donothing]
|
||||
[assigned_missing_action delete|wipe|donothing]
|
||||
|
||||
gam update mobile <MobileID>|query:<QueryMobile> action <MobileAction> [doit] [if_users|match_users <UserTypeEntity>]
|
||||
gam delete mobile <MobileID>
|
||||
gam info mobile <MobileID>
|
||||
@@ -1336,6 +1384,7 @@ gam <UserTypeEntity> delete|del group|groups
|
||||
gam <UserTypeEntity> create|add license <SKUID> [product|productid <ProductID>]
|
||||
gam <UserTypeEntity> update license <SKUID> [product|productid <ProductID>] [from] <SKUID>
|
||||
gam <UserTypeEntity> delete|del license <SKUID> [product|productid <ProductID>]
|
||||
gam <UserTypeEntity> sync license <SKUID> [product|productid <ProductID>]
|
||||
|
||||
gam <UserTypeEntity> update photo <FileNamePattern>
|
||||
gam <UserTypeEntity> delete|del photo
|
||||
@@ -1456,4 +1505,3 @@ gam <UserTypeEntity> show vacation [format]
|
||||
|
||||
gam <UserTypeEntity> signout
|
||||
gam <UserTypeEntity> turnoff2sv
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@ upgrade_only=false
|
||||
gamversion="latest"
|
||||
adminuser=""
|
||||
regularuser=""
|
||||
gam_glibc_vers="2.27 2.23"
|
||||
gam_macos_vers="10.15.4 10.14.6 10.13.6"
|
||||
gam_glibc_vers="2.31 2.27 2.23"
|
||||
gam_macos_vers="10.15.6 10.14.6 10.13.6"
|
||||
|
||||
while getopts "hd:a:o:b:lp:u:r:v:" OPTION
|
||||
do
|
||||
@@ -240,7 +240,7 @@ fi
|
||||
|
||||
# Update profile to add gam command
|
||||
if [ "$update_profile" = true ]; then
|
||||
alias_line="gam() { \"$target_dir/gam/gam\" \"\$@\" ; }"
|
||||
alias_line="function gam() { \"$target_dir/gam/gam\" \"\$@\" ; }"
|
||||
if [ "$gamos" == "linux" ]; then
|
||||
update_profile "$HOME/.bash_aliases" 0 || update_profile "$HOME/.bash_profile" 0 || update_profile "$HOME/.bashrc" 0
|
||||
update_profile "$HOME/.zshrc" 0
|
||||
@@ -288,7 +288,7 @@ while true; do
|
||||
case $yn in
|
||||
[Yy]*)
|
||||
if [ "$adminuser" == "" ]; then
|
||||
read -p "Please enter your G Suite admin email address: " adminuser
|
||||
read -p "Please enter your Google Workspace admin email address: " adminuser
|
||||
fi
|
||||
"$target_dir/gam/gam" create project $adminuser
|
||||
rc=$?
|
||||
@@ -312,7 +312,7 @@ done
|
||||
|
||||
admin_authorized=false
|
||||
while $project_created; do
|
||||
read -p "Are you ready to authorize GAM to perform G Suite management operations as your admin account? (yes or no) " yn
|
||||
read -p "Are you ready to authorize GAM to perform Google Workspace management operations as your admin account? (yes or no) " yn
|
||||
case $yn in
|
||||
[Yy]*)
|
||||
"$target_dir/gam/gam" oauth create $adminuser
|
||||
@@ -337,11 +337,11 @@ done
|
||||
|
||||
service_account_authorized=false
|
||||
while $project_created; do
|
||||
read -p "Are you ready to authorize GAM to manage G Suite user data and settings? (yes or no) " yn
|
||||
read -p "Are you ready to authorize GAM to manage Google Workspace user data and settings? (yes or no) " yn
|
||||
case $yn in
|
||||
[Yy]*)
|
||||
if [ "$regularuser" == "" ]; then
|
||||
read -p "Please enter the email address of a regular G Suite user: " regularuser
|
||||
read -p "Please enter the email address of a regular Google Workspace user: " regularuser
|
||||
fi
|
||||
echo_yellow "Great! Checking service account scopes.This will fail the first time. Follow the steps to authorize and retry. It can take a few minutes for scopes to PASS after they've been authorized in the admin console."
|
||||
"$target_dir/gam/gam" user $adminuser check serviceaccount
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
@ goto createproject
|
||||
)
|
||||
@echo(
|
||||
@set /p adminemail= "Please enter your G Suite admin email address: "
|
||||
@set /p adminemail= "Please enter your Google Workspace admin email address: "
|
||||
@gam create project %adminemail%
|
||||
@if not ERRORLEVEL 1 goto projectdone
|
||||
@echo(
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
:adminauth
|
||||
@echo(
|
||||
@set /p yn= "Are you ready to authorize GAM to perform G Suite management operations as your admin account? [y or n] "
|
||||
@set /p yn= "Are you ready to authorize GAM to perform Google Workspace management operations as your admin account? [y or n] "
|
||||
@if /I "%yn%"=="n" (
|
||||
@ echo(
|
||||
@ echo You can authorize an admin later by running:
|
||||
@@ -59,7 +59,7 @@
|
||||
|
||||
:saauth
|
||||
@echo(
|
||||
@set /p yn= "Are you ready to authorize GAM to manage G Suite user data and settings? [y or n] "
|
||||
@set /p yn= "Are you ready to authorize GAM to manage Google Workspace user data and settings? [y or n] "
|
||||
@if /I "%yn%"=="n" (
|
||||
@ echo(
|
||||
@ echo You can authorize a service account later by running:
|
||||
@@ -73,7 +73,7 @@
|
||||
@ goto saauth
|
||||
)
|
||||
@echo(
|
||||
@set /p regularuser= "Please enter the email address of a regular G Suite user: "
|
||||
@set /p regularuser= "Please enter the email address of a regular Google Workspace user: "
|
||||
@echo Great! Checking service account scopes. This will fail the first time. Follow the steps to authorize and retry. It can take a few minutes for scopes to PASS after they've been authorized in the admin console.
|
||||
@gam user %regularuser% check serviceaccount
|
||||
@if not ERRORLEVEL 1 goto sadone
|
||||
|
||||
@@ -4,10 +4,7 @@ import base64
|
||||
import configparser
|
||||
import csv
|
||||
import datetime
|
||||
import difflib
|
||||
from email import message_from_string
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
import mimetypes
|
||||
import os
|
||||
@@ -54,7 +51,6 @@ from gam.gapi import calendar as gapi_calendar
|
||||
from gam.gapi import cloudidentity as gapi_cloudidentity
|
||||
from gam.gapi.cloudidentity import devices as gapi_cloudidentity_devices
|
||||
from gam.gapi.cloudidentity import groups as gapi_cloudidentity_groups
|
||||
from gam.gapi import directory as gapi_directory
|
||||
from gam.gapi.directory import asps as gapi_directory_asps
|
||||
from gam.gapi.directory import cros as gapi_directory_cros
|
||||
from gam.gapi.directory import customer as gapi_directory_customer
|
||||
@@ -67,6 +63,7 @@ from gam.gapi.directory import privileges as gapi_directory_privileges
|
||||
from gam.gapi.directory import resource as gapi_directory_resource
|
||||
from gam.gapi.directory import roles as gapi_directory_roles
|
||||
from gam.gapi.directory import users as gapi_directory_users
|
||||
from gam.gapi import licensing as gapi_licensing
|
||||
from gam.gapi import siteverification as gapi_siteverification
|
||||
from gam.gapi import errors as gapi_errors
|
||||
from gam.gapi import reports as gapi_reports
|
||||
@@ -510,6 +507,7 @@ def SetGlobalVariables():
|
||||
if GC_Defaults[GC_OAUTH2SERVICE_JSON].find('.') == -1:
|
||||
GC_Defaults[GC_OAUTH2SERVICE_JSON] += '.json'
|
||||
_getOldEnvVar(GC_CLIENT_SECRETS_JSON, 'CLIENTSECRETS')
|
||||
_getOldEnvVar(GC_ADMIN_EMAIL, 'GA_ADMIN_EMAIL')
|
||||
_getOldEnvVar(GC_DOMAIN, 'GA_DOMAIN')
|
||||
_getOldEnvVar(GC_CUSTOMER_ID, 'CUSTOMER_ID')
|
||||
_getOldEnvVar(GC_CHARSET, 'GAM_CHARSET')
|
||||
@@ -1237,15 +1235,17 @@ def doCheckServiceAccount(users):
|
||||
check_scopes.append(USERINFO_EMAIL_SCOPE)
|
||||
long_url = ('https://admin.google.com/ac/owl/domainwidedelegation'
|
||||
f'?clientScopeToAdd={",".join(check_scopes)}'
|
||||
f'&clientIdToAdd={service_account}')
|
||||
f'&clientIdToAdd={service_account}&overwriteClientId=true')
|
||||
short_url = utils.shorten_url(long_url)
|
||||
scopes_failed = f'''Some scopes failed! To authorize them, please go to:
|
||||
|
||||
{short_url}
|
||||
|
||||
You will be redirected to the G Suite admin console. The Client Name and API
|
||||
Scopes fields will be pre-populated. Please click Authorize to allow these
|
||||
scopes access. After authorizing it may take some time for this test to pass so
|
||||
You will be directed to the G Suite admin console Security/API Controls/Domain-wide Delegation page
|
||||
The "Add a new Client ID" box will open
|
||||
Make sure that "Overwrite existing client ID" is checked
|
||||
Please click Authorize to allow these scopes access.
|
||||
After authorizing it may take some time for this test to pass so
|
||||
go grab a cup of coffee and then try this command again.
|
||||
'''
|
||||
controlflow.system_error_exit(1, scopes_failed)
|
||||
@@ -3197,10 +3197,21 @@ def printDriveFileList(users):
|
||||
'kind', 'etag', 'selfLink'
|
||||
]:
|
||||
continue
|
||||
x_attrib = f'{attrib}.{j}.{list_attrib}'
|
||||
if x_attrib not in titles:
|
||||
titles.append(x_attrib)
|
||||
a_file[x_attrib] = l_attrib[list_attrib]
|
||||
if not isinstance(l_attrib[list_attrib], dict):
|
||||
x_attrib = f'{attrib}.{j}.{list_attrib}'
|
||||
if x_attrib not in titles:
|
||||
titles.append(x_attrib)
|
||||
a_file[x_attrib] = l_attrib[list_attrib]
|
||||
else:
|
||||
for sl_attrib in l_attrib[list_attrib]:
|
||||
if sl_attrib in [
|
||||
'kind', 'etag', 'selfLink'
|
||||
]:
|
||||
continue
|
||||
x_attrib = f'{attrib}.{j}.{list_attrib}.{sl_attrib}'
|
||||
if x_attrib not in titles:
|
||||
titles.append(x_attrib)
|
||||
a_file[x_attrib] = l_attrib[list_attrib][sl_attrib]
|
||||
elif isinstance(f_file[attrib], (str, int, bool)):
|
||||
if attrib not in titles:
|
||||
titles.append(attrib)
|
||||
@@ -3454,6 +3465,7 @@ def initializeDriveFileAttributes():
|
||||
|
||||
|
||||
def getDriveFileAttribute(i, body, parameters, myarg, update=False):
|
||||
operation = 'update' if update else 'add'
|
||||
if myarg == 'localfile':
|
||||
parameters[DFA_LOCALFILEPATH] = sys.argv[i + 1]
|
||||
parameters[DFA_LOCALFILENAME] = os.path.basename(
|
||||
@@ -3529,9 +3541,30 @@ def getDriveFileAttribute(i, body, parameters, myarg, update=False):
|
||||
elif myarg == 'writerscantshare':
|
||||
body['writersCanShare'] = False
|
||||
i += 1
|
||||
elif myarg == 'contentrestrictions':
|
||||
body['contentRestrictions'] = [{}]
|
||||
restriction = sys.argv[i+1].lower().replace('_', '')
|
||||
if restriction == 'readonly':
|
||||
body['contentRestrictions'][0]['readOnly'] = getBoolean(
|
||||
sys.argv[i+2], f'gam <users> {operation} drivefile')
|
||||
i += 3
|
||||
if len(sys.argv) > i and sys.argv[i].lower() == 'reason':
|
||||
if body['contentRestrictions'][0]['readOnly']:
|
||||
body['contentRestrictions'][0]['reason'] = sys.argv[i+1]
|
||||
else:
|
||||
controlflow.invalid_argument_exit(
|
||||
'reason', 'contentrestrictions readonly false')
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(
|
||||
restriction, f'gam <users> {operation} drivefile')
|
||||
elif myarg == 'shortcut':
|
||||
body['mimeType'] = MIMETYPE_GA_SHORTCUT
|
||||
body['shortcutDetails'] = {'targetId': sys.argv[i+1]}
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(
|
||||
myarg, f"gam <users> {['add', 'update'][update]} drivefile")
|
||||
myarg, f"gam <users> {operation} drivefile")
|
||||
return i
|
||||
|
||||
|
||||
@@ -4308,73 +4341,6 @@ def getImap(users):
|
||||
)
|
||||
|
||||
|
||||
def getProductAndSKU(sku):
|
||||
l_sku = sku.lower().replace('-', '').replace(' ', '')
|
||||
for a_sku, sku_values in list(SKUS.items()):
|
||||
if l_sku == a_sku.lower().replace(
|
||||
'-',
|
||||
'') or l_sku in sku_values['aliases'] or l_sku == sku_values[
|
||||
'displayName'].lower().replace(' ', ''):
|
||||
return (sku_values['product'], a_sku)
|
||||
try:
|
||||
product = re.search('^([A-Z,a-z]*-[A-Z,a-z]*)', sku).group(1)
|
||||
except AttributeError:
|
||||
product = sku
|
||||
return (product, sku)
|
||||
|
||||
|
||||
def doLicense(users, operation):
|
||||
lic = buildGAPIObject('licensing')
|
||||
sku = sys.argv[5]
|
||||
productId, skuId = getProductAndSKU(sku)
|
||||
i = 6
|
||||
if len(sys.argv) > 6 and sys.argv[i].lower() in ['product', 'productid']:
|
||||
productId = sys.argv[i + 1]
|
||||
i += 2
|
||||
for user in users:
|
||||
if operation == 'delete':
|
||||
print(
|
||||
f'Removing license {_formatSKUIdDisplayName(skuId)} from user {user}'
|
||||
)
|
||||
gapi.call(lic.licenseAssignments(),
|
||||
operation,
|
||||
soft_errors=True,
|
||||
productId=productId,
|
||||
skuId=skuId,
|
||||
userId=user)
|
||||
elif operation == 'insert':
|
||||
print(
|
||||
f'Adding license {_formatSKUIdDisplayName(skuId)} to user {user}'
|
||||
)
|
||||
gapi.call(lic.licenseAssignments(),
|
||||
operation,
|
||||
soft_errors=True,
|
||||
productId=productId,
|
||||
skuId=skuId,
|
||||
body={'userId': user})
|
||||
elif operation == 'patch':
|
||||
try:
|
||||
old_sku = sys.argv[i]
|
||||
if old_sku.lower() == 'from':
|
||||
old_sku = sys.argv[i + 1]
|
||||
except KeyError:
|
||||
controlflow.system_error_exit(
|
||||
2,
|
||||
'You need to specify the user\'s old SKU as the last argument'
|
||||
)
|
||||
_, old_sku = getProductAndSKU(old_sku)
|
||||
print(
|
||||
f'Changing user {user} from license {_formatSKUIdDisplayName(old_sku)} to {_formatSKUIdDisplayName(skuId)}'
|
||||
)
|
||||
gapi.call(lic.licenseAssignments(),
|
||||
operation,
|
||||
soft_errors=True,
|
||||
productId=productId,
|
||||
skuId=old_sku,
|
||||
userId=user,
|
||||
body={'skuId': skuId})
|
||||
|
||||
|
||||
def doPop(users):
|
||||
enable = getBoolean(sys.argv[4], 'gam <users> pop')
|
||||
body = {
|
||||
@@ -8437,7 +8403,7 @@ def _getResoldSubscriptionAttr(arg, customerId):
|
||||
arg[i + 2], 'maximumNumberOfSeats', minVal=0)
|
||||
i += 1
|
||||
elif myarg in ['sku', 'skuid']:
|
||||
_, body['skuId'] = getProductAndSKU(arg[i + 1])
|
||||
_, body['skuId'] = gapi_licensing.getProductAndSKU(arg[i + 1])
|
||||
elif myarg in ['customerauthtoken', 'transfertoken']:
|
||||
customerAuthToken = arg[i + 1]
|
||||
else:
|
||||
@@ -8510,15 +8476,13 @@ def _getValueFromOAuth(field, credentials=None):
|
||||
|
||||
|
||||
def _get_admin_email():
|
||||
if GC_Values[GC_ENABLE_DASA]:
|
||||
if not GC_Values[GC_ADMIN_EMAIL]:
|
||||
GC_Values[GC_ADMIN_EMAIL] = os.environ.get('GA_ADMIN_EMAIL', GC_Defaults[GC_ADMIN_EMAIL])
|
||||
if not GC_Values[GC_ADMIN_EMAIL]:
|
||||
controlflow.system_error_exit(
|
||||
3,
|
||||
f'Environment variable GA_ADMIN_EMAIL must be set when {GM_Globals[GM_ENABLEDASA_TXT]} is present'
|
||||
)
|
||||
if GC_Values[GC_ADMIN_EMAIL]:
|
||||
return GC_Values[GC_ADMIN_EMAIL]
|
||||
if GC_Values[GC_ENABLE_DASA]:
|
||||
controlflow.system_error_exit(
|
||||
3,
|
||||
f'Environment variable GA_ADMIN_EMAIL must be set when {GM_Globals[GM_ENABLEDASA_TXT]} is present'
|
||||
)
|
||||
return _getValueFromOAuth('email')
|
||||
|
||||
def doGetUserInfo(user_email=None):
|
||||
@@ -8792,6 +8756,12 @@ def doGetUserInfo(user_email=None):
|
||||
print(f' {alias}')
|
||||
if getGroups:
|
||||
throw_reasons = [gapi_errors.ErrorReason.FORBIDDEN]
|
||||
kwargs = {}
|
||||
if GC_Values[GC_ENABLE_DASA]:
|
||||
# Allows groups.list() to function but will limit
|
||||
# returned groups to those in same domain as user
|
||||
# so only do this for DASA admins
|
||||
kwargs['domain'] = GC_Values[GC_DOMAIN]
|
||||
try:
|
||||
groups = gapi.get_all_pages(
|
||||
cd.groups(),
|
||||
@@ -8799,7 +8769,7 @@ def doGetUserInfo(user_email=None):
|
||||
'groups',
|
||||
userKey=user_email,
|
||||
fields='groups(name,email),nextPageToken',
|
||||
throw_reasons=throw_reasons)
|
||||
throw_reasons=throw_reasons, **kwargs)
|
||||
if groups:
|
||||
print(f'Groups: ({len(groups)})')
|
||||
for group in groups:
|
||||
@@ -8812,25 +8782,14 @@ def doGetUserInfo(user_email=None):
|
||||
lbatch = lic.new_batch_http_request(callback=user_lic_result)
|
||||
user_licenses = []
|
||||
for sku in skus:
|
||||
productId, skuId = getProductAndSKU(sku)
|
||||
productId, skuId = gapi_licensing.getProductAndSKU(sku)
|
||||
lbatch.add(lic.licenseAssignments().get(userId=user_email,
|
||||
productId=productId,
|
||||
skuId=skuId,
|
||||
fields='skuId'))
|
||||
lbatch.execute()
|
||||
for user_license in user_licenses:
|
||||
print(f' {_formatSKUIdDisplayName(user_license)}')
|
||||
|
||||
|
||||
def _skuIdToDisplayName(skuId):
|
||||
return SKUS[skuId]['displayName'] if skuId in SKUS else skuId
|
||||
|
||||
|
||||
def _formatSKUIdDisplayName(skuId):
|
||||
skuIdDisplay = _skuIdToDisplayName(skuId)
|
||||
if skuId == skuIdDisplay:
|
||||
return skuId
|
||||
return f'{skuId} ({skuIdDisplay})'
|
||||
print(f' {gapi_licensing._formatSKUIdDisplayName(user_license)}')
|
||||
|
||||
|
||||
def doGetAliasInfo(alias_email=None):
|
||||
@@ -9520,13 +9479,13 @@ def doPrintUsers():
|
||||
[groupname['email'] for groupname in groups])
|
||||
if getLicenseFeed:
|
||||
titles.append('Licenses')
|
||||
licenses = doPrintLicenses(returnFields='userId,skuId')
|
||||
licenses = gapi_licensing.print_(returnFields='userId,skuId')
|
||||
if licenses:
|
||||
for user in csvRows:
|
||||
u_licenses = licenses.get(user['primaryEmail'].lower())
|
||||
if u_licenses:
|
||||
user['Licenses'] = licenseDelimiter.join(
|
||||
[_skuIdToDisplayName(skuId) for skuId in u_licenses])
|
||||
[gapi_licensing._skuIdToDisplayName(skuId) for skuId in u_licenses])
|
||||
display.write_csv_file(csvRows, titles, 'Users', todrive)
|
||||
|
||||
|
||||
@@ -9663,172 +9622,6 @@ def doPrintAliases():
|
||||
display.write_csv_file(csvRows, titles, 'Aliases', todrive)
|
||||
|
||||
|
||||
def doPrintLicenses(returnFields=None,
|
||||
skus=None,
|
||||
countsOnly=False,
|
||||
returnCounts=False):
|
||||
lic = buildGAPIObject('licensing')
|
||||
products = []
|
||||
licenses = []
|
||||
licenseCounts = []
|
||||
if not returnFields:
|
||||
csvRows = []
|
||||
todrive = False
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower()
|
||||
if not returnCounts and myarg == 'todrive':
|
||||
todrive = True
|
||||
i += 1
|
||||
elif myarg in ['products', 'product']:
|
||||
products = sys.argv[i + 1].split(',')
|
||||
i += 2
|
||||
elif myarg in ['sku', 'skus']:
|
||||
skus = sys.argv[i + 1].split(',')
|
||||
i += 2
|
||||
elif myarg == 'allskus':
|
||||
skus = sorted(SKUS)
|
||||
products = []
|
||||
i += 1
|
||||
elif myarg == 'gsuite':
|
||||
skus = [
|
||||
skuId for skuId in SKUS
|
||||
if SKUS[skuId]['product'] in ['Google-Apps', '101031']
|
||||
]
|
||||
products = []
|
||||
i += 1
|
||||
elif myarg == 'countsonly':
|
||||
countsOnly = True
|
||||
i += 1
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i],
|
||||
'gam print licenses')
|
||||
if not countsOnly:
|
||||
fields = 'nextPageToken,items(productId,skuId,userId)'
|
||||
titles = ['userId', 'productId', 'skuId']
|
||||
else:
|
||||
fields = 'nextPageToken,items(userId)'
|
||||
if not returnCounts:
|
||||
if skus:
|
||||
titles = ['productId', 'skuId', 'licenses']
|
||||
else:
|
||||
titles = ['productId', 'licenses']
|
||||
else:
|
||||
fields = f'nextPageToken,items({returnFields})'
|
||||
if skus:
|
||||
for sku in skus:
|
||||
if not products:
|
||||
product, sku = getProductAndSKU(sku)
|
||||
else:
|
||||
product = products[0]
|
||||
page_message = gapi.got_total_items_msg(
|
||||
f'Licenses for {SKUS.get(sku, {"displayName": sku})["displayName"]}',
|
||||
'...\n')
|
||||
try:
|
||||
licenses += gapi.get_all_pages(
|
||||
lic.licenseAssignments(),
|
||||
'listForProductAndSku',
|
||||
'items',
|
||||
throw_reasons=[
|
||||
gapi_errors.ErrorReason.INVALID,
|
||||
gapi_errors.ErrorReason.FORBIDDEN
|
||||
],
|
||||
page_message=page_message,
|
||||
customerId=GC_Values[GC_DOMAIN],
|
||||
productId=product,
|
||||
skuId=sku,
|
||||
fields=fields)
|
||||
if countsOnly:
|
||||
licenseCounts.append([
|
||||
'Product', product, 'SKU', sku, 'Licenses',
|
||||
len(licenses)
|
||||
])
|
||||
licenses = []
|
||||
except (gapi_errors.GapiInvalidError,
|
||||
gapi_errors.GapiForbiddenError):
|
||||
pass
|
||||
else:
|
||||
if not products:
|
||||
products = sorted(PRODUCTID_NAME_MAPPINGS)
|
||||
for productId in products:
|
||||
page_message = gapi.got_total_items_msg(
|
||||
f'Licenses for {PRODUCTID_NAME_MAPPINGS.get(productId, productId)}',
|
||||
'...\n')
|
||||
try:
|
||||
licenses += gapi.get_all_pages(
|
||||
lic.licenseAssignments(),
|
||||
'listForProduct',
|
||||
'items',
|
||||
throw_reasons=[
|
||||
gapi_errors.ErrorReason.INVALID,
|
||||
gapi_errors.ErrorReason.FORBIDDEN
|
||||
],
|
||||
page_message=page_message,
|
||||
customerId=GC_Values[GC_DOMAIN],
|
||||
productId=productId,
|
||||
fields=fields)
|
||||
if countsOnly:
|
||||
licenseCounts.append(
|
||||
['Product', productId, 'Licenses',
|
||||
len(licenses)])
|
||||
licenses = []
|
||||
except (gapi_errors.GapiInvalidError,
|
||||
gapi_errors.GapiForbiddenError):
|
||||
pass
|
||||
if countsOnly:
|
||||
if returnCounts:
|
||||
return licenseCounts
|
||||
if skus:
|
||||
for u_license in licenseCounts:
|
||||
csvRows.append({
|
||||
'productId': u_license[1],
|
||||
'skuId': u_license[3],
|
||||
'licenses': u_license[5]
|
||||
})
|
||||
else:
|
||||
for u_license in licenseCounts:
|
||||
csvRows.append({
|
||||
'productId': u_license[1],
|
||||
'licenses': u_license[3]
|
||||
})
|
||||
display.write_csv_file(csvRows, titles, 'Licenses', todrive)
|
||||
return
|
||||
if returnFields:
|
||||
if returnFields == 'userId':
|
||||
userIds = []
|
||||
for u_license in licenses:
|
||||
userId = u_license.get('userId', '').lower()
|
||||
if userId:
|
||||
userIds.append(userId)
|
||||
return userIds
|
||||
userSkuIds = {}
|
||||
for u_license in licenses:
|
||||
userId = u_license.get('userId', '').lower()
|
||||
skuId = u_license.get('skuId')
|
||||
if userId and skuId:
|
||||
userSkuIds.setdefault(userId, [])
|
||||
userSkuIds[userId].append(skuId)
|
||||
return userSkuIds
|
||||
for u_license in licenses:
|
||||
userId = u_license.get('userId', '').lower()
|
||||
skuId = u_license.get('skuId', '')
|
||||
csvRows.append({
|
||||
'userId': userId,
|
||||
'productId': u_license.get('productId', ''),
|
||||
'skuId': _skuIdToDisplayName(skuId)
|
||||
})
|
||||
display.write_csv_file(csvRows, titles, 'Licenses', todrive)
|
||||
|
||||
|
||||
def doShowLicenses():
|
||||
licenseCounts = doPrintLicenses(countsOnly=True, returnCounts=True)
|
||||
for u_license in licenseCounts:
|
||||
line = ''
|
||||
for i in range(0, len(u_license), 2):
|
||||
line += f'{u_license[i]}: {u_license[i+1]}, '
|
||||
print(line[:-2])
|
||||
|
||||
|
||||
def shlexSplitList(entity, dataDelimiter=' ,'):
|
||||
lexer = shlex.shlex(entity, posix=True)
|
||||
lexer.whitespace = dataDelimiter
|
||||
@@ -10043,7 +9836,7 @@ def getUsersToModify(entity_type=None,
|
||||
if not silent:
|
||||
sys.stderr.write('done.\r\n')
|
||||
elif entity_type in ['license', 'licenses', 'licence', 'licences']:
|
||||
users = doPrintLicenses(returnFields='userId', skus=entity.split(','))
|
||||
users = gapi_licensing.print_(returnFields='userId', skus=entity.split(','))
|
||||
elif entity_type in ['file', 'crosfile']:
|
||||
users = []
|
||||
f = fileutils.open_file(entity, strip_utf_bom=True)
|
||||
@@ -11440,8 +11233,6 @@ def ProcessGAMCommand(args):
|
||||
gapi_cloudidentity_devices.print_()
|
||||
elif argument in ['groupmembers', 'groupsmembers']:
|
||||
gapi_directory_groups.print_members()
|
||||
elif argument == 'devices':
|
||||
gapi_cloudidentity_devices.print_()
|
||||
elif argument in ['cigroupmembers', 'cigroupsmembers']:
|
||||
gapi_cloudidentity_groups.print_members()
|
||||
elif argument in ['orgs', 'ous']:
|
||||
@@ -11457,7 +11248,7 @@ def ProcessGAMCommand(args):
|
||||
elif argument == 'mobile':
|
||||
gapi_directory_mobiledevices.print_()
|
||||
elif argument in ['license', 'licenses', 'licence', 'licences']:
|
||||
doPrintLicenses()
|
||||
gapi_licensing.print_()
|
||||
elif argument in ['token', 'tokens', 'oauth', '3lo']:
|
||||
printShowTokens(3, None, None, True)
|
||||
elif argument in ['schema', 'schemas']:
|
||||
@@ -11506,7 +11297,7 @@ def ProcessGAMCommand(args):
|
||||
elif argument in ['guardian', 'guardians']:
|
||||
doPrintShowGuardians(False)
|
||||
elif argument in ['license', 'licenses', 'licence', 'licences']:
|
||||
doShowLicenses()
|
||||
gapi_licensing.show()
|
||||
elif argument in ['project', 'projects']:
|
||||
doPrintShowProjects(False)
|
||||
elif argument in ['sakey', 'sakeys']:
|
||||
@@ -11615,6 +11406,14 @@ def ProcessGAMCommand(args):
|
||||
elif command == 'block':
|
||||
gapi_cloudidentity_devices.block_user()
|
||||
sys.exit(0)
|
||||
elif command in ['issuecommand', 'getcommand']:
|
||||
target = sys.argv[2].lower().replace('_', '')
|
||||
if target == 'cros':
|
||||
if command == 'issuecommand':
|
||||
gapi_directory_cros.issue_command()
|
||||
elif command == 'getcommand':
|
||||
gapi_directory_cros.get_command()
|
||||
sys.exit(0)
|
||||
users = getUsersToModify()
|
||||
command = sys.argv[3].lower()
|
||||
if command == 'print' and len(sys.argv) == 4:
|
||||
@@ -11774,7 +11573,7 @@ def ProcessGAMCommand(args):
|
||||
elif delWhat == 'photo':
|
||||
deletePhoto(users)
|
||||
elif delWhat in ['license', 'licence']:
|
||||
doLicense(users, 'delete')
|
||||
gapi_licensing.delete(users)
|
||||
elif delWhat in ['backupcode', 'backupcodes', 'verificationcodes']:
|
||||
doDelBackupCodes(users)
|
||||
elif delWhat in ['asp', 'asps', 'applicationspecificpasswords']:
|
||||
@@ -11816,7 +11615,7 @@ def ProcessGAMCommand(args):
|
||||
elif addWhat == 'drivefile':
|
||||
createDriveFile(users)
|
||||
elif addWhat in ['license', 'licence']:
|
||||
doLicense(users, 'insert')
|
||||
gapi_licensing.create(users)
|
||||
elif addWhat in ['drivefileacl', 'drivefileacls']:
|
||||
addDriveFileACL(users)
|
||||
elif addWhat in ['label', 'labels']:
|
||||
@@ -11836,6 +11635,13 @@ def ProcessGAMCommand(args):
|
||||
else:
|
||||
controlflow.invalid_argument_exit(addWhat,
|
||||
f'gam <users> {command}')
|
||||
elif command == 'sync':
|
||||
syncWhat = sys.argv[4].lower()
|
||||
if syncWhat in ['license', 'licence']:
|
||||
gapi_licensing.sync(users)
|
||||
else:
|
||||
controlflow.invalid_argument_exit(syncWhat,
|
||||
f'gam <users> {command}')
|
||||
elif command == 'update':
|
||||
updateWhat = sys.argv[4].lower()
|
||||
if updateWhat == 'calendar':
|
||||
@@ -11845,7 +11651,7 @@ def ProcessGAMCommand(args):
|
||||
elif updateWhat == 'photo':
|
||||
doPhoto(users)
|
||||
elif updateWhat in ['license', 'licence']:
|
||||
doLicense(users, 'patch')
|
||||
gapi_licensing.update(users)
|
||||
elif updateWhat == 'user':
|
||||
doUpdateUser(users, 5)
|
||||
elif updateWhat in [
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""GAM is a command line tool which allows Administrators to control their G Suite domain and accounts.
|
||||
"""GAM is a command line tool which allows Administrators to control their Google Workspace domain and accounts.
|
||||
|
||||
With GAM you can programmatically create users, turn on/off services for users like POP and Forwarding and much more.
|
||||
For more information, see https://git.io/gam
|
||||
|
||||
@@ -3,7 +3,6 @@ import sys
|
||||
|
||||
import googleapiclient
|
||||
|
||||
import gam
|
||||
from gam.var import *
|
||||
from gam import controlflow
|
||||
from gam import display
|
||||
@@ -12,16 +11,20 @@ from gam import gapi
|
||||
from gam import utils
|
||||
from gam.gapi import errors as gapi_errors
|
||||
from gam.gapi import cloudidentity as gapi_cloudidentity
|
||||
from gam.gapi.directory import customer as gapi_directory_customer
|
||||
from gam.gapi.directory import groups as gapi_directory_groups
|
||||
|
||||
|
||||
def _get_device_customerid():
|
||||
customer = GC_Values[GC_CUSTOMER_ID]
|
||||
if customer.startswith('C'):
|
||||
customer = customer[1:]
|
||||
return f'customers/{customer}'
|
||||
|
||||
def create():
|
||||
ci = gapi_cloudidentity.build_dwd()
|
||||
customer = f'customers/{GC_Values[GC_CUSTOMER_ID]}'
|
||||
customer = _get_device_customerid()
|
||||
device_types = gapi.get_enum_values_minus_unspecified(
|
||||
ci._rootDesc['schemas']['GoogleAppsCloudidentityDevicesV1Device']['properties']['deviceType']['enum'])
|
||||
body = {}
|
||||
body = {'deviceType': '', 'serialNumber': ''}
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
@@ -35,20 +38,30 @@ def create():
|
||||
', '.join(device_types),
|
||||
sys.argv[i+1])
|
||||
i += 2
|
||||
elif myarg in {'assettag', 'assetid'}:
|
||||
body['assetTag'] = sys.argv[i+1]
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i], 'gam create device')
|
||||
if not body.get('serialNumber') or not body.get('deviceType'):
|
||||
controlflow.invalid_argument_exit(sys.argv[i], 'gam create device')
|
||||
if not body['serialNumber'] or not body['deviceType']:
|
||||
controlflow.system_error_exit(
|
||||
3, 'serial_number and device_type are required arguments for "gam create device".')
|
||||
3, 'serial_number and device_type are required arguments for "gam create device".')
|
||||
result = gapi.call(ci.devices(), 'create', customer=customer, body=body)
|
||||
print(f'Created device {result["response"]["name"]}')
|
||||
|
||||
def info():
|
||||
ci = gapi_cloudidentity.build_dwd()
|
||||
customer = f'customers/{GC_Values[GC_CUSTOMER_ID]}'
|
||||
def _get_device_name():
|
||||
name = sys.argv[3]
|
||||
if name == 'id':
|
||||
name = sys.argv[4]
|
||||
if not name.startswith('devices/'):
|
||||
name = f'devices/{name}'
|
||||
return name
|
||||
|
||||
|
||||
def info():
|
||||
ci = gapi_cloudidentity.build_dwd()
|
||||
customer = _get_device_customerid()
|
||||
name = _get_device_name()
|
||||
device = gapi.call(ci.devices(), 'get', name=name, customer=customer)
|
||||
device_users = gapi.get_all_pages(ci.devices().deviceUsers(), 'list',
|
||||
'deviceUsers', parent=name, customer=customer)
|
||||
@@ -58,8 +71,9 @@ def info():
|
||||
|
||||
def _generic_action(action, device_user=False):
|
||||
ci = gapi_cloudidentity.build_dwd()
|
||||
customer = f'customers/{GC_Values[GC_CUSTOMER_ID]}'
|
||||
|
||||
customer = _get_device_customerid()
|
||||
name = _get_device_name()
|
||||
|
||||
# bah, inconsistencies in API
|
||||
if action == 'delete':
|
||||
kwargs = {'customer': customer}
|
||||
@@ -70,31 +84,17 @@ def _generic_action(action, device_user=False):
|
||||
endpoint = ci.devices().deviceUsers()
|
||||
else:
|
||||
endpoint = ci.devices()
|
||||
name = None
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
# The API calls it "name" but GAM will expose as "id" to avoid admin confusion.
|
||||
if myarg == 'id':
|
||||
name = sys.argv[i+1]
|
||||
if not name.startswith('devices/'):
|
||||
name = f'devices/{name}'
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i], f'gam {action} device')
|
||||
if not name:
|
||||
controlflow.system_error_exit(3, f'id is a required argument for "gam {action} device".')
|
||||
op = gapi.call(endpoint, action, name=name, **kwargs)
|
||||
print(op)
|
||||
print(op)
|
||||
|
||||
def delete():
|
||||
_generic_action('delete')
|
||||
_generic_action('delete')
|
||||
|
||||
def cancel_wipe():
|
||||
_generic_action('cancelWipe')
|
||||
_generic_action('cancelWipe')
|
||||
|
||||
def wipe():
|
||||
_generic_action('wipe')
|
||||
_generic_action('wipe')
|
||||
|
||||
def approve_user():
|
||||
_generic_action('approve', True)
|
||||
@@ -113,11 +113,12 @@ def wipe_user():
|
||||
|
||||
def print_():
|
||||
ci = gapi_cloudidentity.build_dwd()
|
||||
customer = f'customers/{GC_Values[GC_CUSTOMER_ID]}'
|
||||
customer = _get_device_customerid()
|
||||
parent = 'devices/-'
|
||||
filter = None
|
||||
device_filter = None
|
||||
get_device_users = True
|
||||
get_device_views = ['COMPANY_INVENTORY', 'USER_ASSIGNED_DEVICES']
|
||||
view = None
|
||||
orderByList = []
|
||||
titles = []
|
||||
csvRows = []
|
||||
todrive = False
|
||||
@@ -126,38 +127,69 @@ def print_():
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg in ['filter', 'query']:
|
||||
filter = sys.argv[i+1]
|
||||
i += 2
|
||||
device_filter = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'company':
|
||||
view = 'COMPANY_INVENTORY'
|
||||
i += 1
|
||||
elif myarg == 'personal':
|
||||
view = 'USER_ASSIGNED_DEVICES'
|
||||
i += 1
|
||||
elif myarg == 'nocompanydevices':
|
||||
get_device_views.remove('COMPANY_INVENTORY')
|
||||
view = 'USER_ASSIGNED_DEVICES'
|
||||
i += 1
|
||||
elif myarg == 'nopersonaldevices':
|
||||
get_device_views.remove('USER_ASSIGNED_DEVICES')
|
||||
view = 'COMPANY_INVENTORY'
|
||||
i += 1
|
||||
elif myarg == 'nodeviceusers':
|
||||
get_device_users = False
|
||||
i += 1
|
||||
elif myarg == 'todrive':
|
||||
todrive = True
|
||||
i += 1
|
||||
elif myarg == 'orderby':
|
||||
fieldName = sys.argv[i + 1].lower()
|
||||
i += 2
|
||||
if fieldName in DEVICE_ORDERBY_CHOICES_MAP:
|
||||
fieldName = DEVICE_ORDERBY_CHOICES_MAP[fieldName]
|
||||
orderBy = ''
|
||||
if i < len(sys.argv):
|
||||
orderBy = sys.argv[i].lower()
|
||||
if orderBy in SORTORDER_CHOICES_MAP:
|
||||
orderBy = SORTORDER_CHOICES_MAP[orderBy]
|
||||
i += 1
|
||||
if orderBy != 'DESCENDING':
|
||||
orderByList.append(fieldName)
|
||||
else:
|
||||
orderByList.append(f'{fieldName} desc')
|
||||
else:
|
||||
controlflow.expected_argument_exit(
|
||||
'orderby', ', '.join(sorted(DEVICE_ORDERBY_CHOICES_MAP)),
|
||||
fieldName)
|
||||
elif myarg == 'sortheaders':
|
||||
sortHeaders = True
|
||||
i += 1
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i], 'gam print devices')
|
||||
controlflow.invalid_argument_exit(sys.argv[i], 'gam print devices')
|
||||
view_name_map = {
|
||||
None: 'Devices',
|
||||
'COMPANY_INVENTORY': 'Company Devices',
|
||||
'USER_ASSIGNED_DEVICES': 'Personal Devices',
|
||||
}
|
||||
if orderByList:
|
||||
orderBy = ','.join(orderByList)
|
||||
else:
|
||||
orderBy = None
|
||||
devices = []
|
||||
for view in get_device_views:
|
||||
view_name = view_name_map.get(view, 'Devices')
|
||||
page_message = gapi.got_total_items_msg(view_name, '...\n')
|
||||
devices += gapi.get_all_pages(ci.devices(), 'list', 'devices',
|
||||
customer=customer, page_message=page_message,
|
||||
pageSize=100, filter=filter, view=view)
|
||||
page_message = gapi.got_total_items_msg(view_name_map[view], '...\n')
|
||||
devices += gapi.get_all_pages(ci.devices(), 'list', 'devices',
|
||||
customer=customer, page_message=page_message,
|
||||
pageSize=100, filter=device_filter, view=view, orderBy=orderBy)
|
||||
if get_device_users:
|
||||
page_message = gapi.got_total_items_msg('Device Users', '...\n')
|
||||
device_users = gapi.get_all_pages(ci.devices().deviceUsers(), 'list',
|
||||
'deviceUsers', customer=customer, parent=parent,
|
||||
page_message=page_message, pageSize=20, filter=filter)
|
||||
page_message=page_message, pageSize=20, filter=device_filter)
|
||||
for device_user in device_users:
|
||||
for device in devices:
|
||||
if device_user.get('name').startswith(device.get('name')):
|
||||
@@ -180,13 +212,13 @@ def sync():
|
||||
ci = gapi_cloudidentity.build_dwd()
|
||||
device_types = gapi.get_enum_values_minus_unspecified(
|
||||
ci._rootDesc['schemas']['GoogleAppsCloudidentityDevicesV1Device']['properties']['deviceType']['enum'])
|
||||
customer = f'customers/{GC_Values[GC_CUSTOMER_ID]}'
|
||||
filter = None
|
||||
customer = _get_device_customerid()
|
||||
device_filter = None
|
||||
csv_file = None
|
||||
serialnumber_column = 'serialNumber'
|
||||
devicetype_column = 'deviceType'
|
||||
static_devicetype = None
|
||||
assetid_column = None
|
||||
assettag_column = None
|
||||
unassigned_missing_action = 'delete'
|
||||
assigned_missing_action = 'donothing'
|
||||
missing_actions = ['delete', 'wipe', 'donothing']
|
||||
@@ -194,7 +226,7 @@ def sync():
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg in ['filter', 'query']:
|
||||
filter = sys.argv[i+1]
|
||||
device_filter = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'csvfile':
|
||||
csv_file = sys.argv[i+1]
|
||||
@@ -212,17 +244,17 @@ def sync():
|
||||
', '.join(device_types),
|
||||
sys.argv[i+1])
|
||||
i += 2
|
||||
elif myarg == 'assetidcolumn':
|
||||
assetid_column = sys.argv[i+1]
|
||||
elif myarg in {'assettagcolumn', 'assetidcolumn'}:
|
||||
assettag_column = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'unassigned_missing_action':
|
||||
elif myarg == 'unassignedmissingaction':
|
||||
unassigned_missing_action = sys.argv[i+1].lower().replace('_', '')
|
||||
if unassigned_missing_action not in missing_actions:
|
||||
controlflow.expected_argument_exit('unassigned_missing_action',
|
||||
', '.join(missing_actions),
|
||||
sys.argv[i+1])
|
||||
i += 2
|
||||
elif myarg == 'assigned_missing_action':
|
||||
elif myarg == 'assignedmissingaction':
|
||||
assigned_missing_action = sys.argv[i+1].lower().replace('_', '')
|
||||
if assigned_missing_action not in missing_actions:
|
||||
controlflow.expected_argument_exit('assigned_missing_action',
|
||||
@@ -230,44 +262,46 @@ def sync():
|
||||
sys.argv[i+1])
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i], 'gam sync devices')
|
||||
controlflow.invalid_argument_exit(sys.argv[i], 'gam sync devices')
|
||||
if not csv_file:
|
||||
controlflow.system_error_exit(
|
||||
3, 'csvfile is a required argument for "gam sync devices".')
|
||||
f = fileutils.open_file(csv_file)
|
||||
input_file = csv.DictReader(f, restval='')
|
||||
if serialnumber_column not in input_file.fieldnames:
|
||||
controlflow.csv_field_error_exit(serialnumber_column, input_file.fieldnames)
|
||||
if not static_devicetype and devicetype_column not in input_file.fieldnames:
|
||||
controlflow.csv_field_error_exit(devicetype_column, input_file.fieldnames)
|
||||
if assetid_column and assetid_column not in input_file.fieldnames:
|
||||
controlflow.csv_field_error_exit(assetid_column, input_file.fieldnames)
|
||||
if assettag_column and assettag_column not in input_file.fieldnames:
|
||||
controlflow.csv_field_error_exit(assettag_column, input_file.fieldnames)
|
||||
local_devices = []
|
||||
for row in input_file:
|
||||
# upper() is very important to comparison since Google
|
||||
# always return uppercase serials
|
||||
serialnumber = row[serialnumber_column].strip().upper()
|
||||
local_device = {'serialNumber': serialnumber}
|
||||
local_device = {'serialNumber': row[serialnumber_column].strip().upper()}
|
||||
if static_devicetype:
|
||||
local_device['deviceType'] = static_devicetype
|
||||
else:
|
||||
local_device['deviceType'] = row[devicetype_column].strip()
|
||||
if assetid_column:
|
||||
local_device['assetTag'] = row[assetid_column].strip()
|
||||
if assettag_column:
|
||||
local_device['assetTag'] = row[assettag_column].strip()
|
||||
local_devices.append(local_device)
|
||||
fileutils.close_file(f)
|
||||
page_message = gapi.got_total_items_msg('Company Devices', '...\n')
|
||||
device_fields = ['serialNumber', 'deviceType', 'lastSyncTime', 'name']
|
||||
if assetid_column:
|
||||
if assettag_column:
|
||||
device_fields.append('assetTag')
|
||||
fields = f'nextPageToken,devices({",".join(device_fields)})'
|
||||
remote_devices = gapi.get_all_pages(ci.devices(), 'list', 'devices',
|
||||
customer=customer, page_message=page_message,
|
||||
pageSize=100, filter=filter, view='COMPANY_INVENTORY', fields=fields)
|
||||
pageSize=100, filter=device_filter, view='COMPANY_INVENTORY', fields=fields)
|
||||
remote_device_map = {}
|
||||
for remote_device in remote_devices:
|
||||
sn = remote_device['serialNumber']
|
||||
last_sync = remote_device.pop('lastSyncTime')
|
||||
last_sync = remote_device.pop('lastSyncTime', NEVER_TIME_NOMS)
|
||||
name = remote_device.pop('name')
|
||||
remote_device_map[sn] = {'name': name}
|
||||
if last_sync == '1970-01-01T00:00:00Z':
|
||||
if last_sync == NEVER_TIME_NOMS:
|
||||
remote_device_map[sn]['unassigned'] = True
|
||||
devices_to_add = [device for device in local_devices if device not in remote_devices]
|
||||
missing_devices = [device for device in remote_devices if device not in local_devices]
|
||||
@@ -292,6 +326,6 @@ def sync():
|
||||
kwargs = {'customer': customer}
|
||||
else:
|
||||
kwargs = {'body': {'customer': customer}}
|
||||
gapi.call(ci.devices(), unassigned_missing_action,
|
||||
gapi.call(ci.devices(), action,
|
||||
name=name, **kwargs)
|
||||
print(f'{action}d {sn}')
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import csv
|
||||
|
||||
import gam
|
||||
from gam.var import *
|
||||
from gam import controlflow
|
||||
@@ -9,7 +7,6 @@ from gam import utils
|
||||
from gam.gapi import errors as gapi_errors
|
||||
from gam.gapi import cloudidentity as gapi_cloudidentity
|
||||
from gam.gapi.directory import customer as gapi_directory_customer
|
||||
from gam.gapi.directory import groups as gapi_directory_groups
|
||||
|
||||
|
||||
def create():
|
||||
@@ -240,6 +237,11 @@ def print_():
|
||||
for groupEntity in entityList:
|
||||
i += 1
|
||||
groupEmail = groupEntity['groupKey']['id']
|
||||
for k, v in iter(groupEntity.pop('labels', {}).items()):
|
||||
if v == '':
|
||||
groupEntity[f'labels.{k}'] = True
|
||||
else:
|
||||
groupEntity[f'labels.{k}'] = v
|
||||
group = utils.flatten_json(groupEntity)
|
||||
for a_key in group:
|
||||
if a_key not in titles:
|
||||
@@ -307,7 +309,7 @@ def print_():
|
||||
csvRows.append(group)
|
||||
if sortHeaders:
|
||||
display.sort_csv_titles([
|
||||
'Email',
|
||||
'name', 'groupKey.id'
|
||||
], titles)
|
||||
display.write_csv_file(csvRows, titles, 'Groups', todrive)
|
||||
|
||||
@@ -410,6 +412,7 @@ def update():
|
||||
def _getRoleAndUsers():
|
||||
checkSuspended = None
|
||||
role = None
|
||||
expireTime = None
|
||||
i = 5
|
||||
if sys.argv[i].lower() in GROUP_ROLES_MAP:
|
||||
role = GROUP_ROLES_MAP[sys.argv[i].lower()]
|
||||
@@ -417,6 +420,9 @@ def update():
|
||||
if sys.argv[i].lower() in ['suspended', 'notsuspended']:
|
||||
checkSuspended = sys.argv[i].lower() == 'suspended'
|
||||
i += 1
|
||||
if sys.argv[i].lower() in ['expire', 'expires']:
|
||||
expireTime = sys.argv[i+1]
|
||||
i += 2
|
||||
if sys.argv[i].lower() in usergroup_types:
|
||||
users_email = gam.getUsersToModify(entity_type=sys.argv[i].lower(),
|
||||
entity=sys.argv[i + 1],
|
||||
@@ -427,7 +433,7 @@ def update():
|
||||
gam.normalizeEmailAddressOrUID(sys.argv[i],
|
||||
checkForCustomerId=True)
|
||||
]
|
||||
return (role, users_email)
|
||||
return (role, expireTime, users_email)
|
||||
|
||||
ci = gapi_cloudidentity.build('cloudidentity_beta')
|
||||
group = sys.argv[3]
|
||||
@@ -442,7 +448,7 @@ def update():
|
||||
if not parent:
|
||||
return
|
||||
if myarg == 'add':
|
||||
role, users_email = _getRoleAndUsers()
|
||||
role, expireTime, users_email = _getRoleAndUsers()
|
||||
if not role:
|
||||
role = ROLE_MEMBER
|
||||
if len(users_email) > 1:
|
||||
@@ -451,8 +457,10 @@ def update():
|
||||
for user_email in users_email:
|
||||
item = [
|
||||
'gam', 'update', 'cigroup', f'id:{parent}', 'add', role,
|
||||
user_email
|
||||
]
|
||||
if expireTime:
|
||||
item.extend(['expires', expireTime])
|
||||
item.append(user_email)
|
||||
items.append(item)
|
||||
elif len(users_email) > 0:
|
||||
body = {
|
||||
@@ -465,6 +473,10 @@ def update():
|
||||
}
|
||||
if role != ROLE_MEMBER:
|
||||
body['roles'].append({'name': role})
|
||||
if expireTime:
|
||||
for role in body['roles']:
|
||||
if role['name'] == ROLE_MEMBER:
|
||||
role['expiryDetail'] = {'expireTime': expireTime}
|
||||
add_text = [f'as {role}']
|
||||
for i in range(2):
|
||||
try:
|
||||
@@ -497,7 +509,7 @@ def update():
|
||||
elif myarg == 'sync':
|
||||
syncMembersSet = set()
|
||||
syncMembersMap = {}
|
||||
role, users_email = _getRoleAndUsers()
|
||||
role, expireTime, users_email = _getRoleAndUsers()
|
||||
for user_email in users_email:
|
||||
if user_email in ('*', GC_Values[GC_CUSTOMER_ID]):
|
||||
syncMembersSet.add(GC_Values[GC_CUSTOMER_ID])
|
||||
@@ -530,17 +542,18 @@ def update():
|
||||
f'Group: {group}, Will add {len(to_add)} and remove {len(to_remove)} {role}s.\n'
|
||||
)
|
||||
for user in to_add:
|
||||
item = [
|
||||
'gam', 'update', 'cigroup', f'id:{parent}', 'add', role,
|
||||
user
|
||||
]
|
||||
item = ['gam', 'update', 'cigroup', f'id:{parent}', 'add',
|
||||
role,]
|
||||
if expireTime:
|
||||
item.extend(['expires', expireTime])
|
||||
item.append(user)
|
||||
items.append(item)
|
||||
for user in to_remove:
|
||||
items.append([
|
||||
'gam', 'update', 'cigroup', f'id:{parent}', 'remove', user
|
||||
])
|
||||
elif myarg in ['delete', 'remove']:
|
||||
_, users_email = _getRoleAndUsers()
|
||||
_, _, users_email = _getRoleAndUsers()
|
||||
if len(users_email) > 1:
|
||||
sys.stderr.write(
|
||||
f'Group: {group}, Will remove {len(users_email)} emails.\n')
|
||||
@@ -566,7 +579,7 @@ def update():
|
||||
f' Group: {group}, {users_email[0]} Remove Failed: {str(e)}'
|
||||
)
|
||||
elif myarg == 'update':
|
||||
role, users_email = _getRoleAndUsers()
|
||||
role, expireTime, users_email = _getRoleAndUsers()
|
||||
if not role:
|
||||
role = ROLE_MEMBER
|
||||
if len(users_email) > 1:
|
||||
@@ -576,29 +589,43 @@ def update():
|
||||
for user_email in users_email:
|
||||
item = [
|
||||
'gam', 'update', 'cigroup', f'id:{parent}', 'update',
|
||||
role, user_email
|
||||
]
|
||||
role,]
|
||||
if expireTime:
|
||||
item.extend(['expires', expireTime])
|
||||
item.append(user_email)
|
||||
items.append(item)
|
||||
elif len(users_email) > 0:
|
||||
name = membership_email_to_id(ci, parent, users_email[0])
|
||||
addRoles = []
|
||||
removeRoles = []
|
||||
new_role = {'role': role}
|
||||
current_roles = gapi.call(ci.groups().memberships(),
|
||||
'get',
|
||||
name=name,
|
||||
fields='roles').get('roles', [])
|
||||
current_roles = [role['name'] for role in current_roles]
|
||||
for crole in current_roles:
|
||||
if crole != ROLE_MEMBER and crole != role:
|
||||
if crole not in {ROLE_MEMBER, role}:
|
||||
removeRoles.append(crole)
|
||||
if role not in current_roles:
|
||||
addRoles.append({'name': role})
|
||||
new_role = {'name': role}
|
||||
if role == ROLE_MEMBER and expireTime:
|
||||
new_role['expiryDetail'] = {'expireTime': expireTime}
|
||||
expireTime = None
|
||||
addRoles.append(new_role)
|
||||
bodys = []
|
||||
if addRoles:
|
||||
bodys.append({'addRoles': addRoles})
|
||||
if removeRoles:
|
||||
bodys.append({'removeRoles': removeRoles})
|
||||
if expireTime:
|
||||
bodys.append({
|
||||
'name': ROLE_MEMBER,
|
||||
# Note this doesn't actually work for some reason. Only known method to change
|
||||
# expire time right now is to remove/re-add member.
|
||||
'expiryDetail': {
|
||||
'expireTime': expireTime
|
||||
}
|
||||
})
|
||||
for body in bodys:
|
||||
try:
|
||||
gapi.call(ci.groups().memberships(),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import sys
|
||||
|
||||
from gam.var import *
|
||||
from gam import controlflow
|
||||
from gam import gapi
|
||||
from gam.gapi import directory as gapi_directory
|
||||
from gam import utils
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import datetime
|
||||
import time
|
||||
|
||||
import googleapiclient
|
||||
|
||||
from gam.var import *
|
||||
import gam
|
||||
@@ -7,10 +10,94 @@ from gam import display
|
||||
from gam import fileutils
|
||||
from gam import gapi
|
||||
from gam.gapi import directory as gapi_directory
|
||||
from gam.gapi import errors as gapi_errors
|
||||
from gam.gapi.directory import orgunits as gapi_directory_orgunits
|
||||
from gam import utils
|
||||
|
||||
|
||||
def issue_command():
|
||||
cd = gapi_directory.build()
|
||||
i, devices = getCrOSDeviceEntity(3, cd)
|
||||
body = {}
|
||||
final_states = ['EXPIRED', 'CANCELLED', 'EXECUTED_BY_CLIENT']
|
||||
valid_commands = gapi.get_enum_values_minus_unspecified(
|
||||
cd._rootDesc['schemas']
|
||||
['DirectoryChromeosdevicesIssueCommandRequest']
|
||||
['properties']['commandType']['enum'])
|
||||
command_map = {}
|
||||
for valid_command in valid_commands:
|
||||
v = valid_command.lower().replace('_', '')
|
||||
command_map[v] = valid_command
|
||||
times_to_check_status = 1
|
||||
doit = False
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'command':
|
||||
command = sys.argv[i+1].lower().replace('_', '')
|
||||
if command not in command_map:
|
||||
controlflow.system_error_exit(2, f'expected command of ' \
|
||||
f'{", ".join(valid_commands)} got {command}')
|
||||
body['commandType'] = command_map[command]
|
||||
i += 2
|
||||
if command == 'setvolume':
|
||||
body['payload'] = {'volume': int(sys.argv[i])}
|
||||
i += 1
|
||||
elif myarg == 'timestocheckstatus':
|
||||
times_to_check_status = int(sys.argv[i+1])
|
||||
i += 2
|
||||
elif myarg == 'doit':
|
||||
doit = True
|
||||
i += 1
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i], 'gam issuecommand cros')
|
||||
if body['commandType'] == 'WIPE_USERS' and not doit:
|
||||
controlflow.system_error_exit(2, 'wipe_users command requires admin ' \
|
||||
'acknowledge user data will be destroyed with the ' \
|
||||
'doit argument')
|
||||
if body['commandType'] == 'REMOTE_POWERWASH' and not doit:
|
||||
controlflow.system_error_exit(2, 'remote_powerwash command requires ' \
|
||||
'admin acknowledge user data will be destroyed, device will need' \
|
||||
' to be reconnected to WiFi and re-enrolled with the doit argument')
|
||||
for device_id in devices:
|
||||
try:
|
||||
result = gapi.call(cd.customer().devices().chromeos(), 'issueCommand',
|
||||
customerId=GC_Values[GC_CUSTOMER_ID], deviceId=device_id,
|
||||
throw_reasons=[gapi_errors.ErrorReason.FOUR_O_O],
|
||||
body=body)
|
||||
except googleapiclient.errors.HttpError:
|
||||
controlflow.system_error_exit(4, '400 response from Google. This ' \
|
||||
'usually indicates the devices was not in a state where it will' \
|
||||
' accept the command. For example, reboot and take_a_screenshot' \
|
||||
' require the device to be in auto-start kiosk app mode.')
|
||||
display.print_json(result)
|
||||
command_id = result.get('commandId')
|
||||
for i in range(0, times_to_check_status):
|
||||
time.sleep(2)
|
||||
result = gapi.call(cd.customer().devices().chromeos().commands(), 'get',
|
||||
customerId=GC_Values[GC_CUSTOMER_ID], deviceId=device_id,
|
||||
commandId=command_id)
|
||||
display.print_json(result)
|
||||
state = result.get('state')
|
||||
if state in final_states:
|
||||
break
|
||||
|
||||
def get_command():
|
||||
cd = gapi_directory.build()
|
||||
i, devices = getCrOSDeviceEntity(3, cd)
|
||||
command_id = None
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'commandid':
|
||||
command_id = sys.argv[i+1]
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i], 'gam getcommand cros')
|
||||
for device_id in devices:
|
||||
result = gapi.call(cd.customer().devices().chromeos().commands(), 'get',
|
||||
customerId=GC_Values[GC_CUSTOMER_ID], deviceId=device_id,
|
||||
commandId=command_id)
|
||||
display.print_json(result)
|
||||
|
||||
def doUpdateCros():
|
||||
cd = gapi_directory.build()
|
||||
i, devices = getCrOSDeviceEntity(3, cd)
|
||||
|
||||
@@ -221,11 +221,11 @@ def info_member():
|
||||
cd = gapi_directory.build()
|
||||
memberKey = gam.normalizeEmailAddressOrUID(sys.argv[3])
|
||||
groupKey = gam.normalizeEmailAddressOrUID(sys.argv[4])
|
||||
info = gapi.call(cd.members(),
|
||||
'get',
|
||||
memberKey=memberKey,
|
||||
groupKey=groupKey)
|
||||
display.print_json(info)
|
||||
member_info = gapi.call(cd.members(),
|
||||
'get',
|
||||
memberKey=memberKey,
|
||||
groupKey=groupKey)
|
||||
display.print_json(member_info)
|
||||
|
||||
|
||||
GROUP_ARGUMENT_TO_PROPERTY_TITLE_MAP = {
|
||||
@@ -1146,7 +1146,7 @@ def update():
|
||||
i += 2
|
||||
elif myarg == 'admincreated':
|
||||
use_cd_api = True
|
||||
cd_body['adminCreated'] = getBoolean(sys.argv[i + 1], myarg)
|
||||
cd_body['adminCreated'] = gam.getBoolean(sys.argv[i + 1], myarg)
|
||||
i += 2
|
||||
elif myarg == 'getbeforeupdate':
|
||||
gs_get_before_update = True
|
||||
@@ -1242,5 +1242,3 @@ def getGroupAttrValue(myarg, value, gs_object, gs_body, function):
|
||||
gs_body[attrib] = value
|
||||
return
|
||||
controlflow.invalid_argument_exit(myarg, f'gam {function} group')
|
||||
|
||||
|
||||
|
||||
@@ -23,16 +23,16 @@ def delete():
|
||||
def info():
|
||||
cd = gapi_directory.build()
|
||||
resourceId = sys.argv[3]
|
||||
info = gapi.call(cd.mobiledevices(),
|
||||
'get',
|
||||
customerId=GC_Values[GC_CUSTOMER_ID],
|
||||
resourceId=resourceId)
|
||||
device_info = gapi.call(cd.mobiledevices(),
|
||||
'get',
|
||||
customerId=GC_Values[GC_CUSTOMER_ID],
|
||||
resourceId=resourceId)
|
||||
if 'deviceId' in info:
|
||||
info['deviceId'] = info['deviceId'].encode('unicode-escape').decode(
|
||||
device_info['deviceId'] = device_info['deviceId'].encode('unicode-escape').decode(
|
||||
UTF8)
|
||||
attrib = 'securityPatchLevel'
|
||||
if attrib in info and int(info[attrib]):
|
||||
info[attrib] = utils.formatTimestampYMDHMS(info[attrib])
|
||||
if attrib in info and int(device_info[attrib]):
|
||||
device_info[attrib] = utils.formatTimestampYMDHMS(device_info[attrib])
|
||||
display.print_json(info)
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@ def print_():
|
||||
|
||||
|
||||
def update():
|
||||
cd = gapi_directory.build
|
||||
cd = gapi_directory.build()
|
||||
resourceIds = sys.argv[3]
|
||||
match_users = None
|
||||
doit = False
|
||||
@@ -235,5 +235,3 @@ def update():
|
||||
resourceId=device['resourceId'],
|
||||
body=body,
|
||||
customerId=GC_Values[GC_CUSTOMER_ID])
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ from gam import display
|
||||
from gam import gapi
|
||||
from gam.gapi import directory as gapi_directory
|
||||
from gam.gapi import errors as gapi_errors
|
||||
from gam import utils
|
||||
|
||||
|
||||
def create():
|
||||
@@ -229,12 +228,12 @@ def update():
|
||||
if sys.argv[4].lower() in ['move', 'add']:
|
||||
entity_type = sys.argv[5].lower()
|
||||
if entity_type in usergroup_types:
|
||||
users = getUsersToModify(entity_type=entity_type,
|
||||
entity=sys.argv[6])
|
||||
users = gam.getUsersToModify(entity_type=entity_type,
|
||||
entity=sys.argv[6])
|
||||
else:
|
||||
entity_type = 'users'
|
||||
users = getUsersToModify(entity_type=entity_type,
|
||||
entity=sys.argv[5])
|
||||
users = gam.getUsersToModify(entity_type=entity_type,
|
||||
entity=sys.argv[5])
|
||||
if (entity_type.startswith('cros')) or (
|
||||
(entity_type == 'all') and (sys.argv[6].lower() == 'cros')):
|
||||
for l in range(0, len(users), 50):
|
||||
@@ -253,7 +252,7 @@ def update():
|
||||
for user in users:
|
||||
i += 1
|
||||
sys.stderr.write(
|
||||
f' moving {user} to {orgUnitPath}{currentCountNL(i, count)}'
|
||||
f' moving {user} to {orgUnitPath}{gam.currentCountNL(i, count)}'
|
||||
)
|
||||
try:
|
||||
gapi.call(cd.users(),
|
||||
@@ -377,7 +376,7 @@ def getTopLevelOrgId(cd, orgUnitPath):
|
||||
|
||||
def getOrgUnitId(orgUnit, cd=None):
|
||||
if cd is None:
|
||||
cd = buildGAPIObject('directory')
|
||||
cd = gapi_directory.build()
|
||||
orgUnit = getOrgUnitItem(orgUnit)
|
||||
if orgUnit[:3] == 'id:':
|
||||
return (orgUnit, orgUnit)
|
||||
|
||||
@@ -15,7 +15,7 @@ def flatten_privilege_list(privs, parent=None):
|
||||
parent=priv['privilegeName'])
|
||||
priv['children'] = ' '.join(
|
||||
[child['privilegeName'] for child in children])
|
||||
del (priv['childPrivileges'])
|
||||
del priv['childPrivileges']
|
||||
flat_privs = flat_privs + children
|
||||
flat_privs.append(priv)
|
||||
return flat_privs
|
||||
|
||||
@@ -28,5 +28,3 @@ def turn_off_2sv(users):
|
||||
'turnOff',
|
||||
soft_errors=True,
|
||||
userKey=user)
|
||||
|
||||
|
||||
|
||||
299
src/gam/gapi/licensing.py
Normal file
299
src/gam/gapi/licensing.py
Normal file
@@ -0,0 +1,299 @@
|
||||
import re
|
||||
import sys
|
||||
|
||||
import gam
|
||||
from gam.var import *
|
||||
from gam import controlflow
|
||||
from gam import display
|
||||
from gam import gapi
|
||||
from gam.gapi import errors as gapi_errors
|
||||
|
||||
|
||||
def build():
|
||||
return gam.buildGAPIObject('licensing')
|
||||
|
||||
|
||||
def getProductAndSKU(sku):
|
||||
l_sku = sku.lower().replace('-', '').replace(' ', '')
|
||||
for a_sku, sku_values in list(SKUS.items()):
|
||||
if l_sku == a_sku.lower().replace(
|
||||
'-',
|
||||
'') or l_sku in sku_values['aliases'] or l_sku == sku_values[
|
||||
'displayName'].lower().replace(' ', ''):
|
||||
return (sku_values['product'], a_sku)
|
||||
try:
|
||||
product = re.search('^([A-Z,a-z]*-[A-Z,a-z]*)', sku).group(1)
|
||||
except AttributeError:
|
||||
product = sku
|
||||
return (product, sku)
|
||||
|
||||
|
||||
def user_lic_result(request_id, response, exception):
|
||||
if exception:
|
||||
http_status, reason, message = gapi_errors.get_gapi_error_detail(
|
||||
exception,
|
||||
soft_errors=True)
|
||||
print(f'ERROR: {request_id}: {http_status} - {reason} {message}')
|
||||
|
||||
|
||||
def create(users, sku=None):
|
||||
lic = build()
|
||||
if not sku:
|
||||
sku = sys.argv[5]
|
||||
productId, skuId = getProductAndSKU(sku)
|
||||
sku_name = _formatSKUIdDisplayName(skuId)
|
||||
i = 6
|
||||
if len(sys.argv) > 6 and sys.argv[i].lower() in ['product', 'productid']:
|
||||
productId = sys.argv[i+1]
|
||||
i += 2
|
||||
for user in users:
|
||||
print(f'Adding license {sku_name} from to {user}')
|
||||
gapi.call(lic.licenseAssignments(),
|
||||
'insert',
|
||||
soft_errors=True,
|
||||
productId=productId,
|
||||
skuId=skuId,
|
||||
body={'userId': user})
|
||||
|
||||
|
||||
def delete(users, sku=None):
|
||||
lic = build()
|
||||
if not sku:
|
||||
sku = sys.argv[5]
|
||||
productId, skuId = getProductAndSKU(sku)
|
||||
sku_name = _formatSKUIdDisplayName(skuId)
|
||||
i = 6
|
||||
if len(sys.argv) > 6 and sys.argv[i].lower() in ['product', 'productid']:
|
||||
productId = sys.argv[i+1]
|
||||
i += 2
|
||||
for user in users:
|
||||
print(f'Removing license {sku_name} from user {user}')
|
||||
gapi.call(lic.licenseAssignments(),
|
||||
'delete',
|
||||
soft_errors=True,
|
||||
productId=productId,
|
||||
skuId=skuId,
|
||||
userId=user)
|
||||
|
||||
|
||||
def sync(users):
|
||||
sku = sys.argv[5]
|
||||
current_licenses = gam.getUsersToModify(entity_type='license',
|
||||
entity=sku)
|
||||
users_to_license = [user for user in users if user not in current_licenses]
|
||||
users_to_unlicense = [user for user in current_licenses if user not in users]
|
||||
print(f'Need to remove license from {len(users_to_unlicense)} and add to ' \
|
||||
f'{len(users_to_license)} users...')
|
||||
# do the remove first to free up seats
|
||||
delete(users_to_unlicense, sku)
|
||||
create(users_to_license, sku)
|
||||
|
||||
|
||||
def update(users, sku=None, old_sku=None):
|
||||
lic = build()
|
||||
if not sku:
|
||||
sku = sys.argv[5]
|
||||
productId, skuId = getProductAndSKU(sku)
|
||||
sku_name = _formatSKUIdDisplayName(skuId)
|
||||
i = 6
|
||||
if len(sys.argv) > 6 and sys.argv[i].lower() in ['product', 'productid']:
|
||||
productId = sys.argv[i+1]
|
||||
i += 2
|
||||
if not old_sku:
|
||||
try:
|
||||
old_sku = sys.argv[i]
|
||||
if old_sku.lower() == 'from':
|
||||
old_sku = sys.argv[i + 1]
|
||||
except KeyError:
|
||||
controlflow.system_error_exit(
|
||||
2,
|
||||
'You need to specify the user\'s old SKU as the last argument'
|
||||
)
|
||||
_, old_sku = getProductAndSKU(old_sku)
|
||||
old_sku_name = _formatSKUIdDisplayName(old_sku)
|
||||
for user in users:
|
||||
print(f'Changing user {user} from license {old_sku_name} to {sku_name}')
|
||||
gapi.call(lic.licenseAssignments(),
|
||||
'patch',
|
||||
soft_errors=True,
|
||||
productId=productId,
|
||||
skuId=old_sku,
|
||||
userId=user,
|
||||
body={'skuId': skuId})
|
||||
|
||||
|
||||
def print_(returnFields=None,
|
||||
skus=None,
|
||||
countsOnly=False,
|
||||
returnCounts=False):
|
||||
lic = build()
|
||||
products = []
|
||||
licenses = []
|
||||
licenseCounts = []
|
||||
if not returnFields:
|
||||
csvRows = []
|
||||
todrive = False
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower()
|
||||
if not returnCounts and myarg == 'todrive':
|
||||
todrive = True
|
||||
i += 1
|
||||
elif myarg in ['products', 'product']:
|
||||
products = sys.argv[i + 1].split(',')
|
||||
i += 2
|
||||
elif myarg in ['sku', 'skus']:
|
||||
skus = sys.argv[i + 1].split(',')
|
||||
i += 2
|
||||
elif myarg == 'allskus':
|
||||
skus = sorted(SKUS)
|
||||
products = []
|
||||
i += 1
|
||||
elif myarg == 'gsuite':
|
||||
skus = [
|
||||
skuId for skuId in SKUS
|
||||
if SKUS[skuId]['product'] in ['Google-Apps', '101031']
|
||||
]
|
||||
products = []
|
||||
i += 1
|
||||
elif myarg == 'countsonly':
|
||||
countsOnly = True
|
||||
i += 1
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i],
|
||||
'gam print licenses')
|
||||
if not countsOnly:
|
||||
fields = 'nextPageToken,items(productId,skuId,userId)'
|
||||
titles = ['userId', 'productId', 'skuId']
|
||||
else:
|
||||
fields = 'nextPageToken,items(userId)'
|
||||
if not returnCounts:
|
||||
if skus:
|
||||
titles = ['productId', 'skuId', 'licenses']
|
||||
else:
|
||||
titles = ['productId', 'licenses']
|
||||
else:
|
||||
fields = f'nextPageToken,items({returnFields})'
|
||||
if skus:
|
||||
for sku in skus:
|
||||
if not products:
|
||||
product, sku = getProductAndSKU(sku)
|
||||
else:
|
||||
product = products[0]
|
||||
page_message = gapi.got_total_items_msg(
|
||||
f'Licenses for {SKUS.get(sku, {"displayName": sku})["displayName"]}',
|
||||
'...\n')
|
||||
try:
|
||||
licenses += gapi.get_all_pages(
|
||||
lic.licenseAssignments(),
|
||||
'listForProductAndSku',
|
||||
'items',
|
||||
throw_reasons=[
|
||||
gapi_errors.ErrorReason.INVALID,
|
||||
gapi_errors.ErrorReason.FORBIDDEN
|
||||
],
|
||||
page_message=page_message,
|
||||
customerId=GC_Values[GC_DOMAIN],
|
||||
productId=product,
|
||||
skuId=sku,
|
||||
fields=fields)
|
||||
if countsOnly:
|
||||
licenseCounts.append([
|
||||
'Product', product, 'SKU', sku, 'Licenses',
|
||||
len(licenses)
|
||||
])
|
||||
licenses = []
|
||||
except (gapi_errors.GapiInvalidError,
|
||||
gapi_errors.GapiForbiddenError):
|
||||
pass
|
||||
else:
|
||||
if not products:
|
||||
products = sorted(PRODUCTID_NAME_MAPPINGS)
|
||||
for productId in products:
|
||||
page_message = gapi.got_total_items_msg(
|
||||
f'Licenses for {PRODUCTID_NAME_MAPPINGS.get(productId, productId)}',
|
||||
'...\n')
|
||||
try:
|
||||
licenses += gapi.get_all_pages(
|
||||
lic.licenseAssignments(),
|
||||
'listForProduct',
|
||||
'items',
|
||||
throw_reasons=[
|
||||
gapi_errors.ErrorReason.INVALID,
|
||||
gapi_errors.ErrorReason.FORBIDDEN
|
||||
],
|
||||
page_message=page_message,
|
||||
customerId=GC_Values[GC_DOMAIN],
|
||||
productId=productId,
|
||||
fields=fields)
|
||||
if countsOnly:
|
||||
licenseCounts.append(
|
||||
['Product', productId, 'Licenses',
|
||||
len(licenses)])
|
||||
licenses = []
|
||||
except (gapi_errors.GapiInvalidError,
|
||||
gapi_errors.GapiForbiddenError):
|
||||
pass
|
||||
if countsOnly:
|
||||
if returnCounts:
|
||||
return licenseCounts
|
||||
if skus:
|
||||
for u_license in licenseCounts:
|
||||
csvRows.append({
|
||||
'productId': u_license[1],
|
||||
'skuId': u_license[3],
|
||||
'licenses': u_license[5]
|
||||
})
|
||||
else:
|
||||
for u_license in licenseCounts:
|
||||
csvRows.append({
|
||||
'productId': u_license[1],
|
||||
'licenses': u_license[3]
|
||||
})
|
||||
display.write_csv_file(csvRows, titles, 'Licenses', todrive)
|
||||
return
|
||||
if returnFields:
|
||||
if returnFields == 'userId':
|
||||
userIds = []
|
||||
for u_license in licenses:
|
||||
userId = u_license.get('userId', '').lower()
|
||||
if userId:
|
||||
userIds.append(userId)
|
||||
return userIds
|
||||
userSkuIds = {}
|
||||
for u_license in licenses:
|
||||
userId = u_license.get('userId', '').lower()
|
||||
skuId = u_license.get('skuId')
|
||||
if userId and skuId:
|
||||
userSkuIds.setdefault(userId, [])
|
||||
userSkuIds[userId].append(skuId)
|
||||
return userSkuIds
|
||||
for u_license in licenses:
|
||||
userId = u_license.get('userId', '').lower()
|
||||
skuId = u_license.get('skuId', '')
|
||||
csvRows.append({
|
||||
'userId': userId,
|
||||
'productId': u_license.get('productId', ''),
|
||||
'skuId': _skuIdToDisplayName(skuId)
|
||||
})
|
||||
display.write_csv_file(csvRows, titles, 'Licenses', todrive)
|
||||
|
||||
|
||||
def show():
|
||||
licenseCounts = print_(countsOnly=True, returnCounts=True)
|
||||
for u_license in licenseCounts:
|
||||
line = ''
|
||||
for i in range(0, len(u_license), 2):
|
||||
line += f'{u_license[i]}: {u_license[i+1]}, '
|
||||
print(line[:-2])
|
||||
|
||||
|
||||
def _skuIdToDisplayName(skuId):
|
||||
return SKUS[skuId]['displayName'] if skuId in SKUS else skuId
|
||||
|
||||
|
||||
def _formatSKUIdDisplayName(skuId):
|
||||
skuIdDisplay = _skuIdToDisplayName(skuId)
|
||||
if skuId == skuIdDisplay:
|
||||
return skuId
|
||||
return f'{skuId} ({skuIdDisplay})'
|
||||
@@ -11,6 +11,7 @@ from gam import controlflow
|
||||
from gam import display
|
||||
from gam import gapi
|
||||
from gam import utils
|
||||
from gam.gapi.directory import orgunits as gapi_directory_orgunits
|
||||
|
||||
|
||||
def build():
|
||||
@@ -178,7 +179,7 @@ def showUsage():
|
||||
skip_day_numbers = [dow.index(d) for d in skipdaynames if d in dow]
|
||||
i += 2
|
||||
elif report == 'user' and myarg in ['orgunit', 'org', 'ou']:
|
||||
_, orgUnitId = gam.getOrgUnitId(sys.argv[i + 1])
|
||||
_, orgUnitId = gapi_directory_orgunits.getOrgUnitId(sys.argv[i + 1])
|
||||
i += 2
|
||||
elif report == 'user' and myarg in usergroup_types:
|
||||
users = gam.getUsersToModify(myarg, sys.argv[i + 1])
|
||||
@@ -296,7 +297,7 @@ def showReport():
|
||||
tryDate = utils.get_yyyymmdd(sys.argv[i + 1])
|
||||
i += 2
|
||||
elif myarg in ['orgunit', 'org', 'ou']:
|
||||
_, orgUnitId = gam.getOrgUnitId(sys.argv[i + 1])
|
||||
_, orgUnitId = gapi_directory_orgunits.getOrgUnitId(sys.argv[i + 1])
|
||||
i += 2
|
||||
elif myarg == 'fulldatarequired':
|
||||
fullDataRequired = []
|
||||
|
||||
@@ -184,5 +184,5 @@ def update():
|
||||
pass
|
||||
print()
|
||||
print(
|
||||
f'You can now add {a_domain} or it\'s subdomains as secondary or domain aliases of the {GC_Values[GC_DOMAIN]} G Suite Account.'
|
||||
f'You can now add {a_domain} or it\'s subdomains as secondary or domain aliases of the {GC_Values[GC_DOMAIN]} Google Workspace Account.'
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@ import platform
|
||||
import re
|
||||
|
||||
GAM_AUTHOR = 'Jay Lee <jay0lee@gmail.com>'
|
||||
GAM_VERSION = '5.20'
|
||||
GAM_VERSION = '5.24'
|
||||
GAM_LICENSE = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
|
||||
|
||||
GAM_URL = 'https://git.io/gam'
|
||||
@@ -114,10 +114,44 @@ SKUS = {
|
||||
'aliases': ['gau', 'gsb', 'unlimited', 'gsuitebusiness'],
|
||||
'displayName': 'G Suite Business'
|
||||
},
|
||||
'1010020027': {
|
||||
'product': 'Google-Apps',
|
||||
'aliases': ['wsbizstart', 'workspacebusinessstarter'],
|
||||
'displayName': 'Workspace Business Starter'
|
||||
},
|
||||
'1010020028': {
|
||||
'product': 'Google-Apps',
|
||||
'aliases': ['wsbizstan', 'workspacebusinessstandard'],
|
||||
'displayName': 'Workspace Business Standard'
|
||||
},
|
||||
'1010020025': {
|
||||
'product': 'Google-Apps',
|
||||
'aliases': ['wsbizplus', 'workspacebusinessplus'],
|
||||
'displayName': 'Workspace Business Plus'
|
||||
},
|
||||
'1010060001': {
|
||||
'product': 'Google-Apps',
|
||||
'aliases': [
|
||||
'gsuiteessentials', 'essentials', 'd4e', 'driveenterprise',
|
||||
'drive4enterprise', 'wsess', 'workspaceesentials'
|
||||
],
|
||||
'displayName': 'Google Workspace Essentials'
|
||||
},
|
||||
'1010060003': {
|
||||
'product': 'Google-Apps',
|
||||
'aliases': ['wsentess', 'workspaceenterpriseessentials'],
|
||||
'displayName': 'Workspace Enterprise Essentials'
|
||||
},
|
||||
'1010020026': {
|
||||
'product': 'Google-Apps',
|
||||
'aliases': ['wsentstan', 'workspaceenterprisestandard'],
|
||||
'displayName': 'Workspace Enterprise Standard'
|
||||
},
|
||||
'1010020020': {
|
||||
'product': 'Google-Apps',
|
||||
'aliases': ['gae', 'gse', 'enterprise', 'gsuiteenterprise'],
|
||||
'displayName': 'G Suite Enterprise'
|
||||
'aliases': ['gae', 'gse', 'enterprise', 'gsuiteenterprise',
|
||||
'wsentplus', 'workspaceenterpriseplus'],
|
||||
'displayName': 'Workspace Enterprise Plus'
|
||||
},
|
||||
'1010340002': {
|
||||
'product': '101034',
|
||||
@@ -127,15 +161,7 @@ SKUS = {
|
||||
'1010340001': {
|
||||
'product': '101034',
|
||||
'aliases': ['gseau', 'enterprisearchived', 'gsuiteenterprisearchived'],
|
||||
'displayName': 'G Suite Enterprise Archived'
|
||||
},
|
||||
'1010060001': {
|
||||
'product': '101006',
|
||||
'aliases': [
|
||||
'gsuiteessentials', 'essentials', 'd4e', 'driveenterprise',
|
||||
'drive4enterprise'
|
||||
],
|
||||
'displayName': 'G Suite Essentials'
|
||||
'displayName': 'Google Workspace Enterprise Plus Archived'
|
||||
},
|
||||
'Google-Drive-storage-20GB': {
|
||||
'product': 'Google-Drive-storage',
|
||||
@@ -192,11 +218,6 @@ SKUS = {
|
||||
'aliases': ['vfe', 'googlevaultformeremployee'],
|
||||
'displayName': 'Google Vault Former Employee'
|
||||
},
|
||||
'Google-Coordinate': {
|
||||
'product': 'Google-Coordinate',
|
||||
'aliases': ['coordinate', 'googlecoordinate'],
|
||||
'displayName': 'Google Coordinate'
|
||||
},
|
||||
'Google-Chrome-Device-Management': {
|
||||
'product': 'Google-Chrome-Device-Management',
|
||||
'aliases': ['chrome', 'cdm', 'googlechromedevicemanagement'],
|
||||
@@ -210,7 +231,7 @@ PRODUCTID_NAME_MAPPINGS = {
|
||||
'101031': 'G Suite Enterprise for Education',
|
||||
'101033': 'Google Voice',
|
||||
'101034': 'G Suite Archived',
|
||||
'Google-Apps': 'G Suite',
|
||||
'Google-Apps': 'Google Workspace',
|
||||
'Google-Chrome-Device-Management': 'Google Chrome Device Management',
|
||||
'Google-Coordinate': 'Google Coordinate',
|
||||
'Google-Drive-storage': 'Google Drive Storage',
|
||||
@@ -223,7 +244,6 @@ V1_DISCOVERY_APIS = {
|
||||
'appsactivity',
|
||||
'calendar',
|
||||
'drive',
|
||||
'licensing',
|
||||
'oauth2',
|
||||
'reseller',
|
||||
'siteVerification',
|
||||
@@ -367,11 +387,21 @@ CALENDAR_NOTIFICATION_TYPES_MAP = {
|
||||
'agenda': 'agenda',
|
||||
}
|
||||
|
||||
DEVICE_ORDERBY_CHOICES_MAP = {
|
||||
'createtime': 'create_time',
|
||||
'devicetype': 'device_type',
|
||||
'lastsynctime': 'last_sync_time',
|
||||
'model': 'model',
|
||||
'osversion': 'os_version',
|
||||
'serialnumber': 'serial_number'
|
||||
}
|
||||
|
||||
DRIVEFILE_FIELDS_CHOICES_MAP = {
|
||||
'alternatelink': 'alternateLink',
|
||||
'appdatacontents': 'appDataContents',
|
||||
'cancomment': 'canComment',
|
||||
'canreadrevisions': 'canReadRevisions',
|
||||
'contentrestrictions': 'contentRestrictions',
|
||||
'copyable': 'copyable',
|
||||
'copyrequireswriterpermission': 'copyRequiresWriterPermission',
|
||||
'createddate': 'createdDate',
|
||||
@@ -480,16 +510,18 @@ DRIVEFILE_LABEL_CHOICES_MAP = {
|
||||
}
|
||||
|
||||
APPLICATION_VND_GOOGLE_APPS = 'application/vnd.google-apps.'
|
||||
MIMETYPE_GA_DOCUMENT = APPLICATION_VND_GOOGLE_APPS + 'document'
|
||||
MIMETYPE_GA_DRAWING = APPLICATION_VND_GOOGLE_APPS + 'drawing'
|
||||
MIMETYPE_GA_FOLDER = APPLICATION_VND_GOOGLE_APPS + 'folder'
|
||||
MIMETYPE_GA_FORM = APPLICATION_VND_GOOGLE_APPS + 'form'
|
||||
MIMETYPE_GA_FUSIONTABLE = APPLICATION_VND_GOOGLE_APPS + 'fusiontable'
|
||||
MIMETYPE_GA_MAP = APPLICATION_VND_GOOGLE_APPS + 'map'
|
||||
MIMETYPE_GA_PRESENTATION = APPLICATION_VND_GOOGLE_APPS + 'presentation'
|
||||
MIMETYPE_GA_SCRIPT = APPLICATION_VND_GOOGLE_APPS + 'script'
|
||||
MIMETYPE_GA_SITES = APPLICATION_VND_GOOGLE_APPS + 'sites'
|
||||
MIMETYPE_GA_SPREADSHEET = APPLICATION_VND_GOOGLE_APPS + 'spreadsheet'
|
||||
MIMETYPE_GA_DOCUMENT = f'{APPLICATION_VND_GOOGLE_APPS}document'
|
||||
MIMETYPE_GA_DRAWING = f'{APPLICATION_VND_GOOGLE_APPS}drawing'
|
||||
MIMETYPE_GA_FOLDER = f'{APPLICATION_VND_GOOGLE_APPS}folder'
|
||||
MIMETYPE_GA_FORM = f'{APPLICATION_VND_GOOGLE_APPS}form'
|
||||
MIMETYPE_GA_FUSIONTABLE = f'{APPLICATION_VND_GOOGLE_APPS}fusiontable'
|
||||
MIMETYPE_GA_MAP = f'{APPLICATION_VND_GOOGLE_APPS}map'
|
||||
MIMETYPE_GA_PRESENTATION = f'{APPLICATION_VND_GOOGLE_APPS}presentation'
|
||||
MIMETYPE_GA_SCRIPT = f'{APPLICATION_VND_GOOGLE_APPS}script'
|
||||
MIMETYPE_GA_SITES = f'{APPLICATION_VND_GOOGLE_APPS}sites'
|
||||
MIMETYPE_GA_SPREADSHEET = f'{APPLICATION_VND_GOOGLE_APPS}spreadsheet'
|
||||
MIMETYPE_GA_SHORTCUT = f'{APPLICATION_VND_GOOGLE_APPS}shortcut'
|
||||
MIMETYPE_GA_3P_SHORTCUT = f'{APPLICATION_VND_GOOGLE_APPS}drive-sdk'
|
||||
|
||||
MIMETYPE_CHOICES_MAP = {
|
||||
'gdoc': MIMETYPE_GA_DOCUMENT,
|
||||
@@ -501,9 +533,12 @@ MIMETYPE_CHOICES_MAP = {
|
||||
'gfusion': MIMETYPE_GA_FUSIONTABLE,
|
||||
'gpresentation': MIMETYPE_GA_PRESENTATION,
|
||||
'gscript': MIMETYPE_GA_SCRIPT,
|
||||
'gshortcut': MIMETYPE_GA_SHORTCUT,
|
||||
'g3pshortcut': MIMETYPE_GA_3P_SHORTCUT,
|
||||
'gsite': MIMETYPE_GA_SITES,
|
||||
'gsheet': MIMETYPE_GA_SPREADSHEET,
|
||||
'gspreadsheet': MIMETYPE_GA_SPREADSHEET,
|
||||
'shortcut': MIMETYPE_GA_SHORTCUT,
|
||||
}
|
||||
|
||||
DFA_CONVERT = 'convert'
|
||||
@@ -1139,7 +1174,7 @@ GC_CLIENT_SECRETS_JSON = 'client_secrets_json'
|
||||
GC_CONFIG_DIR = 'config_dir'
|
||||
# custmerId from gam.cfg or retrieved from Google
|
||||
GC_CUSTOMER_ID = 'customer_id'
|
||||
# Enable Delegated Admin Service Accounts admin user
|
||||
# Admin email address, required when enable_dasa is true, overrides oauth2.txt value otherwise
|
||||
GC_ADMIN_EMAIL = 'admin_email'
|
||||
# If debug_level > 0: extra_args[u'prettyPrint'] = True,
|
||||
# httplib2.debuglevel = gam_debug_level, appsObj.debug = True
|
||||
@@ -1350,6 +1385,7 @@ GC_VAR_INFO = {
|
||||
# Google API constants
|
||||
|
||||
NEVER_TIME = '1970-01-01T00:00:00.000Z'
|
||||
NEVER_TIME_NOMS = '1970-01-01T00:00:00Z'
|
||||
ROLE_MANAGER = 'MANAGER'
|
||||
ROLE_MEMBER = 'MEMBER'
|
||||
ROLE_OWNER = 'OWNER'
|
||||
|
||||
Reference in New Issue
Block a user