mirror of
https://github.com/GAM-team/GAM.git
synced 2026-06-18 13:11:37 +00:00
Compare commits
10 Commits
20250719.1
...
v7.14.04
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f894e5ffd7 | ||
|
|
5d1379e830 | ||
|
|
aa1b373245 | ||
|
|
1bed2383ff | ||
|
|
332d6c0761 | ||
|
|
597bbe2db9 | ||
|
|
fcb50e1e93 | ||
|
|
048d88c7a0 | ||
|
|
bd4208607b | ||
|
|
7b510075e6 |
3
.github/workflows/build.yml
vendored
3
.github/workflows/build.yml
vendored
@@ -473,8 +473,7 @@ jobs:
|
||||
run: |
|
||||
echo "before anything..."
|
||||
"$PYTHON" -m pip list
|
||||
"$PYTHON" -m pip install --upgrade -r requirements.txt
|
||||
echo "after requirements..."
|
||||
"$PYTHON" -m pip install --upgrade ..[yubikey]
|
||||
"$PYTHON" -m pip list
|
||||
#"$PYTHON" -m pip install --force-reinstall --no-deps --upgrade cryptography
|
||||
echo "after everything..."
|
||||
|
||||
@@ -7,7 +7,6 @@ authors = [
|
||||
{ name="Jay Lee", email="jay0lee@gmail.com" },
|
||||
{ name="Ross Scroggs", email="Ross.Scroggs@gmail.com" },
|
||||
]
|
||||
# The following deps and optional deps should be edited to match: setup.cfg, requirements.txt
|
||||
# notice that yubikey-manager remains optional further down since it is less command and adds
|
||||
#significant compile dependencies.
|
||||
dependencies = [
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
7.14.04
|
||||
|
||||
Fixed bug in `gam print|show cigroups cimember <UserItem>` that generated the following error:
|
||||
```
|
||||
ERROR: Cloud Identity Group: groups/-, Print Failed: Error(4013): Insufficient permissions to retrieve memberships.
|
||||
```
|
||||
|
||||
Updated `gam <UserTypeEntity> update user suspended off` and `gam <UserTypeEntity> unsuspend users`
|
||||
to handle the following error that occurs when trying to unsuspend a user that has been suspended for abuse.
|
||||
```
|
||||
ERROR: 412: adminCannotUnsuspend - Cannot restore a user suspended for abuse.
|
||||
```
|
||||
|
||||
* See: https://support.google.com/a/answer/1110339
|
||||
|
||||
7.14.03
|
||||
|
||||
Fixed bug in `gam print cigroup-members includederivedmembership` that caused a trap.
|
||||
|
||||
@@ -25,7 +25,7 @@ https://github.com/GAM-team/GAM/wiki
|
||||
"""
|
||||
|
||||
__author__ = 'GAM Team <google-apps-manager@googlegroups.com>'
|
||||
__version__ = '7.14.03'
|
||||
__version__ = '7.14.04'
|
||||
__license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
|
||||
|
||||
#pylint: disable=wrong-import-position
|
||||
@@ -36832,11 +36832,11 @@ def doPrintCIGroups():
|
||||
|
||||
ci = buildGAPIObject(API.CLOUDIDENTITY_GROUPS)
|
||||
setTrueCustomerId()
|
||||
parent = f'customers/{GC.Values[GC.CUSTOMER_ID]}'
|
||||
delimiter = GC.Values[GC.CSV_OUTPUT_FIELD_DELIMITER]
|
||||
memberRestrictions = sortHeaders = False
|
||||
memberDisplayOptions = initPGGroupMemberDisplayOptions()
|
||||
pageSize = 500
|
||||
parent = f'customers/{GC.Values[GC.CUSTOMER_ID]}'
|
||||
groupFieldsLists = {'ci': ['groupKey']}
|
||||
csvPF = CSVPrintFile(['email'])
|
||||
FJQC = FormatJSONQuoteChar(csvPF)
|
||||
@@ -36854,7 +36854,7 @@ def doPrintCIGroups():
|
||||
showOwnedBy = convertUIDtoEmailAddress(getEmailAddress(), emailTypes=['user'])
|
||||
elif myarg in {'cimember', 'enterprisemember', 'ciowner'}:
|
||||
emailAddress = convertUIDtoEmailAddress(getEmailAddress(), emailTypes=['user', 'group'])
|
||||
memberQuery = f"member_key_id == '{emailAddress}' && '{CIGROUP_DISCUSSION_FORUM_LABEL}' in labels"
|
||||
memberQuery = f"member_key_id == '{emailAddress}' && '{CIGROUP_DISCUSSION_FORUM_LABEL}' in labels && parent == '{parent}'"
|
||||
entitySelection = None
|
||||
if myarg == 'ciowner':
|
||||
showOwnedBy = emailAddress
|
||||
@@ -37256,6 +37256,7 @@ def _getCIListGroupMembersArgs(listView):
|
||||
def doPrintCIGroupMembers():
|
||||
ci = buildGAPIObject(API.CLOUDIDENTITY_GROUPS)
|
||||
setTrueCustomerId()
|
||||
parent = f'customers/{GC.Values[GC.CUSTOMER_ID]}'
|
||||
memberOptions = initMemberOptions()
|
||||
memberDisplayOptions = initIPSGMGroupMemberDisplayOptions()
|
||||
groupColumn = True
|
||||
@@ -37277,7 +37278,7 @@ def doPrintCIGroupMembers():
|
||||
showOwnedBy = convertUIDtoEmailAddress(getEmailAddress(), emailTypes=['user'])
|
||||
elif myarg in {'cimember', 'enterprisemember', 'ciowner'}:
|
||||
emailAddress = convertUIDtoEmailAddress(getEmailAddress(), emailTypes=['user', 'group'])
|
||||
query = f"member_key_id == '{emailAddress}' && '{CIGROUP_DISCUSSION_FORUM_LABEL}' in labels"
|
||||
query = f"member_key_id == '{emailAddress}' && '{CIGROUP_DISCUSSION_FORUM_LABEL}' in labels && parent == '{parent}'"
|
||||
entityList = None
|
||||
if myarg == 'ciowner':
|
||||
showOwnedBy = emailAddress
|
||||
@@ -37479,6 +37480,7 @@ def doShowCIGroupMembers():
|
||||
|
||||
ci = buildGAPIObject(API.CLOUDIDENTITY_GROUPS)
|
||||
setTrueCustomerId()
|
||||
parent = f'customers/{GC.Values[GC.CUSTOMER_ID]}'
|
||||
subTitle = f'{Msg.ALL} {Ent.Plural(Ent.CLOUD_IDENTITY_GROUP)}'
|
||||
groupFieldsLists = {'ci': ['groupKey', 'name']}
|
||||
entityList = query = showOwnedBy = None
|
||||
@@ -37496,7 +37498,7 @@ def doShowCIGroupMembers():
|
||||
showOwnedBy = convertUIDtoEmailAddress(getEmailAddress(), emailTypes=['user'])
|
||||
elif myarg in {'cimember', 'enterprisemember', 'ciowner'}:
|
||||
emailAddress = convertUIDtoEmailAddress(getEmailAddress(), emailTypes=['user', 'group'])
|
||||
query = f"member_key_id == '{emailAddress}' && '{CIGROUP_DISCUSSION_FORUM_LABEL}' in labels"
|
||||
query = f"member_key_id == '{emailAddress}' && '{CIGROUP_DISCUSSION_FORUM_LABEL}' in labels parent == '{parent}'"
|
||||
entityList = None
|
||||
if myarg == 'ciowner':
|
||||
showOwnedBy = emailAddress
|
||||
@@ -44425,7 +44427,7 @@ def updateUsers(entityList):
|
||||
try:
|
||||
result = callGAPI(cd.users(), 'update',
|
||||
throwReasons=[GAPI.CONDITION_NOT_MET, GAPI.USER_NOT_FOUND, GAPI.DOMAIN_NOT_FOUND,
|
||||
GAPI.FORBIDDEN, GAPI.BAD_REQUEST,
|
||||
GAPI.FORBIDDEN, GAPI.BAD_REQUEST, GAPI.ADMIN_CANNOT_UNSUSPEND,
|
||||
GAPI.INVALID, GAPI.INVALID_INPUT, GAPI.INVALID_PARAMETER,
|
||||
GAPI.INVALID_ORGUNIT, GAPI.INVALID_SCHEMA_VALUE, GAPI.DUPLICATE,
|
||||
GAPI.INSUFFICIENT_ARCHIVED_USER_LICENSES, GAPI.CONFLICT],
|
||||
@@ -44487,7 +44489,8 @@ def updateUsers(entityList):
|
||||
entityActionFailedWarning([Ent.USER, user, Ent.USER, body['primaryEmail']], str(e), i, count)
|
||||
except GAPI.invalidOrgunit:
|
||||
entityActionFailedWarning([Ent.USER, user], Msg.INVALID_ORGUNIT, i, count)
|
||||
except (GAPI.resourceNotFound, GAPI.domainNotFound, GAPI.domainCannotUseApis, GAPI.forbidden, GAPI.badRequest,
|
||||
except (GAPI.resourceNotFound, GAPI.domainNotFound, GAPI.domainCannotUseApis,
|
||||
GAPI.forbidden, GAPI.badRequest, GAPI.adminCannotUnsuspend,
|
||||
GAPI.invalid, GAPI.invalidInput, GAPI.invalidParameter, GAPI.insufficientArchivedUserLicenses,
|
||||
GAPI.conflict, GAPI.badRequest, GAPI.backendError, GAPI.systemError, GAPI.conditionNotMet) as e:
|
||||
entityActionFailedWarning([Ent.USER, user], str(e), i, count)
|
||||
@@ -44655,12 +44658,14 @@ def suspendUnsuspendUsers(entityList):
|
||||
try:
|
||||
callGAPI(cd.users(), 'update',
|
||||
throwReasons=[GAPI.USER_NOT_FOUND, GAPI.DOMAIN_NOT_FOUND,
|
||||
GAPI.DOMAIN_CANNOT_USE_APIS, GAPI.FORBIDDEN, GAPI.BAD_REQUEST],
|
||||
GAPI.DOMAIN_CANNOT_USE_APIS, GAPI.FORBIDDEN, GAPI.BAD_REQUEST,
|
||||
GAPI.ADMIN_CANNOT_UNSUSPEND],
|
||||
userKey=user, body=body)
|
||||
entityActionPerformed([Ent.USER, user], i, count)
|
||||
except GAPI.userNotFound:
|
||||
entityUnknownWarning(Ent.USER, user, i, count)
|
||||
except (GAPI.domainNotFound, GAPI.domainCannotUseApis, GAPI.forbidden, GAPI.badRequest) as e:
|
||||
except (GAPI.domainNotFound, GAPI.domainCannotUseApis, GAPI.forbidden,
|
||||
GAPI.badRequest, GAPI.adminCannotUnsuspend) as e:
|
||||
entityActionFailedWarning([Ent.USER, user], str(e), i, count)
|
||||
|
||||
# gam suspend users <UserTypeEntity> [noactionifalias]
|
||||
@@ -45044,6 +45049,7 @@ def infoUsers(entityList):
|
||||
|
||||
cd = buildGAPIObject(API.DIRECTORY)
|
||||
ci = None
|
||||
setTrueCustomerId()
|
||||
getAliases = getBuildingNames = getCIGroupsTree = getGroups = getLicenses = getSchemas = not GC.Values[GC.QUICK_INFO_USER]
|
||||
getGroupsTree = False
|
||||
FJQC = FormatJSONQuoteChar()
|
||||
@@ -49545,7 +49551,7 @@ def doCourseAddItems(courseIdList, getEntityListArg):
|
||||
addItems = getStringReturnInList(Cmd.OB_COURSE_ALIAS)
|
||||
elif addType == Ent.COURSE_TOPIC:
|
||||
addItems = getStringReturnInList(Cmd.OB_COURSE_TOPIC)
|
||||
else: # addType == Ent.COURSE_ANNOUNCEMENT:
|
||||
else: #elif addType == Ent.COURSE_ANNOUNCEMENT:
|
||||
addItems = [getCourseAnnouncement(True)]
|
||||
courseParticipantLists = None
|
||||
else:
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
ABORTED = 'aborted'
|
||||
ABUSIVE_CONTENT_RESTRICTION = 'abusiveContentRestriction'
|
||||
ACCESS_NOT_CONFIGURED = 'accessNotConfigured'
|
||||
ADMIN_CANNOT_UNSUSPEND = 'adminCannotUnsuspend'
|
||||
ALREADY_EXISTS = 'alreadyExists'
|
||||
APPLY_LABEL_FORBIDDEN = 'applyLabelForbidden'
|
||||
AUTH_ERROR = 'authError'
|
||||
@@ -368,6 +369,8 @@ class abusiveContentRestriction(Exception):
|
||||
pass
|
||||
class accessNotConfigured(Exception):
|
||||
pass
|
||||
class adminCannotUnsuspend(Exception):
|
||||
pass
|
||||
class alreadyExists(Exception):
|
||||
pass
|
||||
class applyLabelForbidden(Exception):
|
||||
@@ -689,6 +692,7 @@ REASON_EXCEPTION_MAP = {
|
||||
ABORTED: aborted,
|
||||
ABUSIVE_CONTENT_RESTRICTION: abusiveContentRestriction,
|
||||
ACCESS_NOT_CONFIGURED: accessNotConfigured,
|
||||
ADMIN_CANNOT_UNSUSPEND: adminCannotUnsuspend,
|
||||
ALREADY_EXISTS: alreadyExists,
|
||||
APPLY_LABEL_FORBIDDEN: applyLabelForbidden,
|
||||
AUTH_ERROR: authError,
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
chardet>=5.2.0
|
||||
cryptography>=44.0.2
|
||||
distro; sys_platform=='linux'
|
||||
filelock>=3.18.0
|
||||
google-api-python-client>=2.167.0
|
||||
google-auth-httplib2>=0.2.0
|
||||
google-auth-oauthlib>=1.2.2
|
||||
google-auth>=2.39.0
|
||||
httplib2>=0.22.0
|
||||
lxml>=5.4.0
|
||||
passlib>=1.7.4
|
||||
pathvalidate>=3.2.3
|
||||
python-dateutil
|
||||
yubikey-manager>=5.6.1
|
||||
@@ -1,55 +0,0 @@
|
||||
[metadata]
|
||||
name = GAM for Google Workspace
|
||||
version = attr: gam__version__
|
||||
description = Command line management for Google Workspaces
|
||||
long_description = file: readme.md
|
||||
long_description_content_type = text/markdown
|
||||
url = https://github.com/GAM-team/GAM
|
||||
author = GAM Team
|
||||
author_email = google-apps-manager@googlegroups.com
|
||||
license = Apache
|
||||
license_files = LICENSE
|
||||
keywords = google, oauth2, gsuite, google-apps, google-admin-sdk, google-drive, google-cloud, google-calendar, gam, google-api, oauth2-client, google-workspace
|
||||
classifiers =
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3 :: Only
|
||||
Programming Language :: Python :: 3.9
|
||||
Programming Language :: Python :: 3.10
|
||||
Programming Language :: Python :: 3.11
|
||||
Programming Language :: Python :: 3.12
|
||||
Programming Language :: Python :: 3.13
|
||||
License :: OSI Approved :: Apache License
|
||||
|
||||
[options]
|
||||
packages = find:
|
||||
python_requires = >= 3.9
|
||||
# The following files should be edited to match: pyproject.toml, requirements.txt
|
||||
install_requires =
|
||||
chardet >= 5.2.0
|
||||
cryptography >= 44.0.2
|
||||
distro; sys_platform == 'linux'
|
||||
filelock >= 3.18.0
|
||||
google-api-python-client >= 2.167.0
|
||||
google-auth-httplib2 >= 0.2.0
|
||||
google-auth-oauthlib >= 1.2.2
|
||||
google-auth >= 2.39.0
|
||||
httplib2 >= 0.22.0
|
||||
lxml >= 5.4.0
|
||||
passlib >= 1.7.4
|
||||
pathvalidate >= 3.2.3
|
||||
python-dateutil
|
||||
yubikey-manager >= 5.6.1
|
||||
|
||||
[options.package_data]
|
||||
* = *.pem
|
||||
|
||||
# used during pip install .[test]
|
||||
[options.extras_require]
|
||||
test = pre-commit
|
||||
|
||||
[options.entry_points]
|
||||
console_scripts =
|
||||
gam = gam.__main__:main
|
||||
|
||||
[bdist_wheel]
|
||||
universal = True
|
||||
@@ -1,3 +0,0 @@
|
||||
from setuptools import setup
|
||||
|
||||
setup()
|
||||
@@ -1,20 +1,14 @@
|
||||
# Install GAM as Python Library
|
||||
|
||||
Thanks to Jay Lee for showing me how to do this.
|
||||
|
||||
On Windows, you need to install Git to use the pip command.
|
||||
* See: https://pythoninoffice.com/python-pip-install-from-github/
|
||||
|
||||
Scroll down to Install Git
|
||||
|
||||
You can install GAM as a Python library with pip.
|
||||
```
|
||||
pip install git+https://github.com/GAM-team/GAM.git#subdirectory=src
|
||||
pip install gam7
|
||||
```
|
||||
|
||||
Or as a PEP 508 Requirement Specifier, e.g. in requirements.txt file:
|
||||
```
|
||||
gam-for-google-workspace @ git+https://github.com/GAM-team/GAM.git#subdirectory=src
|
||||
gam7
|
||||
```
|
||||
|
||||
Or a pyproject.toml file:
|
||||
@@ -23,13 +17,13 @@ Or a pyproject.toml file:
|
||||
name = "your-project"
|
||||
# ...
|
||||
dependencies = [
|
||||
"gam-for-google-workspace @ git+https://github.com/GAM-team/GAM.git#subdirectory=src"
|
||||
"gam7"
|
||||
]
|
||||
```
|
||||
|
||||
Target a specific revision or tag:
|
||||
Target a specific version:
|
||||
```
|
||||
gam-for-google-workspace @ git+https://github.com/GAM-team/GAM.git@v7.12.01#subdirectory=src
|
||||
gam7==/7.13.3
|
||||
```
|
||||
|
||||
## Using the library
|
||||
|
||||
@@ -214,6 +214,12 @@ The option `updatesheetprotectedranges` only applies to items in `<DriveFileEnti
|
||||
* ACLs with role reader or commenter will be removed from existing protected ranges
|
||||
* ACLs with role writer or higher will be added to existing protected ranges
|
||||
|
||||
`
|
||||
`enforceexpansiveaccess` defaults to the value of `gam.cfg/enforce_expansive_access` that controls
|
||||
the ability to update inherited ACLs.
|
||||
* False - Inherited ACLs can be updated
|
||||
* True = Inherited ACLs can not be updated
|
||||
|
||||
By default, the file ID is displayed in the output; to see the file name, use the `showtitles`
|
||||
option; this requires an additional API call per file.
|
||||
|
||||
@@ -234,6 +240,11 @@ The option `updatesheetprotectedranges` only applies to items in `<DriveFileEnti
|
||||
* Sheet Protected Ranges are updated to reflect the deleted ACL; additional API calls are required.
|
||||
* ACLs with any role will be removed from existing protected ranges
|
||||
|
||||
`enforceexpansiveaccess` defaults to the value of `gam.cfg/enforce_expansive_access` that controls
|
||||
the ability to delete delete inherited ACLs.
|
||||
* False - Inherited ACLs can be deleted
|
||||
* True = Inherited ACLs can not be deleted
|
||||
|
||||
By default, the file ID is displayed in the output; to see the file name, use the `showtitles`
|
||||
option; this requires an additional API call per file.
|
||||
|
||||
@@ -266,6 +277,11 @@ gam <UserTypeEntity> delete permissions <DriveFileEntity> <DriveFilePermissionID
|
||||
<PermissionMatch>* [<PermissionMatchAction>]
|
||||
[enforceexpansiveaccess [<Boolean>]]
|
||||
```
|
||||
`enforceexpansiveaccess` defaults to the value of `gam.cfg/enforce_expansive_access` that controls
|
||||
the ability to delete delete inherited ACLs.
|
||||
* False - Inherited ACLs can be deleted
|
||||
* True = Inherited ACLs can not be deleted
|
||||
|
||||
When deleting permissions from JSON data, permissions with role `owner` true are never processed.
|
||||
|
||||
## Display file permissions/sharing
|
||||
|
||||
Reference in New Issue
Block a user