Compare commits

..

11 Commits

Author SHA1 Message Date
Ross Scroggs
0c6825fa12 Updated gam [<UserTypeEntity>] show shareddriveacls ... formatjson
Some checks failed
Build and test GAM / build (Win64, build, 9, VC-WIN64A, windows-2022) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 3, linux-aarch64, [self-hosted linux arm64]) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 5, linux-aarch64, [self-hosted linux arm64], yes) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 7, darwin64-arm64, macos-14) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 8, darwin64-arm64, macos-15) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 1, linux-x86_64, ubuntu-22.04) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 2, linux-x86_64, ubuntu-24.04) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 4, linux-x86_64, ubuntu-22.04, yes) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 6, darwin64-x86_64, macos-13) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 10, ubuntu-24.04, 3.9) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 11, ubuntu-24.04, 3.10) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 12, ubuntu-24.04, 3.11) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 13, ubuntu-24.04, 3.12) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Check for Google Root CA Updates / check-apis (push) Has been cancelled
Build and test GAM / merge (push) Has been cancelled
Build and test GAM / publish (push) Has been cancelled
2024-12-27 19:27:04 -08:00
Ross Scroggs
6a82343668 Update gam-install.sh
Some checks are pending
Build and test GAM / build (Win64, build, 9, VC-WIN64A, windows-2022) (push) Waiting to run
Build and test GAM / build (aarch64, build, 3, linux-aarch64, [self-hosted linux arm64]) (push) Waiting to run
Build and test GAM / build (aarch64, build, 5, linux-aarch64, [self-hosted linux arm64], yes) (push) Waiting to run
Build and test GAM / build (aarch64, build, 7, darwin64-arm64, macos-14) (push) Waiting to run
Build and test GAM / build (aarch64, build, 8, darwin64-arm64, macos-15) (push) Waiting to run
Build and test GAM / build (x86_64, build, 1, linux-x86_64, ubuntu-22.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 2, linux-x86_64, ubuntu-24.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 4, linux-x86_64, ubuntu-22.04, yes) (push) Waiting to run
Build and test GAM / build (x86_64, build, 6, darwin64-x86_64, macos-13) (push) Waiting to run
Build and test GAM / build (x86_64, test, 10, ubuntu-24.04, 3.9) (push) Waiting to run
Build and test GAM / build (x86_64, test, 11, ubuntu-24.04, 3.10) (push) Waiting to run
Build and test GAM / build (x86_64, test, 12, ubuntu-24.04, 3.11) (push) Waiting to run
Build and test GAM / build (x86_64, test, 13, ubuntu-24.04, 3.12) (push) Waiting to run
Build and test GAM / merge (push) Blocked by required conditions
Build and test GAM / publish (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Check for Google Root CA Updates / check-apis (push) Waiting to run
2024-12-27 12:21:43 -08:00
Ross Scroggs
47ec93140e Updated code to eliminate trap caused by bug introduced in 7.02.00
Some checks failed
Build and test GAM / build (Win64, build, 9, VC-WIN64A, windows-2022) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 3, linux-aarch64, [self-hosted linux arm64]) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 5, linux-aarch64, [self-hosted linux arm64], yes) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 7, darwin64-arm64, macos-14) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 8, darwin64-arm64, macos-15) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 1, linux-x86_64, ubuntu-22.04) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 2, linux-x86_64, ubuntu-24.04) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 4, linux-x86_64, ubuntu-22.04, yes) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 6, darwin64-x86_64, macos-13) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 10, ubuntu-24.04, 3.9) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 11, ubuntu-24.04, 3.10) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 12, ubuntu-24.04, 3.11) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 13, ubuntu-24.04, 3.12) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Check for Google Root CA Updates / check-apis (push) Has been cancelled
Build and test GAM / merge (push) Has been cancelled
Build and test GAM / publish (push) Has been cancelled
2024-12-26 08:36:25 -08:00
Ross Scroggs
d0d5ac74da Fix issue #1732
Some checks failed
Build and test GAM / build (Win64, build, 9, VC-WIN64A, windows-2022) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 3, linux-aarch64, [self-hosted linux arm64]) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 5, linux-aarch64, [self-hosted linux arm64], yes) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 7, darwin64-arm64, macos-14) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 8, darwin64-arm64, macos-15) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 1, linux-x86_64, ubuntu-22.04) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 2, linux-x86_64, ubuntu-24.04) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 4, linux-x86_64, ubuntu-22.04, yes) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 6, darwin64-x86_64, macos-13) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 10, ubuntu-24.04, 3.9) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 11, ubuntu-24.04, 3.10) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 12, ubuntu-24.04, 3.11) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 13, ubuntu-24.04, 3.12) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Check for Google Root CA Updates / check-apis (push) Has been cancelled
Build and test GAM / merge (push) Has been cancelled
Build and test GAM / publish (push) Has been cancelled
2024-12-23 12:24:30 -08:00
Ross Scroggs
a9e28e966a Added option archive to gam <UserTypeEntity> update license <NewSKUID> from <OldSKUID>
Some checks failed
Build and test GAM / build (Win64, build, 9, VC-WIN64A, windows-2022) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 3, linux-aarch64, [self-hosted linux arm64]) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 5, linux-aarch64, [self-hosted linux arm64], yes) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 7, darwin64-arm64, macos-14) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 8, darwin64-arm64, macos-15) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 1, linux-x86_64, ubuntu-22.04) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 2, linux-x86_64, ubuntu-24.04) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 4, linux-x86_64, ubuntu-22.04, yes) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 6, darwin64-x86_64, macos-13) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 10, ubuntu-24.04, 3.9) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 11, ubuntu-24.04, 3.10) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 12, ubuntu-24.04, 3.11) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 13, ubuntu-24.04, 3.12) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Check for Google Root CA Updates / check-apis (push) Has been cancelled
Build and test GAM / merge (push) Has been cancelled
Build and test GAM / publish (push) Has been cancelled
2024-12-18 08:24:15 -08:00
Ross Scroggs
89970bbf0d Updated gam <UserTypeEntity> archive messages <GroupItem>
Some checks are pending
Build and test GAM / build (Win64, build, 9, VC-WIN64A, windows-2022) (push) Waiting to run
Build and test GAM / build (aarch64, build, 3, linux-aarch64, [self-hosted linux arm64]) (push) Waiting to run
Build and test GAM / build (aarch64, build, 5, linux-aarch64, [self-hosted linux arm64], yes) (push) Waiting to run
Build and test GAM / build (aarch64, build, 7, darwin64-arm64, macos-14) (push) Waiting to run
Build and test GAM / build (aarch64, build, 8, darwin64-arm64, macos-15) (push) Waiting to run
Build and test GAM / build (x86_64, build, 1, linux-x86_64, ubuntu-22.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 2, linux-x86_64, ubuntu-24.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 4, linux-x86_64, ubuntu-22.04, yes) (push) Waiting to run
Build and test GAM / build (x86_64, build, 6, darwin64-x86_64, macos-13) (push) Waiting to run
Build and test GAM / build (x86_64, test, 10, ubuntu-24.04, 3.9) (push) Waiting to run
Build and test GAM / build (x86_64, test, 11, ubuntu-24.04, 3.10) (push) Waiting to run
Build and test GAM / build (x86_64, test, 12, ubuntu-24.04, 3.11) (push) Waiting to run
Build and test GAM / build (x86_64, test, 13, ubuntu-24.04, 3.12) (push) Waiting to run
Build and test GAM / merge (push) Blocked by required conditions
Build and test GAM / publish (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Check for Google Root CA Updates / check-apis (push) Waiting to run
2024-12-17 09:04:59 -08:00
Ross Scroggs
0b16bded50 Add support for locking/unlocking cigroups #1728
Some checks failed
Build and test GAM / build (Win64, build, 9, VC-WIN64A, windows-2022) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 3, linux-aarch64, [self-hosted linux arm64]) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 5, linux-aarch64, [self-hosted linux arm64], yes) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 7, darwin64-arm64, macos-14) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 8, darwin64-arm64, macos-15) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 1, linux-x86_64, ubuntu-22.04) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 2, linux-x86_64, ubuntu-24.04) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 4, linux-x86_64, ubuntu-22.04, yes) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 6, darwin64-x86_64, macos-13) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 10, ubuntu-24.04, 3.9) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 11, ubuntu-24.04, 3.10) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 12, ubuntu-24.04, 3.11) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 13, ubuntu-24.04, 3.12) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Check for Google Root CA Updates / check-apis (push) Has been cancelled
Build and test GAM / merge (push) Has been cancelled
Build and test GAM / publish (push) Has been cancelled
2024-12-13 19:48:39 -08:00
Jay Lee
2ea6f773cd catch non-success HTTP codes on GitHub URL retrieval
Some checks are pending
Build and test GAM / build (Win64, build, 9, VC-WIN64A, windows-2022) (push) Waiting to run
Build and test GAM / build (aarch64, build, 3, linux-aarch64, [self-hosted linux arm64]) (push) Waiting to run
Build and test GAM / build (aarch64, build, 5, linux-aarch64, [self-hosted linux arm64], yes) (push) Waiting to run
Build and test GAM / build (aarch64, build, 7, darwin64-arm64, macos-14) (push) Waiting to run
Build and test GAM / build (aarch64, build, 8, darwin64-arm64, macos-15) (push) Waiting to run
Build and test GAM / build (x86_64, build, 1, linux-x86_64, ubuntu-22.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 2, linux-x86_64, ubuntu-24.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 4, linux-x86_64, ubuntu-22.04, yes) (push) Waiting to run
Build and test GAM / build (x86_64, build, 6, darwin64-x86_64, macos-13) (push) Waiting to run
Build and test GAM / build (x86_64, test, 10, ubuntu-24.04, 3.9) (push) Waiting to run
Build and test GAM / build (x86_64, test, 11, ubuntu-24.04, 3.10) (push) Waiting to run
Build and test GAM / build (x86_64, test, 12, ubuntu-24.04, 3.11) (push) Waiting to run
Build and test GAM / build (x86_64, test, 13, ubuntu-24.04, 3.12) (push) Waiting to run
Build and test GAM / merge (push) Blocked by required conditions
Build and test GAM / publish (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Check for Google Root CA Updates / check-apis (push) Waiting to run
2024-12-12 12:52:02 -08:00
Jay Lee
bb922dcff6 gam-install.sh full rollback
Some checks are pending
Build and test GAM / build (Win64, build, 9, VC-WIN64A, windows-2022) (push) Waiting to run
Build and test GAM / build (aarch64, build, 3, linux-aarch64, [self-hosted linux arm64]) (push) Waiting to run
Build and test GAM / build (aarch64, build, 5, linux-aarch64, [self-hosted linux arm64], yes) (push) Waiting to run
Build and test GAM / build (aarch64, build, 7, darwin64-arm64, macos-14) (push) Waiting to run
Build and test GAM / build (aarch64, build, 8, darwin64-arm64, macos-15) (push) Waiting to run
Build and test GAM / build (x86_64, build, 1, linux-x86_64, ubuntu-22.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 2, linux-x86_64, ubuntu-24.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 4, linux-x86_64, ubuntu-22.04, yes) (push) Waiting to run
Build and test GAM / build (x86_64, build, 6, darwin64-x86_64, macos-13) (push) Waiting to run
Build and test GAM / build (x86_64, test, 10, ubuntu-24.04, 3.9) (push) Waiting to run
Build and test GAM / build (x86_64, test, 11, ubuntu-24.04, 3.10) (push) Waiting to run
Build and test GAM / build (x86_64, test, 12, ubuntu-24.04, 3.11) (push) Waiting to run
Build and test GAM / build (x86_64, test, 13, ubuntu-24.04, 3.12) (push) Waiting to run
Build and test GAM / merge (push) Blocked by required conditions
Build and test GAM / publish (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Check for Google Root CA Updates / check-apis (push) Waiting to run
2024-12-11 15:31:57 -08:00
Jay Lee
b38bf3e9bb undo shell install changes that MacOS doesn't like 2024-12-11 15:27:10 -08:00
Jay Lee
39e5a45d72 gam-install.sh: check curl response code 2024-12-11 15:23:25 -08:00
8 changed files with 276 additions and 141 deletions

View File

@@ -3929,6 +3929,7 @@ gam update cigroup <GroupEntity> [copyfrom <GroupItem>] <GroupAttribute>
[security|makesecuritygroup| [security|makesecuritygroup|
dynamicsecurity|makedynamicsecuritygroup| dynamicsecurity|makedynamicsecuritygroup|
lockedsecurity|makelockedsecuritygroup] lockedsecurity|makelockedsecuritygroup]
[locked|unlocked]
[dynamic <QueryDynamicGroup>] [dynamic <QueryDynamicGroup>]
[memberrestrictions <QueryMemberRestrictions>] [memberrestrictions <QueryMemberRestrictions>]
gam update cigroups <GroupEntity> create|add [<GroupRole>] gam update cigroups <GroupEntity> create|add [<GroupRole>]
@@ -7820,7 +7821,8 @@ gam <UserTypeEntity> delete noteacl <NotesNameEntity>
# Users - Licenses # Users - Licenses
gam <UserTypeEntity> create|add license <SKUIDList> [product|productid <ProductID>] [preview] [actioncsv] gam <UserTypeEntity> create|add license <SKUIDList> [product|productid <ProductID>] [preview] [actioncsv]
gam <UserTypeEntity> update license <SKUID> [product|productid <ProductID>] [from] <SKUID> [preview] [actioncsv] gam <UserTypeEntity> update license <NewSKUID> [product|productid <ProductID>] [from] <OldSKUID>
[preview|archive] [actioncsv]
gam <UserTypeEntity> delete license <SKUIDList> [product|productid <ProductID>] [preview] [actioncsv] gam <UserTypeEntity> delete license <SKUIDList> [product|productid <ProductID>] [preview] [actioncsv]
gam <UserTypeEntity> sync license <SKUIDList> [product|productid <ProductID>] [addonly|removeonly] [allskus|onesku] [preview] [actioncsv] gam <UserTypeEntity> sync license <SKUIDList> [product|productid <ProductID>] [addonly|removeonly] [allskus|onesku] [preview] [actioncsv]

View File

@@ -1,3 +1,49 @@
7.02.05
Updated `gam [<UserTypeEntity>] show shareddriveacls ... formatjson` to not display this line
which interferes with the JSON output.
```
User: user@domain.com, Show N Shared Drives
```
7.02.04
Updated code to eliminate trap caused by bug introduced in 7.02.00 that occurs when an invalid domain or OU is specified.
7.02.03
Added option `archive` to `gam <UserTypeEntity> update license <NewSKUID> from <OldSKUID>` that causes GAM
to archive `<UserTypeEntity>` after updating their license to `<NewSKUID>`. This will be used when you want to
archive a user with a non-archivable license. The `<NewSKUID>` license is assigned to the user and it then converts
to the equivalent Archived User license when the user is archived.
`<NewSKUID>` must be one of the following SKUs:
```
Google-Apps-Unlimited - G Suite Business
1010020020 - Google Workspace Enterprise Plus
1010020025 - Google Workspace Business Plus
1010020026 - Google Workspace Enterprise Standard
1010020027 - Google Workspace Business Starter
1010020028 - Google Workspace Business Standard
```
7.02.02
Updated `gam <UserTypeEntity> archive messages <GroupItem>` to retry the following unexpected error
that occurs after many messages have been successfully archived.
`ERROR: 404: notFound - Unable to lookup group`
7.02.01
Added options `locked` and `unlocked` to `gam update cigroups` that allow locking/unlocking groups.
* See: https://workspaceupdates.googleblog.com/2024/12/locked-groups-open-beta.html
You'll have to do a `gam oauth create` and enable the following scope to use these options:
```
[*] 22) Cloud Identity Groups API Beta (Enables group locking/unlocking)
```
7.02.00 7.02.00
Improved the error message displayed for user service account access commands when: Improved the error message displayed for user service account access commands when:

View File

@@ -97,13 +97,6 @@ else
fi fi
} }
reverse() {
for (( i = ${#*}; i > 0; i-- ))
{
echo ${!i}
}
}
if [ "$gamversion" == "latest" ]; then if [ "$gamversion" == "latest" ]; then
release_url="https://api.github.com/repos/GAM-team/GAM/releases/latest" release_url="https://api.github.com/repos/GAM-team/GAM/releases/latest"
elif [ "$gamversion" == "prerelease" -o "$gamversion" == "draft" ]; then elif [ "$gamversion" == "prerelease" -o "$gamversion" == "draft" ]; then
@@ -126,9 +119,16 @@ release_json=$(curl \
-H "Accept: application/vnd.github+json" \ -H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \ -H "X-GitHub-Api-Version: 2022-11-28" \
"$release_url" \ "$release_url" \
2>&1 /dev/null) --fail-with-body)
curl_exit_code=$?
if [ $curl_exit_code -ne 0 ]; then
echo_red "ERROR retrieving URL: ${release_json}"
exit
else
echo_green "done"
fi
echo_yellow "Getting file and download URL..." echo_yellow "Calculating download URL for this device..."
# Python is sadly the nearest to universal way to safely handle JSON with Bash # Python is sadly the nearest to universal way to safely handle JSON with Bash
# At least this code should be compatible with just about any Python version ever # At least this code should be compatible with just about any Python version ever
# unlike GAM itself. If some users don't have Python we can try grep / sed / etc # unlike GAM itself. If some users don't have Python we can try grep / sed / etc
@@ -323,6 +323,8 @@ echo_yellow "Downloading ${download_url} to $temp_archive_dir ($check_type)..."
(cd "$temp_archive_dir" && curl -O -L -s "${curl_opts[@]}" "$download_url") (cd "$temp_archive_dir" && curl -O -L -s "${curl_opts[@]}" "$download_url")
mkdir -p "$target_dir" mkdir -p "$target_dir"
echo_yellow "Deleting contents of $target_dir/gam7/lib"
rm -frv "$target_dir/gam7/lib"
echo_yellow "Extracting archive to $target_dir" echo_yellow "Extracting archive to $target_dir"
if [[ "$name" =~ tar.xz|tar.gz|tar ]]; then if [[ "$name" =~ tar.xz|tar.gz|tar ]]; then
@@ -379,7 +381,7 @@ while true; do
;; ;;
[Nn]*) [Nn]*)
# config_cmd="config no_browser true" # config_cmd="config no_browser true"
touch "$target_dir/gam/nobrowser.txt" > /dev/null 2>&1 touch "$target_dir/gam7/nobrowser.txt" > /dev/null 2>&1
break break
;; ;;
*) *)
@@ -487,4 +489,3 @@ echo_green "GAM installation and setup complete!"
if [ "$update_profile" = true ]; then if [ "$update_profile" = true ]; then
echo_green "Please restart your terminal shell or to get started right away run:\n\n$alias_line" echo_green "Please restart your terminal shell or to get started right away run:\n\n$alias_line"
fi fi

