Compare commits

..

57 Commits
v6.22 ... v6.25

Author SHA1 Message Date
Jay Lee
1f32536ff7 Update build.yml 2022-09-02 12:15:07 -04:00
Jay Lee
7979206f21 Update build.yml 2022-09-02 09:45:10 -04:00
Jay Lee
f7901790ad Update build.yml 2022-09-02 09:44:16 -04:00
Jay Lee
7fae16f962 Update build.yml 2022-09-01 14:56:23 -04:00
Jay Lee
1dd76012f8 Update __init__.py 2022-09-01 14:34:55 -04:00
Jay Lee
8fd3f4ee7d Update setup.cfg 2022-08-31 16:27:21 -04:00
Jay Lee
e30b8ed53e Update setup.cfg 2022-08-31 16:26:06 -04:00
Jay Lee
e0960d9113 Update setup.cfg 2022-08-31 16:16:55 -04:00
Jay Lee
35dda1cd34 Update setup.cfg 2022-08-31 15:56:59 -04:00
Jay Lee
ef2253fe58 Update setup.cfg 2022-08-31 15:52:57 -04:00
Jay Lee
ecea3aed7e [no ci] 6.25 2022-08-31 15:52:22 -04:00
Jay Lee
2e81cae271 add roots.pem to MSI 2022-08-31 15:44:06 -04:00
Jay Lee
080eede356 Update devices.py 2022-08-31 14:10:22 -04:00
Jay Lee
fe37c687e4 Update build.yml 2022-08-31 11:59:10 -04:00
Jay Lee
27efef1d9b Update build.yml 2022-08-31 08:41:52 -04:00
Jay Lee
52aa1ac0da Update build.yml 2022-08-31 08:39:18 -04:00
Jay Lee
b5c23fdb83 Update gam.spec 2022-08-31 08:30:53 -04:00
Jay Lee
0b16c9aef4 Default to Google's roots.pem CA file 2022-08-31 08:29:34 -04:00
Jay Lee
3be97acd9c Update build.yml 2022-08-31 08:27:11 -04:00
GitHub Action
8df8e6797f [ci skip] Updated roots.pem 2022-08-31 12:19:58 +00:00
Jay Lee
156ba44656 Update get-roots.yml 2022-08-31 08:19:42 -04:00
Jay Lee
1b3663d60c Update get-roots.yml 2022-08-31 08:15:38 -04:00
Jay Lee
8f0ea2f6a5 Rename get-roots.yaml to get-roots.yml 2022-08-31 08:13:59 -04:00
Jay Lee
5e34b12e5c Update get-roots.yaml 2022-08-31 08:13:11 -04:00
Jay Lee
d124575a91 Update get-roots.yaml 2022-08-31 08:11:35 -04:00
Jay Lee
f5364ab4d0 Download Google roots.pem 2022-08-31 08:10:19 -04:00
Jay Lee
b5580c5649 Update var.py 2022-08-29 17:07:31 -04:00
Jay Lee
e9200ea8fb Update var.py 2022-08-29 17:03:00 -04:00
Jay Lee
2e0c280ea6 Update oauth.py 2022-08-29 17:02:41 -04:00
Ross Scroggs
3948a414b5 Back to client access for user invitations (#1553) 2022-08-24 15:24:38 -04:00
Jay Lee
2c83068605 enable user invite scope by default 2022-08-24 17:53:57 +00:00
Jay Lee
6f6ccad00b further refine gam checkconn 2022-08-22 15:16:40 +00:00
Ross Scroggs
bd18f14137 checkconnection cleanup (#1552) 2022-08-22 09:19:39 -04:00
Jay Lee
d54ca7ee43 Update build.yml 2022-08-20 09:31:46 -04:00
Jay Lee
19452c2461 Fix info customer 2022-08-20 13:10:23 +00:00
Jay Lee
4e2e96a6dd Update build.yml 2022-08-20 08:54:15 -04:00
Jay Lee
7957d131c0 Merge branch 'main' of https://github.com/GAM-team/GAM 2022-08-19 20:37:32 +00:00
Jay Lee
ca9dfaff1d gam checkconn first shot 2022-08-19 20:33:19 +00:00
Ross Scroggs
7e9475791b Handle Reports API bug (#1550)
* Handle Reports API bug

* Handle Reports API bug
2022-08-18 19:07:17 -04:00
Jay Lee
c8fb44a7c4 Update build.yml 2022-08-10 19:42:57 -04:00
Jay Lee
bb70183bc7 Update build.yml 2022-08-10 09:11:14 -04:00
Jay Lee
ff80ba1814 Update build.yml 2022-08-09 10:52:51 -04:00
Jay Lee
5d292dcaf7 Update var.py 2022-08-04 14:34:49 -04:00
Jay Lee
bcc5c4520f Update build.yml 2022-08-04 13:44:48 -04:00
Jay Lee
aa7ea59b5e Update oauth.py 2022-08-04 09:33:13 -04:00
Jay Lee
16e85d6d5c pass CA file to fetch_token so it's used by requests 2022-08-03 11:26:36 -04:00
Ross Scroggs
453e65ec53 Clean up calendar ACL documentation (#1545) 2022-08-02 10:05:03 -04:00
Jay Lee
4cbcb9418c Update build.yml 2022-08-02 07:11:48 -04:00
Jay Lee
f2120229e2 Update userinvitations.py 2022-07-20 18:52:32 -04:00
Ross Scroggs
4d2db30000 Update course field names (#1542) 2022-07-18 17:17:11 -04:00
Ross Scroggs
ca575b267b Bound sleep time in create project (#1541) 2022-07-15 13:35:53 -04:00
Jay Lee
3216666a94 retry project creation status check 10 times instead of 5 2022-07-15 11:08:36 -04:00
Ross Scroggs
4ef5606f05 Add cros_ou and cros_ou_and_children to <CrOSTypeEntity> (#1538)
* Add cros_ou and cros_ou_and_children to <CrOSTypeEntity>

Most useful here:
`gam update org|ou <OrgUnitPath> add|move <CrOSTypeEntity>`

* Update GamCommands.txt

* Code cleanup/appease pylint
2022-07-14 13:48:02 -04:00
Ross Scroggs
6122dc3353 Allow print of cros OU and children (#1537) 2022-07-14 09:53:43 -04:00
Jay Lee
14ae792091 Update build.yml 2022-07-13 14:17:14 -04:00
Ross Scroggs
9da5065700 Two updates (#1536)
New CRoS actions

Allow child privileges in create|update adminrole
2022-07-12 14:07:04 -04:00
Jay Lee
22e155998d Update gam-install.sh 2022-06-30 14:56:51 -04:00
17 changed files with 1410 additions and 96 deletions

View File

@@ -71,22 +71,22 @@ jobs:
arch: x86_64
steps:
- uses: actions/checkout@master
- uses: actions/checkout@v3
with:
persist-credentials: false
fetch-depth: 0
- name: Cache multiple paths
uses: actions/cache@v2
uses: actions/cache@v3
id: cache-python-ssl
with:
path: |
bin
key: gam-${{ matrix.jid }}-20220621
key: gam-${{ matrix.jid }}-20220901
- name: Use pre-compiled Python for testing
if: matrix.python != ''
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python }}
@@ -445,6 +445,16 @@ jobs:
echo -e "GAM: ${gam}\nGAMPATH: ${gampath}"
"${PYTHON}" -m PyInstaller --clean --distpath="${gampath}" gam.spec
- name: Copy extra package files
if: matrix.goal == 'build'
run: |
cp -v roots.pem $gampath
cp -v LICENSE $gampath
cp -v GamCommands.txt $gampath
if [[ "${RUNNER_OS}" == "Windows" ]]; then
cp -v gam-setup.bat $gampath
fi
- name: Basic Tests all jobs
run: |
$PYTHON -m unittest discover --start-directory ./ --pattern "*_test.py" --buffer
@@ -456,8 +466,6 @@ jobs:
- name: Linux/MacOS package
if: runner.os != 'Windows' && matrix.goal == 'build'
run: |
cp -v LICENSE $gampath
cp -v GamCommands.txt $gampath
if [[ "${RUNNER_OS}" == "macOS" ]]; then
GAM_ARCHIVE="gam-${GAMVERSION}-macos-universal2.tar.xz"
elif [[ "${RUNNER_OS}" == "Linux" ]]; then
@@ -501,9 +509,6 @@ jobs:
- name: Windows package
if: runner.os == 'Windows' && matrix.goal != 'test'
run: |
cp -v LICENSE $gampath
cp -v GamCommands.txt $gampath
cp -v gam-setup.bat $gampath
cd dist/
GAM_ARCHIVE="../gam-${GAMVERSION}-windows-${GAM_ARCHIVE_ARCH}.zip"
/c/Program\ Files/7-Zip/7z.exe a -tzip $GAM_ARCHIVE gam "-xr@${GITHUB_WORKSPACE}/.github/actions/package_exclusions.txt" -bb3
@@ -545,6 +550,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 +581,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 +667,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}"
@@ -694,7 +700,7 @@ jobs:
# done
- name: Archive production artifacts
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
if: github.event_name == 'push' && matrix.goal != 'test'
with:
name: gam-binaries
@@ -702,3 +708,32 @@ jobs:
src/*.tar.xz
src/*.zip
src/*.msi
publish:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v3
with:
persist-credentials: false
fetch-depth: 0
- name: Download artifacts
uses: actions/download-artifact@v3
- name: Set datetime version string
id: dateversion
run: |
echo "::set-output name=dateversion::$(date +'%Y%m%d.%k%M%S')"
- uses: "marvinpinto/action-automatic-releases@latest"
name: Publish draft release
with:
repo_token: "${{ secrets.GITHUB_TOKEN }}"
automatic_release_tag: ${{ steps.dateversion.outputs.dateversion }}
prerelease: false
draft: true
title: "GAM ${{ steps.dateversion.outputs.dateversion }}"
files: |
gam-binaries/*

36
.github/workflows/get-roots.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
name: Check for Google Root CA Updates
on:
push:
pull_request:
schedule:
- cron: '23 23 * * *'
defaults:
run:
shell: bash
working-directory: src
jobs:
check-apis:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
with:
persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal token
fetch-depth: 0 # otherwise, you will failed to push refs to dest repo
- name: Check for updates
run: curl -o ./roots.pem -vvvv https://pki.goog/roots.pem
- name: Commit file
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add roots.pem
git diff --quiet && git diff --staged --quiet || git commit -am '[ci skip] Updated roots.pem'
- name: Push changes
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -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.

View File

@@ -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
;;

View File

@@ -5,9 +5,7 @@ import sys
import importlib
from PyInstaller.utils.hooks import copy_metadata
# dynamically determine where httplib2/cacerts.txt lives
proot = os.path.dirname(importlib.import_module('httplib2').__file__)
extra_files = [(os.path.join(proot, 'cacerts.txt'), 'httplib2')]
extra_files = []
extra_files += copy_metadata('google-api-python-client')
extra_files += [('cbcm-v1.1beta1.json', '.')]

View File

@@ -55,6 +55,9 @@
<Component Id="gamcommands_txt" Guid="58ff9c45-a7c9-4e22-8845-a9a92610c1f3">
<File Name="gamcommands.txt" KeyPath="yes" />
</Component>
<Component Id="roots_pem" Guid="18ff9c45-a3c9-4e22-8445-a8a92610c1f3">
<File Name="roots.pem" KeyPath="yes" />
</Component>
</ComponentGroup>
</Fragment>

View File

@@ -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',
@@ -7213,7 +7285,7 @@ def _createClientSecretsOauth2service(httpObj, projectId, login_hint):
'code':
'ThisIsAnInvalidCodeOnlyBeingUsedToTestIfClientAndSecretAreValid',
'redirect_uri':
'urn:ietf:wg:oauth:2.0:oob',
'http://127.0.0.1:8080/',
'grant_type':
'authorization_code'
}
@@ -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':

View File

@@ -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:

View File

@@ -80,7 +80,7 @@ def _parse_action(action):
def info():
ci = gapi_cloudidentity.build_dwd()
customer = _get_device_customerid()
name = _get_device_name()
_, name = _get_deviceuser_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)

View File

@@ -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']

View File

@@ -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)

View File

@@ -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)

View File

@@ -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')

View File

@@ -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)

View File

@@ -8,7 +8,7 @@ import platform
import re
GAM_AUTHOR = 'Jay Lee <jay0lee@gmail.com>'
GAM_VERSION = '6.22'
GAM_VERSION = '6.25'
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: '
@@ -1201,6 +1202,7 @@ _DEFAULT_CHARSET = UTF8
_FN_CLIENT_SECRETS_JSON = 'client_secrets.json'
_FN_OAUTH2SERVICE_JSON = 'oauth2service.json'
_FN_OAUTH2_TXT = 'oauth2.txt'
_FN_ROOTS_PEM = 'roots.pem'
#
GM_Globals = {
GM_SYSEXITRC: 0,
@@ -1342,7 +1344,7 @@ GC_Defaults = {
GC_CSV_ROW_DROP_FILTER: '',
GC_TLS_MIN_VERSION: TLS_MIN,
GC_TLS_MAX_VERSION: None,
GC_CA_FILE: None,
GC_CA_FILE: _FN_ROOTS_PEM,
}
GC_Values = {}

1130
src/roots.pem Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[metadata]
name = GAM for Google Workspace
version = 6.0.22
version = attr: gam.var.GAM_VERSION
description = Command line management for Google Workspaces
long_description = file: readme.md
long_description_content_type = text/markdown
@@ -37,6 +37,9 @@ install_requires =
yubikey-manager >= 4.0.0
pathvalidate
[options.package_data]
* = *.pem
# used during pip install .[test]
[options.extras_require]
test = pre-commit