Compare commits

...

10 Commits

Author SHA1 Message Date
Ross Scroggs
f894e5ffd7 Fixed bug in gam print|show cigroups cimember <UserItem> 2025-07-21 15:54:39 -07:00
Ross Scroggs
5d1379e830 Merge branch 'main' of https://github.com/GAM-team/GAM 2025-07-21 14:07:07 -07:00
Ross Scroggs
aa1b373245 Handle additional error when unsuspending a user
ERROR: 412: adminCannotUnsuspend - Cannot restore a user suspended for abuse
2025-07-21 14:07:03 -07:00
Jay Lee
1bed2383ff remove deprecated and duplicate data requirements.txt
You can install deps with:

pip install /path/to/gam/folder/with/pyproject.toml/file/in/it
2025-07-21 20:49:24 +00:00
Jay Lee
332d6c0761 actions: install yubikey libs so they are in binary builds
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
2025-07-21 16:13:18 -04:00
Jay Lee
597bbe2db9 actions: fix pip install from pyproject.toml 2025-07-21 16:09:34 -04:00
Jay Lee
fcb50e1e93 point gha at pyporojects.toml, deprecate requirements.txt 2025-07-21 16:01:08 -04:00
Jay Lee
048d88c7a0 setup.cfg/py no longer necessary, pyproject.toml should do 2025-07-21 19:56:14 +00:00
Jay Lee
bd4208607b wiki: update pip install instructions 2025-07-21 15:53:51 -04:00
Ross Scroggs
7b510075e6 Update Users-Drive-Permissions.md 2025-07-21 09:07:32 -07:00
10 changed files with 57 additions and 96 deletions

View File

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

View File

@@ -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 = [

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,3 +0,0 @@
from setuptools import setup
setup()

View File

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

View File

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