View File

@@ -25,7 +25,7 @@ https://github.com/GAM-team/GAM/wiki
""" """
__author__ = 'GAM Team <google-apps-manager@googlegroups.com>' __author__ = 'GAM Team <google-apps-manager@googlegroups.com>'
__version__ = '7.02.00' __version__ = '7.02.05'
__license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)' __license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
#pylint: disable=wrong-import-position #pylint: disable=wrong-import-position
@@ -628,7 +628,7 @@ def formatKeyValueList(prefixStr, kvList, suffixStr):
msg += suffixStr msg += suffixStr
return msg return msg
# Something's wrong with CustomerID # Something's wrong with CustomerID??
def accessErrorMessage(cd, errMsg=None): def accessErrorMessage(cd, errMsg=None):
if cd is None: if cd is None:
cd = buildGAPIObject(API.DIRECTORY) cd = buildGAPIObject(API.DIRECTORY)
@@ -652,10 +652,12 @@ def accessErrorMessage(cd, errMsg=None):
Ent.DOMAIN, GC.Values[GC.DOMAIN], Ent.DOMAIN, GC.Values[GC.DOMAIN],
Ent.USER, GM.Globals[GM.ADMIN]])+[Msg.ACCESS_FORBIDDEN], Ent.USER, GM.Globals[GM.ADMIN]])+[Msg.ACCESS_FORBIDDEN],
'') '')
return formatKeyValueList('', if errMsg:
[Ent.Singular(Ent.CUSTOMER_ID), GC.Values[GC.CUSTOMER_ID], return formatKeyValueList('',
errMsg], [Ent.Singular(Ent.CUSTOMER_ID), GC.Values[GC.CUSTOMER_ID],
'') errMsg],
'')
return None
def accessErrorExit(cd, errMsg=None): def accessErrorExit(cd, errMsg=None):
systemErrorExit(INVALID_DOMAIN_RC, accessErrorMessage(cd or buildGAPIObject(API.DIRECTORY), errMsg)) systemErrorExit(INVALID_DOMAIN_RC, accessErrorMessage(cd or buildGAPIObject(API.DIRECTORY), errMsg))
@@ -5791,6 +5793,8 @@ def convertUIDtoEmailAddressWithType(emailAddressOrUID, cd=None, sal=None, email
def convertUIDtoEmailAddress(emailAddressOrUID, cd=None, emailTypes=None, def convertUIDtoEmailAddress(emailAddressOrUID, cd=None, emailTypes=None,
checkForCustomerId=False, ciGroupsAPI=False, aliasAllowed=True): checkForCustomerId=False, ciGroupsAPI=False, aliasAllowed=True):
if ciGroupsAPI: if ciGroupsAPI:
if emailAddressOrUID.startswith('cbcm-browser.') or emailAddressOrUID.startswith('chrome-os-device.'):
return emailAddressOrUID
normalizedEmailAddressOrUID = normalizeEmailAddressOrUID(emailAddressOrUID, ciGroupsAPI=ciGroupsAPI) normalizedEmailAddressOrUID = normalizeEmailAddressOrUID(emailAddressOrUID, ciGroupsAPI=ciGroupsAPI)
if normalizedEmailAddressOrUID.startswith('cbcm-browser.') or normalizedEmailAddressOrUID.startswith('chrome-os-device.'): if normalizedEmailAddressOrUID.startswith('cbcm-browser.') or normalizedEmailAddressOrUID.startswith('chrome-os-device.'):
return normalizedEmailAddressOrUID return normalizedEmailAddressOrUID
@@ -5974,11 +5978,8 @@ def _checkMemberCategory(member, memberDisplayOptions):
return True return True
return False return False
CIGROUP_MEMBER_API = API.CLOUDIDENTITY_GROUPS
CIGROUP_MEMBERKEY = 'preferredMemberKey'
def _checkCIMemberCategory(member, memberDisplayOptions): def _checkCIMemberCategory(member, memberDisplayOptions):
member_email = member.get(CIGROUP_MEMBERKEY,{}).get('id', '') member_email = member.get('preferredMemberKey', {}).get('id', '')
if member_email.find('@') > 0: if member_email.find('@') > 0:
_, domain = member_email.lower().split('@', 1) _, domain = member_email.lower().split('@', 1)
category = 'internal' if domain in memberDisplayOptions['internalDomains'] else 'external' category = 'internal' if domain in memberDisplayOptions['internalDomains'] else 'external'
@@ -5992,7 +5993,7 @@ def _checkCIMemberCategory(member, memberDisplayOptions):
def getCIGroupMemberRoleFixType(member): def getCIGroupMemberRoleFixType(member):
''' fixes missing type and returns the highest role of member ''' ''' fixes missing type and returns the highest role of member '''
if 'type' not in member: if 'type' not in member:
if member[CIGROUP_MEMBERKEY]['id'] == GC.Values[GC.CUSTOMER_ID]: if member['preferredMemberKey']['id'] == GC.Values[GC.CUSTOMER_ID]:
member['type'] = Ent.TYPE_CUSTOMER member['type'] = Ent.TYPE_CUSTOMER
else: else:
member['type'] = Ent.TYPE_OTHER member['type'] = Ent.TYPE_OTHER
@@ -6012,7 +6013,7 @@ def getCIGroupTransitiveMemberRoleFixType(groupName, tmember):
''' map transitive member to normal member ''' ''' map transitive member to normal member '''
tid = tmember['preferredMemberKey'][0].get('id', GC.Values[GC.CUSTOMER_ID]) if tmember['preferredMemberKey'] else '' tid = tmember['preferredMemberKey'][0].get('id', GC.Values[GC.CUSTOMER_ID]) if tmember['preferredMemberKey'] else ''
ttype, tname = tmember['member'].split('/') ttype, tname = tmember['member'].split('/')
member = {'name': f'{groupName}/membershipd/{tname}', CIGROUP_MEMBERKEY: {'id': tid}} member = {'name': f'{groupName}/membershipd/{tname}', 'preferredMemberKey': {'id': tid}}
if 'type' not in tmember: if 'type' not in tmember:
if tid == GC.Values[GC.CUSTOMER_ID]: if tid == GC.Values[GC.CUSTOMER_ID]:
member['type'] = Ent.TYPE_CUSTOMER member['type'] = Ent.TYPE_CUSTOMER
@@ -6091,6 +6092,11 @@ def convertGroupEmailToCloudID(ci, group, i=0, count=0):
Act.Set(action) Act.Set(action)
return (ci, None, None) return (ci, None, None)
CIGROUP_DISCUSSION_FORUM_LABEL = 'cloudidentity.googleapis.com/groups.discussion_forum'
CIGROUP_DYNAMIC_LABEL = 'cloudidentity.googleapis.com/groups.dynamic'
CIGROUP_SECURITY_LABEL = 'cloudidentity.googleapis.com/groups.security'
CIGROUP_LOCKED_LABEL = 'cloudidentity.googleapis.com/groups.locked'
def getCIGroupMembershipGraph(ci, member): def getCIGroupMembershipGraph(ci, member):
if not ci: if not ci:
ci = buildGAPIObject(API.CLOUDIDENTITY_GROUPS) ci = buildGAPIObject(API.CLOUDIDENTITY_GROUPS)
@@ -6099,7 +6105,7 @@ def getCIGroupMembershipGraph(ci, member):
result = callGAPI(ci.groups().memberships(), 'getMembershipGraph', result = callGAPI(ci.groups().memberships(), 'getMembershipGraph',
throwReasons=GAPI.CIGROUP_LIST_THROW_REASONS, retryReasons=GAPI.CIGROUP_RETRY_REASONS, throwReasons=GAPI.CIGROUP_LIST_THROW_REASONS, retryReasons=GAPI.CIGROUP_RETRY_REASONS,
parent=parent, parent=parent,
query=f"member_key_id == '{member}' && 'cloudidentity.googleapis.com/groups.discussion_forum' in labels") query=f"member_key_id == '{member}' && CIGROUP_DISCUSSION_FORUM_LABEL in labels")
return (ci, result.get('response', {})) return (ci, result.get('response', {}))
except (GAPI.resourceNotFound, GAPI.domainNotFound, GAPI.domainCannotUseApis, except (GAPI.resourceNotFound, GAPI.domainNotFound, GAPI.domainCannotUseApis,
GAPI.forbidden, GAPI.badRequest, GAPI.invalid, GAPI.invalidArgument, GAPI.forbidden, GAPI.badRequest, GAPI.invalid, GAPI.invalidArgument,
@@ -6190,7 +6196,7 @@ def getItemsToModify(entityType, entity, memberRoles=None, isSuspended=None, isA
pageMessage=getPageMessageForWhom(), pageMessage=getPageMessageForWhom(),
throwReasons=GAPI.CIGROUP_LIST_THROW_REASONS, retryReasons=GAPI.CIGROUP_RETRY_REASONS, throwReasons=GAPI.CIGROUP_LIST_THROW_REASONS, retryReasons=GAPI.CIGROUP_RETRY_REASONS,
parent=groupName, view='FULL', parent=groupName, view='FULL',
fields=f'nextPageToken,memberships(name,{CIGROUP_MEMBERKEY}(id),roles(name),type)', pageSize=GC.Values[GC.MEMBER_MAX_RESULTS]) fields='nextPageToken,memberships(name,preferredMemberKey(id),roles(name),type)', pageSize=GC.Values[GC.MEMBER_MAX_RESULTS])
except (GAPI.resourceNotFound, GAPI.domainNotFound, GAPI.domainCannotUseApis, except (GAPI.resourceNotFound, GAPI.domainNotFound, GAPI.domainCannotUseApis,
GAPI.forbidden, GAPI.badRequest, GAPI.invalid, GAPI.invalidArgument, GAPI.forbidden, GAPI.badRequest, GAPI.invalid, GAPI.invalidArgument,
GAPI.systemError, GAPI.permissionDenied, GAPI.serviceNotAvailable): GAPI.systemError, GAPI.permissionDenied, GAPI.serviceNotAvailable):
@@ -6200,7 +6206,7 @@ def getItemsToModify(entityType, entity, memberRoles=None, isSuspended=None, isA
for member in result: for member in result:
getCIGroupMemberRoleFixType(member) getCIGroupMemberRoleFixType(member)
if member['type'] == Ent.TYPE_USER: if member['type'] == Ent.TYPE_USER:
email = member.get(CIGROUP_MEMBERKEY, {}).get('id', '') email = member.get('preferredMemberKey', {}).get('id', '')
if (email and _checkMemberRole(member, validRoles) and email not in entitySet): if (email and _checkMemberRole(member, validRoles) and email not in entitySet):
entitySet.add(email) entitySet.add(email)
entityList.append(email) entityList.append(email)
@@ -6350,7 +6356,7 @@ def getItemsToModify(entityType, entity, memberRoles=None, isSuspended=None, isA
else: else:
_showInvalidEntity(Ent.GROUP, group) _showInvalidEntity(Ent.GROUP, group)
elif entityType in {Cmd.ENTITY_CIGROUP, Cmd.ENTITY_CIGROUPS}: elif entityType in {Cmd.ENTITY_CIGROUP, Cmd.ENTITY_CIGROUPS}:
ci = buildGAPIObject(CIGROUP_MEMBER_API) ci = buildGAPIObject(API.CLOUDIDENTITY_GROUPS)
groups = convertEntityToList(entity, nonListEntityType=entityType in {Cmd.ENTITY_CIGROUP}) groups = convertEntityToList(entity, nonListEntityType=entityType in {Cmd.ENTITY_CIGROUP})
for group in groups: for group in groups:
if validateEmailAddressOrUID(group, checkPeople=False, ciGroupsAPI=True): if validateEmailAddressOrUID(group, checkPeople=False, ciGroupsAPI=True):
@@ -6362,7 +6368,7 @@ def getItemsToModify(entityType, entity, memberRoles=None, isSuspended=None, isA
pageMessage=getPageMessageForWhom(), pageMessage=getPageMessageForWhom(),
throwReasons=GAPI.CIGROUP_LIST_THROW_REASONS, retryReasons=GAPI.CIGROUP_RETRY_REASONS, throwReasons=GAPI.CIGROUP_LIST_THROW_REASONS, retryReasons=GAPI.CIGROUP_RETRY_REASONS,
parent=name, view='FULL', parent=name, view='FULL',
fields=f'nextPageToken,memberships({CIGROUP_MEMBERKEY}(id),roles(name),type)', fields='nextPageToken,memberships(preferredMemberKey(id),roles(name),type)',
pageSize=GC.Values[GC.MEMBER_MAX_RESULTS]) pageSize=GC.Values[GC.MEMBER_MAX_RESULTS])
except (GAPI.resourceNotFound, GAPI.domainNotFound, GAPI.domainCannotUseApis, except (GAPI.resourceNotFound, GAPI.domainNotFound, GAPI.domainCannotUseApis,
GAPI.forbidden, GAPI.badRequest, GAPI.invalid, GAPI.invalidArgument, GAPI.forbidden, GAPI.badRequest, GAPI.invalid, GAPI.invalidArgument,
@@ -6372,7 +6378,7 @@ def getItemsToModify(entityType, entity, memberRoles=None, isSuspended=None, isA
continue continue
for member in result: for member in result:
getCIGroupMemberRoleFixType(member) getCIGroupMemberRoleFixType(member)
email = member.get(CIGROUP_MEMBERKEY, {}).get('id', '') email = member.get('preferredMemberKey', {}).get('id', '')
if (email and (groupMemberType in ('ALL', member['type'])) and if (email and (groupMemberType in ('ALL', member['type'])) and
_checkMemberRole(member, validRoles) and email not in entitySet): _checkMemberRole(member, validRoles) and email not in entitySet):
entitySet.add(email) entitySet.add(email)
@@ -22682,7 +22688,7 @@ def _processPeopleContactPhotos(users, function):
with open(os.path.expanduser(filename), 'rb') as f: with open(os.path.expanduser(filename), 'rb') as f:
image_data = f.read() image_data = f.read()
callGAPI(people.people(), function, callGAPI(people.people(), function,
throwReasons=[GAPI.NOT_FOUND, GAPI.INTERNAL_ERROR]+GAPI.PEOPLE_ACCESS_THROW_REASONS, throwReasons=[GAPI.NOT_FOUND, GAPI.INVALID_ARGUMENT, GAPI.INTERNAL_ERROR]+GAPI.PEOPLE_ACCESS_THROW_REASONS,
resourceName=resourceName, resourceName=resourceName,
body={'photoBytes': base64.urlsafe_b64encode(image_data).decode(UTF8)}) body={'photoBytes': base64.urlsafe_b64encode(image_data).decode(UTF8)})
entityActionPerformed([entityType, user, peopleEntityType, resourceName, Ent.PHOTO, filename], j, jcount) entityActionPerformed([entityType, user, peopleEntityType, resourceName, Ent.PHOTO, filename], j, jcount)
@@ -22718,7 +22724,7 @@ def _processPeopleContactPhotos(users, function):
else: #elif function == 'deleteContactPhoto': else: #elif function == 'deleteContactPhoto':
filename = '' filename = ''
callGAPI(people.people(), function, callGAPI(people.people(), function,
throwReasons=[GAPI.NOT_FOUND, GAPI.INTERNAL_ERROR, GAPI.PHOTO_NOT_FOUND]+GAPI.PEOPLE_ACCESS_THROW_REASONS, throwReasons=[GAPI.NOT_FOUND, GAPI.INVALID_ARGUMENT, GAPI.INTERNAL_ERROR, GAPI.PHOTO_NOT_FOUND]+GAPI.PEOPLE_ACCESS_THROW_REASONS,
resourceName=resourceName) resourceName=resourceName)
entityActionPerformed([entityType, user, peopleEntityType, resourceName, Ent.PHOTO, filename], j, jcount) entityActionPerformed([entityType, user, peopleEntityType, resourceName, Ent.PHOTO, filename], j, jcount)
except (GAPI.notFound, GAPI.internalError): except (GAPI.notFound, GAPI.internalError):
@@ -22726,7 +22732,7 @@ def _processPeopleContactPhotos(users, function):
continue continue
except GAPI.photoNotFound: except GAPI.photoNotFound:
entityDoesNotHaveItemWarning([entityType, user, peopleEntityType, resourceName, Ent.PHOTO, filename], j, jcount) entityDoesNotHaveItemWarning([entityType, user, peopleEntityType, resourceName, Ent.PHOTO, filename], j, jcount)
except (OSError, IOError) as e: except (GAPI.invalidArgument, OSError, IOError) as e:
entityActionFailedWarning([entityType, user, peopleEntityType, resourceName, Ent.PHOTO, filename], str(e), j, jcount) entityActionFailedWarning([entityType, user, peopleEntityType, resourceName, Ent.PHOTO, filename], str(e), j, jcount)
except (GAPI.serviceNotAvailable, GAPI.forbidden): except (GAPI.serviceNotAvailable, GAPI.forbidden):
ClientAPIAccessDeniedExit() ClientAPIAccessDeniedExit()
@@ -23165,8 +23171,8 @@ def printShowContactDelegates(users):
continue continue
jcount = len(delegates) jcount = len(delegates)
if not csvPF: if not csvPF:
entityPerformActionNumItems([Ent.USER, user], jcount, Ent.CONTACT_DELEGATE, i, count)
if not csvStyle: if not csvStyle:
entityPerformActionNumItems([Ent.USER, user], jcount, Ent.CONTACT_DELEGATE, i, count)
Ind.Increment() Ind.Increment()
j = 0 j = 0
for delegate in delegates: for delegate in delegates:
@@ -31649,7 +31655,7 @@ def doCreateGroup(ciGroupsAPI=False):
parent = f'customers/{GC.Values[GC.CUSTOMER_ID]}' parent = f'customers/{GC.Values[GC.CUSTOMER_ID]}'
body = {'groupKey': {'id': groupEmail}, body = {'groupKey': {'id': groupEmail},
'parent': parent, 'parent': parent,
'labels': {'cloudidentity.googleapis.com/groups.discussion_forum': ''}, 'labels': {CIGROUP_DISCUSSION_FORUM_LABEL: ''},
} }
gs_body = {} gs_body = {}
while Cmd.ArgumentsRemaining(): while Cmd.ArgumentsRemaining():
@@ -32171,8 +32177,8 @@ def doUpdateGroups():
elif myarg == 'getbeforeupdate': elif myarg == 'getbeforeupdate':
getBeforeUpdate = True getBeforeUpdate = True
elif myarg in {'security', 'makesecuritygroup'}: elif myarg in {'security', 'makesecuritygroup'}:
ci_body['labels'] = {'cloudidentity.googleapis.com/groups.discussion_forum': '', ci_body['labels'] = {CIGROUP_DISCUSSION_FORUM_LABEL: '',
'cloudidentity.googleapis.com/groups.security': ''} CIGROUP_SECURITY_LABEL: ''}
elif myarg == 'json': elif myarg == 'json':
gs_body.update(getJSON(GROUP_JSON_SKIP_FIELDS)) gs_body.update(getJSON(GROUP_JSON_SKIP_FIELDS))
elif myarg == 'accesstype': elif myarg == 'accesstype':
@@ -32691,7 +32697,7 @@ def checkMemberMatch(member, memberOptions):
def checkCIMemberMatch(member, memberOptions): def checkCIMemberMatch(member, memberOptions):
if not memberOptions[MEMBEROPTION_MATCHPATTERN]: if not memberOptions[MEMBEROPTION_MATCHPATTERN]:
return True return True
if memberOptions[MEMBEROPTION_MATCHPATTERN].match(member.get(CIGROUP_MEMBERKEY, {}).get('id', '')): if memberOptions[MEMBEROPTION_MATCHPATTERN].match(member.get('preferredMemberKey', {}).get('id', '')):
return memberOptions[MEMBEROPTION_DISPLAYMATCH] return memberOptions[MEMBEROPTION_DISPLAYMATCH]
return not memberOptions[MEMBEROPTION_DISPLAYMATCH] return not memberOptions[MEMBEROPTION_DISPLAYMATCH]
@@ -33279,7 +33285,7 @@ def addMemberInfoToRow(row, groupMembers, typesSet, memberOptions, memberDisplay
if not ciGroupsAPI: if not ciGroupsAPI:
member_email = member.get('email', member.get('id', None)) member_email = member.get('email', member.get('id', None))
else: else:
member_email = member.get(CIGROUP_MEMBERKEY, {}).get('id', member['name']) member_email = member.get('preferredMemberKey', {}).get('id', member['name'])
if not member_email: if not member_email:
writeStderr(f' Not sure what to do with: {member}\n') writeStderr(f' Not sure what to do with: {member}\n')
continue continue
@@ -33789,9 +33795,9 @@ def doPrintGroups():
csvPF.writeCSVfile('Groups') csvPF.writeCSVfile('Groups')
def mapCIGroupMemberFieldNames(member): def mapCIGroupMemberFieldNames(member):
member['email'] = member[CIGROUP_MEMBERKEY].pop('id') member['email'] = member['preferredMemberKey'].pop('id')
if not member[CIGROUP_MEMBERKEY]: if not member['preferredMemberKey']:
member.pop(CIGROUP_MEMBERKEY) member.pop('preferredMemberKey')
if 'name' in member: if 'name' in member:
member['id'] = member.pop('name') member['id'] = member.pop('name')
@@ -34556,7 +34562,8 @@ def doCreateCIGroup():
# [security|makesecuritygroup|dynamicsecurity|makedynamicsecuritygroup] # [security|makesecuritygroup|dynamicsecurity|makedynamicsecuritygroup]
# [dynamic <QueryDynamicGroup>] # [dynamic <QueryDynamicGroup>]
# [memberrestrictions <QueryMemberRestrictions>] # [memberrestrictions <QueryMemberRestrictions>]
# gam update cigroups <GroupEntity> create [<GroupRole>] # [locked|unlocked]
# gam update cigroups <GroupEntity> add|create [<GroupRole>]
# [usersonly|groupsonly] # [usersonly|groupsonly]
# [notsuspended|suspended] [notarchived|archived] # [notsuspended|suspended] [notarchived|archived]
# [expire|expires <Time>] [preview] [actioncsv] # [expire|expires <Time>] [preview] [actioncsv]
@@ -34613,14 +34620,14 @@ def doUpdateCIGroups():
def _getMemberEmail(member): def _getMemberEmail(member):
if member['type'] == Ent.TYPE_CUSTOMER: if member['type'] == Ent.TYPE_CUSTOMER:
return member['id'] return member['id']
return member.get(CIGROUP_MEMBERKEY, {}).get('id', '') return member.get('preferredMemberKey', {}).get('id', '')
def checkDynamicGroup(ci, group, i, count): def checkDynamicGroup(ci, group, i, count):
try: try:
result = callGAPI(ci.groups(), 'get', result = callGAPI(ci.groups(), 'get',
throwReasons=GAPI.CIGROUP_GET_THROW_REASONS, retryReasons=GAPI.CIGROUP_RETRY_REASONS, throwReasons=GAPI.CIGROUP_GET_THROW_REASONS, retryReasons=GAPI.CIGROUP_RETRY_REASONS,
name=group, fields='labels') name=group, fields='labels')
if 'cloudidentity.googleapis.com/groups.dynamic' in result.get('labels', {}): if CIGROUP_DYNAMIC_LABEL in result.get('labels', {}):
entityActionNotPerformedWarning([entityType, group], Msg.DYNAMIC_GROUP_MEMBERSHIP_CANNOT_BE_MODIFIED, i, count) entityActionNotPerformedWarning([entityType, group], Msg.DYNAMIC_GROUP_MEMBERSHIP_CANNOT_BE_MODIFIED, i, count)
return True return True
return False return False
@@ -34681,7 +34688,7 @@ def doUpdateCIGroups():
j = 0 j = 0
for member in addMembers: for member in addMembers:
j += 1 j += 1
body = {CIGROUP_MEMBERKEY: {'id': member}, 'roles': [{'name': Ent.ROLE_MEMBER}]} body = {'preferredMemberKey': {'id': member}, 'roles': [{'name': Ent.ROLE_MEMBER}]}
if role != Ent.ROLE_MEMBER: if role != Ent.ROLE_MEMBER:
body['roles'].append({'name': role}) body['roles'].append({'name': role})
elif expireTime not in {None, NEVER_TIME}: elif expireTime not in {None, NEVER_TIME}:
@@ -34728,12 +34735,14 @@ def doUpdateCIGroups():
Ind.Decrement() Ind.Decrement()
cd = buildGAPIObject(API.DIRECTORY) cd = buildGAPIObject(API.DIRECTORY)
ci = buildGAPIObject(CIGROUP_MEMBER_API) ci = buildGAPIObject(API.CLOUDIDENTITY_GROUPS)
cib = None
entityType = Ent.CLOUD_IDENTITY_GROUP entityType = Ent.CLOUD_IDENTITY_GROUP
csvPF = None csvPF = None
getBeforeUpdate = preview = False getBeforeUpdate = preview = False
entityList = getEntityList(Cmd.OB_GROUP_ENTITY) entityList = getEntityList(Cmd.OB_GROUP_ENTITY)
CL_subCommand = getChoice(UPDATE_GROUP_SUBCMDS, defaultChoice=None) CL_subCommand = getChoice(UPDATE_GROUP_SUBCMDS, defaultChoice=None)
lockGroup = None
if not CL_subCommand: if not CL_subCommand:
gs_body = {} gs_body = {}
ci_body = {} ci_body = {}
@@ -34749,22 +34758,26 @@ def doUpdateCIGroups():
ci_body['dynamicGroupMetadata']['queries'].append({'resourceType': 'USER', ci_body['dynamicGroupMetadata']['queries'].append({'resourceType': 'USER',
'query': getString(Cmd.OB_QUERY)}) 'query': getString(Cmd.OB_QUERY)})
elif myarg in {'security', 'makesecuritygroup'}: elif myarg in {'security', 'makesecuritygroup'}:
ci_body['labels'] = {'cloudidentity.googleapis.com/groups.discussion_forum': '', ci_body['labels'] = {CIGROUP_DISCUSSION_FORUM_LABEL: '',
'cloudidentity.googleapis.com/groups.security': ''} CIGROUP_SECURITY_LABEL: ''}
elif myarg in {'dynamicsecurity', 'makedynamicsecuritygroup'}: elif myarg in {'dynamicsecurity', 'makedynamicsecuritygroup'}:
ci_body['labels'] = {'cloudidentity.googleapis.com/groups.discussion_forum': '', ci_body['labels'] = {CIGROUP_DISCUSSION_FORUM_LABEL: '',
'cloudidentity.googleapis.com/groups.dynamic': '', CIGROUP_DYNAMIC_LABEL: '',
'cloudidentity.googleapis.com/groups.security': ''} CIGROUP_SECURITY_LABEL: ''}
elif myarg in {'locked', 'makelockedsecuritygroup'}: elif myarg in {'lockedsecurity', 'makelockedsecuritygroup'}:
ci_body['labels'] = {'cloudidentity.googleapis.com/groups.discussion_forum': '', ci_body['labels'] = {CIGROUP_DISCUSSION_FORUM_LABEL: '',
'cloudidentity.googleapis.com/groups.locked': '', CIGROUP_LOCKED_LABEL: '',
'cloudidentity.googleapis.com/groups.security': ''} CIGROUP_SECURITY_LABEL: ''}
elif myarg in ['memberrestriction', 'memberrestrictions']: elif myarg in ['memberrestriction', 'memberrestrictions']:
query = getString(Cmd.OB_QUERY, minLen=0) query = getString(Cmd.OB_QUERY, minLen=0)
member_types = {'USER': '1', 'SERVICE_ACCOUNT': '2', 'GROUP': '3',} member_types = {'USER': '1', 'SERVICE_ACCOUNT': '2', 'GROUP': '3',}
for key, val in iter(member_types.items()): for key, val in iter(member_types.items()):
query = query.replace(key, val) query = query.replace(key, val)
se_body['memberRestriction'] = {'query': query} se_body['memberRestriction'] = {'query': query}
elif myarg == 'locked':
lockGroup = True
elif myarg == 'unlocked':
lockGroup = False
elif myarg == 'json': elif myarg == 'json':
gs_body.update(getJSON(GROUP_JSON_SKIP_FIELDS)) gs_body.update(getJSON(GROUP_JSON_SKIP_FIELDS))
elif myarg == 'accesstype': elif myarg == 'accesstype':
@@ -34782,7 +34795,7 @@ def doUpdateCIGroups():
settings = gs_body settings = gs_body
elif not ci_body: elif not ci_body:
return return
elif not ci_body and not se_body: elif not ci_body and not se_body and lockGroup is None:
return return
Act.Set(Act.UPDATE) Act.Set(Act.UPDATE)
i = 0 i = 0
@@ -34828,14 +34841,55 @@ def doUpdateCIGroups():
except GAPI.required: except GAPI.required:
entityActionFailedWarning([entityType, group], Msg.INVALID_JSON_SETTING, i, count) entityActionFailedWarning([entityType, group], Msg.INVALID_JSON_SETTING, i, count)
continue continue
if ci_body or se_body: if ci_body or se_body or lockGroup is not None:
_, name, _ = convertGroupEmailToCloudID(ci, group, i, count) _, name, _ = convertGroupEmailToCloudID(ci, group, i, count)
if not name: if not name:
continue continue
cipl = ci
twoUpdates = False
if 'labels' in ci_body or lockGroup is not None:
try:
cigInfo = callGAPI(ci.groups(), 'get',
throwReasons=GAPI.CIGROUP_GET_THROW_REASONS,
retryReasons=GAPI.CIGROUP_RETRY_REASONS,
name=name, fields='labels')
except (GAPI.notFound, GAPI.groupNotFound, GAPI.resourceNotFound, GAPI.domainNotFound, GAPI.domainCannotUseApis, GAPI.backendError,
GAPI.invalid, GAPI.invalidMember, GAPI.invalidParameter, GAPI.invalidInput, GAPI.forbidden, GAPI.badRequest,
GAPI.permissionDenied, GAPI.systemError, GAPI.serviceLimit, GAPI.serviceNotAvailable) as e:
entityActionFailedWarning([Ent.CLOUD_IDENTITY_GROUP, group], str(e), i, count)
continue
# If a group currently isn't a security group or locked, and we want to add security and locked,
# we have to do two commands to meet a beta requirement
ci_body.setdefault('labels', {})
if ((CIGROUP_SECURITY_LABEL not in cigInfo['labels']) and
(CIGROUP_LOCKED_LABEL not in cigInfo['labels']) and
((CIGROUP_SECURITY_LABEL in ci_body['labels']) and
((CIGROUP_LOCKED_LABEL in ci_body['labels']) or lockGroup))):
twoUpdates = True
ci_body['labels'].update(cigInfo['labels'])
if lockGroup is not None:
if lockGroup:
if CIGROUP_LOCKED_LABEL not in ci_body['labels']:
ci_body['labels'][CIGROUP_LOCKED_LABEL] = ''
else:
if CIGROUP_LOCKED_LABEL in ci_body['labels']:
ci_body['labels'].pop(CIGROUP_LOCKED_LABEL)
if CIGROUP_LOCKED_LABEL in ci_body['labels']:
if cib is None:
cib = buildGAPIObject(API.CLOUDIDENTITY_GROUPS_BETA)
cipl = cib
if ci_body: if ci_body:
try: try:
callGAPI(ci.groups(), 'patch', if twoUpdates:
throwReasons=GAPI.CIGROUP_UPDATE_THROW_REASONS, retryReasons=GAPI.CIGROUP_RETRY_REASONS, ci_body['labels'].pop(CIGROUP_LOCKED_LABEL)
callGAPI(cipl.groups(), 'patch',
throwReasons=GAPI.CIGROUP_UPDATE_THROW_REASONS,
retryReasons=GAPI.CIGROUP_RETRY_REASONS,
name=name, body=ci_body, updateMask=','.join(list(ci_body.keys())))
ci_body['labels'][CIGROUP_LOCKED_LABEL] = ''
callGAPI(cipl.groups(), 'patch',
throwReasons=GAPI.CIGROUP_UPDATE_THROW_REASONS,
retryReasons=GAPI.CIGROUP_RETRY_REASONS,
name=name, body=ci_body, updateMask=','.join(list(ci_body.keys()))) name=name, body=ci_body, updateMask=','.join(list(ci_body.keys())))
except (GAPI.notFound, GAPI.groupNotFound, GAPI.domainNotFound, GAPI.domainCannotUseApis, except (GAPI.notFound, GAPI.groupNotFound, GAPI.domainNotFound, GAPI.domainCannotUseApis,
GAPI.forbidden, GAPI.badRequest, GAPI.invalid, GAPI.invalidInput, GAPI.invalidArgument, GAPI.forbidden, GAPI.badRequest, GAPI.invalid, GAPI.invalidInput, GAPI.invalidArgument,
@@ -35010,7 +35064,7 @@ def doUpdateCIGroups():
pageMessage=getPageMessageForWhom(), pageMessage=getPageMessageForWhom(),
throwReasons=GAPI.MEMBERS_THROW_REASONS, retryReasons=GAPI.MEMBERS_RETRY_REASONS, throwReasons=GAPI.MEMBERS_THROW_REASONS, retryReasons=GAPI.MEMBERS_RETRY_REASONS,
parent=parent, view='FULL', parent=parent, view='FULL',
fields=f'nextPageToken,memberships(name,{CIGROUP_MEMBERKEY}(id),roles(name),type)', fields='nextPageToken,memberships(name,preferredMemberKey(id),roles(name),type)',
pageSize=GC.Values[GC.MEMBER_MAX_RESULTS]) pageSize=GC.Values[GC.MEMBER_MAX_RESULTS])
except (GAPI.groupNotFound, GAPI.domainNotFound, GAPI.domainCannotUseApis, GAPI.invalid, GAPI.forbidden): except (GAPI.groupNotFound, GAPI.domainNotFound, GAPI.domainCannotUseApis, GAPI.invalid, GAPI.forbidden):
entityUnknownWarning(Ent.CLOUD_IDENTITY_GROUP, group) entityUnknownWarning(Ent.CLOUD_IDENTITY_GROUP, group)
@@ -35025,7 +35079,7 @@ def doUpdateCIGroups():
for member in result: for member in result:
getCIGroupMemberRoleFixType(member) getCIGroupMemberRoleFixType(member)
role = member['role'] if not ignoreRole else Ent.ROLE_ALL role = member['role'] if not ignoreRole else Ent.ROLE_ALL
email = member.get(CIGROUP_MEMBERKEY, {}).get('id', '') email = member.get('preferredMemberKey', {}).get('id', '')
if groupMemberType in ('ALL', member['type']) and role in rolesSet: if groupMemberType in ('ALL', member['type']) and role in rolesSet:
cleanAddress = _cleanConsumerAddress(email, currentMembersMaps[role]) cleanAddress = _cleanConsumerAddress(email, currentMembersMaps[role])
currentMembersSets[role].add(cleanAddress) currentMembersSets[role].add(cleanAddress)
@@ -35099,7 +35153,7 @@ def doUpdateCIGroups():
removeRoles = [] removeRoles = []
postUpdateRoles = [] postUpdateRoles = []
memberRoles = callGAPI(ci.groups().memberships(), 'get', memberRoles = callGAPI(ci.groups().memberships(), 'get',
name=memberName, fields=f'name,{CIGROUP_MEMBERKEY},roles,type') name=memberName, fields='name,preferredMemberKey,roles,type')
getCIGroupMemberRoleFixType(memberRoles) getCIGroupMemberRoleFixType(memberRoles)
current_roles = [crole['name'] for crole in memberRoles['roles']] current_roles = [crole['name'] for crole in memberRoles['roles']]
# When upgrading role, strip any expiryDetail from member before role changes # When upgrading role, strip any expiryDetail from member before role changes
@@ -35185,7 +35239,7 @@ def doUpdateCIGroups():
pageMessage=getPageMessageForWhom(), pageMessage=getPageMessageForWhom(),
throwReasons=GAPI.MEMBERS_THROW_REASONS, retryReasons=GAPI.MEMBERS_RETRY_REASONS, throwReasons=GAPI.MEMBERS_THROW_REASONS, retryReasons=GAPI.MEMBERS_RETRY_REASONS,
parent=parent, view='FULL', parent=parent, view='FULL',
fields=f'nextPageToken,memberships(name,{CIGROUP_MEMBERKEY}(id),roles(name),type)', fields='nextPageToken,memberships(name,preferredMemberKey(id),roles(name),type)',
pageSize=GC.Values[GC.MEMBER_MAX_RESULTS]) pageSize=GC.Values[GC.MEMBER_MAX_RESULTS])
except (GAPI.groupNotFound, GAPI.domainNotFound, GAPI.domainCannotUseApis, GAPI.invalid, GAPI.forbidden): except (GAPI.groupNotFound, GAPI.domainNotFound, GAPI.domainCannotUseApis, GAPI.invalid, GAPI.forbidden):
entityUnknownWarning(entityType, group, i, count) entityUnknownWarning(entityType, group, i, count)
@@ -35274,7 +35328,7 @@ def doInfoCIGroups():
for member in cachedGroupMembers[group_id]: for member in cachedGroupMembers[group_id]:
member_id = member.get('name', '') member_id = member.get('name', '')
member_id = member_id.split('/')[-1] member_id = member_id.split('/')[-1]
member_email = member.get(CIGROUP_MEMBERKEY, {}).get('id') member_email = member.get('preferredMemberKey', {}).get('id')
member_type = member.get('type', 'USER').lower() member_type = member.get('type', 'USER').lower()
if showRole: if showRole:
getCIGroupMemberRoleFixType(member) getCIGroupMemberRoleFixType(member)
@@ -35290,7 +35344,7 @@ def doInfoCIGroups():
if not groupFieldsLists['ci']: if not groupFieldsLists['ci']:
groupFieldsLists['ci'] = ['groupKey'] groupFieldsLists['ci'] = ['groupKey']
ci = buildGAPIObject(CIGROUP_MEMBER_API) ci = buildGAPIObject(API.CLOUDIDENTITY_GROUPS)
entityList = getEntityList(Cmd.OB_GROUP_ENTITY) entityList = getEntityList(Cmd.OB_GROUP_ENTITY)
getAliases = getSecuritySettings = getUsers = True getAliases = getSecuritySettings = getUsers = True
showJoinDate = True showJoinDate = True
@@ -35394,7 +35448,7 @@ def doInfoCIGroups():
printEntitiesCount(entityType, members) printEntitiesCount(entityType, members)
Ind.Increment() Ind.Increment()
for member in members: for member in members:
memberEmail = member.get(CIGROUP_MEMBERKEY, {}).get('id', member['name']) memberEmail = member.get('preferredMemberKey', {}).get('id', member['name'])
getCIGroupMemberRoleFixType(member) getCIGroupMemberRoleFixType(member)
memberDetails = [member['role'].lower(), f'{memberEmail} ({member["type"].lower()})'] memberDetails = [member['role'].lower(), f'{memberEmail} ({member["type"].lower()})']
if checkCategory: if checkCategory:
@@ -35425,7 +35479,7 @@ def doInfoCIGroups():
def checkCIGroupShowOwnedBy(showOwnedBy, members): def checkCIGroupShowOwnedBy(showOwnedBy, members):
for member in members: for member in members:
if member[CIGROUP_MEMBERKEY]['id'] == showOwnedBy: if member['preferredMemberKey']['id'] == showOwnedBy:
if member['role'] == Ent.ROLE_OWNER: if member['role'] == Ent.ROLE_OWNER:
return True return True
return False return False
@@ -35692,7 +35746,7 @@ def doPrintCIGroups():
False, False, True) False, False, True)
csvPF.WriteRow(row) csvPF.WriteRow(row)
ci = buildGAPIObject(CIGROUP_MEMBER_API) ci = buildGAPIObject(API.CLOUDIDENTITY_GROUPS)
setTrueCustomerId() setTrueCustomerId()
delimiter = GC.Values[GC.CSV_OUTPUT_FIELD_DELIMITER] delimiter = GC.Values[GC.CSV_OUTPUT_FIELD_DELIMITER]
memberRestrictions = sortHeaders = False memberRestrictions = sortHeaders = False
@@ -35716,7 +35770,7 @@ def doPrintCIGroups():
showOwnedBy = convertUIDtoEmailAddress(getEmailAddress(), emailTypes=['user']) showOwnedBy = convertUIDtoEmailAddress(getEmailAddress(), emailTypes=['user'])
elif myarg in {'cimember', 'enterprisemember', 'ciowner'}: elif myarg in {'cimember', 'enterprisemember', 'ciowner'}:
emailAddress = convertUIDtoEmailAddress(getEmailAddress(), emailTypes=['user', 'group']) emailAddress = convertUIDtoEmailAddress(getEmailAddress(), emailTypes=['user', 'group'])
memberQuery = f"member_key_id == '{emailAddress}' && 'cloudidentity.googleapis.com/groups.discussion_forum' in labels" memberQuery = f"member_key_id == '{emailAddress}' && CIGROUP_DISCUSSION_FORUM_LABEL in labels"
entitySelection = None entitySelection = None
if myarg == 'ciowner': if myarg == 'ciowner':
showOwnedBy = emailAddress showOwnedBy = emailAddress
@@ -35809,18 +35863,21 @@ def doPrintCIGroups():
getFullFieldsList.append(field) getFullFieldsList.append(field)
else: else:
getFullFieldsList = list(CIGROUP_FULL_FIELDS) getFullFieldsList = list(CIGROUP_FULL_FIELDS)
getFullFields = ','.join(getFullFieldsList) getFullFields = ','.join(getFullFieldsList)#
cipl = ci
if query: if query:
method = 'search' method = 'search'
if 'parent' not in query: if 'parent' not in query:
query += f" && parent == '{parent}'" query += f" && parent == '{parent}'"
kwargs = {'query': query} kwargs = {'query': query}
if CIGROUP_LOCKED_LABEL in query:
cipl = buildGAPIObject(API.CLOUDIDENTITY_GROUPS_BETA)
else: else:
method = 'list' method = 'list'
kwargs = {'parent': parent} kwargs = {'parent': parent}
printGettingAllAccountEntities(Ent.CLOUD_IDENTITY_GROUP, query) printGettingAllAccountEntities(Ent.CLOUD_IDENTITY_GROUP, query)
try: try:
entityList = callGAPIpages(ci.groups(), method, 'groups', entityList = callGAPIpages(cipl.groups(), method, 'groups',
pageMessage=getPageMessage(showFirstLastItems=True), messageAttribute=['groupKey', 'id'], pageMessage=getPageMessage(showFirstLastItems=True), messageAttribute=['groupKey', 'id'],
throwReasons=GAPI.CIGROUP_LIST_THROW_REASONS, retryReasons=GAPI.CIGROUP_RETRY_REASONS, throwReasons=GAPI.CIGROUP_LIST_THROW_REASONS, retryReasons=GAPI.CIGROUP_RETRY_REASONS,
view='FULL', fields=fieldsnp, pageSize=pageSize, **kwargs) view='FULL', fields=fieldsnp, pageSize=pageSize, **kwargs)
@@ -35876,7 +35933,7 @@ def doPrintCIGroups():
try: try:
groupMembers = callGAPIpages(ci.groups().memberships(), 'list', 'memberships', groupMembers = callGAPIpages(ci.groups().memberships(), 'list', 'memberships',
throwReasons=GAPI.MEMBERS_THROW_REASONS, retryReasons=GAPI.MEMBERS_RETRY_REASONS, throwReasons=GAPI.MEMBERS_THROW_REASONS, retryReasons=GAPI.MEMBERS_RETRY_REASONS,
pageMessage=getPageMessage(), messageAttribute=[CIGROUP_MEMBERKEY, 'id'], pageMessage=getPageMessage(), messageAttribute=['preferredMemberKey', 'id'],
parent=groupEntity['name'], view='FULL', fields='*', pageSize=pageSize) parent=groupEntity['name'], view='FULL', fields='*', pageSize=pageSize)
except (GAPI.groupNotFound, GAPI.domainNotFound, GAPI.domainCannotUseApis, GAPI.invalid, GAPI.forbidden): except (GAPI.groupNotFound, GAPI.domainNotFound, GAPI.domainCannotUseApis, GAPI.invalid, GAPI.forbidden):
entityUnknownWarning(Ent.CLOUD_IDENTITY_GROUP, groupEmail, i, count) entityUnknownWarning(Ent.CLOUD_IDENTITY_GROUP, groupEmail, i, count)
@@ -35994,7 +36051,7 @@ def getCIGroupMembers(ci, groupName, memberRoles, membersList, membersSet, i, co
if memberOptions[MEMBEROPTION_NODUPLICATES]: if memberOptions[MEMBEROPTION_NODUPLICATES]:
for member in groupMembers: for member in groupMembers:
getCIGroupMemberRoleFixType(member) getCIGroupMemberRoleFixType(member)
memberName = member.get(CIGROUP_MEMBERKEY, {}).get('id', '') memberName = member.get('preferredMemberKey', {}).get('id', '')
if (_checkMemberRole(member, validRoles) and if (_checkMemberRole(member, validRoles) and
(not checkCategory or _checkCIMemberCategory(member, memberDisplayOptions)) and (not checkCategory or _checkCIMemberCategory(member, memberDisplayOptions)) and
memberName not in membersSet): memberName not in membersSet):
@@ -36012,7 +36069,7 @@ def getCIGroupMembers(ci, groupName, memberRoles, membersList, membersSet, i, co
groupMemberList = [] groupMemberList = []
for member in groupMembers: for member in groupMembers:
getCIGroupMemberRoleFixType(member) getCIGroupMemberRoleFixType(member)
memberName = member.get(CIGROUP_MEMBERKEY, {}).get('id', '') memberName = member.get('preferredMemberKey', {}).get('id', '')
if member['type'] != Ent.TYPE_GROUP: if member['type'] != Ent.TYPE_GROUP:
if (member['type'] in typesSet and if (member['type'] in typesSet and
checkCIMemberMatch(member, memberOptions) and checkCIMemberMatch(member, memberOptions) and
@@ -36063,21 +36120,20 @@ CIGROUPMEMBERS_FIELDS_CHOICE_MAP = {
'createtime': 'createTime', 'createtime': 'createTime',
'expiretime': 'expireTime', 'expiretime': 'expireTime',
'id': 'name', 'id': 'name',
'memberkey': CIGROUP_MEMBERKEY, 'memberkey': 'preferredMemberKey',
'name': 'name', 'name': 'name',
'preferredmemberkey': CIGROUP_MEMBERKEY, 'preferredmemberkey': 'preferredMemberKey',
'role': 'roles', 'role': 'roles',
'roles': 'roles', 'roles': 'roles',
'type': 'type', 'type': 'type',
'updatetime': 'updateTime', 'updatetime': 'updateTime',
'useremail': CIGROUP_MEMBERKEY, 'useremail': 'preferredMemberKey',
} }
CIGROUPMEMBERS_DEFAULT_FIELDS = [ CIGROUPMEMBERS_DEFAULT_FIELDS = [
# 'type', 'roles', 'name', 'memberkey', 'preferredmemberkey', 'createtime', 'updatetime', 'expiretime'] 'type', 'roles', 'name', 'preferredmemberkey', 'createtime', 'updatetime', 'expiretime']
'type', 'roles', 'name', CIGROUP_MEMBERKEY.lower(), 'createtime', 'updatetime', 'expiretime']
CIGROUPMEMBERS_SORT_FIELDS = [ CIGROUPMEMBERS_SORT_FIELDS = [
'type', 'role', 'id', 'email', 'type', 'role', 'id', 'email',
'name', f'{CIGROUP_MEMBERKEY}.id', f'{CIGROUP_MEMBERKEY}.namespace', 'name', 'preferredMemberKey.id', 'preferredMemberKey.namespace',
'createTime', 'updateTime', 'expireTime' 'createTime', 'updateTime', 'expireTime'
] ]
CIGROUPMEMBERS_TIME_OBJECTS = {'createTime', 'updateTime', 'expireTime'} CIGROUPMEMBERS_TIME_OBJECTS = {'createTime', 'updateTime', 'expireTime'}
@@ -36094,7 +36150,7 @@ CIGROUPMEMBERS_TIME_OBJECTS = {'createTime', 'updateTime', 'expireTime'}
# [(recursive [noduplicates])|includederivedmembership] [nogroupeemail] # [(recursive [noduplicates])|includederivedmembership] [nogroupeemail]
# [formatjson [quotechar <Character>]] # [formatjson [quotechar <Character>]]
def doPrintCIGroupMembers(): def doPrintCIGroupMembers():
ci = buildGAPIObject(CIGROUP_MEMBER_API) ci = buildGAPIObject(API.CLOUDIDENTITY_GROUPS)
setTrueCustomerId() setTrueCustomerId()
memberOptions = initMemberOptions() memberOptions = initMemberOptions()
memberDisplayOptions = initIPSGMGroupMemberDisplayOptions() memberDisplayOptions = initIPSGMGroupMemberDisplayOptions()
@@ -36116,7 +36172,7 @@ def doPrintCIGroupMembers():
showOwnedBy = convertUIDtoEmailAddress(getEmailAddress(), emailTypes=['user']) showOwnedBy = convertUIDtoEmailAddress(getEmailAddress(), emailTypes=['user'])
elif myarg in {'cimember', 'enterprisemember', 'ciowner'}: elif myarg in {'cimember', 'enterprisemember', 'ciowner'}:
emailAddress = convertUIDtoEmailAddress(getEmailAddress(), emailTypes=['user', 'group']) emailAddress = convertUIDtoEmailAddress(getEmailAddress(), emailTypes=['user', 'group'])
query = f"member_key_id == '{emailAddress}' && 'cloudidentity.googleapis.com/groups.discussion_forum' in labels" query = f"member_key_id == '{emailAddress}' && CIGROUP_DISCUSSION_FORUM_LABEL in labels"
entityList = None entityList = None
if myarg == 'ciowner': if myarg == 'ciowner':
showOwnedBy = emailAddress showOwnedBy = emailAddress
@@ -36292,7 +36348,7 @@ def doShowCIGroupMembers():
if (_checkMemberRole(member, rolesSet) and if (_checkMemberRole(member, rolesSet) and
member['type'] in typesSet and member['type'] in typesSet and
checkCIMemberMatch(member, memberOptions)): checkCIMemberMatch(member, memberOptions)):
memberDetails = f'{member.get("role", Ent.ROLE_MEMBER)}, {member["type"]}, {member[CIGROUP_MEMBERKEY]["id"]}' memberDetails = f'{member.get("role", Ent.ROLE_MEMBER)}, {member["type"]}, {member["preferredMemberKey"]["id"]}'
if checkCategory: if checkCategory:
memberDetails += f', {member["category"]}' memberDetails += f', {member["category"]}'
for field in ['createTime', 'updateTime', 'expireTime']: for field in ['createTime', 'updateTime', 'expireTime']:
@@ -36301,11 +36357,11 @@ def doShowCIGroupMembers():
printKeyValueList([memberDetails]) printKeyValueList([memberDetails])
if not includeDerivedMembership and (member['type'] == Ent.TYPE_GROUP) and (maxdepth == -1 or depth < maxdepth): if not includeDerivedMembership and (member['type'] == Ent.TYPE_GROUP) and (maxdepth == -1 or depth < maxdepth):
_, gname = member['name'].rsplit('/', 1) _, gname = member['name'].rsplit('/', 1)
_showGroup(f'groups/{gname}', member[CIGROUP_MEMBERKEY]['id'], depth+1) _showGroup(f'groups/{gname}', member['preferredMemberKey']['id'], depth+1)
if depth == 0 or Ent.TYPE_GROUP in typesSet: if depth == 0 or Ent.TYPE_GROUP in typesSet:
Ind.Decrement() Ind.Decrement()
ci = buildGAPIObject(CIGROUP_MEMBER_API) ci = buildGAPIObject(API.CLOUDIDENTITY_GROUPS)
setTrueCustomerId() setTrueCustomerId()
subTitle = f'{Msg.ALL} {Ent.Plural(Ent.CLOUD_IDENTITY_GROUP)}' subTitle = f'{Msg.ALL} {Ent.Plural(Ent.CLOUD_IDENTITY_GROUP)}'
groupFieldsLists = {'ci': ['groupKey', 'name']} groupFieldsLists = {'ci': ['groupKey', 'name']}
@@ -36323,7 +36379,7 @@ def doShowCIGroupMembers():
showOwnedBy = convertUIDtoEmailAddress(getEmailAddress(), emailTypes=['user']) showOwnedBy = convertUIDtoEmailAddress(getEmailAddress(), emailTypes=['user'])
elif myarg in {'cimember', 'enterprisemember', 'ciowner'}: elif myarg in {'cimember', 'enterprisemember', 'ciowner'}:
emailAddress = convertUIDtoEmailAddress(getEmailAddress(), emailTypes=['user', 'group']) emailAddress = convertUIDtoEmailAddress(getEmailAddress(), emailTypes=['user', 'group'])
query = f"member_key_id == '{emailAddress}' && 'cloudidentity.googleapis.com/groups.discussion_forum' in labels" query = f"member_key_id == '{emailAddress}' && CIGROUP_DISCUSSION_FORUM_LABEL in labels"
entityList = None entityList = None
if myarg == 'ciowner': if myarg == 'ciowner':
showOwnedBy = emailAddress showOwnedBy = emailAddress
@@ -43337,11 +43393,7 @@ def getUserAttributes(cd, updateCmd, noUid=False):
body['name'][up] = getString(Cmd.OB_STRING, minLen=0, maxLen=60) body['name'][up] = getString(Cmd.OB_STRING, minLen=0, maxLen=60)
elif up == 'displayName': elif up == 'displayName':
body.setdefault('name', {}) body.setdefault('name', {})
# sigh, the API is wonky. If we set just displayName body['name']['displayName'] = getString(Cmd.OB_STRING, minLen=0, maxLen=256)
# we get an error. But if we also "set" fullName which is
# really just a concat of first/last name and can't be set
# then it works. Go figure.
body['name']['displayName'] = body['name']['fullName'] = getString(Cmd.OB_STRING, minLen=0, maxLen=256)
elif PwdOpts.ProcessPropertyArgument(myarg, up, body): elif PwdOpts.ProcessPropertyArgument(myarg, up, body):
pass pass
elif propertyClass == UProp.PC_BOOLEAN: elif propertyClass == UProp.PC_BOOLEAN:
@@ -44833,7 +44885,7 @@ def infoUsers(entityList):
group_email = group_name_mapping[group_name] group_email = group_name_mapping[group_name]
for edge in adj.get('edges', []): for edge in adj.get('edges', []):
seen_group_count[group_email] = seen_group_count.get(group_email, 0) + 1 seen_group_count[group_email] = seen_group_count.get(group_email, 0) + 1
member_email = edge.get(CIGROUP_MEMBERKEY, {}).get('id') member_email = edge.get('preferredMemberKey', {}).get('id')
edges.append((member_email, group_email)) edges.append((member_email, group_email))
printUserCIGroupMap(user['primaryEmail'], group_displayname_mapping, seen_group_count, edges, 'direct') printUserCIGroupMap(user['primaryEmail'], group_displayname_mapping, seen_group_count, edges, 'direct')
if max(seen_group_count.values()) > 1: if max(seen_group_count.values()) > 1:
@@ -53517,7 +53569,7 @@ def printDriveActivity(users):
except GAPI.badRequest as e: except GAPI.badRequest as e:
entityActionFailedWarning([Ent.USER, user, entityType, fileId], str(e), i, count) entityActionFailedWarning([Ent.USER, user, entityType, fileId], str(e), i, count)
continue continue
except GAPI.serviceNotAvailable: except GAPI.serviceNotAvailable as e:
userDriveServiceNotEnabledWarning(user, str(e), i, count) userDriveServiceNotEnabledWarning(user, str(e), i, count)
continue continue
csvPF.writeCSVfile('Drive Activity') csvPF.writeCSVfile('Drive Activity')
@@ -65516,31 +65568,29 @@ def printShowSharedDrives(users, useDomainAdminAccess=False):
else: else:
matchedFeed = feed matchedFeed = feed
jcount = len(matchedFeed) jcount = len(matchedFeed)
if jcount == 0:
setSysExitRC(NO_ENTITIES_FOUND_RC)
if not csvPF: if not csvPF:
if not FJQC.formatJSON: if not FJQC.formatJSON:
entityPerformActionNumItems([Ent.USER, user], jcount, Ent.SHAREDDRIVE, i, count) entityPerformActionNumItems([Ent.USER, user], jcount, Ent.SHAREDDRIVE, i, count)
if jcount == 0: Ind.Increment()
setSysExitRC(NO_ENTITIES_FOUND_RC) j = 0
for shareddrive in matchedFeed:
j += 1
shareddrive = stripNonShowFields(shareddrive)
_showSharedDrive(user, shareddrive, j, jcount, FJQC)
Ind.Decrement()
else: else:
if not csvPF: for shareddrive in matchedFeed:
Ind.Increment() shareddrive = stripNonShowFields(shareddrive)
j = 0 if FJQC.formatJSON:
for shareddrive in matchedFeed: row = {'User': user, 'id': shareddrive['id'], 'name': shareddrive['name']}
j += 1 if not useDomainAdminAccess:
shareddrive = stripNonShowFields(shareddrive) row['role'] = shareddrive['role'] if not guiRoles else SHAREDDRIVE_API_GUI_ROLES_MAP[shareddrive['role']]
_showSharedDrive(user, shareddrive, j, jcount, FJQC) row['JSON'] = json.dumps(cleanJSON(shareddrive, timeObjects=SHAREDDRIVE_TIME_OBJECTS), ensure_ascii=False, sort_keys=True)
Ind.Decrement() csvPF.WriteRow(row)
else: else:
for shareddrive in matchedFeed: csvPF.WriteRowTitles(flattenJSON(shareddrive, flattened={'User': user}, timeObjects=SHAREDDRIVE_TIME_OBJECTS))
shareddrive = stripNonShowFields(shareddrive)
if FJQC.formatJSON:
row = {'User': user, 'id': shareddrive['id'], 'name': shareddrive['name']}
if not useDomainAdminAccess:
row['role'] = shareddrive['role'] if not guiRoles else SHAREDDRIVE_API_GUI_ROLES_MAP[shareddrive['role']]
row['JSON'] = json.dumps(cleanJSON(shareddrive, timeObjects=SHAREDDRIVE_TIME_OBJECTS), ensure_ascii=False, sort_keys=True)
csvPF.WriteRow(row)
else:
csvPF.WriteRowTitles(flattenJSON(shareddrive, flattened={'User': user}, timeObjects=SHAREDDRIVE_TIME_OBJECTS))
if csvPF: if csvPF:
csvPF.writeCSVfile('SharedDrives') csvPF.writeCSVfile('SharedDrives')
@@ -65599,29 +65649,27 @@ def doPrintShowOrgunitSharedDrives():
customer=_getCustomersCustomerIdWithC(), customer=_getCustomersCustomerIdWithC(),
filter="type == 'shared_drive'") filter="type == 'shared_drive'")
jcount = len(sds) jcount = len(sds)
if jcount == 0:
setSysExitRC(NO_ENTITIES_FOUND_RC)
if not csvPF: if not csvPF:
if not FJQC.formatJSON: if not FJQC.formatJSON:
entityPerformActionNumItems([Ent.ORGANIZATIONAL_UNIT, orgUnitPath], jcount, Ent.SHAREDDRIVE) entityPerformActionNumItems([Ent.ORGANIZATIONAL_UNIT, orgUnitPath], jcount, Ent.SHAREDDRIVE)
if jcount == 0: Ind.Increment()
setSysExitRC(NO_ENTITIES_FOUND_RC) j = 0
for shareddrive in sds:
j += 1
_getOrgUnitSharedDriveInfo(shareddrive)
_showOrgUnitSharedDrive(shareddrive, j, jcount, FJQC)
Ind.Decrement()
else: else:
if not csvPF: for shareddrive in sds:
Ind.Increment() _getOrgUnitSharedDriveInfo(shareddrive)
j = 0 if FJQC.formatJSON:
for shareddrive in sds: row = {'name': shareddrive['name']}
j += 1 row['JSON'] = json.dumps(cleanJSON(shareddrive), ensure_ascii=False, sort_keys=True)
_getOrgUnitSharedDriveInfo(shareddrive) csvPF.WriteRow(row)
_showOrgUnitSharedDrive(shareddrive, j, jcount, FJQC) else:
Ind.Decrement() csvPF.WriteRowTitles(flattenJSON(shareddrive))
else:
for shareddrive in sds:
_getOrgUnitSharedDriveInfo(shareddrive)
if FJQC.formatJSON:
row = {'name': shareddrive['name']}
row['JSON'] = json.dumps(cleanJSON(shareddrive), ensure_ascii=False, sort_keys=True)
csvPF.WriteRow(row)
else:
csvPF.WriteRowTitles(flattenJSON(shareddrive))
if csvPF: if csvPF:
csvPF.writeCSVfile('OrgUnit {orgUnitPath} SharedDrives') csvPF.writeCSVfile('OrgUnit {orgUnitPath} SharedDrives')
@@ -65706,7 +65754,7 @@ SHOW_NO_PERMISSIONS_DRIVES_CHOICE_MAP = {
# [oneitemperrow] [maxitems <Integer>] # [oneitemperrow] [maxitems <Integer>]
# [shownopermissionsdrives false|true|only] # [shownopermissionsdrives false|true|only]
# [<DrivePermissionsFieldName>*|(fields <DrivePermissionsFieldNameList>)] # [<DrivePermissionsFieldName>*|(fields <DrivePermissionsFieldNameList>)]
# [formatjsn] # [formatjson]
def printShowSharedDriveACLs(users, useDomainAdminAccess=False): def printShowSharedDriveACLs(users, useDomainAdminAccess=False):
def _printPermissionRow(baserow, permission): def _printPermissionRow(baserow, permission):
row = baserow.copy() row = baserow.copy()
@@ -65896,7 +65944,8 @@ def printShowSharedDriveACLs(users, useDomainAdminAccess=False):
if jcount == 0: if jcount == 0:
setSysExitRC(NO_ENTITIES_FOUND_RC) setSysExitRC(NO_ENTITIES_FOUND_RC)
if not csvPF: if not csvPF:
entityPerformActionNumItems([Ent.USER, user], jcount, Ent.SHAREDDRIVE, i, count) if not FJQC.formatJSON:
entityPerformActionNumItems([Ent.USER, user], jcount, Ent.SHAREDDRIVE, i, count)
Ind.Increment() Ind.Increment()
j = 0 j = 0
for shareddrive in matchFeed: for shareddrive in matchFeed:
@@ -67123,7 +67172,7 @@ LICENSE_PREVIEW_TITLES = ['user', 'productId', 'skuId', 'action', 'message']
def getLicenseParameters(operation): def getLicenseParameters(operation):
lic = buildGAPIObject(API.LICENSING) lic = buildGAPIObject(API.LICENSING)
parameters = {LICENSE_PRODUCT_SKUIDS: [], 'csvPF': None, 'preview': False, 'syncOperation': 'addremove', 'syncACLsMode': None} parameters = {LICENSE_PRODUCT_SKUIDS: [], 'csvPF': None, 'preview': False, 'syncOperation': 'addremove', 'syncACLsMode': None, 'archive': False}
skuLocation = Cmd.Location() skuLocation = Cmd.Location()
if operation != 'patch': if operation != 'patch':
parameters[LICENSE_PRODUCT_SKUIDS] = getGoogleSKUList(allowUnknownProduct=True) parameters[LICENSE_PRODUCT_SKUIDS] = getGoogleSKUList(allowUnknownProduct=True)
@@ -67153,6 +67202,10 @@ def getLicenseParameters(operation):
if operation == 'patch': if operation == 'patch':
titles.insert(2, 'oldskuId') titles.insert(2, 'oldskuId')
parameters['csvPF'] = CSVPrintFile(titles) parameters['csvPF'] = CSVPrintFile(titles)
elif operation == 'patch' and myarg == 'archive':
if skuId not in SKU.ARCHIVABLE_SKUS:
usageErrorExit(Msg.SKU_HAS_NO_MATCHING_ARCHIVED_USER_SKU.format(skuId))
parameters['archive'] = True
else: else:
unknownArgumentExit() unknownArgumentExit()
for productSku in parameters[LICENSE_PRODUCT_SKUIDS]: for productSku in parameters[LICENSE_PRODUCT_SKUIDS]:
@@ -67258,13 +67311,17 @@ def createLicense(users):
if parameters['csvPF']: if parameters['csvPF']:
parameters['csvPF'].writeCSVfile('Create Licenses') parameters['csvPF'].writeCSVfile('Create Licenses')
# gam <UserTypeEntity> update license <SKUID> [product|productid <ProductID>] [from] <SKUID> [preview] [actioncsv] # gam <UserTypeEntity> update license <SKUID> [product|productid <ProductID>] [from] <SKUID>
# [preview] [actioncsv] [archive]
def updateLicense(users): def updateLicense(users):
lic, parameters = getLicenseParameters('patch') lic, parameters = getLicenseParameters('patch')
j, jcount, users = getEntityArgument(users) j, jcount, users = getEntityArgument(users)
Act.Set([Act.UPDATE, Act.UPDATE_PREVIEW][parameters['preview']]) Act.Set([Act.UPDATE, Act.UPDATE_PREVIEW][parameters['preview']])
cd = None
if parameters['preview']: if parameters['preview']:
message = Act.PREVIEW message = Act.PREVIEW
elif parameters['archive']:
cd = buildGAPIObject(API.DIRECTORY)
productId, skuId, oldSkuId = parameters[LICENSE_PRODUCT_SKUIDS][0] productId, skuId, oldSkuId = parameters[LICENSE_PRODUCT_SKUIDS][0]
body = {'skuId': skuId} body = {'skuId': skuId}
entityPerformActionModifierNumItems([Ent.LICENSE, SKU.skuIdToDisplayName(skuId)], Msg.FOR, jcount, Ent.USER) entityPerformActionModifierNumItems([Ent.LICENSE, SKU.skuIdToDisplayName(skuId)], Msg.FOR, jcount, Ent.USER)
@@ -67290,6 +67347,22 @@ def updateLicense(users):
except GAPI.userNotFound as e: except GAPI.userNotFound as e:
message = str(e) message = str(e)
entityUnknownWarning(Ent.USER, user, j, jcount) entityUnknownWarning(Ent.USER, user, j, jcount)
if parameters['archive'] and message == Act.SUCCESS:
Act.Set(Act.ARCHIVE)
try:
callGAPI(cd.users(), 'update',
throwReasons=[GAPI.USER_NOT_FOUND, GAPI.DOMAIN_NOT_FOUND,
GAPI.FORBIDDEN, GAPI.BAD_REQUEST,
GAPI.INSUFFICIENT_ARCHIVED_USER_LICENSES],
retryReasons=[GAPI.INSUFFICIENT_ARCHIVED_USER_LICENSES],
userKey=user, body={'archived': True})
entityActionPerformed([Ent.USER, user], j, jcount)
except GAPI.userNotFound:
entityUnknownWarning(Ent.USER, user, j, jcount)
except (GAPI.domainNotFound, GAPI.domainCannotUseApis, GAPI.forbidden, GAPI.badRequest,
GAPI.insufficientArchivedUserLicenses) as e:
entityActionFailedWarning([Ent.USER, user], str(e), j, jcount)
Act.Set(Act.UPDATE)
if parameters['csvPF']: if parameters['csvPF']:
_writeLicenseAction(productId, skuId, oldSkuId, parameters, user, Act.UPDATE, message) _writeLicenseAction(productId, skuId, oldSkuId, parameters, user, Act.UPDATE, message)
Ind.Decrement() Ind.Decrement()
@@ -69629,8 +69702,9 @@ def archiveMessages(users):
stream.write(base64.urlsafe_b64decode(str(message['raw']))) stream.write(base64.urlsafe_b64decode(str(message['raw'])))
try: try:
callGAPI(gm.archive(), 'insert', callGAPI(gm.archive(), 'insert',
throwReasons=GAPI.GMAIL_THROW_REASONS+[GAPI.BAD_REQUEST, GAPI.INVALID, throwReasons=GAPI.GMAIL_THROW_REASONS+[GAPI.NOT_FOUND, GAPI.BAD_REQUEST, GAPI.INVALID,
GAPI.FAILED_PRECONDITION, GAPI.FORBIDDEN], GAPI.FAILED_PRECONDITION, GAPI.FORBIDDEN],
retryReasons=[GAPI.NOT_FOUND],
groupId=group, media_body=googleapiclient.http.MediaIoBaseUpload(stream, mimetype='message/rfc822', resumable=True)) groupId=group, media_body=googleapiclient.http.MediaIoBaseUpload(stream, mimetype='message/rfc822', resumable=True))
if not csvPF: if not csvPF:
entityActionPerformed([Ent.USER, user, entityType, messageId], j, jcount) entityActionPerformed([Ent.USER, user, entityType, messageId], j, jcount)
@@ -69639,6 +69713,9 @@ def archiveMessages(users):
except GAPI.serviceNotAvailable: except GAPI.serviceNotAvailable:
userGmailServiceNotEnabledWarning(user, i, count) userGmailServiceNotEnabledWarning(user, i, count)
break break
except GAPI.notFound as e:
_processMessageFailed(user, messageId, str(e), j, jcount)
break
except (GAPI.badRequest, GAPI.invalid, GAPI.failedPrecondition, GAPI.forbidden, except (GAPI.badRequest, GAPI.invalid, GAPI.failedPrecondition, GAPI.forbidden,
googleapiclient.errors.MediaUploadSizeError) as e: googleapiclient.errors.MediaUploadSizeError) as e:
_processMessageFailed(user, messageId, str(e), j, jcount) _processMessageFailed(user, messageId, str(e), j, jcount)

View File

@@ -45,10 +45,11 @@ CLASSROOM = 'classroom'
CLOUDCHANNEL = 'cloudchannel' CLOUDCHANNEL = 'cloudchannel'
CLOUDIDENTITY_DEVICES = 'cloudidentitydevices' CLOUDIDENTITY_DEVICES = 'cloudidentitydevices'
CLOUDIDENTITY_GROUPS = 'cloudidentitygroups' CLOUDIDENTITY_GROUPS = 'cloudidentitygroups'
CLOUDIDENTITY_GROUPS_BETA = 'cloudidentitygroupsbeta'
CLOUDIDENTITY_INBOUND_SSO = 'cloudidentityinboundsso' CLOUDIDENTITY_INBOUND_SSO = 'cloudidentityinboundsso'
CLOUDIDENTITY_ORGUNITS = 'cloudidentityorgunits' CLOUDIDENTITY_ORGUNITS = 'cloudidentityorgunits'
CLOUDIDENTITY_POLICY = 'cloudidentitypolicy'
CLOUDIDENTITY_ORGUNITS_BETA = 'cloudidentityorgunitsbeta' CLOUDIDENTITY_ORGUNITS_BETA = 'cloudidentityorgunitsbeta'
CLOUDIDENTITY_POLICY = 'cloudidentitypolicy'
CLOUDIDENTITY_USERINVITATIONS = 'cloudidentityuserinvitations' CLOUDIDENTITY_USERINVITATIONS = 'cloudidentityuserinvitations'
CLOUDRESOURCEMANAGER = 'cloudresourcemanager' CLOUDRESOURCEMANAGER = 'cloudresourcemanager'
CONTACTS = 'contacts' CONTACTS = 'contacts'
@@ -224,6 +225,7 @@ _INFO = {
CLOUDCHANNEL: {'name': 'Channel Channel API', 'version': 'v1', 'v2discovery': True}, CLOUDCHANNEL: {'name': 'Channel Channel API', 'version': 'v1', 'v2discovery': True},
CLOUDIDENTITY_DEVICES: {'name': 'Cloud Identity Devices API', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'}, CLOUDIDENTITY_DEVICES: {'name': 'Cloud Identity Devices API', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
CLOUDIDENTITY_GROUPS: {'name': 'Cloud Identity Groups API', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'}, CLOUDIDENTITY_GROUPS: {'name': 'Cloud Identity Groups API', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
CLOUDIDENTITY_GROUPS_BETA: {'name': 'Cloud Identity Groups API', 'version': 'v1beta1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
CLOUDIDENTITY_INBOUND_SSO: {'name': 'Cloud Identity Inbound SSO API', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'}, CLOUDIDENTITY_INBOUND_SSO: {'name': 'Cloud Identity Inbound SSO API', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
CLOUDIDENTITY_ORGUNITS: {'name': 'Cloud Identity OrgUnits API', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'}, CLOUDIDENTITY_ORGUNITS: {'name': 'Cloud Identity OrgUnits API', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
CLOUDIDENTITY_ORGUNITS_BETA: {'name': 'Cloud Identity OrgUnits API', 'version': 'v1beta1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'}, CLOUDIDENTITY_ORGUNITS_BETA: {'name': 'Cloud Identity OrgUnits API', 'version': 'v1beta1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
@@ -361,6 +363,10 @@ _CLIENT_SCOPES = [
'api': CLOUDIDENTITY_GROUPS, 'api': CLOUDIDENTITY_GROUPS,
'subscopes': READONLY, 'subscopes': READONLY,
'scope': 'https://www.googleapis.com/auth/cloud-identity.groups'}, 'scope': 'https://www.googleapis.com/auth/cloud-identity.groups'},
{'name': 'Cloud Identity Groups API Beta (Enables group locking/unlocking)',
'api': CLOUDIDENTITY_GROUPS_BETA,
'subscopes': [],
'scope': 'https://www.googleapis.com/auth/cloud-identity.groups'},
{'name': 'Cloud Identity - Inbound SSO Settings', {'name': 'Cloud Identity - Inbound SSO Settings',
'api': CLOUDIDENTITY_INBOUND_SSO, 'api': CLOUDIDENTITY_INBOUND_SSO,
'subscopes': READONLY, 'subscopes': READONLY,

View File

@@ -459,6 +459,7 @@ SERVICE_NOT_APPLICABLE = 'Service not applicable/Does not exist'
SERVICE_NOT_APPLICABLE_THIS_ADDRESS = 'Service not applicable for this address: {0}' SERVICE_NOT_APPLICABLE_THIS_ADDRESS = 'Service not applicable for this address: {0}'
SERVICE_NOT_ENABLED = '{0} Service/App not enabled' SERVICE_NOT_ENABLED = '{0} Service/App not enabled'
SHORTCUT_TARGET_CAPABILITY_IS_FALSE = '{0} capability {1} is False' SHORTCUT_TARGET_CAPABILITY_IS_FALSE = '{0} capability {1} is False'
SKU_HAS_NO_MATCHING_ARCHIVED_USER_SKU = 'SKU {0} has no matching Archived User SKU'
STARTING_THREAD = 'Starting thread' STARTING_THREAD = 'Starting thread'
STATISTICS_COPY_FILE = 'Total: {0}, Copied: {1}, Shortcut created {2}, Shortcut exists {3}, Duplicate: {4}, Copy Failed: {5}, Not copyable: {6}, In skipids: {7}, Permissions Failed: {8}, Protected Ranges Failed: {9}' STATISTICS_COPY_FILE = 'Total: {0}, Copied: {1}, Shortcut created {2}, Shortcut exists {3}, Duplicate: {4}, Copy Failed: {5}, Not copyable: {6}, In skipids: {7}, Permissions Failed: {8}, Protected Ranges Failed: {9}'
STATISTICS_COPY_FOLDER = 'Total: {0}, Copied: {1}, Shortcut created {2}, Shortcut exists {3}, Duplicate: {4}, Merged: {5}, Copy Failed: {6}, Not writable: {7}, Permissions Failed: {8}' STATISTICS_COPY_FOLDER = 'Total: {0}, Copied: {1}, Shortcut created {2}, Shortcut exists {3}, Duplicate: {4}, Merged: {5}, Copy Failed: {6}, Not writable: {7}, Permissions Failed: {8}'

View File

@@ -182,6 +182,8 @@ _SKUS = {
'product': 'Google-Chrome-Device-Management', 'aliases': ['chrome', 'cdm', 'googlechromedevicemanagement'], 'displayName': 'Google Chrome Device Management'} 'product': 'Google-Chrome-Device-Management', 'aliases': ['chrome', 'cdm', 'googlechromedevicemanagement'], 'displayName': 'Google Chrome Device Management'}
} }
ARCHIVABLE_SKUS = {'1010020020', '1010020025', '1010020026', '1010020027', '1010020028', 'Google-Apps-Unlimited'}
def getProductAndSKU(sku): def getProductAndSKU(sku):
l_sku = sku.lower().replace('-', '').replace(' ', '').replace('"', '').replace("'", '').strip() l_sku = sku.lower().replace('-', '').replace(' ', '').replace('"', '').replace("'", '').strip()
if l_sku.startswith('nv:'): if l_sku.startswith('nv:'):

View File

@@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
__version__ = "2.146.0" __version__ = "2.156.0"