mirror of
https://github.com/GAM-team/GAM.git
synced 2026-06-08 00:01:38 +00:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93534b8626 | ||
|
|
8ffa4e3822 | ||
|
|
97db67790d | ||
|
|
d5e6ab46d3 | ||
|
|
a11e07ffa4 | ||
|
|
9bd564bbca | ||
|
|
6f35df439c | ||
|
|
baacc4c77b | ||
|
|
10ef587ce1 | ||
|
|
251ba6a8b3 | ||
|
|
4c97aa8ccf | ||
|
|
d7f5a59115 | ||
|
|
79e6835c30 | ||
|
|
ec2bd02391 | ||
|
|
8ba0b55346 | ||
|
|
f1dfcc877d | ||
|
|
f0fa7aa25e | ||
|
|
2445b4f92d | ||
|
|
eb2b9f0e4f | ||
|
|
91c0d66a14 | ||
|
|
0dd05e7b93 | ||
|
|
da93cdbc06 | ||
|
|
cf750e0d58 |
23
.github/workflows/build.yml
vendored
23
.github/workflows/build.yml
vendored
@@ -23,7 +23,7 @@ defaults:
|
||||
|
||||
env:
|
||||
SCRATCH_COUNTER: 14
|
||||
OPENSSL_CONFIG_OPTS: no-fips --api=3.0.0 no-docs no-ssl3 no-tls1 no-tls1_1 no-dtls no-comp no-srp no-psk no-engine no-dynamic-engine no-nextprotoneg no-weak-ssl-ciphers no-idea no-seed no-camellia no-sm2 no-sm3 no-sm4 no-rc2 no-rc4 no-rc5 no-md2 no-md4 no-cast no-des no-shared no-tests -O3
|
||||
OPENSSL_CONFIG_OPTS: no-fips --api=3.0.0 no-docs no-tls1 no-tls1_1 no-dtls no-comp no-srp no-psk no-nextprotoneg no-weak-ssl-ciphers no-idea no-seed no-camellia no-sm2 no-sm3 no-sm4 no-rc2 no-rc4 no-rc5 no-md2 no-md4 no-cast no-des no-shared -fPIC no-tests -O3
|
||||
OPENSSL_INSTALL_PATH: ${{ github.workspace }}/bin/ssl
|
||||
OPENSSL_SOURCE_PATH: ${{ github.workspace }}/src/openssl
|
||||
PYTHON_INSTALL_PATH: ${{ github.workspace }}/bin/python
|
||||
@@ -165,7 +165,7 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
cache.tar.xz
|
||||
key: gam-${{ matrix.jid }}-20260408-01
|
||||
key: gam-${{ matrix.jid }}-20260416
|
||||
|
||||
- name: Untar Cache archive
|
||||
if: matrix.goal == 'build' && steps.cache-python-ssl.outputs.cache-hit == 'true'
|
||||
@@ -343,7 +343,14 @@ jobs:
|
||||
run: |
|
||||
cd "${OPENSSL_SOURCE_PATH}"
|
||||
# --libdir=lib is needed so Python can find OpenSSL libraries
|
||||
"${PERL}" ./Configure --libdir=lib --prefix="${OPENSSL_INSTALL_PATH}" $OPENSSL_CONFIG_OPTS
|
||||
# Python doesn't like OpenSSL 4.0 with ASM on Linux arm64
|
||||
# disable for now
|
||||
if ([ "$RUNNER_OS" == "Linux" ] && [ "$RUNNER_ARCH" == "ARM64" ]); then
|
||||
"${PERL}" ./Configure --libdir=lib --prefix="${OPENSSL_INSTALL_PATH}" no-asm $OPENSSL_CONFIG_OPTS
|
||||
else
|
||||
"${PERL}" ./Configure --libdir=lib --prefix="${OPENSSL_INSTALL_PATH}" $OPENSSL_CONFIG_OPTS
|
||||
fi
|
||||
|
||||
|
||||
- name: Rename GNU link on Windows
|
||||
if: matrix.goal == 'build' && runner.os == 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
|
||||
@@ -393,13 +400,11 @@ jobs:
|
||||
cd "${GITHUB_WORKSPACE}/src"
|
||||
git clone https://github.com/python/cpython.git
|
||||
cd "${PYTHON_SOURCE_PATH}"
|
||||
if [[ "${RUNNER_OS}" == "Linux" ]]; then
|
||||
# TEMP lock Linux to 3.14.3 until the PyInstaller multiprocess issues are resolved.
|
||||
export LATEST_STABLE_TAG="v3.14.3"
|
||||
else
|
||||
export LATEST_STABLE_TAG=$(git tag --list | grep -v a | grep -v rc | grep -v b | sort -Vr | head -n1)
|
||||
fi
|
||||
export LATEST_STABLE_TAG=$(git tag --list | grep -v a | grep -v rc | grep -v b | sort -Vr | head -n1)
|
||||
git checkout "${LATEST_STABLE_TAG}"
|
||||
# Temp Python 3.14 patch to work with OpenSSL 4
|
||||
# Python 3.15 in Oct 2026 should support 4 OOB
|
||||
patch -p1 < "${GITHUB_WORKSPACE}/src/tools/py314-ossl4.diff"
|
||||
export COMPILED_PYTHON_VERSION=${LATEST_STABLE_TAG:1} # Trim the "v" prefix
|
||||
echo "COMPILED_PYTHON_VERSION=${COMPILED_PYTHON_VERSION}" >> $GITHUB_ENV
|
||||
|
||||
|
||||
@@ -3826,6 +3826,7 @@ gam audit monitor list <EmailAddress>
|
||||
(sendmessagedenynotification <Boolean>)|
|
||||
(spammoderationlevel allow|moderate|silently_moderate|reject)|
|
||||
(whocanadd all_members_can_add|all_managers_can_add|all_owners_can_add|none_can_add)|
|
||||
(whocanaddexternalmembers only_owners_can_add_external_members|end_users_can_add_external_members)|
|
||||
(whocancontactowner anyone_can_contact|all_in_domain_can_contact|all_members_can_contact|all_managers_can_contact|all_owners_can_contact)|
|
||||
(whocanjoin anyone_can_join|all_in_domain_can_join|invited_can_join|can_request_to_join)|
|
||||
(whocanleavegroup all_members_can_leave|all_managers_can_leave|all_owners_can_leave|none_can_leave)|
|
||||
@@ -3955,6 +3956,7 @@ gam delete group|groups <GroupEntity> [noactionifalias]
|
||||
spammoderationlevel|
|
||||
whocanaddreferences|
|
||||
whocanadd|
|
||||
whocanaddexternalmembers|
|
||||
whocanapprovemessages|
|
||||
whocanassigntopics|
|
||||
whocanassistcontent|
|
||||
|
||||
@@ -1,3 +1,29 @@
|
||||
7.41.00
|
||||
|
||||
Upgraded to Python 3.14.4 and OpenSSL 4.0.0.
|
||||
|
||||
7.40.03
|
||||
|
||||
Added option `whocanaddexternalmembers only_owners_can_add_external_members|end_users_can_add_external_members` to `<GroupSettingsAttribute>`.
|
||||
It appears that `allowexternalmembers true` must be set in the same command.
|
||||
Added option `whocanaddexternalmembers` to `<GroupFieldName>`.
|
||||
These options are not in general release as of 2026-04-13; experiment.
|
||||
|
||||
7.40.02
|
||||
|
||||
Updated `gam info|print cigroups` and `gam print|show cigroup-members` to handle trap caused
|
||||
by API returning invalid member data; `preferredMemberKey` with no `id`.
|
||||
|
||||
7.40.01
|
||||
|
||||
Updated `gam <UserTypeEntity> print filelist|filecounts` to handle the `permissionDetails` subfield
|
||||
of the `permissions` field for My Drives; this useful when trying to display permission inheritance.
|
||||
An additional API call per file is required to get the `permissionDetails` subfield.
|
||||
```
|
||||
gam user user@domain.com print filelist fields id,name,mimetype,basicpermissions,permissiondetails oneitemperrow
|
||||
gam user user@domain.com print filelist fields id,name,mimetype,basicpermissions,permissiondetails pm inherited false em pmfilter oneitemperrow
|
||||
```
|
||||
|
||||
7.40.00
|
||||
|
||||
Updated `gam print|show businessprofileaccounts` (client access) to
|
||||
|
||||
@@ -11,5 +11,7 @@ from gam.__main__ import main
|
||||
if __name__ == '__main__':
|
||||
if platform.system() != 'Linux':
|
||||
multiprocessing.freeze_support()
|
||||
multiprocessing.set_start_method('spawn', force=True)
|
||||
# Python 3.14.4 and PyInstaller 6.19.0 don't play nice with Linux forkserver
|
||||
# use spawn everywhere for now.
|
||||
multiprocessing.set_start_method('spawn', force=True)
|
||||
main()
|
||||
|
||||
@@ -25,7 +25,7 @@ https://github.com/GAM-team/GAM/wiki
|
||||
"""
|
||||
|
||||
__author__ = 'GAM Team <google-apps-manager@googlegroups.com>'
|
||||
__version__ = '7.40.00'
|
||||
__version__ = '7.41.00'
|
||||
__license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
|
||||
|
||||
# pylint: disable=wrong-import-position
|
||||
@@ -6118,9 +6118,12 @@ def _checkCIMemberCategory(member, memberDisplayOptions):
|
||||
return True
|
||||
|
||||
def getCIGroupMemberRoleFixType(member):
|
||||
''' fixes missing type and returns the highest role of member '''
|
||||
''' fixes missing type/id and returns the highest role of member '''
|
||||
if 'type' not in member:
|
||||
if member['preferredMemberKey']['id'] == GC.Values[GC.CUSTOMER_ID]:
|
||||
if 'id' not in member['preferredMemberKey']:
|
||||
member['preferredMemberKey']['id'] = GC.Values[GC.CUSTOMER_ID]
|
||||
member['type'] = Ent.TYPE_CUSTOMER
|
||||
elif member['preferredMemberKey']['id'] == GC.Values[GC.CUSTOMER_ID]:
|
||||
member['type'] = Ent.TYPE_CUSTOMER
|
||||
else:
|
||||
member['type'] = Ent.TYPE_OTHER
|
||||
@@ -33823,6 +33826,9 @@ GROUP_SETTINGS_ATTRIBUTES = {
|
||||
'sendmessagedenynotification': ['sendMessageDenyNotification', {GC.VAR_TYPE: GC.TYPE_BOOLEAN}],
|
||||
'spammoderationlevel': ['spamModerationLevel', {GC.VAR_TYPE: GC.TYPE_CHOICE,
|
||||
'choices': {'allow': 'ALLOW', 'moderate': 'MODERATE', 'silentlymoderate': 'SILENTLY_MODERATE', 'reject': 'REJECT'}}],
|
||||
'whocanaddexternalmembers': ['whoCanAddExternalMembers', {GC.VAR_TYPE: GC.TYPE_CHOICE,
|
||||
'choices': {'onlyadminscanaddexternalmembers': 'ONLY_ADMINS_CAN_ADD_EXTERNAL_MEMBERS',
|
||||
'enduserscanaddexternalmembers': 'END_USERS_CAN_ADD_EXTERNAL_MEMBERS'}}],
|
||||
'whocancontactowner': ['whoCanContactOwner', {GC.VAR_TYPE: GC.TYPE_CHOICE,
|
||||
'choices': {'anyonecancontact': 'ANYONE_CAN_CONTACT', 'allindomaincancontact': 'ALL_IN_DOMAIN_CAN_CONTACT',
|
||||
'allmemberscancontact': 'ALL_MEMBERS_CAN_CONTACT', 'allmanagerscancontact': 'ALL_MANAGERS_CAN_CONTACT',
|
||||
@@ -37196,14 +37202,14 @@ def doUpdateCIGroups():
|
||||
throwReasons=GAPI.MEMBERS_THROW_REASONS+[GAPI.DUPLICATE, GAPI.MEMBER_NOT_FOUND, GAPI.RESOURCE_NOT_FOUND,
|
||||
GAPI.INVALID_MEMBER, GAPI.CYCLIC_MEMBERSHIPS_NOT_ALLOWED,
|
||||
GAPI.CONDITION_NOT_MET, GAPI.FAILED_PRECONDITION, GAPI.PERMISSION_DENIED,
|
||||
GAPI.ALREADY_EXISTS, GAPI.CONFLICT],
|
||||
GAPI.ALREADY_EXISTS, GAPI.INVALID_ARGUMENT, GAPI.CONFLICT],
|
||||
parent=group, body=body, fields='')
|
||||
_showSuccess(group, member, role, expireTime, j, jcount)
|
||||
except (GAPI.groupNotFound, GAPI.domainNotFound, GAPI.domainCannotUseApis, GAPI.invalid, GAPI.forbidden):
|
||||
entityUnknownWarning(entityType, group, i, count)
|
||||
except (GAPI.duplicate, GAPI.memberNotFound, GAPI.resourceNotFound,
|
||||
GAPI.invalidMember, GAPI.cyclicMembershipsNotAllowed, GAPI.conditionNotMet,
|
||||
GAPI.failedPrecondition, GAPI.permissionDenied, GAPI.alreadyExists) as e:
|
||||
GAPI.failedPrecondition, GAPI.permissionDenied, GAPI.alreadyExists, GAPI.invalidArgument) as e:
|
||||
_showFailure(group, member, role, str(e), j, jcount)
|
||||
except GAPI.conflict:
|
||||
_showSuccess(group, member, role, expireTime, j, jcount, Msg.ACTION_MAY_BE_DELAYED)
|
||||
@@ -57522,7 +57528,8 @@ def _mapDriveInfo(f_file, parentsSubFields, showParentsIdsAsList):
|
||||
|
||||
DRIVEFILE_BASIC_PERMISSION_FIELDS = [
|
||||
'displayName', 'id', 'emailAddress', 'domain', 'role', 'type',
|
||||
'allowFileDiscovery', 'expirationTime', 'deleted', 'permissionDetails' #permissionDetails must be last
|
||||
'allowFileDiscovery', 'expirationTime', 'deleted', 'inheritedPermissionsDisabled',
|
||||
'permissionDetails' #permissionDetails must be last
|
||||
]
|
||||
|
||||
DRIVE_FIELDS_CHOICE_MAP = {
|
||||
@@ -57531,7 +57538,7 @@ DRIVE_FIELDS_CHOICE_MAP = {
|
||||
'appproperties': 'appProperties',
|
||||
'basicpermissions': ['permissions.displayName', 'permissions.id', 'permissions.emailAddress', 'permissions.domain',
|
||||
'permissions.role', 'permissions.type', 'permissions.allowFileDiscovery',
|
||||
'permissions.expirationTime', 'permissions.deleted'],
|
||||
'permissions.expirationTime', 'permissions.deleted', 'permissions.inheritedPermissionsDisabled'],
|
||||
'cancomment': 'capabilities.canComment',
|
||||
'canreadrevisions': 'capabilities.canReadRevisions',
|
||||
'capabilities': 'capabilities',
|
||||
@@ -57895,19 +57902,37 @@ def _setSkipObjects(skipObjects, skipTitles, fieldsList):
|
||||
skipObjects.add(field)
|
||||
fieldsList.append('parents')
|
||||
|
||||
def _setGetPermissionsForSharedDrives(fieldsList):
|
||||
getPermissionsForSharedDrives = False
|
||||
permissionsFieldsList = []
|
||||
def _setGetPermissionsForMyDriveSharedDrives(fieldsList, permissionsFieldsList):
|
||||
getPermissionDetailsForMyDrive = getPermissionsForSharedDrives = False
|
||||
permissionsFields = None
|
||||
for field in fieldsList:
|
||||
if field.startswith('permissions'):
|
||||
getPermissionsForSharedDrives = True
|
||||
if field.find('.') != -1:
|
||||
field, subField = field.split('.', 1)
|
||||
permissionsFieldsList.append(subField)
|
||||
if getPermissionsForSharedDrives:
|
||||
else:
|
||||
permissionsFieldsList.append('*')
|
||||
if permissionsFieldsList:
|
||||
if 'permissionDetails' in permissionsFieldsList:
|
||||
getPermissionDetailsForMyDrive = True
|
||||
getPermissionsForSharedDrives = True
|
||||
permissionsFields = getItemFieldsFromFieldsList('permissions', permissionsFieldsList, True)
|
||||
return (getPermissionsForSharedDrives, permissionsFields)
|
||||
return (getPermissionDetailsForMyDrive, getPermissionsForSharedDrives, permissionsFields)
|
||||
|
||||
# Do file permissions have to be gotten by API call?
|
||||
def _setGetCheckFilePermissions(fileinfo, getPermissionDetailsForMyDrive, getPermissionsForSharedDrives, driveId, DLP):
|
||||
filePerms = fileinfo.get('permissions')
|
||||
if getPermissionsForSharedDrives and driveId and not filePerms:
|
||||
return True
|
||||
if getPermissionDetailsForMyDrive:
|
||||
return True
|
||||
if DLP.PM.checkDetails:
|
||||
if not filePerms:
|
||||
return True
|
||||
permDetails = filePerms[0].get('permissionDetails')
|
||||
if (not permDetails) or 'inherited' not in permDetails[0]:
|
||||
return True
|
||||
return False
|
||||
|
||||
SHOWLABELS_CHOICES = {'details', 'ids'}
|
||||
|
||||
@@ -57953,6 +57978,7 @@ def showFileInfo(users):
|
||||
returnIdOnly = showParentsIdsAsList = showNoParents = stripCRsFromName = False
|
||||
pathDelimiter = '/'
|
||||
showLabels = None
|
||||
permissionsFieldsList = []
|
||||
simpleLists = []
|
||||
skipObjects = set()
|
||||
fileIdEntity = getDriveFileEntity()
|
||||
@@ -57976,8 +58002,7 @@ def showFileInfo(users):
|
||||
elif myarg == 'showlabels':
|
||||
showLabels = getChoice(SHOWLABELS_CHOICES)
|
||||
elif myarg == 'showshareddrivepermissions':
|
||||
getPermissionsForSharedDrives = True
|
||||
permissionsFields = f'nextPageToken,permissions({",".join(DRIVEFILE_BASIC_PERMISSION_FIELDS)})'
|
||||
permissionsFieldsList = DRIVEFILE_BASIC_PERMISSION_FIELDS.copy()
|
||||
elif myarg == 'returnidonly':
|
||||
returnIdOnly = True
|
||||
elif myarg == 'followshortcuts':
|
||||
@@ -57986,9 +58011,8 @@ def showFileInfo(users):
|
||||
pass
|
||||
else:
|
||||
FJQC.GetFormatJSON(myarg)
|
||||
_, getPermissionsForSharedDrives, permissionsFields = _setGetPermissionsForMyDriveSharedDrives(DFF.fieldsList, permissionsFieldsList)
|
||||
if DFF.fieldsList:
|
||||
if not getPermissionsForSharedDrives:
|
||||
getPermissionsForSharedDrives, permissionsFields = _setGetPermissionsForSharedDrives(DFF.fieldsList)
|
||||
_setSelectionFields()
|
||||
if followShortcuts:
|
||||
DFF.fieldsList.extend(['mimeType', 'shortcutDetails'])
|
||||
@@ -58101,7 +58125,7 @@ def showFileInfo(users):
|
||||
if not FJQC.formatJSON:
|
||||
showJSON(None, result, skipObjects=skipObjects, timeObjects=DRIVE_TIME_OBJECTS, simpleLists=simpleLists,
|
||||
dictObjectsKey={'owners': 'displayName', 'fields': 'id', 'labels': 'id', 'user': 'emailAddress', 'parents': 'id',
|
||||
'permissions': 'displayName'})
|
||||
'permissions': 'displayName', 'permissionDetails': 'inherited'})
|
||||
Ind.Decrement()
|
||||
else:
|
||||
printLine(json.dumps(cleanJSON(result, skipObjects=skipObjects, timeObjects=DRIVE_TIME_OBJECTS), ensure_ascii=False, sort_keys=True))
|
||||
@@ -58850,6 +58874,7 @@ class PermissionMatch():
|
||||
self.permissionMatchKeep = self.permissionMatchOr = True
|
||||
self.permissionFields = set()
|
||||
self.clearDefaultMatch = False
|
||||
self.checkDetails = False
|
||||
|
||||
def GetMatch(self):
|
||||
startEndTime = StartEndTime('expirationstart', 'expirationend')
|
||||
@@ -58931,9 +58956,11 @@ class PermissionMatch():
|
||||
elif myarg == 'inherited':
|
||||
body[myarg] = getBoolean()
|
||||
self.permissionFields.add('permissionDetails')
|
||||
self.checkDetails = True
|
||||
elif myarg == 'permtype':
|
||||
body['permissionType'] = getChoice(DRIVEFILE_ACL_PERMISSION_DETAILS_TYPES)
|
||||
self.permissionFields.add('permissionDetails')
|
||||
self.checkDetails = True
|
||||
elif myarg in {'em', 'endmatch'}:
|
||||
break
|
||||
else:
|
||||
@@ -58981,16 +59008,25 @@ class PermissionMatch():
|
||||
elif field in {'allowFileDiscovery', 'deleted'}:
|
||||
if value != permission.get(field, False):
|
||||
break
|
||||
elif field == 'inherited':
|
||||
if 'permissionDetails' in permission:
|
||||
if value != permission['permissionDetails'][0].get(field, False):
|
||||
break
|
||||
else:
|
||||
break
|
||||
elif field == 'permissionType':
|
||||
if 'permissionDetails' in permission:
|
||||
if value != permission['permissionDetails'][0].get(field, ''):
|
||||
break
|
||||
elif field in {'inherited', 'permissionType'}:
|
||||
permDetails = permission.pop('permissionDetails', None)
|
||||
if permDetails:
|
||||
dfltValue = False if field == 'inherited' else ''
|
||||
permission['permissionDetails'] = []
|
||||
if permission.get('inheritedPermissionsDisabled', False):
|
||||
obreak = False
|
||||
for permissionDetail in permDetails:
|
||||
if permissionDetail.get('inherited', False):
|
||||
continue
|
||||
permission['permissionDetails'].append(permissionDetail)
|
||||
if value != permissionDetail.get(field, dfltValue):
|
||||
obreak = True
|
||||
if obreak:
|
||||
break
|
||||
else:
|
||||
permission['permissionDetails'].append(permDetails[len(permDetails)-1])
|
||||
if value != permDetails[0].get(field, dfltValue):
|
||||
break
|
||||
else:
|
||||
break
|
||||
elif field in {'expirationstart', 'expirationend'}:
|
||||
@@ -59432,7 +59468,8 @@ def printFileList(users):
|
||||
def _printFileInfo(drive, user, f_file, cleanFileName):
|
||||
nonlocal getSharedDriveACLsCount
|
||||
driveId = f_file.get('driveId')
|
||||
checkSharedDrivePermissions = getPermissionsForSharedDrives and driveId and 'permissions' not in f_file
|
||||
getCheckFilePermissions = _setGetCheckFilePermissions(f_file, getPermissionDetailsForMyDrive, getPermissionsForSharedDrives,
|
||||
driveId, DLP)
|
||||
if (f_file.get('noDisplay', False) or
|
||||
not DLP.CheckShowOwnedBy(f_file) or
|
||||
not DLP.CheckShowSharedByMe(f_file) or
|
||||
@@ -59440,10 +59477,10 @@ def printFileList(users):
|
||||
not DLP.CheckMimeType(f_file) or
|
||||
not DLP.CheckFileSize(f_file, sizeField) or
|
||||
not DLP.CheckFilenameMatch(f_file) or
|
||||
(not checkSharedDrivePermissions and not DLP.CheckFilePermissionMatches(f_file)) or
|
||||
(not getCheckFilePermissions and not DLP.CheckFilePermissionMatches(f_file)) or
|
||||
(DLP.onlySharedDrives and not driveId)):
|
||||
return
|
||||
if checkSharedDrivePermissions:
|
||||
if getCheckFilePermissions:
|
||||
if not incrementalPrint:
|
||||
getSharedDriveACLsCount += 1
|
||||
if getSharedDriveACLsCount % 100 == 0:
|
||||
@@ -59610,7 +59647,7 @@ def printFileList(users):
|
||||
csvPFco = None
|
||||
FJQC = FormatJSONQuoteChar(csvPF)
|
||||
addPathsToJSON = continueOnInvalidQuery = countsRowFilter = buildTree = countsOnly = filepath = fullpath = folderPathOnly = \
|
||||
getPermissionsForSharedDrives = mimeTypeInQuery = noRecursion = oneItemPerRow = stripCRsFromName = \
|
||||
getPermissionDetailsForMyDrive = getPermissionsForSharedDrives = mimeTypeInQuery = noRecursion = oneItemPerRow = stripCRsFromName = \
|
||||
showParentsIdsAsList = showDepth = showParent = showSize = showSizeUnits = showMimeTypeSize = showSource = False
|
||||
sizeField = 'quotaBytesUsed'
|
||||
pathDelimiter = '/'
|
||||
@@ -59621,6 +59658,7 @@ def printFileList(users):
|
||||
maxdepth = -1
|
||||
nodataFields = []
|
||||
simpleLists = ['permissionIds', 'spaces']
|
||||
permissionsFieldsList = []
|
||||
skipObjects = set()
|
||||
fileIdEntity = {}
|
||||
selectSubQuery = ''
|
||||
@@ -59704,8 +59742,7 @@ def printFileList(users):
|
||||
elif myarg == 'showlabels':
|
||||
showLabels = getChoice(SHOWLABELS_CHOICES)
|
||||
elif myarg == 'showshareddrivepermissions':
|
||||
getPermissionsForSharedDrives = True
|
||||
permissionsFields = f'nextPageToken,permissions({",".join(DRIVEFILE_BASIC_PERMISSION_FIELDS)})'
|
||||
permissionsFieldsList = DRIVEFILE_BASIC_PERMISSION_FIELDS.copy()
|
||||
elif myarg == 'pmfilter':
|
||||
pmselect = False
|
||||
elif myarg == 'oneitemperrow':
|
||||
@@ -59759,11 +59796,9 @@ def printFileList(users):
|
||||
btkwargs = fileIdEntity['shareddrive']
|
||||
DLP.Finalize(fileIdEntity)
|
||||
if DLP.PM.permissionMatches:
|
||||
getPermissionsForSharedDrives = True
|
||||
permissionsFields = f'nextPageToken,permissions({",".join(DRIVEFILE_BASIC_PERMISSION_FIELDS)})'
|
||||
elif DFF.fieldsList:
|
||||
if not getPermissionsForSharedDrives:
|
||||
getPermissionsForSharedDrives, permissionsFields = _setGetPermissionsForSharedDrives(DFF.fieldsList)
|
||||
permissionsFieldsList += list(DLP.PM.permissionFields)
|
||||
getPermissionDetailsForMyDrive, getPermissionsForSharedDrives, permissionsFields = \
|
||||
_setGetPermissionsForMyDriveSharedDrives(DFF.fieldsList, permissionsFieldsList)
|
||||
if DFF.fieldsList:
|
||||
_setSelectionFields()
|
||||
fields = getFieldsFromFieldsList(DFF.fieldsList)
|
||||
@@ -60650,10 +60685,11 @@ def printShowFileCounts(users):
|
||||
fieldsList.append('driveId')
|
||||
DLP.Finalize(fileIdEntity)
|
||||
if DLP.PM.permissionMatches:
|
||||
getPermissionDetailsForMyDrive = DLP.PM.checkDetails
|
||||
getPermissionsForSharedDrives = True
|
||||
permissionsFields = 'nextPageToken,permissions'
|
||||
permissionsFields = f'nextPageToken,permissions({",".join(DLP.PM.permissionFields)})'
|
||||
else:
|
||||
getPermissionsForSharedDrives = False
|
||||
getPermissionDetailsForMyDrive = getPermissionsForSharedDrives = False
|
||||
_setSelectionFields()
|
||||
if csvPF:
|
||||
sortTitles = ['User', 'id', 'name', 'Total', 'Item cap'] if fileIdEntity.get('shareddrive') else ['User', 'Total']
|
||||
@@ -60694,16 +60730,17 @@ def printShowFileCounts(users):
|
||||
for files in feed:
|
||||
for f_file in files:
|
||||
driveId = f_file.get('driveId')
|
||||
checkSharedDrivePermissions = getPermissionsForSharedDrives and driveId and 'permissions' not in f_file
|
||||
getCheckFilePermissions = _setGetCheckFilePermissions(f_file, getPermissionDetailsForMyDrive, getPermissionsForSharedDrives,
|
||||
driveId, DLP)
|
||||
if (not DLP.CheckShowOwnedBy(f_file) or
|
||||
not DLP.CheckShowSharedByMe(f_file) or
|
||||
not DLP.CheckExcludeTrashed(f_file) or
|
||||
not DLP.CheckFileSize(f_file, sizeField) or
|
||||
not DLP.CheckFilenameMatch(f_file) or
|
||||
(not checkSharedDrivePermissions and not DLP.CheckFilePermissionMatches(f_file)) or
|
||||
(not getCheckFilePermissions and not DLP.CheckFilePermissionMatches(f_file)) or
|
||||
(DLP.onlySharedDrives and not driveId)):
|
||||
continue
|
||||
if checkSharedDrivePermissions:
|
||||
if getCheckFilePermissions:
|
||||
try:
|
||||
f_file['permissions'] = callGAPIpages(drive.permissions(), 'list', 'permissions',
|
||||
throwReasons=GAPI.DRIVE3_GET_ACL_REASONS+[GAPI.BAD_REQUEST],
|
||||
@@ -67094,7 +67131,7 @@ def emptyDriveTrash(users):
|
||||
userDriveServiceNotEnabledWarning(user, str(e), i, count)
|
||||
|
||||
def _getDriveFileACLPrintKeysTimeObjects():
|
||||
printKeys = ['id', 'type', 'emailAddress', 'domain', 'role', 'permissionDetails',
|
||||
printKeys = ['id', 'role', 'type', 'emailAddress', 'domain', 'permissionDetails',
|
||||
'expirationTime', 'photoLink', 'allowFileDiscovery', 'deleted',
|
||||
'pendingOwner', 'view']
|
||||
timeObjects = {'expirationTime'}
|
||||
@@ -67143,14 +67180,14 @@ def _showDriveFilePermission(permission, printKeys, timeObjects, i=0, count=0):
|
||||
printKeyValueList([key, ''])
|
||||
Ind.Increment()
|
||||
for detail in value:
|
||||
printKeyValueList(['role', detail['role']])
|
||||
Ind.Increment()
|
||||
printKeyValueList(['type', detail['permissionType']])
|
||||
if 'additionalRoles' in detail:
|
||||
printKeyValueList(['additionalRoles', ','.join(detail['additionalRoles'])])
|
||||
printKeyValueList(['inherited', detail['inherited']])
|
||||
Ind.Increment()
|
||||
if detail['inherited']:
|
||||
printKeyValueList(['inheritedFrom', detail.get('inheritedFrom', UNKNOWN)])
|
||||
printKeyValueList(['permissionType', detail['permissionType']])
|
||||
printKeyValueList(['role', detail['role']])
|
||||
if 'additionalRoles' in detail:
|
||||
printKeyValueList(['additionalRoles', ','.join(detail['additionalRoles'])])
|
||||
Ind.Decrement()
|
||||
Ind.Decrement()
|
||||
elif key not in timeObjects:
|
||||
|
||||
@@ -36,10 +36,13 @@ def main():
|
||||
if __name__ == '__main__':
|
||||
if getattr(sys, 'frozen', False): # we're frozen:
|
||||
multiprocessing.freeze_support()
|
||||
if platform.system() == 'Linux':
|
||||
#if platform.system() == 'Linux':
|
||||
# set explictly since it's not default in Python < 3.14, forkserver should
|
||||
# be safer than fork and less likely to see bulk command hangs.
|
||||
multiprocessing.set_start_method('forkserver')
|
||||
else:
|
||||
multiprocessing.set_start_method('spawn')
|
||||
#multiprocessing.set_start_method('forkserver')
|
||||
#else:
|
||||
|
||||
# Python 3.14.4 and PyInstaller 6.19.0 don't play nice with forkserver
|
||||
# on Linux. For the time being use spawn everywhere.
|
||||
multiprocessing.set_start_method('spawn')
|
||||
main()
|
||||
|
||||
67
src/tools/py314-ossl4.diff
Normal file
67
src/tools/py314-ossl4.diff
Normal file
@@ -0,0 +1,67 @@
|
||||
diff --git a/Modules/_ssl.c b/Modules/_ssl.c
|
||||
index 1235eff72f7..f68e34b7560 100644
|
||||
--- a/Modules/_ssl.c
|
||||
+++ b/Modules/_ssl.c
|
||||
@@ -134,6 +134,17 @@ static void _PySSLFixErrno(void) {
|
||||
#error Unsupported OpenSSL version
|
||||
#endif
|
||||
|
||||
+#if (OPENSSL_VERSION_NUMBER >= 0x40000000L)
|
||||
+# define OPENSSL_NO_SSL3
|
||||
+# define OPENSSL_NO_TLS1
|
||||
+# define OPENSSL_NO_TLS1_1
|
||||
+# define OPENSSL_NO_TLS1_2
|
||||
+# define OPENSSL_NO_SSL3_METHOD
|
||||
+# define OPENSSL_NO_TLS1_METHOD
|
||||
+# define OPENSSL_NO_TLS1_1_METHOD
|
||||
+# define OPENSSL_NO_TLS1_2_METHOD
|
||||
+#endif
|
||||
+
|
||||
/* OpenSSL API 1.1.0+ does not include version methods */
|
||||
#ifndef OPENSSL_NO_SSL3_METHOD
|
||||
extern const SSL_METHOD *SSLv3_method(void);
|
||||
@@ -1133,7 +1144,7 @@ _asn1obj2py(_sslmodulestate *state, const ASN1_OBJECT *name, int no_name)
|
||||
|
||||
static PyObject *
|
||||
_create_tuple_for_attribute(_sslmodulestate *state,
|
||||
- ASN1_OBJECT *name, ASN1_STRING *value)
|
||||
+ const ASN1_OBJECT *name, const ASN1_STRING *value)
|
||||
{
|
||||
Py_ssize_t buflen;
|
||||
PyObject *pyattr;
|
||||
@@ -1162,16 +1173,16 @@ _create_tuple_for_attribute(_sslmodulestate *state,
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
-_create_tuple_for_X509_NAME (_sslmodulestate *state, X509_NAME *xname)
|
||||
+_create_tuple_for_X509_NAME (_sslmodulestate *state, const X509_NAME *xname)
|
||||
{
|
||||
PyObject *dn = NULL; /* tuple which represents the "distinguished name" */
|
||||
PyObject *rdn = NULL; /* tuple to hold a "relative distinguished name" */
|
||||
PyObject *rdnt;
|
||||
PyObject *attr = NULL; /* tuple to hold an attribute */
|
||||
int entry_count = X509_NAME_entry_count(xname);
|
||||
- X509_NAME_ENTRY *entry;
|
||||
- ASN1_OBJECT *name;
|
||||
- ASN1_STRING *value;
|
||||
+ const X509_NAME_ENTRY *entry;
|
||||
+ const ASN1_OBJECT *name;
|
||||
+ const ASN1_STRING *value;
|
||||
int index_counter;
|
||||
int rdn_level = -1;
|
||||
int retcode;
|
||||
@@ -6506,9 +6517,15 @@ sslmodule_init_constants(PyObject *m)
|
||||
ADD_INT_CONST("PROTOCOL_TLS", PY_SSL_VERSION_TLS);
|
||||
ADD_INT_CONST("PROTOCOL_TLS_CLIENT", PY_SSL_VERSION_TLS_CLIENT);
|
||||
ADD_INT_CONST("PROTOCOL_TLS_SERVER", PY_SSL_VERSION_TLS_SERVER);
|
||||
+#ifndef OPENSSL_NO_TLS1
|
||||
ADD_INT_CONST("PROTOCOL_TLSv1", PY_SSL_VERSION_TLS1);
|
||||
+#endif
|
||||
+#ifndef OPENSSL_NO_TLS1_1
|
||||
ADD_INT_CONST("PROTOCOL_TLSv1_1", PY_SSL_VERSION_TLS1_1);
|
||||
+#endif
|
||||
+#ifndef OPENSSL_NO_TLS1_2
|
||||
ADD_INT_CONST("PROTOCOL_TLSv1_2", PY_SSL_VERSION_TLS1_2);
|
||||
+#endif
|
||||
|
||||
#define ADD_OPTION(NAME, VALUE) if (sslmodule_add_option(m, NAME, (VALUE)) < 0) return -1
|
||||
@@ -428,7 +428,7 @@ See https://support.google.com/chrome/a/answer/3523633 for full details.
|
||||
Thanks to Jay for most of the following.
|
||||
|
||||
Send a remote command to the managed Chrome OS device. It's important to note that the device must be in a proper state to accept the command or an error may be returned.
|
||||
For example, the `reboot`, `set_volume` and `take_a_screenshot` commands only work if the device is configured in auto-start kiosk app mode.
|
||||
For example, the `set_volume` and `take_a_screenshot` commands only work if the device is configured in auto-start kiosk app mode.
|
||||
|
||||
The `wipe_users` and `remote_powerwash` commands will erase all user data on the device and the `remote_powerwash` command will require that the device is physically reconnected to the
|
||||
WiFi network and re-enrolled before it can be managed again. These commands require the `doit` argument so that the admin confirms the potential loss of user data and management.
|
||||
|
||||
@@ -76,12 +76,14 @@ and Cloud Identity Premium accounts.
|
||||
<RESubstitution> ::= <String>>
|
||||
|
||||
<GroupSettingsAttribute> ::=
|
||||
(accesstype public|team|announcementonly|restricted)|
|
||||
(allowexternalmembers <Boolean>)|
|
||||
(allowwebposting <Boolean>)|
|
||||
(archiveonly <Boolean>)|
|
||||
(customfootertext <String>)|
|
||||
(customreplyto <EmailAddress>)|
|
||||
(defaultmessagedenynotificationtext <String>)|
|
||||
(defaultsender self|group)|
|
||||
(description <String>)|
|
||||
(enablecollaborativeinbox|collaborative <Boolean>)|
|
||||
(includeinglobaladdresslist|gal <Boolean>)|
|
||||
@@ -95,7 +97,8 @@ and Cloud Identity Premium accounts.
|
||||
(sendmessagedenynotification <Boolean>)|
|
||||
(spammoderationlevel allow|moderate|silently_moderate|reject)|
|
||||
(whocanadd all_members_can_add|all_managers_can_add|all_owners_can_add|none_can_add)|
|
||||
(whocancontactowner anyone_can_contact|all_in_domain_can_contact|all_members_can_contact|all_managers_can_contact)|
|
||||
(whocanaddexternalmembers only_owners_can_add_external_members|end_users_can_add_external_members)|
|
||||
(whocancontactowner anyone_can_contact|all_in_domain_can_contact|all_members_can_contact|all_managers_can_contact|all_owners_can_contact)|
|
||||
(whocanjoin anyone_can_join|all_in_domain_can_join|invited_can_join|can_request_to_join)|
|
||||
(whocanleavegroup all_members_can_leave|all_managers_can_leave|all_owners_can_leave|none_can_leave)|
|
||||
(whocanpostmessage none_can_post|all_managers_can_post|all_members_can_post|all_owners_can_post|all_in_domain_can_post|anyone_can_post)|
|
||||
@@ -139,7 +142,7 @@ and Cloud Identity Premium accounts.
|
||||
<GroupAttribute> ::=
|
||||
<JSONData>|
|
||||
<GroupSettingsAttribute>|
|
||||
(whocandiscovergroup allmemberscandiscover|allindomaincandiscover|anyonecandiscover)|
|
||||
(whocandiscovergroup all_members_can_discover|all_in_domain_can_discover|anyone_can_discover)|
|
||||
(whocanassistcontent all_members|owners_and_managers|managers_only|owners_only|none)|
|
||||
(whocanmoderatecontent all_members|owners_and_managers|owners_only|none)|
|
||||
(whocanmoderatemembers all_members|owners_and_managers|owners_only|none)|
|
||||
@@ -182,6 +185,7 @@ and Cloud Identity Premium accounts.
|
||||
spammoderationlevel|
|
||||
whocanaddreferences|
|
||||
whocanadd|
|
||||
whocanaddexternalmembers|
|
||||
whocanapprovemessages|
|
||||
whocanassigntopics|
|
||||
whocanassistcontent|
|
||||
|
||||
@@ -10,6 +10,35 @@ Add the `-s` option to the end of the above commands to suppress creating the `g
|
||||
|
||||
See [Downloads-Installs-GAM7](https://github.com/GAM-team/GAM/wiki/Downloads-Installs) for Windows or other options, including manual installation
|
||||
|
||||
### 7.40.03
|
||||
|
||||
Added option `whocanaddexternalmembers only_owners_can_add_external_members|end_users_can_add_external_members` to `<GroupSettingsAttribute>`.
|
||||
It appears that `allowexternalmembers true` must be set in the same command.
|
||||
Added option `whocanaddexternalmembers` to `<GroupFieldName>`.
|
||||
These options are not in general release as of 2026-04-13; experiment.
|
||||
|
||||
### 7.40.02
|
||||
|
||||
Updated `gam info|print cigroups` and `gam print|show cigroup-members` to handle trap caused
|
||||
by API returning invalid member data; `preferredMemberKey` with no `id`.
|
||||
|
||||
### 7.40.01
|
||||
|
||||
Updated `gam <UserTypeEntity> print filelist|filecounts` to handle the `permissionDetails` subfield
|
||||
of the `permissions` field for My Drives; this useful when trying to display permission inheritance.
|
||||
An additional API call per file is required to get the `permissionDetails` subfield.
|
||||
```
|
||||
gam user user@domain.com print filelist fields id,name,mimetype,basicpermissions,permissiondetails oneitemperrow
|
||||
gam user user@domain.com print filelist fields id,name,mimetype,basicpermissions,permissiondetails pm inherited false em pmfilter oneitemperrow
|
||||
```
|
||||
|
||||
### 7.40.00
|
||||
|
||||
Updated `gam print|show businessprofileaccounts` (client access) to
|
||||
`gam <UserTypeEntity> print|show businessprofileaccounts` (service account access).
|
||||
You'll need to run `gam user user@domain.com update serviceaccount` and
|
||||
select `2) Business Account Management API`.
|
||||
|
||||
### 7.39.08
|
||||
|
||||
Fixed bug in `gam oauth create` that caused a trap when `0) Business Account Management API` was selected.
|
||||
|
||||
@@ -79,12 +79,13 @@ See [Collections of Items](Collections-of-Items)
|
||||
(isarchived <Boolean>)|
|
||||
(memberscanpostasthegroup <Boolean>)|
|
||||
(messagemoderationlevel moderate_all_messages|moderate_non_members|moderate_new_members|moderate_none)|
|
||||
(name <String>)|
|
||||
(name|displayname <String>)|
|
||||
(primarylanguage <Language>)|
|
||||
(replyto reply_to_custom|reply_to_sender|reply_to_list|reply_to_owner|reply_to_ignore|reply_to_managers)|
|
||||
(sendmessagedenynotification <Boolean>)|
|
||||
(spammoderationlevel allow|moderate|silently_moderate|reject)|
|
||||
(whocanadd all_members_can_add|all_managers_can_add|all_owners_can_add|none_can_add)|
|
||||
(whocanaddexternalmembers only_owners_can_add_external_members|end_users_can_add_external_members)|
|
||||
(whocancontactowner anyone_can_contact|all_in_domain_can_contact|all_members_can_contact|all_managers_can_contact|all_owners_can_contact)|
|
||||
(whocanjoin anyone_can_join|all_in_domain_can_join|invited_can_join|can_request_to_join)|
|
||||
(whocanleavegroup all_members_can_leave|all_managers_can_leave|all_owners_can_leave|none_can_leave)|
|
||||
@@ -172,6 +173,7 @@ See [Collections of Items](Collections-of-Items)
|
||||
spammoderationlevel|
|
||||
whocanaddreferences|
|
||||
whocanadd|
|
||||
whocanaddexternalmembers|
|
||||
whocanapprovemessages|
|
||||
whocanassigntopics|
|
||||
whocanassistcontent|
|
||||
|
||||
@@ -251,10 +251,10 @@ writes the credentials into the file oauth2.txt.
|
||||
```
|
||||
gamteam@server:/Users/gamteam$ rm -f /Users/gamteam/GAMConfig/oauth2.txt
|
||||
gamteam@server:/Users/gamteam$ gam version
|
||||
GAM 7.39.08 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.40.03 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.14.4 64-bit final
|
||||
macOS Tahoe 26.4 arm64
|
||||
macOS Tahoe 26.4.1 arm64
|
||||
Path: /Users/gamteam/bin/gam7
|
||||
Config File: /Users/gamteam/GAMConfig/gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
||||
|
||||
@@ -1034,7 +1034,7 @@ writes the credentials into the file oauth2.txt.
|
||||
```
|
||||
C:\>del C:\GAMConfig\oauth2.txt
|
||||
C:\>gam version
|
||||
GAM 7.39.08 - https://github.com/GAM-team/GAM - pythonsource
|
||||
GAM 7.40.03 - https://github.com/GAM-team/GAM - pythonsource
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.14.4 64-bit final
|
||||
Windows 11 10.0.26200 AMD64
|
||||
|
||||
@@ -9,12 +9,13 @@
|
||||
|
||||
|
||||
## Introduction
|
||||
To use these commands you add the 'Business Account Management API' to your project and update client authorization.
|
||||
To use these commands you must add the 'Business Account Management API' to your project and update
|
||||
service account authorization.
|
||||
```
|
||||
gam update project
|
||||
gam oauth create
|
||||
gam user user@domain.com update serviceaccount
|
||||
...
|
||||
[*] 0) Business Account Management API
|
||||
[*] 2) Business Account Management API
|
||||
|
||||
```
|
||||
## Definitions
|
||||
@@ -22,13 +23,13 @@ gam oauth create
|
||||
|
||||
## Display Business Profile Accounts
|
||||
```
|
||||
gam <UserTyoeEntity> show businessprofileaccounts
|
||||
gam <UserTypeEntity> show businessprofileaccounts
|
||||
[type locationgroup|organization|personal|usergroup]
|
||||
```
|
||||
Gam displays the information as an indented list of keys and values.
|
||||
|
||||
```
|
||||
gam <UserTyoeEntity> print businessprofileaccounts [todrive <ToDriveAttribute>*]
|
||||
gam <UserTypeEntity> print businessprofileaccounts [todrive <ToDriveAttribute>*]
|
||||
[type locationgroup|organization|personal|usergroup]
|
||||
```
|
||||
Gam displays the information as columns of fields.
|
||||
@@ -3,10 +3,10 @@
|
||||
Print the current version of Gam with details
|
||||
```
|
||||
gam version
|
||||
GAM 7.39.08 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.40.03 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.14.4 64-bit final
|
||||
macOS Tahoe 26.4 arm64
|
||||
macOS Tahoe 26.4.1 arm64
|
||||
Path: /Users/gamteam/bin/gam7
|
||||
Config File: /Users/gamteam/GamConfig/gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
||||
Time: 2026-02-15T07:51:00-08:00
|
||||
@@ -15,10 +15,10 @@ Time: 2026-02-15T07:51:00-08:00
|
||||
Print the current version of Gam with details and time offset information
|
||||
```
|
||||
gam version timeoffset
|
||||
GAM 7.39.08 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.40.03 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.14.4 64-bit final
|
||||
macOS Tahoe 26.4 arm64
|
||||
macOS Tahoe 26.4.1 arm64
|
||||
Path: /Users/gamteam/bin/gam7
|
||||
Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
||||
Your system time differs from www.googleapis.com by less than 1 second
|
||||
@@ -27,10 +27,10 @@ Your system time differs from www.googleapis.com by less than 1 second
|
||||
Print the current version of Gam with extended details and SSL information
|
||||
```
|
||||
gam version extended
|
||||
GAM 7.39.08 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.40.03 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.14.4 64-bit final
|
||||
macOS Tahoe 26.4 arm64
|
||||
macOS Tahoe 26.4.1 arm64
|
||||
Path: /Users/gamteam/bin/gam7
|
||||
Config File: /Users/gamteam/GamConfig/gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
||||
Time: 2026-02-15T07:51:00-08:00
|
||||
@@ -68,7 +68,7 @@ MacOS High Sierra 10.13.6 x86_64
|
||||
Path: /Users/gamteam/bin/gam7
|
||||
Version Check:
|
||||
Current: 5.35.08
|
||||
Latest: 7.39.08
|
||||
Latest: 7.40.03
|
||||
echo $?
|
||||
1
|
||||
```
|
||||
@@ -76,7 +76,7 @@ echo $?
|
||||
Print the current version number without details
|
||||
```
|
||||
gam version simple
|
||||
7.39.08
|
||||
7.40.03
|
||||
```
|
||||
In Linux/MacOS you can do:
|
||||
```
|
||||
@@ -86,10 +86,10 @@ echo $VER
|
||||
Print the current version of Gam and address of this Wiki
|
||||
```
|
||||
gam help
|
||||
GAM 7.39.08 - https://github.com/GAM-team/GAM
|
||||
GAM 7.40.03 - https://github.com/GAM-team/GAM
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.14.4 64-bit final
|
||||
macOS Tahoe 26.4 arm64
|
||||
macOS Tahoe 26.4.1 arm64
|
||||
Path: /Users/gamteam/bin/gam7
|
||||
Config File: /Users/gamteam/GamConfig/gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
||||
Time: 2026-02-15T07:51:00-08:00
|
||||
|
||||
@@ -70,7 +70,6 @@ Client Access
|
||||
* [Administrators](Administrators)
|
||||
* [Alert Center](Alert-Center)
|
||||
* [Aliases](Aliases)
|
||||
* [Business Account Management](Business-Account-Management)
|
||||
* [Calendars](Calendars)
|
||||
* [Calendars - Access](Calendars-Access)
|
||||
* [Calendars - Events](Calendars-Events)
|
||||
@@ -140,6 +139,7 @@ Special Service Account Access
|
||||
|
||||
Service Account Access
|
||||
* [Users - Analytics Admin](Users-Analytics-Admin)
|
||||
* [Users - Business Account Management](Users-Business-Account-Management)
|
||||
* [Users - Calendars](Users-Calendars)
|
||||
* [Users - Calendars - Access](Users-Calendars-Access)
|
||||
* [Users - Calendars - Events](Users-Calendars-Events)
|
||||
|
||||
Reference in New Issue
Block a user