mirror of
https://github.com/GAM-team/GAM.git
synced 2026-06-04 14:21:39 +00:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5580c5649 | ||
|
|
e9200ea8fb | ||
|
|
2e0c280ea6 | ||
|
|
3948a414b5 | ||
|
|
2c83068605 | ||
|
|
6f6ccad00b | ||
|
|
bd18f14137 | ||
|
|
d54ca7ee43 | ||
|
|
19452c2461 | ||
|
|
4e2e96a6dd | ||
|
|
7957d131c0 | ||
|
|
ca9dfaff1d | ||
|
|
7e9475791b | ||
|
|
c8fb44a7c4 | ||
|
|
bb70183bc7 | ||
|
|
ff80ba1814 | ||
|
|
5d292dcaf7 | ||
|
|
bcc5c4520f | ||
|
|
aa7ea59b5e | ||
|
|
16e85d6d5c | ||
|
|
453e65ec53 | ||
|
|
4cbcb9418c | ||
|
|
f2120229e2 | ||
|
|
4d2db30000 | ||
|
|
ca575b267b | ||
|
|
3216666a94 | ||
|
|
4ef5606f05 | ||
|
|
6122dc3353 | ||
|
|
14ae792091 | ||
|
|
9da5065700 | ||
|
|
22e155998d |
9
.github/workflows/build.yml
vendored
9
.github/workflows/build.yml
vendored
@@ -82,7 +82,7 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
bin
|
||||
key: gam-${{ matrix.jid }}-20220621
|
||||
key: gam-${{ matrix.jid }}-20220802
|
||||
|
||||
- name: Use pre-compiled Python for testing
|
||||
if: matrix.python != ''
|
||||
@@ -545,6 +545,7 @@ jobs:
|
||||
echo "OAUTHFILE=${OAUTHFILE}" >> $GITHUB_ENV
|
||||
export gam_user="gam-gha-${JID}@pdl.jaylee.us"
|
||||
echo "gam_user=${gam_user}" >> $GITHUB_ENV
|
||||
$gam checkconn
|
||||
$gam oauth info
|
||||
$gam info domain
|
||||
$gam oauth refresh
|
||||
@@ -575,7 +576,7 @@ jobs:
|
||||
$gam info cigroup $newgroup
|
||||
$gam update group $newgroup add owner $gam_user
|
||||
$gam update group $newgroup add member $newuser
|
||||
$gam create admin $newuser _GROUPS_EDITOR_ROLE CUSTOMER condition nonsecuritygroup
|
||||
$gam create admin $newuser _GROUPS_EDITOR_ROLE CUSTOMER # condition nonsecuritygroup
|
||||
$gam csv sample.csv gam create user ~~email~~ firstname "GHA Bulk" lastname ~~email~~ gha.jid $JID
|
||||
$gam csv sample.csv gam update user ~~email~~ recoveryphone 12125121110 recoveryemail jay0lee@gmail.com password random
|
||||
$gam csv sample.csv gam update user ~~email~~ recoveryphone "" recoveryemail ""
|
||||
@@ -661,8 +662,8 @@ jobs:
|
||||
$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
|
||||
$gam report admin start -3d todrive
|
||||
$gam print devices nopersonaldevices nodeviceusers filter "serial:$JID$JID$JID$JID-" | $gam csv - gam delete device id ~name
|
||||
$gam print userinvitations
|
||||
$gam print userinvitations | $gam csv - gam send userinvitation ~name
|
||||
#$gam print userinvitations
|
||||
#$gam print userinvitations | $gam csv - gam send userinvitation ~name
|
||||
$gam create caalevel "zzz_${newbase}" basic condition ipsubnetworks 1.1.1.1/32,2.2.2.2/32 endcondition
|
||||
$gam print caalevels
|
||||
$gam delete caalevel "zzz_${newbase}"
|
||||
|
||||
@@ -653,7 +653,8 @@ Specify a collection of ChromeOS devices by directly specifying them
|
||||
(crosfile <FileName>)|
|
||||
(croscsvfile <FileName>:<FieldName>)|
|
||||
(crosquery <QueryCrOS>)|
|
||||
(crosqueries <QueryCrOSList>)
|
||||
(crosqueries <QueryCrOSList>)|
|
||||
(cros_ou|cros_ou_and_children <OrgUnitPath>)
|
||||
|
||||
## Collections of Users
|
||||
|
||||
@@ -871,6 +872,7 @@ Specify a collection of Users by directly specifying them or by specifying items
|
||||
<UserBasicAttribute>|
|
||||
<UserMultiAttribute>
|
||||
|
||||
gam checkconnection
|
||||
gam version [check|checkrc|simple|extended] [timeoffset] [location <HostName>]
|
||||
gam help
|
||||
|
||||
@@ -1066,9 +1068,9 @@ gam delete alias|nickname [user|group|target] <UniqueID>|<EmailAddress>
|
||||
gam info alias|nickname <EmailAddress>
|
||||
gam print aliases|nicknames [todrive] [shownoneditable] [nogroups] [nousers] [(query <QueryUser>)|(queries <QueryUserList)]
|
||||
|
||||
gam calendar <CalendarItem> add <CalendarACLRole> ([user] <EmailAddress>)|(group <EmailAddress>)|(domain <DomainName>) [sendnotifications <Boolean>]
|
||||
gam calendar <CalendarItem> add <CalendarACLRole> ([user] <EmailAddress>)|(group <EmailAddress>)|(domain <DomainName>)|domain|default [sendnotifications <Boolean>]
|
||||
gam calendar <CalendarItem> update <CalendarACLRole> ([user] <EmailAddress>)|(group <EmailAddress>)|(domain <DomainName>)|domain|default [sendnotifications <Boolean>]
|
||||
gam calendar <CalendarItem> del|delete ([user] <EmailAddress>)|(group <EmailAddress>)|(domain <DomainName>)|domainx|default
|
||||
gam calendar <CalendarItem> del|delete ([user] <EmailAddress>)|(group <EmailAddress>)|(domain <DomainName>)|domain|default
|
||||
gam calendar <CalendarItem> del|delete id <CalendarACLRuleID>
|
||||
gam calendar <CalendarItem> showacl
|
||||
gam calendar <CalendarItem> printacl [todrive]
|
||||
@@ -1303,7 +1305,9 @@ gam update chatmessage name <String>
|
||||
deprovision_retiring_device|
|
||||
deprovision_upgrade_transfer|
|
||||
disable|
|
||||
reenable
|
||||
reenable|
|
||||
pre_provisioned_disable|
|
||||
pre_provisioned_reenable
|
||||
|
||||
gam update cros <CrOSEntity> action <CrOSAction> [acknowledge_device_touch_requirement]
|
||||
|
||||
@@ -1321,7 +1325,7 @@ gam update cros <CrOSEntity> <CrOSAttribute>+
|
||||
gam info cros <CrOSEntity> [nolists] [listlimit <Number>] [start <Date>] [end <Date>]
|
||||
[basic|full|allfields] <CrOSFieldName>* [fields <CrOSFieldNameList>] [downloadfile latest|<Time>] [targetfolder <FilePath>]
|
||||
|
||||
gam print cros [todrive] [(query <QueryCrOS>)|(queries <QueryCrOSList>)] [limittoou <OrgUnitItem>]
|
||||
gam print cros [todrive] [(query <QueryCrOS>)|(queries <QueryCrOSList>)] [limittoou|cros_ou|cros_ou_and_children <OrgUnitItem>]
|
||||
[orderby <CrOSOrderByFieldName> [ascending|descending]] [nolists|<CrOSListFieldName>*] [listlimit <Number>] [start <Date>] [end <Date>]
|
||||
[basic|full|allfields] <CrOSFieldName>* [fields <CrOSFieldNameList>] [sortheaders]
|
||||
gam <CrOSTypeEntity> print
|
||||
@@ -1329,7 +1333,7 @@ gam <CrOSTypeEntity> print
|
||||
Summary of printing:
|
||||
gam print cros
|
||||
Prints a header row and deviceId for all CrOS devices.
|
||||
gam <CrOSTypeEntity> print cros
|
||||
gam <CrOSTypeEntity> print
|
||||
Prints no header row and deviceId for specified CrOS devices.
|
||||
gam print cros ... basic|full
|
||||
Prints a header row and selected fields for specified CrOS devices.
|
||||
@@ -1343,7 +1347,7 @@ One set of values for all <CrOSListFieldName> fields specified will be output on
|
||||
The listlimit <Number> argument limits the number of repetitions to <Number>; if not specified or <Number> equals zero, there is no limit.
|
||||
The start <Date> and end <Date> arguments constrain activeTimeRanges, cpuStatusReports, deviceFiles and systemRamFreeReports to fall within the specified <Dates>.
|
||||
|
||||
gam print crosactivity [todrive] [(query <QueryCrOS>)|(queries <QueryCrOSList>)] [limittoou <OrgUnitItem>]
|
||||
gam print crosactivity [todrive] [(query <QueryCrOS>)|(queries <QueryCrOSList>)] [limittoou|cros_ou|cros_ou_and_children <OrgUnitItem>]
|
||||
[recentusers] [timeranges] [both] [devicefiles] [all] [listlimit <Number>] [start <Date>] [end <Date>] [delimiter <Character>]
|
||||
|
||||
The basic column headers are: deviceId,annotatedAssetId,annotatedLocation,serialNumber,orgUnitPath.
|
||||
|
||||
@@ -28,7 +28,7 @@ upgrade_only=false
|
||||
gamversion="latest"
|
||||
adminuser=""
|
||||
regularuser=""
|
||||
gam_glibc_vers="2.31"
|
||||
gam_glibc_vers="2.35"
|
||||
|
||||
while getopts "hd:a:o:b:lp:u:r:v:" OPTION
|
||||
do
|
||||
@@ -113,10 +113,9 @@ case $gamos in
|
||||
done
|
||||
case $gamarch in
|
||||
x86_64) gamfile="linux-x86_64-$useglibc.tar.xz";;
|
||||
arm64|aarch64) gamfile="linux-aarch64-glibc2.28.tar.xz";;
|
||||
arm|armv7l) gamfile="linux-armv7l-glibc2.28.tar.xz";;
|
||||
arm64|aarch64) gamfile="linux-aarch64-$useglibc.tar.xz";;
|
||||
*)
|
||||
echo_red "ERROR: this installer currently only supports x86_64, arm and arm64 Linux. Looks like you're running on $gamarch. Exiting."
|
||||
echo_red "ERROR: this installer currently only supports x86_64 and arm64 Linux. Looks like you're running on $gamarch. Exiting."
|
||||
exit
|
||||
esac
|
||||
;;
|
||||
|
||||
@@ -719,8 +719,11 @@ def doGAMCheckForUpdates(forceCheck=False):
|
||||
continue_on_error=True,
|
||||
display_errors=forceCheck)
|
||||
return
|
||||
except (httplib2.HttpLib2Error, httplib2.ServerNotFoundError, RuntimeError,
|
||||
socket.timeout):
|
||||
except (httplib2.HttpLib2Error,
|
||||
httplib2.ServerNotFoundError,
|
||||
RuntimeError,
|
||||
ConnectionError,
|
||||
TimeoutError):
|
||||
return
|
||||
|
||||
|
||||
@@ -746,6 +749,72 @@ def getOSPlatform():
|
||||
return f'{myos} {pltfrm}'
|
||||
|
||||
|
||||
def checkConnection():
|
||||
hosts = [
|
||||
'api.github.com',
|
||||
'raw.githubusercontent.com',
|
||||
'gam-shortn.appspot.com',
|
||||
'accounts.google.com',
|
||||
'oauth2.googleapis.com',
|
||||
'www.googleapis.com',
|
||||
]
|
||||
api_hosts = []
|
||||
for api in API_VER_MAPPING:
|
||||
api = API_NAME_MAPPING.get(api, api)
|
||||
api = f'{api}.googleapis.com'
|
||||
if api not in api_hosts and api not in hosts:
|
||||
api_hosts.append(api)
|
||||
api_hosts.sort()
|
||||
hosts.extend(api_hosts)
|
||||
httpc = transport.create_http(timeout=10)
|
||||
httpc.follow_redirects = False
|
||||
headers = {'user-agent': GAM_INFO}
|
||||
okay = createGreenText('OK')
|
||||
not_okay = createRedText('ERROR')
|
||||
gen_firewall = 'You may have security software or a firewall on your machine or network that is preventing GAM from making a secure connection to this host. Check your network configuration or try running GAM on a hotspot or home network to see if the problem exists only on your organization\'s network.'
|
||||
host_count = len(hosts)
|
||||
try_count = 0
|
||||
success_count = 0
|
||||
for host in hosts:
|
||||
try_count += 1
|
||||
ip = socket.gethostbyname(host)
|
||||
check_line = f'Checking {host} ({ip}) ({try_count}/{host_count})...'
|
||||
sys.stdout.write(f'{check_line:<80}')
|
||||
sys.stdout.flush()
|
||||
try:
|
||||
httpc.request(f'https://{host}/', 'HEAD', headers=headers)
|
||||
success_count += 1
|
||||
print(okay)
|
||||
except BrokenPipeError:
|
||||
print(f'{not_okay}\n Broken pipe. {gen_firewall}')
|
||||
except ConnectionAbortedError:
|
||||
print(f'{not_okay}\n Connection aborted. {gen_firewall}')
|
||||
except ConnectionRefusedError:
|
||||
print(f'{not_okay}\n Connection refused. {gen_firewall}')
|
||||
except ConnectionResetError:
|
||||
print(f'{not_okay}\n Connection reset by peer. {gen_firewall}')
|
||||
except httplib2.error.ServerNotFoundError:
|
||||
print(f'{not_okay}\n Failed to find server. Your DNS is probably misconfigured.')
|
||||
except ssl.SSLError as e:
|
||||
if e.reason == 'SSLV3_ALERT_HANDSHAKE_FAILURE':
|
||||
print(f'{not_okay}\n GAM expects to connect with TLS 1.3 or newer and that failed. If your firewall / proxy server is not compatible with TLS 1.3 then you can tell GAM to allow TLS 1.2 by setting the GAM_TLS_MIN_VERSION=TLSv1_2 environment variable.')
|
||||
elif e.reason == 'CERTIFICATE_VERIFY_FAILED':
|
||||
print(f'{not_okay}\n Certificate verification failed. If you are behind a firewall / proxy server that does TLS / SSL inspection you may need to point GAM at your certificate authority file by setting the GAM_CA_FILE=/path/to/your/certauth.pem environment variable.')
|
||||
elif e.strerror.startswith('TLS/SSL connection has been closed'):
|
||||
print(f'{not_okay}\n TLS connection was closed. {gen_firewall}')
|
||||
else:
|
||||
print(f'{not_okay}\n {e.reason}: {str(e)}')
|
||||
except TimeoutError:
|
||||
print(f'{not_okay}\n Timed out trying to connect to host. {gen_firewall}')
|
||||
except Exception as e:
|
||||
# include the exception class so we know what to catch in the future
|
||||
print(f'{not_okay}\n {type(e).__name__} - {str(e)}')
|
||||
print()
|
||||
if success_count == host_count:
|
||||
print(createGreenText('All hosts passed!'))
|
||||
else:
|
||||
controlflow.system_error_exit(3, createYellowText('Some hosts failed to connect! Please follow the recommendations for those hosts to correct any issues and try again.'))
|
||||
|
||||
def doGAMVersion(checkForArgs=True):
|
||||
force_check = extended = simple = timeOffset = False
|
||||
testLocation = 'admin.googleapis.com'
|
||||
@@ -2247,6 +2316,7 @@ def doGetCourseInfo():
|
||||
|
||||
COURSE_ARGUMENT_TO_PROPERTY_MAP = {
|
||||
'alternatelink': 'alternateLink',
|
||||
'calendarid': 'calendarId',
|
||||
'coursegroupemail': 'courseGroupEmail',
|
||||
'coursematerialsets': 'courseMaterialSets',
|
||||
'coursestate': 'courseState',
|
||||
@@ -2254,7 +2324,9 @@ COURSE_ARGUMENT_TO_PROPERTY_MAP = {
|
||||
'description': 'description',
|
||||
'descriptionheading': 'descriptionHeading',
|
||||
'enrollmentcode': 'enrollmentCode',
|
||||
'gradebooksettings': 'gradebookSettings',
|
||||
'guardiansenabled': 'guardiansEnabled',
|
||||
'heading': 'descriptionHeading',
|
||||
'id': 'id',
|
||||
'name': 'name',
|
||||
'ownerid': 'ownerId',
|
||||
@@ -7555,7 +7627,7 @@ def doCreateProject():
|
||||
create_operation = gapi.call(crm.projects(), 'create', body=body)
|
||||
operation_name = create_operation['name']
|
||||
time.sleep(8) # Google recommends always waiting at least 5 seconds
|
||||
for i in range(1, 5):
|
||||
for i in range(1, 10):
|
||||
print('Checking project status...')
|
||||
status = gapi.call(crm.operations(), 'get', name=operation_name)
|
||||
if 'error' in status:
|
||||
@@ -7614,7 +7686,7 @@ and accept the Terms of Service (ToS). As soon as you've accepted the ToS popup,
|
||||
controlflow.system_error_exit(1, status)
|
||||
if status.get('done', False):
|
||||
break
|
||||
sleep_time = i**2
|
||||
sleep_time = min(2**i, 60)
|
||||
print(f'Project still being created. Sleeping {sleep_time} seconds')
|
||||
time.sleep(sleep_time)
|
||||
if create_again:
|
||||
@@ -7854,14 +7926,13 @@ def doCreateOrRotateServiceAccountKeys(iam=None,
|
||||
body={'publicKeyData': publicKeyData})
|
||||
break
|
||||
except googleapiclient.errors.HttpError as err:
|
||||
if hasattr(err, 'error_details') and \
|
||||
err.error_details == 'The given public key already exists.':
|
||||
print('WARNING: that key already exists.')
|
||||
result = {'name': oldPrivateKeyId}
|
||||
break
|
||||
elif hasattr(err, 'error_details'):
|
||||
controlflow.system_error_exit(
|
||||
4, err.error_details)
|
||||
if hasattr(err, 'error_details'):
|
||||
if err.error_details == 'The given public key already exists.':
|
||||
print('WARNING: that key already exists.')
|
||||
result = {'name': oldPrivateKeyId}
|
||||
break
|
||||
controlflow.system_error_exit(
|
||||
4, err.error_details)
|
||||
else:
|
||||
controlflow.system_error_exit(
|
||||
4, err)
|
||||
@@ -8169,7 +8240,7 @@ def printShowSharedDrives(users, csvFormat):
|
||||
todrive = False
|
||||
useDomainAdminAccess = False
|
||||
q = None
|
||||
get_orgunits = True
|
||||
get_orgunits = True
|
||||
i = 5
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
@@ -8191,7 +8262,7 @@ def printShowSharedDrives(users, csvFormat):
|
||||
tds = []
|
||||
titles = []
|
||||
if get_orgunits and useDomainAdminAccess:
|
||||
ou_map = gapi_directory_orgunits.orgid_to_org_map()
|
||||
ou_map = gapi_directory_orgunits.orgid_to_org_map()
|
||||
for user in users:
|
||||
sys.stderr.write(f'Getting Shared Drives for {user}\n')
|
||||
user, drive = buildDrive3GAPIObject(user)
|
||||
@@ -10294,8 +10365,13 @@ def getUsersToModify(entity_type=None,
|
||||
elif entity_type == 'cros':
|
||||
users = entity.replace(',', ' ').split()
|
||||
entity = 'cros'
|
||||
elif entity_type in ['crosquery', 'crosqueries', 'cros_sn']:
|
||||
if entity_type == 'cros_sn':
|
||||
elif entity_type in ['crosquery', 'crosqueries', 'cros_sn', 'cros_ou', 'cros_ou_and_children']:
|
||||
orgUnitPath = includeChildOrgunits = None
|
||||
if entity_type in {'cros_ou', 'cros_ou_and_children'}:
|
||||
orgUnitPath = entity
|
||||
includeChildOrgunits = entity_type == 'cros_ou_and_children'
|
||||
queries = [None]
|
||||
elif entity_type == 'cros_sn':
|
||||
queries = [f'id:{sn}' for sn in shlexSplitList(entity)]
|
||||
elif entity_type == 'crosqueries':
|
||||
queries = shlexSplitList(entity)
|
||||
@@ -10312,9 +10388,11 @@ def getUsersToModify(entity_type=None,
|
||||
'list',
|
||||
'chromeosdevices',
|
||||
page_message=page_message,
|
||||
query=query,
|
||||
customerId=GC_Values[GC_CUSTOMER_ID],
|
||||
fields='nextPageToken,chromeosdevices(deviceId)',
|
||||
query=query)
|
||||
orgUnitPath=orgUnitPath,
|
||||
includeChildOrgunits=includeChildOrgunits,
|
||||
fields='nextPageToken,chromeosdevices(deviceId)')
|
||||
for member in members:
|
||||
deviceId = member['deviceId']
|
||||
if deviceId not in usersSet:
|
||||
@@ -10516,7 +10594,6 @@ OAUTH2_SCOPES = [
|
||||
'name': 'Cloud Identity - User Invitations',
|
||||
'subscopes': ['readonly'],
|
||||
'scopes': 'https://www.googleapis.com/auth/cloud-identity.userinvitations',
|
||||
'offByDefault': True,
|
||||
},
|
||||
{
|
||||
'name': 'Contact Delegation',
|
||||
@@ -11373,6 +11450,9 @@ def ProcessGAMCommand(args):
|
||||
elif command == 'version':
|
||||
doGAMVersion()
|
||||
sys.exit(0)
|
||||
elif command in ['checkconnection', 'checkconn']:
|
||||
checkConnection()
|
||||
sys.exit(0)
|
||||
elif command == 'create':
|
||||
argument = sys.argv[2].lower()
|
||||
if argument == 'user':
|
||||
|
||||
@@ -24,7 +24,10 @@ from gam import controlflow
|
||||
from gam import display
|
||||
from gam import fileutils
|
||||
from gam import transport
|
||||
from gam.var import GM_Globals, GM_WINDOWS
|
||||
from gam.var import (GC_CA_FILE,
|
||||
GC_Values,
|
||||
GM_Globals,
|
||||
GM_WINDOWS)
|
||||
from gam import utils
|
||||
|
||||
|
||||
@@ -633,7 +636,10 @@ class _ShortURLFlow(google_auth_oauthlib.flow.InstalledAppFlow):
|
||||
parsed_params = parse_qs(parsed_url.query)
|
||||
code = parsed_params.get('code', [None])[0]
|
||||
try:
|
||||
self.fetch_token(code=code)
|
||||
fetch_args = {'code': code}
|
||||
if GC_Values.get(GC_CA_FILE):
|
||||
fetch_args['verify'] = GC_Values.get(GC_CA_FILE)
|
||||
self.fetch_token(**fetch_args)
|
||||
break
|
||||
except Exception as e:
|
||||
if not userInput:
|
||||
|
||||
@@ -25,7 +25,7 @@ def _reduce_name(name):
|
||||
|
||||
def is_invitable_user(email):
|
||||
'''return email isInvitableUser'''
|
||||
svc = gapi_cloudidentity.build_dwd('cloudidentity_beta')
|
||||
svc = gapi_cloudidentity.build('cloudidentity')
|
||||
customer = _get_customerid()
|
||||
encoded_email = quote_plus(email)
|
||||
name = f'{customer}/userinvitations/{encoded_email}'
|
||||
@@ -35,7 +35,7 @@ def is_invitable_user(email):
|
||||
|
||||
def _generic_action(action):
|
||||
'''generic function to call actionable APIs'''
|
||||
svc = gapi_cloudidentity.build_dwd('cloudidentity_beta')
|
||||
svc = gapi_cloudidentity.build('cloudidentity')
|
||||
customer = _get_customerid()
|
||||
email = sys.argv[3].lower()
|
||||
encoded_email = quote_plus(email)
|
||||
@@ -55,7 +55,7 @@ def _generic_action(action):
|
||||
|
||||
def _generic_get(get_type):
|
||||
'''generic function to call read data APIs'''
|
||||
svc = gapi_cloudidentity.build_dwd('cloudidentity_beta')
|
||||
svc = gapi_cloudidentity.build('cloudidentity')
|
||||
customer = _get_customerid()
|
||||
email = sys.argv[3].lower()
|
||||
encoded_email = quote_plus(email)
|
||||
@@ -75,7 +75,7 @@ def bulk_is_invitable(emails):
|
||||
if response.get('isInvitableUser'):
|
||||
rows.append({'invitableUsers': request_id})
|
||||
|
||||
svc = gapi_cloudidentity.build_dwd('cloudidentity_beta')
|
||||
svc = gapi_cloudidentity.build('cloudidentity')
|
||||
customer = _get_customerid()
|
||||
todrive = False
|
||||
#batch_size = 1000
|
||||
@@ -139,7 +139,7 @@ USERINVITATION_STATE_CHOICES_MAP = {
|
||||
|
||||
def print_():
|
||||
'''gam print userinvitations'''
|
||||
svc = gapi_cloudidentity.build_dwd('cloudidentity_beta')
|
||||
svc = gapi_cloudidentity.build('cloudidentity')
|
||||
customer = _get_customerid()
|
||||
todrive = False
|
||||
titles = ['name', 'state', 'updateTime']
|
||||
|
||||
@@ -151,12 +151,19 @@ def doUpdateCros():
|
||||
elif action == 'deprovisionupgradetransfer':
|
||||
action = 'deprovision'
|
||||
deprovisionReason = 'upgrade_transfer'
|
||||
elif action not in ['disable', 'reenable']:
|
||||
elif action in ['disable', 'reenable']:
|
||||
pass
|
||||
elif action == 'preprovisioneddisable':
|
||||
action = 'pre_provisioned_disable'
|
||||
elif action == 'preprovisionedreenable':
|
||||
action = 'pre_provisioned_reenable'
|
||||
else:
|
||||
controlflow.system_error_exit(2, f'expected action of ' \
|
||||
f'deprovision_same_model_replace, ' \
|
||||
f'deprovision_different_model_replace, ' \
|
||||
f'deprovision_retiring_device, ' \
|
||||
f'deprovision_upgrade_transfer, disable or reenable,'
|
||||
f'deprovision_upgrade_transfer, disable, reenable, '\
|
||||
f'pre_provisioned_disable, pre_provisioned_reenable'\
|
||||
f' got {action}')
|
||||
action_body = {'action': action}
|
||||
if deprovisionReason:
|
||||
@@ -448,7 +455,7 @@ def doPrintCrosActivity():
|
||||
selectActiveTimeRanges = selectDeviceFiles = selectRecentUsers = False
|
||||
listLimit = 0
|
||||
delimiter = ','
|
||||
orgUnitPath = None
|
||||
orgUnitPath = includeChildOrgunits = None
|
||||
queries = [None]
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
@@ -456,8 +463,9 @@ def doPrintCrosActivity():
|
||||
if myarg in ['query', 'queries']:
|
||||
queries = gam.getQueries(myarg, sys.argv[i + 1])
|
||||
i += 2
|
||||
elif myarg == 'limittoou':
|
||||
elif myarg in {'limittoou', 'crosou', 'crosouandchildren'}:
|
||||
orgUnitPath = gapi_directory_orgunits.getOrgUnitItem(sys.argv[i + 1])
|
||||
includeChildOrgunits = myarg == 'crosouandchildren'
|
||||
i += 2
|
||||
elif myarg == 'todrive':
|
||||
todrive = True
|
||||
@@ -524,8 +532,9 @@ def doPrintCrosActivity():
|
||||
query=query,
|
||||
customerId=GC_Values[GC_CUSTOMER_ID],
|
||||
projection='FULL',
|
||||
fields=fields,
|
||||
orgUnitPath=orgUnitPath)
|
||||
orgUnitPath=orgUnitPath,
|
||||
includeChildOrgunits=includeChildOrgunits,
|
||||
fields=fields)
|
||||
for cros in all_cros:
|
||||
row = {}
|
||||
skip_attribs = ['recentUsers', 'activeTimeRanges', 'deviceFiles']
|
||||
@@ -612,7 +621,7 @@ def doPrintCrosDevices():
|
||||
csvRows = []
|
||||
display.add_field_to_csv_file('deviceid', CROS_ARGUMENT_TO_PROPERTY_MAP,
|
||||
fieldsList, fieldsTitles, titles)
|
||||
projection = orderBy = sortOrder = orgUnitPath = None
|
||||
projection = orderBy = sortOrder = orgUnitPath = includeChildOrgunits = None
|
||||
queries = [None]
|
||||
noLists = sortHeaders = False
|
||||
selectedLists = {}
|
||||
@@ -624,8 +633,9 @@ def doPrintCrosDevices():
|
||||
if myarg in ['query', 'queries']:
|
||||
queries = gam.getQueries(myarg, sys.argv[i + 1])
|
||||
i += 2
|
||||
elif myarg == 'limittoou':
|
||||
elif myarg in {'limittoou', 'crosou', 'crosouandchildren'}:
|
||||
orgUnitPath = gapi_directory_orgunits.getOrgUnitItem(sys.argv[i + 1])
|
||||
includeChildOrgunits = myarg == 'crosouandchildren'
|
||||
i += 2
|
||||
elif myarg == 'todrive':
|
||||
todrive = True
|
||||
@@ -729,6 +739,7 @@ def doPrintCrosDevices():
|
||||
customerId=GC_Values[GC_CUSTOMER_ID],
|
||||
projection=projection,
|
||||
orgUnitPath=orgUnitPath,
|
||||
includeChildOrgunits=includeChildOrgunits,
|
||||
orderBy=orderBy,
|
||||
sortOrder=sortOrder,
|
||||
fields=fields)
|
||||
|
||||
@@ -95,12 +95,10 @@ def doGetCustomerInfo():
|
||||
continue
|
||||
except gapi.errors.GapiForbiddenError:
|
||||
return
|
||||
warnings = result.get('warnings', [])
|
||||
fullDataRequired = ['accounts']
|
||||
usage = result.get('usageReports')
|
||||
has_reports = bool(usage)
|
||||
fullData, tryDate = gapi_reports._check_full_data_available(
|
||||
warnings, tryDate, fullDataRequired, has_reports)
|
||||
result, tryDate, fullDataRequired, False)
|
||||
if fullData < 0:
|
||||
print('No user report available.')
|
||||
sys.exit(1)
|
||||
|
||||
@@ -58,22 +58,32 @@ def getRoleId(role):
|
||||
|
||||
|
||||
def getPrivileges(body, privs, action):
|
||||
all_privileges = gapi_directory_privileges.print_(return_only=True)
|
||||
def expandChildPrivileges(privilege):
|
||||
for childPrivilege in privilege.get('childPrivileges', []):
|
||||
childPrivileges[childPrivilege['privilegeName']] = childPrivilege['serviceId']
|
||||
expandChildPrivileges(childPrivilege)
|
||||
|
||||
allPrivileges = {}
|
||||
ouPrivileges = {}
|
||||
childPrivileges = {}
|
||||
for privilege in gapi_directory_privileges.print_(return_only=True):
|
||||
allPrivileges[privilege['privilegeName']] = privilege['serviceId']
|
||||
if privilege['isOuScopable']:
|
||||
ouPrivileges[privilege['privilegeName']] = privilege['serviceId']
|
||||
expandChildPrivileges(privilege)
|
||||
if privs == 'ALL':
|
||||
body['rolePrivileges'] = [
|
||||
{'privilegeName': p['privilegeName'], 'serviceId': p['serviceId']} for p in all_privileges
|
||||
]
|
||||
body['rolePrivileges'] = [{'privilegeName': priv, 'serviceId': v} for priv, v in allPrivileges.items()]
|
||||
elif privs == 'ALL_OU':
|
||||
body['rolePrivileges'] = [
|
||||
{'privilegeName': p['privilegeName'], 'serviceId': p['serviceId']} for p in all_privileges if p.get('isOuScopable')
|
||||
]
|
||||
body['rolePrivileges'] = [{'privilegeName': priv, 'serviceId': v} for priv, v in ouPrivileges.items()]
|
||||
else:
|
||||
body.setdefault('rolePrivileges', [])
|
||||
for priv in privs.split(','):
|
||||
for p in all_privileges:
|
||||
if priv == p['privilegeName']:
|
||||
body['rolePrivileges'].append({'privilegeName': p['privilegeName'], 'serviceId': p['serviceId']})
|
||||
break
|
||||
if priv in allPrivileges:
|
||||
body['rolePrivileges'].append({'privilegeName': priv, 'serviceId': allPrivileges[priv]})
|
||||
elif priv in ouPrivileges:
|
||||
body['rolePrivileges'].append({'privilegeName': priv, 'serviceId': ouPrivileges[priv]})
|
||||
elif priv in childPrivileges:
|
||||
body['rolePrivileges'].append({'privilegeName': priv, 'serviceId': childPrivileges[priv]})
|
||||
else:
|
||||
controlflow.invalid_argument_exit(priv,
|
||||
f'gam {action} adminrole privileges')
|
||||
|
||||
@@ -85,15 +85,13 @@ def showUsageParameters():
|
||||
customerId=customerId,
|
||||
fields='warnings,usageReports(parameters(name))',
|
||||
**kwargs)
|
||||
warnings = result.get('warnings', [])
|
||||
usage = result.get('usageReports')
|
||||
has_reports = bool(usage)
|
||||
fullData, tryDate = _check_full_data_available(
|
||||
warnings, tryDate, fullDataRequired, has_reports)
|
||||
result, tryDate, fullDataRequired, False)
|
||||
if fullData < 0:
|
||||
print('No usage parameters available.')
|
||||
sys.exit(1)
|
||||
if has_reports:
|
||||
if usage:
|
||||
for parameter in usage[0]['parameters']:
|
||||
name = parameter.get('name')
|
||||
if name:
|
||||
@@ -350,10 +348,8 @@ def showReport():
|
||||
orgUnitID=orgUnitId,
|
||||
fields='warnings,usageReports',
|
||||
maxResults=1)
|
||||
warnings = one_page.get('warnings', [])
|
||||
has_reports = bool(one_page.get('usageReports'))
|
||||
fullData, tryDate = _check_full_data_available(
|
||||
warnings, tryDate, fullDataRequired, has_reports)
|
||||
one_page, tryDate, fullDataRequired, True)
|
||||
if fullData < 0:
|
||||
print('No user report available.')
|
||||
sys.exit(1)
|
||||
@@ -382,7 +378,7 @@ def showReport():
|
||||
for user_report in usage:
|
||||
if 'entity' not in user_report:
|
||||
continue
|
||||
row = {'email': user_report['entity']['userEmail'], 'date': tryDate}
|
||||
row = {'email': user_report['entity'].get('userEmail', 'Unknown'), 'date': tryDate}
|
||||
for item in user_report.get('parameters', []):
|
||||
if 'name' not in item:
|
||||
continue
|
||||
@@ -407,10 +403,8 @@ def showReport():
|
||||
customerId=customerId,
|
||||
date=tryDate,
|
||||
fields='warnings,usageReports')
|
||||
warnings = first_page.get('warnings', [])
|
||||
has_reports = bool(first_page.get('usageReports'))
|
||||
fullData, tryDate = _check_full_data_available(
|
||||
warnings, tryDate, fullDataRequired, has_reports)
|
||||
first_page, tryDate, fullDataRequired, False)
|
||||
if fullData < 0:
|
||||
print('No customer report available.')
|
||||
sys.exit(1)
|
||||
@@ -563,15 +557,16 @@ def _adjust_date(errMsg):
|
||||
return str(match_date.group(1))
|
||||
|
||||
|
||||
def _check_full_data_available(warnings, tryDate, fullDataRequired,
|
||||
has_reports):
|
||||
def _check_full_data_available(result, tryDate, fullDataRequired,
|
||||
checkUserEmail):
|
||||
one_day = datetime.timedelta(days=1)
|
||||
tryDateTime = datetime.datetime.strptime(tryDate, YYYYMMDD_FORMAT)
|
||||
# move to day before if we don't have at least one usageReport
|
||||
if not has_reports:
|
||||
usage = result.get('usageReports')
|
||||
if not usage:
|
||||
tryDateTime -= one_day
|
||||
return (0, tryDateTime.strftime(YYYYMMDD_FORMAT))
|
||||
for warning in warnings:
|
||||
for warning in result.get('warnings', []):
|
||||
if warning['code'] == 'PARTIAL_DATA_AVAILABLE':
|
||||
for app in warning['data']:
|
||||
if app['key'] == 'application' and \
|
||||
@@ -586,4 +581,8 @@ def _check_full_data_available(warnings, tryDate, fullDataRequired,
|
||||
app['value'] != 'docs' and \
|
||||
(not fullDataRequired or app['value'] in fullDataRequired):
|
||||
return (-1, tryDate)
|
||||
if checkUserEmail:
|
||||
if 'entity' not in usage[0] or 'userEmail' not in usage[0]['entity']:
|
||||
tryDateTime -= one_day
|
||||
return (0, tryDateTime.strftime(YYYYMMDD_FORMAT))
|
||||
return (1, tryDate)
|
||||
|
||||
@@ -8,7 +8,7 @@ import platform
|
||||
import re
|
||||
|
||||
GAM_AUTHOR = 'Jay Lee <jay0lee@gmail.com>'
|
||||
GAM_VERSION = '6.22'
|
||||
GAM_VERSION = '6.24'
|
||||
GAM_LICENSE = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
|
||||
|
||||
GAM_URL = 'https://jaylee.us/gam'
|
||||
@@ -31,7 +31,8 @@ usergroup_types = [
|
||||
'ou_and_child', 'ou_and_children_ns', 'ou_and_child_ns',
|
||||
'ou_and_children_susp', 'ou_and_child_susp', 'query', 'queries', 'license',
|
||||
'licenses', 'licence', 'licences', 'file', 'csv', 'csvfile', 'all', 'cros',
|
||||
'cros_sn', 'crosquery', 'crosqueries', 'crosfile', 'croscsv', 'croscsvfile'
|
||||
'cros_sn', 'crosquery', 'crosqueries', 'crosfile', 'croscsv', 'croscsvfile',
|
||||
'cros_ou', 'cros_ou_and_children'
|
||||
]
|
||||
ERROR_PREFIX = 'ERROR: '
|
||||
WARNING_PREFIX = 'WARNING: '
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
name = GAM for Google Workspace
|
||||
version = 6.0.22
|
||||
version = 6.0.23
|
||||
description = Command line management for Google Workspaces
|
||||
long_description = file: readme.md
|
||||
long_description_content_type = text/markdown
|
||||
|
||||
Reference in New Issue
Block a user