Added enforce_expansive_access Boolean variable to gam.cfg
Some checks failed
Build and test GAM / build (build, 1, Build Intel Ubuntu Jammy, ubuntu-22.04) (push) Has been cancelled
Build and test GAM / build (build, 10, Build Intel Windows, windows-2022) (push) Has been cancelled
Build and test GAM / build (build, 11, Build Arm Windows, windows-11-arm) (push) Has been cancelled
Build and test GAM / build (build, 2, Build Intel Ubuntu Noble, ubuntu-24.04) (push) Has been cancelled
Build and test GAM / build (build, 3, Build Arm Ubuntu Noble, ubuntu-24.04-arm) (push) Has been cancelled
Build and test GAM / build (build, 4, Build Arm Ubuntu Jammy, ubuntu-22.04-arm) (push) Has been cancelled
Build and test GAM / build (build, 5, Build Intel StaticX Legacy, ubuntu-22.04, yes) (push) Has been cancelled
Build and test GAM / build (build, 6, Build Arm StaticX Legacy, ubuntu-22.04-arm, yes) (push) Has been cancelled
Build and test GAM / build (build, 7, Build Intel MacOS, macos-13) (push) Has been cancelled
Build and test GAM / build (build, 8, Build Arm MacOS 14, macos-14) (push) Has been cancelled
Build and test GAM / build (build, 9, Build Arm MacOS 15, macos-15) (push) Has been cancelled
Build and test GAM / build (test, 12, Test Python 3.10, ubuntu-24.04, 3.10) (push) Has been cancelled
Build and test GAM / build (test, 13, Test Python 3.11, ubuntu-24.04, 3.11) (push) Has been cancelled
Build and test GAM / build (test, 14, Test Python 3.12, ubuntu-24.04, 3.12) (push) Has been cancelled
Build and test GAM / build (test, 15, Test Python 3.14-dev, ubuntu-24.04, 3.14-dev) (push) Has been cancelled
Build and test GAM / merge (push) Has been cancelled
Build and test GAM / publish (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Check for Google Root CA Updates / check-apis (push) Has been cancelled
Push wiki / pushwiki (push) Has been cancelled

This commit is contained in:
Ross Scroggs
2025-06-04 17:34:57 -07:00
parent dde1354bd0
commit ee874858b4
6 changed files with 65 additions and 44 deletions

View File

@@ -1,4 +1,4 @@
7.08.03
7.09.00
Removed the overly broad service account `IAM and Access Management API` scope `https://www.googleapis.com/auth/cloud-platform`
from DWD. The `gam <UserTypeEntity> check|Update serviceaccount` commands issue an error message if this scope
@@ -6,6 +6,19 @@ is enabled promptig you to update your service account authorization so that the
GAM commands that need IAM access now use the more limited scope `https://www.googleapis.com/auth/iam` in a non-DWD manner.
Added `enforce_expansive_access` Boolean variable to `gam.cfg` that provides the default value
for option `enforceexpansiveaccess` in all commands that delete or update drive file ACLs/permissions.
```
gam <UserTypeEntity> delete permissions
gam <UserTypeEntity> delete drivefileacl
gam <UserTypeEntity> update drivefileacl
gam <UserTypeEntity> copy drivefile
gam <UserTypeEntity> move drivefile
gam <UserTypeEntity> transfer ownership
gam <UserTypeEntity> claim ownership
gam <UserTypeEntity> transfer drive
```
Updated to Python 3.13.4
7.08.02

View File

@@ -11,7 +11,7 @@ if __name__ == '__main__':
# One time initialization
if platform.system() != 'Linux':
multiprocessing.freeze_support()
multiprocessing.set_start_method('spawn')
multiprocessing.set_start_method('spawn', force=True)
initializeLogging()
#
CallGAMCommand(['gam', 'version'])

View File

@@ -11,5 +11,5 @@ from gam.__main__ import main
if __name__ == '__main__':
if platform.system() != 'Linux':
multiprocessing.freeze_support()
multiprocessing.set_start_method('spawn')
multiprocessing.set_start_method('spawn', force=True)
main()

View File

@@ -25,7 +25,7 @@ https://github.com/GAM-team/GAM/wiki
"""
__author__ = 'GAM Team <google-apps-manager@googlegroups.com>'
__version__ = '7.08.03'
__version__ = '7.09.00'
__license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
#pylint: disable=wrong-import-position
@@ -10632,7 +10632,6 @@ def getOAuthClientIDAndSecret():
invalidClientSecretsJsonExit(str(e))
def getScopesFromUser(scopesList, clientAccess, currentScopes=None):
forceOffScopes = []
OAUTH2_CMDS = ['s', 'u', 'e', 'c']
oauth2_menu = ''
numScopes = len(scopesList)
@@ -10683,9 +10682,6 @@ Continue to authorization by entering a 'c'
break
i += 1
else:
for api in currentScopes:
if api in API.FORCE_OFF_SA_SCOPES:
forceOffScopes.extend(currentScopes[api])
i = 0
for a_scope in scopesList:
selectedScopes[i] = ' '
@@ -10754,12 +10750,12 @@ Continue to authorization by entering a 'c'
for i in range(numScopes):
selectedScopes[i] = ' '
elif selection == 'e':
return (None, None)
return None
break
sys.stdout.write(f'{ERROR_PREFIX}Invalid input "{choice}"\n')
if selection == 'c':
break
return (selectedScopes, forceOffScopes)
return selectedScopes
def _localhost_to_ip():
'''returns IPv4 or IPv6 loopback address which localhost resolves to.
@@ -11106,7 +11102,7 @@ def doOAuthRequest(currentScopes, login_hint, verifyScopes=False):
client_id, client_secret = getOAuthClientIDAndSecret()
scopesList = API.getClientScopesList(GC.Values[GC.TODRIVE_CLIENTACCESS])
if not currentScopes or verifyScopes:
selectedScopes, _ = getScopesFromUser(scopesList, True, currentScopes)
selectedScopes = getScopesFromUser(scopesList, True, currentScopes)
if selectedScopes is None:
return False
scopes = set(API.REQUIRED_SCOPES)
@@ -12237,7 +12233,7 @@ def checkServiceAccount(users):
def authorizeScopes(message):
long_url = ('https://admin.google.com/ac/owl/domainwidedelegation'
f'?clientScopeToAdd={",".join(checkScopes)}'
f'?clientScopeToAdd={",".join(sorted(checkScopesSet-API.FORCE_OFF_SA_SCOPES))}'
f'&clientIdToAdd={service_account}&overwriteClientId=true')
if GC.Values[GC.DOMAIN]:
long_url += f'&dn={GC.Values[GC.DOMAIN]}'
@@ -12249,11 +12245,12 @@ def checkServiceAccount(users):
allScopes = API.getSvcAcctScopes(GC.Values[GC.USER_SERVICE_ACCOUNT_ACCESS_ONLY], Act.Get() == Act.UPDATE)
checkScopesSet = set()
saScopes = {}
addForceOffScopes = True
useColor = False
forceOffScopes = []
while Cmd.ArgumentsRemaining():
myarg = getArgument()
if myarg in {'scope', 'scopes'}:
addForceOffScopes = False
for scope in getString(Cmd.OB_API_SCOPE_URL_LIST).lower().replace(',', ' ').split():
api = API.getSvcAcctScopeAPI(scope)
if api is not None:
@@ -12270,22 +12267,22 @@ def checkServiceAccount(users):
testPass = createGreenText('PASS')
testFail = createRedText('FAIL')
testWarn = createYellowText('WARN')
testDisable = createRedText('DISABLE')
testSkip = createGreenText('SKIP')
else:
testPass = 'PASS'
testFail = 'FAIL'
testWarn = 'WARN'
testDisable = 'DISABLE'
testSkip = 'SKIP'
if Act.Get() == Act.CHECK:
if not checkScopesSet:
currentScopes = GM.Globals[GM.SVCACCT_SCOPES]
for api in currentScopes:
if api in API.FORCE_OFF_SA_SCOPES:
forceOffScopes.extend(currentScopes[api])
else:
checkScopesSet.update(currentScopes[api])
for scope in iter(GM.Globals[GM.SVCACCT_SCOPES].values()):
checkScopesSet.update(scope)
else:
if not checkScopesSet:
scopesList = API.getSvcAcctScopesList(GC.Values[GC.USER_SERVICE_ACCOUNT_ACCESS_ONLY], True)
selectedScopes, forceOffScopes = getScopesFromUser(scopesList, False, GM.Globals[GM.SVCACCT_SCOPES] if GM.Globals[GM.SVCACCT_SCOPES_DEFINED] else None)
selectedScopes = getScopesFromUser(scopesList, False, GM.Globals[GM.SVCACCT_SCOPES] if GM.Globals[GM.SVCACCT_SCOPES_DEFINED] else None)
if selectedScopes is None:
return False
i = 0
@@ -12312,6 +12309,8 @@ def checkServiceAccount(users):
json.dumps(GM.Globals[GM.OAUTH2SERVICE_JSON_DATA], ensure_ascii=False, sort_keys=True, indent=2),
continueOnError=False)
checkScopes = sorted(checkScopesSet)
if addForceOffScopes:
checkScopes.extend(sorted(API.FORCE_OFF_SA_SCOPES))
jcount = len(checkScopes)
printMessage(Msg.SYSTEM_TIME_STATUS)
offsetSeconds, offsetFormatted = getLocalGoogleTimeOffset()
@@ -12400,24 +12399,23 @@ def checkServiceAccount(users):
if credentials.token:
token_info = callGAPI(oa2, 'tokeninfo', access_token=credentials.token)
if scope in token_info.get('scope', '').split(' ') and user == token_info.get('email', user).lower():
if scope not in API.FORCE_OFF_SA_SCOPES:
scopeStatus = testPass
else:
scopeStatus = testDisable
allScopesPass = False
else:
if scope not in API.FORCE_OFF_SA_SCOPES:
scopeStatus = testFail
allScopesPass = False
else:
scopeStatus = testSkip
else:
if scope not in API.FORCE_OFF_SA_SCOPES:
scopeStatus = testFail
allScopesPass = False
printPassFail(scope, f'{scopeStatus}{currentCount(j, jcount)}')
if forceOffScopes:
allScopesPass = False
if useColor:
scopeStatus = createRedText('DISABLE')
else:
scopeStatus = 'DISABLE'
jcount = len(forceOffScopes)
j = 0
for scope in forceOffScopes:
j +=1
scopeStatus = testSkip
printPassFail(scope, f'{scopeStatus}{currentCount(j, jcount)}')
Ind.Decrement()
service_account = GM.Globals[GM.OAUTH2SERVICE_JSON_DATA]['client_id']
@@ -58737,7 +58735,7 @@ def initCopyMoveOptions(copyCmd):
'showPermissionMessages': False,
'sendEmailIfRequired': False,
'useDomainAdminAccess': False,
'enforceExpansiveAccess': False,
'enforceExpansiveAccess': GC.Values[GC.ENFORCE_EXPANSIVE_ACCESS],
'copiedShortcutsPointToCopiedFiles': True,
'createShortcutsForNonmovableFiles': False,
'duplicateFiles': DUPLICATE_FILE_OVERWRITE_OLDER,
@@ -62117,7 +62115,8 @@ def transferDrive(users):
targetUserFolderPattern = '#user# old files'
targetUserOrphansFolderPattern = '#user# orphaned files'
targetIds = [None, None]
createShortcutsForNonmovableFiles = enforceExpansiveAccess = False
createShortcutsForNonmovableFiles = False
enforceExpansiveAccess = GC.Values[GC.ENFORCE_EXPANSIVE_ACCESS]
mergeWithTarget = False
thirdPartyOwners = {}
skipFileIdEntity = initDriveFileEntity()
@@ -62423,7 +62422,8 @@ def transferOwnership(users):
body = {}
newOwner = getEmailAddress()
OBY = OrderBy(DRIVEFILE_ORDERBY_CHOICE_MAP)
changeParents = enforceExpansiveAccess = filepath = includeTrashed = noRecursion = False
changeParents = filepath = includeTrashed = noRecursion = False
enforceExpansiveAccess = GC.Values[GC.ENFORCE_EXPANSIVE_ACCESS]
pathDelimiter = '/'
csvPF = fileTree = None
addParents = ''
@@ -62749,7 +62749,8 @@ def claimOwnership(users):
onlyOwners = set()
skipOwners = set()
subdomains = []
enforceExpansiveAccess = filepath = includeTrashed = False
filepath = includeTrashed = False
enforceExpansiveAccess = GC.Values[GC.ENFORCE_EXPANSIVE_ACCESS]
pathDelimiter = '/'
addParents = ''
parentBody = {}
@@ -63524,7 +63525,7 @@ def doCreateDriveFileACL():
def updateDriveFileACLs(users, useDomainAdminAccess=False):
fileIdEntity = getDriveFileEntity()
isEmail, permissionId = getPermissionId()
enforceExpansiveAccess = None
enforceExpansiveAccess = GC.Values[GC.ENFORCE_EXPANSIVE_ACCESS]
removeExpiration = showTitles = updateSheetProtectedRanges = False
showDetails = True
csvPF = None
@@ -63853,7 +63854,7 @@ def doCreatePermissions():
def deleteDriveFileACLs(users, useDomainAdminAccess=False):
fileIdEntity = getDriveFileEntity()
isEmail, permissionId = getPermissionId()
enforceExpansiveAccess = None
enforceExpansiveAccess = GC.Values[GC.ENFORCE_EXPANSIVE_ACCESS]
showTitles = updateSheetProtectedRanges = False
while Cmd.ArgumentsRemaining():
myarg = getArgument()
@@ -63982,7 +63983,7 @@ def deletePermissions(users, useDomainAdminAccess=False):
jsonData = getJSON([])
PM = PermissionMatch()
PM.SetDefaultMatch(False, {'role': 'owner'})
enforceExpansiveAccess = False
enforceExpansiveAccess = GC.Values[GC.ENFORCE_EXPANSIVE_ACCESS]
while Cmd.ArgumentsRemaining():
myarg = getArgument()
if myarg in ADMIN_ACCESS_OPTIONS:

View File

@@ -132,7 +132,10 @@ APIS_NEEDING_ACCESS_TOKEN = {
CBCM: ['https://www.googleapis.com/auth/admin.directory.device.chromebrowsers']
}
#
FORCE_OFF_SA_SCOPES = {IAM}
FORCE_OFF_SA_SCOPES = {
'https://www.googleapis.com/auth/cloud-identity',
'https://www.googleapis.com/auth/cloud-platform',
}
#
REFRESH_PERM_ERRORS = [
'invalid_grant: reauth related error (rapt_required)', # no way to reauth today
@@ -652,7 +655,7 @@ _SVCACCT_SCOPES = [
# 'api': IAM,
# 'offByDefault': True,
# 'subscopes': [],
# 'scope': IAM_SCOPE},
# 'scope': CLOUD_PLATFORM_SCOPE},
{'name': 'Keep API',
'api': KEEP,
'subscopes': READONLY,

View File

@@ -163,6 +163,8 @@ EMAIL_BATCH_SIZE = 'email_batch_size'
ENABLE_DASA = 'enable_dasa'
# Enable Cloud Session Reauthentication by borrowing a RAPT token from gcloud command
ENABLE_GCLOUD_REAUTH = 'enable_gcloud_reauth'
# Value for enforceExpansiveAccess for commands that delete or update drive file ACLs/permissions.
ENFORCE_EXPANSIVE_ACCESS = 'enforce_expansive_access'
# When retrieving lists of calendar events from API, how many should be retrieved in each chunk
EVENT_MAX_RESULTS = 'event_max_results'
# Path to extra_args.txt
@@ -377,6 +379,7 @@ Defaults = {
DEVICE_MAX_RESULTS: '200',
DOMAIN: '',
DRIVE_DIR: '',
ENFORCE_EXPANSIVE_ACCESS: FALSE,
DRIVE_MAX_RESULTS: '1000',
DRIVE_V3_BETA: FALSE,
DRIVE_V3_NATIVE_NAMES: TRUE,
@@ -545,6 +548,7 @@ VAR_INFO = {
DEVICE_MAX_RESULTS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 200)},
DOMAIN: {VAR_TYPE: TYPE_STRING, VAR_ENVVAR: 'GA_DOMAIN', VAR_LIMITS: (0, None)},
DRIVE_DIR: {VAR_TYPE: TYPE_DIRECTORY, VAR_ENVVAR: 'GAMDRIVEDIR'},
ENFORCE_EXPANSIVE_ACCESS: {VAR_TYPE: TYPE_BOOLEAN},
DRIVE_MAX_RESULTS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 1000)},
DRIVE_V3_BETA: {VAR_TYPE: TYPE_BOOLEAN},
DRIVE_V3_NATIVE_NAMES: {VAR_TYPE: TYPE_BOOLEAN},