mirror of
https://github.com/GAM-team/GAM.git
synced 2026-06-23 07:31:36 +00:00
Compare commits
48 Commits
20250801.0
...
v7.18.07
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef77e6f7e9 | ||
|
|
66816863e0 | ||
|
|
7d67cd08f6 | ||
|
|
0d1b622831 | ||
|
|
a1000f7778 | ||
|
|
30fea18f93 | ||
|
|
8bbb9b6e85 | ||
|
|
4b5390f6cf | ||
|
|
941abe608a | ||
|
|
0d23175216 | ||
|
|
c9c9fbd604 | ||
|
|
9d0d7c5aa8 | ||
|
|
65b23113a9 | ||
|
|
4d3edf7203 | ||
|
|
3c5d3fc569 | ||
|
|
f0d984c693 | ||
|
|
6efe1a596f | ||
|
|
598e5cf315 | ||
|
|
3fb0b840a1 | ||
|
|
ee3fa9715c | ||
|
|
3369a7a506 | ||
|
|
a6d176c033 | ||
|
|
120d72e67a | ||
|
|
0082a5a0b9 | ||
|
|
9971cdbfb3 | ||
|
|
837bb9f84f | ||
|
|
805b196532 | ||
|
|
a23238f97f | ||
|
|
e88e2f0d7f | ||
|
|
13ed3fe80c | ||
|
|
37bfcc1251 | ||
|
|
cb7e70ce31 | ||
|
|
25006765de | ||
|
|
594164944e | ||
|
|
56ed6e8a81 | ||
|
|
159184be73 | ||
|
|
51f4f3c401 | ||
|
|
320d5425c1 | ||
|
|
6d9839a328 | ||
|
|
1002f71d32 | ||
|
|
101a01cef0 | ||
|
|
45230860f6 | ||
|
|
ef1018f286 | ||
|
|
63c0c58bf6 | ||
|
|
aa443ae6cb | ||
|
|
dbec314359 | ||
|
|
8270f0a82a | ||
|
|
2a4cd66227 |
18
.github/workflows/build.yml
vendored
18
.github/workflows/build.yml
vendored
@@ -126,7 +126,7 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
cache.tar.xz
|
||||
key: gam-${{ matrix.jid }}-20250701
|
||||
key: gam-${{ matrix.jid }}-20250814
|
||||
|
||||
- name: Untar Cache archive
|
||||
if: matrix.goal == 'build' && steps.cache-python-ssl.outputs.cache-hit == 'true'
|
||||
@@ -216,9 +216,8 @@ jobs:
|
||||
|
||||
- name: MacOS import developer certificates for signing
|
||||
if: runner.os == 'macOS'
|
||||
uses: apple-actions/import-codesign-certs@v3
|
||||
uses: apple-actions/import-codesign-certs@95e84a1a18f2bdbc5c6ab9b7f4429372e4b13a8b # 5.0.3
|
||||
with:
|
||||
keychain: signing_temp
|
||||
p12-file-base64: ${{ secrets.CERTIFICATES_P12 }}
|
||||
p12-password: ${{ secrets.CERTIFICATES_P12_PASSWORD }}
|
||||
|
||||
@@ -1011,12 +1010,15 @@ jobs:
|
||||
echo "Date version: ${dateversion}"
|
||||
echo "dateversion=${dateversion}" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: "marvinpinto/action-automatic-releases@latest"
|
||||
name: Publish draft release
|
||||
- name: Publish draft release
|
||||
uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8
|
||||
with:
|
||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
automatic_release_tag: "${{ steps.dateversion.outputs.dateversion }}"
|
||||
prerelease: false
|
||||
draft: true
|
||||
prerelease: false
|
||||
tag_name: "${{ steps.dateversion.outputs.dateversion }}"
|
||||
overwrite_files: true
|
||||
fail_on_unmatched_files: true
|
||||
files: |
|
||||
gam-binaries/*
|
||||
|
||||
|
||||
|
||||
@@ -539,6 +539,7 @@ If an item contains spaces, it should be surrounded by ".
|
||||
Must match this Python Regular Expression: [a-zA-Z0-9 '"!-]{4,30}
|
||||
<PropertyKey> ::= <String>
|
||||
<PropertyValue> ::= <String>
|
||||
<PubSubTopicName> ::= <String>
|
||||
<QueryAlert> ::= <String>
|
||||
See: https://developers.google.com/admin-sdk/alertcenter/guides/query-filters
|
||||
<QueryBrowser> ::= <String>
|
||||
@@ -1511,15 +1512,22 @@ gam show privileges
|
||||
<RoleItem> ::= id:<String>|uid:<string>|<String>
|
||||
|
||||
gam create adminrole <String> [description <String>]
|
||||
privileges all|all_ou|<PrivilegesList>|(select <FileSelector>|<CSVFileSelector>)
|
||||
privileges all|all_ou|<PrivilegeList>|(select <FileSelector>|<CSVFileSelector>)|<JSONData>
|
||||
[csv [todrive <ToDriveAttribute>*] [formatjson [quotechar <Character>]] (addcsvdata <FieldName> <String>)*]
|
||||
gam update adminrole <RoleItem> [name <String>] [description <String>]
|
||||
[privileges all|all_ou|<PrivilegesList>|(select <FileSelector>|<CSVFileSelector>)]
|
||||
[privileges all|all_ou|<PrivilegeList>|(select <FileSelector>|<CSVFileSelector>)|<JSONData>]
|
||||
[csv [todrive <ToDriveAttribute>*] [formatjson [quotechar <Character>]] (addcsvdata <FieldName> <String>)*]
|
||||
gam delete adminrole <RoleItem>
|
||||
gam info adminrole <RoleItem> [privileges]
|
||||
[formatjson]
|
||||
gam print adminroles|roles [todrive <ToDriveAttribute>*]
|
||||
[role <RoleItem>] [privileges] [oneitemperrow]
|
||||
[nosystemroles]
|
||||
[formatjson [quotechar <Character>]]
|
||||
gam show adminroles|roles
|
||||
[role <RoleItem>] [privileges]
|
||||
[nosystemroles]
|
||||
[formatjson]
|
||||
|
||||
gam create|add admin <EmailAddress>|<UniqueID> <RoleItem> customer|(org_unit <OrgUnitItem>)
|
||||
[condition securitygroup|nonsecuritygroup]
|
||||
@@ -1533,6 +1541,10 @@ gam show admins
|
||||
|
||||
# Alert Center
|
||||
|
||||
gam show alertsettings
|
||||
gam update alertsettings <PubsubTopicName>
|
||||
gam clear alertsettings
|
||||
|
||||
gam delete alert <AlertID>
|
||||
gam undelete alert <AlertID>
|
||||
gam info alert <AlertID> [formatjson]
|
||||
@@ -4270,19 +4282,19 @@ gam show policies
|
||||
<SSOProfileItem> ::= <SSOProfileDisplayName>|<SSOProfileName>
|
||||
<SSOProfileItemList> ::= "<SSOProfileItem>(,<SSOProfileItem>)*"
|
||||
|
||||
gam create inboundssoprofile [name <SSOProfileDisplayName>]
|
||||
gam create inboundssoprofile [saml|oidc] [name <SSOProfileDisplayName>]
|
||||
[entityid <String>] [loginurl <URL>] [logouturl <URL>] [changepasswordurl <URL>]
|
||||
[returnnameonly]
|
||||
gam update inboundssoprofile <SSOProfileItem>
|
||||
gam update inboundssoprofile [saml|oidc] <SSOProfileItem>
|
||||
[entityid <String>] [loginurl <URL>] [logouturl <URL>] [changepasswordurl <URL>]
|
||||
[returnnameonly]
|
||||
gam delete inboundssoprofile <SSOProfileItem>
|
||||
gam delete inboundssoprofile [saml|oidc] <SSOProfileItem>
|
||||
|
||||
gam info inboundssoprofile <SSOProfileItem>
|
||||
gam info inboundssoprofile [all|saml|oidc] <SSOProfileItem>
|
||||
[formatjson]
|
||||
gam show inboundssoprofiles
|
||||
gam show inboundssoprofiles [all|saml|oidc]
|
||||
[formatjson]
|
||||
gam print inboundssoprofiles [todrive <ToDriveAttribute>*]
|
||||
gam print inboundssoprofiles [all|saml|oidc] [todrive <ToDriveAttribute>*]
|
||||
[[formatjson [quotechar <Character>]]
|
||||
|
||||
<SSOCredentialsName> ::= [id:]inboundSamlSsoProfiles/<String>/idpCredentials/<String>
|
||||
@@ -4306,10 +4318,14 @@ gam print inboundssocredentials [profile|profiles <SSOProfileItemList>]
|
||||
orgunits/<String> |
|
||||
orgunit:<OrgUnitPath>
|
||||
|
||||
gam create inboundssoassignment (group <GroupItem> rank <Number>)|(ou|org|orgunit <OrgUnitItem>)
|
||||
(mode sso_off)|(mode saml_sso profile <SSOProfileItem>)(mode domain_wide_saml_if_enabled) [neverredirect]
|
||||
gam update inboundssoassignment [(group <GroupItem> rank <Number>)|(ou|org|orgunit <OrgUnitItem>)]
|
||||
[(mode sso_off)|(mode saml_sso profile <SSOProfileItem>)(mode domain_wide_saml_if_enabled)] [neverredirect]
|
||||
gam create inboundssoassignment
|
||||
(group <GroupItem> rank <Number>)|(ou|org|orgunit <OrgUnitItem>)
|
||||
(mode sso_off)|(mode saml_sso profile <SSOProfileItem>)|(mode oidc_sso profile <SSOProfileName>}|(mode domain_wide_saml_if_enabled)
|
||||
[neverredirect]
|
||||
gam update inboundssoassignment <SSOAssignmentName>
|
||||
[(group <GroupItem> rank <Number>)|(ou|org|orgunit <OrgUnitItem>)]
|
||||
(mode sso_off)|(mode saml_sso profile <SSOProfileItem>)|(mode oidc_sso profile <SSOProfileName>}|(mode domain_wide_saml_if_enabled)
|
||||
[neverredirect]
|
||||
gam delete inboundssoassignment <SSOAssignmentSelector>
|
||||
|
||||
gam info inboundssoassignment <SSOAssignmentSelector>
|
||||
|
||||
@@ -1,3 +1,57 @@
|
||||
7.18.07
|
||||
|
||||
Updated `gam <UserTypeEntity> print drivelastmodification` to put `addcsvdata` columns
|
||||
after `User,id,name` rather than after the last column.
|
||||
|
||||
7.18.06
|
||||
|
||||
Updated `gam <UserTypeEntity> delete|modify messages` to improve the handling
|
||||
of the following error.
|
||||
```
|
||||
quotaExceeded - User-rate limit exceeded
|
||||
```
|
||||
|
||||
7.18.05
|
||||
|
||||
Added support for Inbound SSO OIDC profiles.
|
||||
|
||||
Currently, if you enter `gam select <SectionName>` and nothing else on the command line,
|
||||
GAM performs no action. Now, it will be treated as if you entered:
|
||||
`gam select <SectionName> save`
|
||||
|
||||
Updated to Python 3.13.7.
|
||||
|
||||
7.18.04
|
||||
|
||||
Added commands to display/manage Alert Center Pub/Sub notifications.
|
||||
* See: https://github.com/GAM-team/GAM/wiki/Alert-Center#configuring-settings
|
||||
|
||||
7.18.03
|
||||
|
||||
Updated `gam oauth create` to give a warning if the number of selected scopes will
|
||||
probably cause Google to generate a "Something went wrong" error.
|
||||
|
||||
7.18.02
|
||||
|
||||
Upgraded to OpenSSL 3.5.2.
|
||||
|
||||
7.18.01
|
||||
|
||||
Added option `nosystemroles` to `gam print|show adminroles` that causes GAM
|
||||
to only display non-system roles.
|
||||
|
||||
Added option `formatjson` to `gam info|print|show adminroles`; this will be most useful
|
||||
when the `privileges` option is used.
|
||||
|
||||
Updated `gam create|update adminrole` to allow specification of privileges with
|
||||
JSON data: `privileges <JSONData>`. These two updates make it easier to copy admin roles.
|
||||
|
||||
Updated `gam create|update adminrole` to allow output of the created/updated
|
||||
role data in CSV format; by default, GAM displays `<RoleName>(<RoleID>) created|updated`.
|
||||
```
|
||||
csv [todrive <ToDriveAttribute>*] [formatjson [quotechar <Character>]] (addcsvdata <FieldName> <String>)*
|
||||
```
|
||||
|
||||
7.18.00
|
||||
|
||||
Added commands to display Business Profile Accounts.
|
||||
|
||||
@@ -25,7 +25,7 @@ https://github.com/GAM-team/GAM/wiki
|
||||
"""
|
||||
|
||||
__author__ = 'GAM Team <google-apps-manager@googlegroups.com>'
|
||||
__version__ = '7.18.00'
|
||||
__version__ = '7.18.07'
|
||||
__license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
|
||||
|
||||
#pylint: disable=wrong-import-position
|
||||
@@ -3952,14 +3952,20 @@ def SetGlobalVariables():
|
||||
if checkArgumentPresent(Cmd.SELECT_CMD):
|
||||
sectionName = _selectSection()
|
||||
GM.Globals[GM.SECTION] = sectionName # Save section for inner gams
|
||||
while Cmd.ArgumentsRemaining():
|
||||
if checkArgumentPresent('save'):
|
||||
GM.Globals[GM.PARSER].set(configparser.DEFAULTSECT, GC.SECTION, sectionName)
|
||||
_writeGamCfgFile(GM.Globals[GM.PARSER], GM.Globals[GM.GAM_CFG_FILE], Act.SAVE)
|
||||
elif checkArgumentPresent('verify'):
|
||||
_verifyValues(sectionName, inputFilterSectionName, outputFilterSectionName)
|
||||
else:
|
||||
break
|
||||
# If command line is simply: gam select <SectionName>
|
||||
# assume save
|
||||
if not Cmd.ArgumentsRemaining():
|
||||
GM.Globals[GM.PARSER].set(configparser.DEFAULTSECT, GC.SECTION, sectionName)
|
||||
_writeGamCfgFile(GM.Globals[GM.PARSER], GM.Globals[GM.GAM_CFG_FILE], Act.SAVE)
|
||||
else:
|
||||
while Cmd.ArgumentsRemaining():
|
||||
if checkArgumentPresent('save'):
|
||||
GM.Globals[GM.PARSER].set(configparser.DEFAULTSECT, GC.SECTION, sectionName)
|
||||
_writeGamCfgFile(GM.Globals[GM.PARSER], GM.Globals[GM.GAM_CFG_FILE], Act.SAVE)
|
||||
elif checkArgumentPresent('verify'):
|
||||
_verifyValues(sectionName, inputFilterSectionName, outputFilterSectionName)
|
||||
else:
|
||||
break
|
||||
GM.Globals[GM.GAM_CFG_SECTION_NAME] = sectionName
|
||||
# showsections
|
||||
if checkArgumentPresent(Cmd.SHOWSECTIONS_CMD):
|
||||
@@ -10672,9 +10678,6 @@ Select all default scopes by entering an 's'; yields [*] for default scopes, [ ]
|
||||
Unselect all scopes by entering a 'u'; yields [ ] for all scopes
|
||||
Exit without changes/authorization by entering an 'e'
|
||||
Continue to authorization by entering a 'c'
|
||||
'''
|
||||
if clientAccess:
|
||||
oauth2_menu += ''' Note, if all scopes are selected, Google will probably generate an authorization error
|
||||
'''
|
||||
menu = oauth2_menu % tuple(range(numScopes))
|
||||
selectedScopes = ['*'] * numScopes
|
||||
@@ -10776,7 +10779,25 @@ Continue to authorization by entering a 'c'
|
||||
break
|
||||
sys.stdout.write(f'{ERROR_PREFIX}Invalid input "{choice}"\n')
|
||||
if selection == 'c':
|
||||
break
|
||||
if clientAccess:
|
||||
numSelectedScopes = 0
|
||||
i = 0
|
||||
for a_scope in scopesList:
|
||||
if selectedScopes[i] == '*':
|
||||
if a_scope['scope']:
|
||||
numSelectedScopes += 1
|
||||
elif selectedScopes[i] != ' ':
|
||||
numSelectedScopes += 1
|
||||
i += 1
|
||||
if numSelectedScopes <= API.NUM_CLIENT_SCOPES_ERROR_LIMIT:
|
||||
break
|
||||
# If number of scopes is > 48 we'll probably get an error
|
||||
writeStdout(Msg.NUM_SELECTED_CLIENT_SCOPES.format(numSelectedScopes, API.NUM_CLIENT_SCOPES_ERROR_LIMIT))
|
||||
choice = readStdin('\nPlease enter c to continue to authorization or any other key to amend selection: ')
|
||||
if choice and choice.lower() == 'c':
|
||||
break
|
||||
else:
|
||||
break
|
||||
return selectedScopes
|
||||
|
||||
def _localhost_to_ip():
|
||||
@@ -11467,13 +11488,14 @@ def _createOauth2serviceJSON(httpObj, projectInfo, svcAcctInfo, create_key=True)
|
||||
iam = getAPIService(API.IAM, httpObj)
|
||||
try:
|
||||
service_account = callGAPI(iam.projects().serviceAccounts(), 'create',
|
||||
throwReasons=[GAPI.NOT_FOUND, GAPI.PERMISSION_DENIED, GAPI.ALREADY_EXISTS],
|
||||
throwReasons=[GAPI.FAILED_PRECONDITION, GAPI.NOT_FOUND,
|
||||
GAPI.PERMISSION_DENIED, GAPI.ALREADY_EXISTS],
|
||||
name=f'projects/{projectInfo["projectId"]}',
|
||||
body={'accountId': svcAcctInfo['name'],
|
||||
'serviceAccount': {'displayName': svcAcctInfo['displayName'],
|
||||
'description': svcAcctInfo['description']}})
|
||||
entityActionPerformed([Ent.PROJECT, projectInfo['projectId'], Ent.SVCACCT, service_account['name'].rsplit('/', 1)[-1]])
|
||||
except (GAPI.notFound, GAPI.permissionDenied) as e:
|
||||
except (GAPI.failedPrecondition, GAPI.notFound, GAPI.permissionDenied) as e:
|
||||
entityActionFailedWarning([Ent.PROJECT, projectInfo['projectId']], str(e))
|
||||
return False
|
||||
except GAPI.alreadyExists as e:
|
||||
@@ -16621,10 +16643,14 @@ def getRoleId():
|
||||
invalidChoiceExit(role, GM.Globals[GM.MAP_ROLE_NAME_TO_ID], True)
|
||||
return (role, roleId)
|
||||
|
||||
PRINT_ADMIN_ROLES_FIELDS = ['roleId', 'roleName', 'roleDescription', 'isSuperAdminRole', 'isSystemRole']
|
||||
|
||||
# gam create adminrole <String> [description <String>]
|
||||
# privileges all|all_ou|<PrivilegesList>|(select <FileSelector>|<CSVFileSelector>)
|
||||
# privileges all|all_ou|<PrivilegeList>|(select <FileSelector>|<CSVFileSelector>)|<JSONData>
|
||||
# [csv [todrive <ToDriveAttribute>*] [formatjson [quotechar <Character>]] (addcsvdata <FieldName> <String>)*]
|
||||
# gam update adminrole <RoleItem> [name <String>] [description <String>]
|
||||
# [privileges all|all_ou|<PrivilegesList>|(select <FileSelector>|<CSVFileSelector>)]
|
||||
# [privileges all|all_ou|<PrivilegeList>|(select <FileSelector>|<CSVFileSelector>)|<JSONData>]
|
||||
# [csv [todrive <ToDriveAttribute>*] [formatjson [quotechar <Character>]] (addcsvdata <FieldName> <String>)*]
|
||||
def doCreateUpdateAdminRoles():
|
||||
def expandChildPrivileges(privilege):
|
||||
for childPrivilege in privilege.get('childPrivileges', []):
|
||||
@@ -16641,6 +16667,9 @@ def doCreateUpdateAdminRoles():
|
||||
allPrivileges = {}
|
||||
ouPrivileges = {}
|
||||
childPrivileges = {}
|
||||
csvPF = None
|
||||
FJQC = FormatJSONQuoteChar(None)
|
||||
addCSVData = {}
|
||||
for privilege in _listPrivileges(cd):
|
||||
allPrivileges[privilege['privilegeName']] = privilege['serviceId']
|
||||
if privilege['isOuScopable']:
|
||||
@@ -16654,6 +16683,8 @@ def doCreateUpdateAdminRoles():
|
||||
body['rolePrivileges'] = [{'privilegeName': p, 'serviceId': v} for p, v in allPrivileges.items()]
|
||||
elif privs == 'ALL_OU':
|
||||
body['rolePrivileges'] = [{'privilegeName': p, 'serviceId': v} for p, v in ouPrivileges.items()]
|
||||
elif privs == 'JSON':
|
||||
body['rolePrivileges'] = getJSON(['roleId', 'roleName', 'isAdminRole', 'isSystemRole']).get('rolePrivileges', [])
|
||||
else:
|
||||
if privs == 'SELECT':
|
||||
privsList = [p.upper() for p in getEntityList(Cmd.OB_PRIVILEGE_LIST)]
|
||||
@@ -16675,25 +16706,59 @@ def doCreateUpdateAdminRoles():
|
||||
else:
|
||||
invalidChoiceExit(p, list(allPrivileges.keys())+list(ouPrivileges.keys())+list(childPrivileges.keys()), True)
|
||||
elif myarg == 'description':
|
||||
body['roleDescription'] = getString(Cmd.OB_STRING)
|
||||
body['roleDescription'] = getString(Cmd.OB_STRING, minLen=0)
|
||||
elif myarg == 'name':
|
||||
body['roleName'] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'csv':
|
||||
csvPF = CSVPrintFile(PRINT_ADMIN_ROLES_FIELDS)
|
||||
FJQC.SetCsvPF(csvPF)
|
||||
elif csvPF and myarg == 'todrive':
|
||||
csvPF.GetTodriveParameters()
|
||||
elif csvPF and myarg == 'addcsvdata':
|
||||
k = getString(Cmd.OB_STRING)
|
||||
addCSVData[k] = getString(Cmd.OB_STRING, minLen=0)
|
||||
else:
|
||||
unknownArgumentExit()
|
||||
FJQC.GetFormatJSONQuoteChar(myarg, True)
|
||||
if not updateCmd and not body.get('rolePrivileges'):
|
||||
missingArgumentExit('privileges')
|
||||
if csvPF:
|
||||
if addCSVData:
|
||||
csvPF.AddTitles(sorted(addCSVData.keys()))
|
||||
if not FJQC.formatJSON:
|
||||
csvPF.AddTitles('rolePrivileges')
|
||||
else:
|
||||
csvPF.AddJSONTitles(sorted(addCSVData.keys()))
|
||||
csvPF.MoveJSONTitlesToEnd(['JSON'])
|
||||
fieldsList = ','.join(PRINT_ADMIN_ROLES_FIELDS+['rolePrivileges'])
|
||||
else:
|
||||
fieldsList = 'roleId,roleName'
|
||||
try:
|
||||
if not updateCmd:
|
||||
result = callGAPI(cd.roles(), 'insert',
|
||||
throwReasons=[GAPI.BAD_REQUEST, GAPI.CUSTOMER_NOT_FOUND,
|
||||
GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED]+[GAPI.DUPLICATE],
|
||||
customer=GC.Values[GC.CUSTOMER_ID], body=body, fields='roleId,roleName')
|
||||
customer=GC.Values[GC.CUSTOMER_ID], body=body, fields=fieldsList)
|
||||
else:
|
||||
result = callGAPI(cd.roles(), 'patch',
|
||||
throwReasons=[GAPI.BAD_REQUEST, GAPI.CUSTOMER_NOT_FOUND,
|
||||
GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED]+[GAPI.NOT_FOUND, GAPI.FAILED_PRECONDITION, GAPI.CONFLICT],
|
||||
customer=GC.Values[GC.CUSTOMER_ID], roleId=roleId, body=body, fields='roleId,roleName')
|
||||
entityActionPerformed([Ent.ADMIN_ROLE, f"{result['roleName']}({result['roleId']})"])
|
||||
customer=GC.Values[GC.CUSTOMER_ID], roleId=roleId, body=body, fields=fieldsList)
|
||||
if not csvPF:
|
||||
entityActionPerformed([Ent.ADMIN_ROLE, f"{result['roleName']}({result['roleId']})"])
|
||||
else:
|
||||
if not FJQC.formatJSON:
|
||||
if addCSVData:
|
||||
result.update(addCSVData)
|
||||
csvPF.WriteRowNoFilter(result)
|
||||
else:
|
||||
row = {}
|
||||
for field in PRINT_ADMIN_ROLES_FIELDS:
|
||||
if field in result:
|
||||
row[field] = result[field]
|
||||
if addCSVData:
|
||||
row.update(addCSVData)
|
||||
row['JSON'] = json.dumps(cleanJSON(result), ensure_ascii=False, sort_keys=True)
|
||||
csvPF.WriteRowNoFilter(row)
|
||||
except GAPI.duplicate as e:
|
||||
entityActionFailedWarning([Ent.ADMIN_ROLE, f"{body['roleName']}"], str(e))
|
||||
except (GAPI.notFound, GAPI.failedPrecondition, GAPI.conflict) as e:
|
||||
@@ -16702,6 +16767,8 @@ def doCreateUpdateAdminRoles():
|
||||
accessErrorExit(cd)
|
||||
except (GAPI.forbidden, GAPI.permissionDenied) as e:
|
||||
ClientAPIAccessDeniedExit(str(e))
|
||||
if csvPF:
|
||||
csvPF.writeCSVfile('Admin Roles')
|
||||
|
||||
# gam delete adminrole <RoleItem>
|
||||
def doDeleteAdminRole():
|
||||
@@ -16721,9 +16788,10 @@ def doDeleteAdminRole():
|
||||
except (GAPI.forbidden, GAPI.permissionDenied) as e:
|
||||
ClientAPIAccessDeniedExit(str(e))
|
||||
|
||||
PRINT_ADMIN_ROLES_FIELDS = ['roleId', 'roleName', 'roleDescription', 'isSuperAdminRole', 'isSystemRole']
|
||||
|
||||
def _showAdminRole(role, i=0, count=0):
|
||||
def _showAdminRole(role, FJQC, i=0, count=0):
|
||||
if FJQC.formatJSON:
|
||||
printLine(json.dumps(cleanJSON(role), ensure_ascii=False, sort_keys=True))
|
||||
return
|
||||
printEntity([Ent.ADMIN_ROLE, role['roleName']], i, count)
|
||||
Ind.Increment()
|
||||
for field in PRINT_ADMIN_ROLES_FIELDS:
|
||||
@@ -16744,15 +16812,21 @@ def _showAdminRole(role, i=0, count=0):
|
||||
Ind.Decrement()
|
||||
|
||||
# gam info adminrole <RoleItem> [privileges]
|
||||
# [formatjson]
|
||||
# gam print adminroles|roles [todrive <ToDriveAttribute>*]
|
||||
# [role <RoleItem>] [privileges] [oneitemperrow]
|
||||
# [nosystemroles]
|
||||
# [formatjson [quotechar <Character>]]
|
||||
# gam show adminroles|roles
|
||||
# [role <RoleItem>] [privileges]
|
||||
# [nosystemroles]
|
||||
# [formatjson]
|
||||
def doInfoPrintShowAdminRoles():
|
||||
cd = buildGAPIObject(API.DIRECTORY)
|
||||
fieldsList = PRINT_ADMIN_ROLES_FIELDS[:]
|
||||
csvPF = CSVPrintFile(fieldsList, PRINT_ADMIN_ROLES_FIELDS) if Act.csvFormat() else None
|
||||
oneItemPerRow = False
|
||||
FJQC = FormatJSONQuoteChar(csvPF)
|
||||
noSystemRoles = oneItemPerRow = False
|
||||
if Act.Get() != Act.INFO:
|
||||
roleId = None
|
||||
else:
|
||||
@@ -16767,13 +16841,17 @@ def doInfoPrintShowAdminRoles():
|
||||
fieldsList.append('rolePrivileges')
|
||||
elif myarg == 'oneitemperrow':
|
||||
oneItemPerRow = True
|
||||
elif myarg == 'nosystemroles':
|
||||
noSystemRoles = True
|
||||
else:
|
||||
unknownArgumentExit()
|
||||
if csvPF and 'rolePrivileges' in fieldsList:
|
||||
if not oneItemPerRow:
|
||||
csvPF.AddTitles(['rolePrivileges'])
|
||||
else:
|
||||
csvPF.AddTitles(['privilegeName', 'serviceId'])
|
||||
FJQC.GetFormatJSONQuoteChar(myarg, True)
|
||||
if csvPF:
|
||||
if 'rolePrivileges' in fieldsList:
|
||||
if not oneItemPerRow:
|
||||
if not FJQC.formatJSON:
|
||||
csvPF.AddTitles(['rolePrivileges'])
|
||||
else:
|
||||
csvPF.AddTitles(['privilegeName', 'serviceId'])
|
||||
try:
|
||||
if roleId is None:
|
||||
fields = getItemFieldsFromFieldsList('items', fieldsList)
|
||||
@@ -16783,6 +16861,8 @@ def doInfoPrintShowAdminRoles():
|
||||
throwReasons=[GAPI.BAD_REQUEST, GAPI.CUSTOMER_NOT_FOUND,
|
||||
GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
|
||||
customer=GC.Values[GC.CUSTOMER_ID], fields=fields)
|
||||
if noSystemRoles:
|
||||
roles = [role for role in roles if not role.get('isSystemRole', False)]
|
||||
else:
|
||||
fields = getFieldsFromFieldsList(fieldsList)
|
||||
roles = [callGAPI(cd.roles(), 'get',
|
||||
@@ -16801,23 +16881,38 @@ def doInfoPrintShowAdminRoles():
|
||||
role.setdefault('isSystemRole', False)
|
||||
if not csvPF:
|
||||
count = len(roles)
|
||||
performActionNumItems(count, Ent.ADMIN_ROLE)
|
||||
if not FJQC.formatJSON:
|
||||
performActionNumItems(count, Ent.ADMIN_ROLE)
|
||||
Ind.Increment()
|
||||
i = 0
|
||||
for role in roles:
|
||||
i += 1
|
||||
_showAdminRole(role, i, count)
|
||||
_showAdminRole(role, FJQC, i, count)
|
||||
Ind.Decrement()
|
||||
else:
|
||||
for role in roles:
|
||||
if not oneItemPerRow or 'rolePrivileges' not in role:
|
||||
csvPF.WriteRowTitles(flattenJSON(role))
|
||||
row = flattenJSON(role)
|
||||
if not FJQC.formatJSON:
|
||||
csvPF.WriteRowTitles(row)
|
||||
elif csvPF.CheckRowTitles(row):
|
||||
row = {}
|
||||
for field in PRINT_ADMIN_ROLES_FIELDS:
|
||||
if field in role:
|
||||
row[field] = role[field]
|
||||
row['JSON'] = json.dumps(cleanJSON(role), ensure_ascii=False, sort_keys=True)
|
||||
csvPF.WriteRowNoFilter(row)
|
||||
else:
|
||||
privileges = role.pop('rolePrivileges')
|
||||
baserow = flattenJSON(role)
|
||||
for privilege in privileges:
|
||||
row = flattenJSON(privilege, flattened=baserow.copy())
|
||||
csvPF.WriteRowTitles(row)
|
||||
if not FJQC.formatJSON:
|
||||
csvPF.WriteRowTitles(row)
|
||||
elif csvPF.CheckRowTitles(row):
|
||||
row = baserow.copy()
|
||||
row['JSON'] = json.dumps(cleanJSON(privilege), ensure_ascii=False, sort_keys=True)
|
||||
csvPF.WriteRowNoFilter(row)
|
||||
if csvPF:
|
||||
csvPF.writeCSVfile('Admin Roles')
|
||||
|
||||
@@ -26993,6 +27088,8 @@ def _getChatMemberEmail(cd, member):
|
||||
if member['member']['type'] == 'HUMAN':
|
||||
_, memberUid = member['member']['name'].split('/')
|
||||
member['member']['email'], _ = convertUIDtoEmailAddressWithType(f'uid:{memberUid}', cd, None, emailTypes=['user'])
|
||||
if member['member']['email'].find('@') == -1:
|
||||
member['member']['email'] = 'id:'+member['member']['email']
|
||||
elif 'groupMember' in member:
|
||||
_, memberUid = member['groupMember']['name'].split('/')
|
||||
member['groupMember']['email'], _ = convertUIDtoEmailAddressWithType(f'uid:{memberUid}', cd, None, emailTypes=['group'])
|
||||
@@ -37710,6 +37807,54 @@ def doDeleteOrUndeleteAlert():
|
||||
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.permissionDenied):
|
||||
userAlertsServiceNotEnabledWarning(user)
|
||||
|
||||
def _showAlertSettings(settings):
|
||||
notifications = settings.get('notifications', [])
|
||||
count = len(notifications)
|
||||
entityPerformAction([Ent.ALERT_SETTINGS, None])
|
||||
i = 0
|
||||
for notification in notifications:
|
||||
i += 1
|
||||
printEntity([Ent.NOTIFICATION, None], i, count)
|
||||
Ind.Increment()
|
||||
showJSON(None, notification)
|
||||
Ind.Decrement()
|
||||
|
||||
# gam show alertsettings
|
||||
def doShowAlertSettings():
|
||||
checkForExtraneousArguments()
|
||||
user, ac = buildGAPIServiceObject(API.ALERTCENTER, _getAdminEmail())
|
||||
if not ac:
|
||||
return
|
||||
try:
|
||||
settings = callGAPI(ac.v1beta1(), 'getSettings',
|
||||
throwReasons=GAPI.ALERT_THROW_REASONS)
|
||||
_showAlertSettings(settings)
|
||||
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.permissionDenied):
|
||||
userAlertsServiceNotEnabledWarning(user)
|
||||
|
||||
# gam update alertsettings <PubsubTopicName>
|
||||
def doUpdateAlertSettings(clear=False):
|
||||
if not clear:
|
||||
body = {'notifications':
|
||||
[{'cloudPubsubTopic': {'topicName': getString(Cmd.OB_PUBSUB_TOPIC_NAME)}}]}
|
||||
else:
|
||||
body = {'notifications': []}
|
||||
checkForExtraneousArguments()
|
||||
user, ac = buildGAPIServiceObject(API.ALERTCENTER, _getAdminEmail())
|
||||
if not ac:
|
||||
return
|
||||
try:
|
||||
settings = callGAPI(ac.v1beta1(), 'updateSettings',
|
||||
throwReasons=GAPI.ALERT_THROW_REASONS,
|
||||
body=body)
|
||||
_showAlertSettings(settings)
|
||||
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.permissionDenied):
|
||||
userAlertsServiceNotEnabledWarning(user)
|
||||
|
||||
# gam clear alertsettings
|
||||
def doClearAlertSettings():
|
||||
doUpdateAlertSettings(clear=True)
|
||||
|
||||
ALERT_TIME_OBJECTS = {'createTime', 'startTime', 'endTime'}
|
||||
|
||||
def _showAlert(alert, FJQC, i=0, count=0):
|
||||
@@ -44756,21 +44901,44 @@ def waitForMailbox(entityList):
|
||||
Ind.Decrement()
|
||||
|
||||
def getUserLicenses(lic, user, skus):
|
||||
def _callbackGetLicense(_, response, exception):
|
||||
def _callbackGetLicense(request_id, response, exception):
|
||||
if exception is None:
|
||||
if response and 'skuId' in response:
|
||||
licenses.append(response['skuId'])
|
||||
del sku_calls[request_id]
|
||||
else:
|
||||
_, reason, _ = checkGAPIError(exception, softErrors=True)
|
||||
reasons_to_quit = [
|
||||
GAPI.ACCESS_NOT_CONFIGURED, # license API not turned on
|
||||
GAPI.PERMISSION_DENIED, # Admin doesn't have rights to license assignments
|
||||
GAPI.NOT_FOUND # API call succeeded, user does not have this license
|
||||
]
|
||||
if reason in reasons_to_quit:
|
||||
del sku_calls[request_id]
|
||||
|
||||
licenses = []
|
||||
svcargs = dict([('userId', user['primaryEmail']), ('productId', None), ('skuId', None), ('fields', 'skuId')]+GM.Globals[GM.EXTRA_ARGS_LIST])
|
||||
method = getattr(lic.licenseAssignments(), 'get')
|
||||
dbatch = lic.new_batch_http_request(callback=_callbackGetLicense)
|
||||
sku_calls = {}
|
||||
for sku in skus:
|
||||
svcparms = svcargs.copy()
|
||||
svcparms['productId'] = sku[0]
|
||||
svcparms['skuId'] = sku[1]
|
||||
dbatch.add(method(**svcparms))
|
||||
dbatch.execute()
|
||||
sku_id = sku[1]
|
||||
svcparms['skuId'] = sku_id
|
||||
sku_calls[sku_id] = method(**svcparms)
|
||||
try_count = 0
|
||||
while sku_calls:
|
||||
try_count += 1
|
||||
dbatch = lic.new_batch_http_request(callback=_callbackGetLicense)
|
||||
for sku_id, sku_call in sku_calls.items():
|
||||
dbatch.add(sku_call, request_id=sku_id)
|
||||
dbatch.execute()
|
||||
if sku_calls:
|
||||
if try_count >= 5:
|
||||
# give up and return what we have
|
||||
return licenses
|
||||
time.sleep(5)
|
||||
return licenses
|
||||
|
||||
USER_NAME_PROPERTY_PRINT_ORDER = [
|
||||
@@ -46255,9 +46423,30 @@ def checkCIUserIsInvitable(users):
|
||||
return
|
||||
csvPF.writeCSVfile('Invitable Users')
|
||||
|
||||
INBOUNDSSO_INPUT_MODE_CHOICE_MAP = {
|
||||
'saml': 'saml',
|
||||
'samlsso': 'saml',
|
||||
'oidc': 'oidc',
|
||||
'oidcsso': 'oidc',
|
||||
}
|
||||
|
||||
INBOUNDSSO_OUTPUT_MODE_CHOICE_MAP = {
|
||||
'all': 'all',
|
||||
'saml': 'saml',
|
||||
'samlsso': 'saml',
|
||||
'oidc': 'oidc',
|
||||
'oidcsso': 'oidc',
|
||||
}
|
||||
|
||||
INBOUNDSSO_ALL_SAML = {'all', 'saml'}
|
||||
INBOUNDSSO_ALL_OIDC = {'all', 'oidc'}
|
||||
|
||||
INBOUNDSSO_MODE_CHOICE_MAP = {
|
||||
'ssooff': 'SSO_OFF',
|
||||
'saml': 'SAML_SSO',
|
||||
'samlsso': 'SAML_SSO',
|
||||
'oidc': 'OIDC_SSO',
|
||||
'oidcsso': 'OIDC_SSO',
|
||||
'domainwidesamlifenabled': 'DOMAIN_WIDE_SAML_IF_ENABLED'
|
||||
}
|
||||
|
||||
@@ -46267,29 +46456,49 @@ def getCIOrgunitID(cd, orgunit):
|
||||
ou_id = ou_id[3:]
|
||||
return f'orgUnits/{ou_id}'
|
||||
|
||||
def _getInboundSSOProfiles(ci):
|
||||
def _getInboundSSOProfiles(ci, mode):
|
||||
customer = normalizeChannelCustomerID(GC.Values[GC.CUSTOMER_ID])
|
||||
try:
|
||||
return callGAPIpages(ci.inboundSamlSsoProfiles(), 'list', 'inboundSamlSsoProfiles',
|
||||
throwReasons=GAPI.CISSO_LIST_THROW_REASONS,
|
||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
||||
bailOnInternalError=True,
|
||||
filter=f'customer=="{customer}"')
|
||||
except (GAPI.notFound, GAPI.domainNotFound, GAPI.domainCannotUseApis,
|
||||
GAPI.forbidden, GAPI.badRequest, GAPI.invalid,
|
||||
GAPI.systemError, GAPI.permissionDenied, GAPI.internalError, GAPI.serviceNotAvailable) as e:
|
||||
entityActionFailedWarning([Ent.INBOUND_SSO_PROFILE, customer], str(e))
|
||||
return []
|
||||
profiles = []
|
||||
if mode in INBOUNDSSO_ALL_SAML:
|
||||
try:
|
||||
profiles.extend(callGAPIpages(ci.inboundSamlSsoProfiles(), 'list', 'inboundSamlSsoProfiles',
|
||||
throwReasons=GAPI.CISSO_LIST_THROW_REASONS,
|
||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
||||
bailOnInternalError=True,
|
||||
filter=f'customer=="{customer}"'))
|
||||
except (GAPI.notFound, GAPI.domainNotFound, GAPI.domainCannotUseApis,
|
||||
GAPI.forbidden, GAPI.badRequest, GAPI.invalid,
|
||||
GAPI.systemError, GAPI.permissionDenied, GAPI.internalError, GAPI.serviceNotAvailable) as e:
|
||||
entityActionFailedWarning([Ent.INBOUND_SSO_PROFILE, customer], str(e))
|
||||
if mode in INBOUNDSSO_ALL_OIDC:
|
||||
try:
|
||||
profiles.extend(callGAPIpages(ci.inboundOidcSsoProfiles(), 'list', 'inboundOidcSsoProfiles',
|
||||
throwReasons=GAPI.CISSO_LIST_THROW_REASONS,
|
||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
||||
bailOnInternalError=True,
|
||||
filter=f'customer=="{customer}"'))
|
||||
except (GAPI.notFound, GAPI.domainNotFound, GAPI.domainCannotUseApis,
|
||||
GAPI.forbidden, GAPI.badRequest, GAPI.invalid,
|
||||
GAPI.systemError, GAPI.permissionDenied, GAPI.internalError, GAPI.serviceNotAvailable) as e:
|
||||
entityActionFailedWarning([Ent.INBOUND_SSO_PROFILE, customer], str(e))
|
||||
return profiles
|
||||
|
||||
def _convertInboundSSOProfileDisplaynameToName(ci=None, displayName=''):
|
||||
def _convertInboundSSOProfileDisplaynameToName(ci, mode, displayName='',
|
||||
entityType=Ent.INBOUND_SSO_PROFILE):
|
||||
if displayName.lower().startswith('id:') or displayName.lower().startswith('uid:'):
|
||||
displayName = displayName.split(':', 1)[1]
|
||||
if not displayName.startswith('inboundSamlSsoProfiles/'):
|
||||
displayName = f'inboundSamlSsoProfiles/{displayName}'
|
||||
if mode == 'all':
|
||||
if not (displayName.startswith('inboundSamlSsoProfiles/') and
|
||||
displayName.startswith('inboundOidcSsoProfiles/')):
|
||||
displayName = f'inboundSamlSsoProfiles/{displayName}'
|
||||
elif mode == 'saml':
|
||||
if not displayName.startswith('inboundSamlSsoProfiles/'):
|
||||
displayName = f'inboundSamlSsoProfiles/{displayName}'
|
||||
else:
|
||||
if not displayName.startswith('inboundOidcSsoProfiles/'):
|
||||
displayName = f'inboundOidcSsoProfiles/{displayName}'
|
||||
return displayName
|
||||
if not ci:
|
||||
ci = buildGAPIObject(API.CLOUDIDENTITY_INBOUND_SSO)
|
||||
profiles = _getInboundSSOProfiles(ci)
|
||||
profiles = _getInboundSSOProfiles(ci, mode)
|
||||
matches = []
|
||||
for profile in profiles:
|
||||
if displayName.lower() == profile.get('displayName', '').lower():
|
||||
@@ -46297,30 +46506,50 @@ def _convertInboundSSOProfileDisplaynameToName(ci=None, displayName=''):
|
||||
if len(matches) == 1:
|
||||
return matches[0]['name']
|
||||
if len(matches) == 0:
|
||||
usageErrorExit(Msg.NO_SSO_PROFILE_MATCHES.format(displayName))
|
||||
errMsg = Msg.MULTIPLE_SSO_PROFILES_MATCH.format(displayName)
|
||||
for m in matches:
|
||||
errMsg += f' {m["name"]} {m["displayName"]}\n'
|
||||
usageErrorExit(errMsg)
|
||||
errMsg = Msg.NO_SSO_PROFILE_MATCHES.format(displayName)
|
||||
else:
|
||||
errMsg = Msg.MULTIPLE_SSO_PROFILES_MATCH.format(displayName)
|
||||
for m in matches:
|
||||
errMsg += f' {m["name"]} {m["displayName"]}\n'
|
||||
entityActionFailedWarning([entityType, None], errMsg)
|
||||
return None
|
||||
|
||||
def _getInboundSSOProfileArguments(body):
|
||||
def _getInboundSSOProfileArguments(body, mode):
|
||||
returnNameOnly = False
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if myarg == 'name':
|
||||
body['displayName'] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'entityid':
|
||||
body.setdefault('idpConfig', {})['entityId'] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'loginurl':
|
||||
body.setdefault('idpConfig', {})['singleSignOnServiceUri'] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'logouturl':
|
||||
body.setdefault('idpConfig', {})['logoutRedirectUri'] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'changepasswordurl':
|
||||
body.setdefault('idpConfig', {})['changePasswordUri'] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'returnnameonly':
|
||||
returnNameOnly = True
|
||||
else:
|
||||
unknownArgumentExit()
|
||||
if mode == 'saml':
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if myarg == 'name':
|
||||
body['displayName'] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'entityid':
|
||||
body.setdefault('idpConfig', {})['entityId'] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'loginurl':
|
||||
body.setdefault('idpConfig', {})['singleSignOnServiceUri'] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'logouturl':
|
||||
body.setdefault('idpConfig', {})['logoutRedirectUri'] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'changepasswordurl':
|
||||
body.setdefault('idpConfig', {})['changePasswordUri'] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'returnnameonly':
|
||||
returnNameOnly = True
|
||||
else:
|
||||
unknownArgumentExit()
|
||||
else:
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if myarg == 'name':
|
||||
body['displayName'] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'issueruri':
|
||||
body.setdefault('idpConfig', {})['issuerUri'] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'changepasswordurl':
|
||||
body.setdefault('idpConfig', {})['changePasswordUri'] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'clientid':
|
||||
body.setdefault('rpConfig', {})['clientId'] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'clientsecret':
|
||||
body.setdefault('rpConfig', {})['clientSecret'] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'returnnameonly':
|
||||
returnNameOnly = True
|
||||
else:
|
||||
unknownArgumentExit()
|
||||
return (returnNameOnly, body)
|
||||
|
||||
def _showInboundSSOProfile(profile, FJQC, i=0, count=0):
|
||||
@@ -46351,18 +46580,24 @@ def _processInboundSSOProfileResult(result, returnNameOnly, kvlist, function):
|
||||
else:
|
||||
writeStdout('inProgress\n')
|
||||
|
||||
# gam create inboundssoprofile [name <SSOProfileName>]
|
||||
def _getInboundSSOModeService(ci):
|
||||
mode = getChoice(INBOUNDSSO_INPUT_MODE_CHOICE_MAP, defaultChoice='saml', mapChoice=True)
|
||||
service = ci.inboundSamlSsoProfiles() if mode == 'saml' else ci.inboundOidcSsoProfiles()
|
||||
return (mode, service)
|
||||
|
||||
# gam create inboundssoprofile [saml|oidc] [name <SSOProfileName>]
|
||||
# [entityid <String>] [loginurl <URL>] [logouturl <URL>] [changepasswordurl <URL>]
|
||||
# [returnnameonly]
|
||||
def doCreateInboundSSOProfile():
|
||||
ci = buildGAPIObject(API.CLOUDIDENTITY_INBOUND_SSO)
|
||||
mode, service = _getInboundSSOModeService(ci)
|
||||
body = {'customer': normalizeChannelCustomerID(GC.Values[GC.CUSTOMER_ID]),
|
||||
'displayName': 'SSO Profile'
|
||||
}
|
||||
returnNameOnly, body = _getInboundSSOProfileArguments(body)
|
||||
returnNameOnly, body = _getInboundSSOProfileArguments(body, mode)
|
||||
kvlist = [Ent.INBOUND_SSO_PROFILE, body['displayName']]
|
||||
try:
|
||||
result = callGAPI(ci.inboundSamlSsoProfiles(), 'create',
|
||||
result = callGAPI(service, 'create',
|
||||
throwReasons=GAPI.CISSO_CREATE_THROW_REASONS,
|
||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
||||
bailOnInternalError=True,
|
||||
@@ -46373,16 +46608,19 @@ def doCreateInboundSSOProfile():
|
||||
GAPI.systemError, GAPI.permissionDenied, GAPI.internalError, GAPI.serviceNotAvailable) as e:
|
||||
entityActionFailedWarning(kvlist, str(e))
|
||||
|
||||
# gam update inboundssoprofile <SSOProfileItem>
|
||||
# gam update inboundssoprofile [saml|oidc] <SSOProfileItem>
|
||||
# [entityid <String>] [loginurl <URL>] [logouturl <URL>] [changepasswordurl <URL>]
|
||||
# [returnnameonly]
|
||||
def doUpdateInboundSSOProfile():
|
||||
ci = buildGAPIObject(API.CLOUDIDENTITY_INBOUND_SSO)
|
||||
name = _convertInboundSSOProfileDisplaynameToName(ci, getString(Cmd.OB_STRING))
|
||||
returnNameOnly, body = _getInboundSSOProfileArguments({})
|
||||
mode, service = _getInboundSSOModeService(ci)
|
||||
name = _convertInboundSSOProfileDisplaynameToName(ci, mode, getString(Cmd.OB_STRING))
|
||||
if not name:
|
||||
return
|
||||
returnNameOnly, body = _getInboundSSOProfileArguments({}, mode)
|
||||
kvlist = [Ent.INBOUND_SSO_PROFILE, name]
|
||||
try:
|
||||
result = callGAPI(ci.inboundSamlSsoProfiles(), 'patch',
|
||||
result = callGAPI(service, 'patch',
|
||||
throwReasons=GAPI.CISSO_UPDATE_THROW_REASONS,
|
||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
||||
bailOnInternalError=True,
|
||||
@@ -46395,14 +46633,17 @@ def doUpdateInboundSSOProfile():
|
||||
GAPI.systemError, GAPI.permissionDenied, GAPI.internalError, GAPI.serviceNotAvailable) as e:
|
||||
entityActionFailedWarning(kvlist, str(e))
|
||||
|
||||
# gam delete inboundssoprofile <SSOProfileItem>
|
||||
# gam delete inboundssoprofile [saml|oidc] <SSOProfileItem>
|
||||
def doDeleteInboundSSOProfile():
|
||||
ci = buildGAPIObject(API.CLOUDIDENTITY_INBOUND_SSO)
|
||||
name = _convertInboundSSOProfileDisplaynameToName(ci, getString(Cmd.OB_STRING))
|
||||
mode, service = _getInboundSSOModeService(ci)
|
||||
name = _convertInboundSSOProfileDisplaynameToName(ci, mode, getString(Cmd.OB_STRING))
|
||||
if not name:
|
||||
return
|
||||
checkForExtraneousArguments()
|
||||
kvlist = [Ent.INBOUND_SSO_PROFILE, name]
|
||||
try:
|
||||
result = callGAPI(ci.inboundSamlSsoProfiles(), 'delete',
|
||||
result = callGAPI(service, 'delete',
|
||||
throwReasons=GAPI.CISSO_UPDATE_THROW_REASONS,
|
||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
||||
bailOnInternalError=True,
|
||||
@@ -46415,33 +46656,54 @@ def doDeleteInboundSSOProfile():
|
||||
GAPI.systemError, GAPI.permissionDenied, GAPI.internalError, GAPI.serviceNotAvailable) as e:
|
||||
entityActionFailedWarning(kvlist, str(e))
|
||||
|
||||
def _getInboundSSOProfile(ci, name):
|
||||
def _getInboundSSOProfileByName(ci, mode, name):
|
||||
notFound = False
|
||||
kvlist = [Ent.INBOUND_SSO_PROFILE, name]
|
||||
try:
|
||||
return callGAPI(ci.inboundSamlSsoProfiles(), 'get',
|
||||
throwReasons=GAPI.CISSO_GET_THROW_REASONS,
|
||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
||||
bailOnInternalError=True,
|
||||
name=name)
|
||||
except GAPI.notFound:
|
||||
if mode in INBOUNDSSO_ALL_SAML:
|
||||
try:
|
||||
return callGAPI(ci.inboundSamlSsoProfiles(), 'get',
|
||||
throwReasons=GAPI.CISSO_GET_THROW_REASONS,
|
||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
||||
bailOnInternalError=True,
|
||||
name=name)
|
||||
except GAPI.notFound:
|
||||
notFound = True
|
||||
except (GAPI.domainNotFound, GAPI.domainCannotUseApis, GAPI.forbidden,
|
||||
GAPI.badRequest, GAPI.invalid, GAPI.systemError, GAPI.permissionDenied, GAPI.internalError, GAPI.serviceNotAvailable) as e:
|
||||
entityActionFailedWarning(kvlist, str(e))
|
||||
if mode in INBOUNDSSO_ALL_OIDC:
|
||||
try:
|
||||
return callGAPI(ci.inboundOidcSsoProfiles(), 'get',
|
||||
throwReasons=GAPI.CISSO_GET_THROW_REASONS,
|
||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
||||
bailOnInternalError=True,
|
||||
name=name)
|
||||
except GAPI.notFound:
|
||||
notFound = True
|
||||
except (GAPI.domainNotFound, GAPI.domainCannotUseApis, GAPI.forbidden,
|
||||
GAPI.badRequest, GAPI.invalid, GAPI.systemError, GAPI.permissionDenied, GAPI.internalError, GAPI.serviceNotAvailable) as e:
|
||||
entityActionFailedWarning(kvlist, str(e))
|
||||
if notFound:
|
||||
entityActionFailedWarning(kvlist, Msg.DOES_NOT_EXIST)
|
||||
except (GAPI.domainNotFound, GAPI.domainCannotUseApis, GAPI.forbidden,
|
||||
GAPI.badRequest, GAPI.invalid, GAPI.systemError, GAPI.permissionDenied, GAPI.internalError, GAPI.serviceNotAvailable) as e:
|
||||
entityActionFailedWarning(kvlist, str(e))
|
||||
return None
|
||||
|
||||
# gam info inboundssoprofile <SSOProfileItem> [formatjson]
|
||||
# gam info inboundssoprofile [all|saml|oidc] <SSOProfileItem> [formatjson]
|
||||
def doInfoInboundSSOProfile():
|
||||
ci = buildGAPIObject(API.CLOUDIDENTITY_INBOUND_SSO)
|
||||
name = _convertInboundSSOProfileDisplaynameToName(ci, getString(Cmd.OB_STRING))
|
||||
mode = getChoice(INBOUNDSSO_OUTPUT_MODE_CHOICE_MAP, defaultChoice='all', mapChoice=True)
|
||||
name = getString(Cmd.OB_STRING)
|
||||
FJQC = FormatJSONQuoteChar(formatJSONOnly=True)
|
||||
profile = _getInboundSSOProfile(ci, name)
|
||||
name = _convertInboundSSOProfileDisplaynameToName(ci, mode, name)
|
||||
if not name:
|
||||
return
|
||||
mode = 'saml' if name.startswith('inboundSamlSsoProfiles/') else 'oidc'
|
||||
profile = _getInboundSSOProfileByName(ci, mode, name)
|
||||
if profile:
|
||||
_showInboundSSOProfile(profile, FJQC)
|
||||
|
||||
# gam show inboundssoprofile
|
||||
# gam show inboundssoprofile [all|saml|oidc]
|
||||
# [formatjson]
|
||||
# gam print inboundssoprofile [todrive <ToDriveAttribute>*]
|
||||
# gam print inboundssoprofile [all|saml|oidc] [todrive <ToDriveAttribute>*]
|
||||
# [[formatjson [quotechar <Character>]]
|
||||
def doPrintShowInboundSSOProfiles():
|
||||
ci = buildGAPIObject(API.CLOUDIDENTITY_INBOUND_SSO)
|
||||
@@ -46449,6 +46711,7 @@ def doPrintShowInboundSSOProfiles():
|
||||
csvPF = CSVPrintFile(['name']) if Act.csvFormat() else None
|
||||
FJQC = FormatJSONQuoteChar(csvPF)
|
||||
cfilter = f'customer=="{customer}"'
|
||||
mode = getChoice(INBOUNDSSO_OUTPUT_MODE_CHOICE_MAP, defaultChoice='all', mapChoice=True)
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if csvPF and myarg == 'todrive':
|
||||
@@ -46457,7 +46720,7 @@ def doPrintShowInboundSSOProfiles():
|
||||
FJQC.GetFormatJSONQuoteChar(myarg, True)
|
||||
if csvPF:
|
||||
printGettingAllAccountEntities(Ent.INBOUND_SSO_PROFILE, cfilter)
|
||||
profiles = _getInboundSSOProfiles(ci)
|
||||
profiles = _getInboundSSOProfiles(ci, mode)
|
||||
if not csvPF:
|
||||
count = len(profiles)
|
||||
if not FJQC.formatJSON:
|
||||
@@ -46527,6 +46790,7 @@ def _processInboundSSOCredentialsResult(result, kvlist, function):
|
||||
# (pemfile <FileName>)|(generatekey [keysize 1024|2048|4096]) [replaceolddest]
|
||||
def doCreateInboundSSOCredential():
|
||||
ci = buildGAPIObject(API.CLOUDIDENTITY_INBOUND_SSO)
|
||||
mode = 'saml'
|
||||
profile = None
|
||||
generateKey = replaceOldest = False
|
||||
keySize = 2048
|
||||
@@ -46534,7 +46798,11 @@ def doCreateInboundSSOCredential():
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if myarg == 'profile':
|
||||
profile = _convertInboundSSOProfileDisplaynameToName(ci, getString(Cmd.OB_STRING))
|
||||
profile = _convertInboundSSOProfileDisplaynameToName(ci, mode,
|
||||
getString(Cmd.OB_STRING),
|
||||
Ent.INBOUND_SSO_CREDENTIALS)
|
||||
if not profile:
|
||||
return
|
||||
elif myarg == 'pemfile':
|
||||
pemData = readFile(getString(Cmd.OB_FILE_NAME))
|
||||
elif myarg == 'generatekey':
|
||||
@@ -46638,15 +46906,24 @@ def doPrintShowInboundSSOCredentials():
|
||||
ci = buildGAPIObject(API.CLOUDIDENTITY_INBOUND_SSO)
|
||||
csvPF = CSVPrintFile(['name']) if Act.csvFormat() else None
|
||||
FJQC = FormatJSONQuoteChar(csvPF)
|
||||
mode = 'saml'
|
||||
profiles = []
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if myarg in {'profile', 'profiles'}:
|
||||
profiles = [_convertInboundSSOProfileDisplaynameToName(ci, profile) for profile in getString(Cmd.OB_STRING_LIST).split(',')]
|
||||
errors = 0
|
||||
for profile in getEntityList(Cmd.OB_STRING_LIST, shlexSplit=True):
|
||||
name = _convertInboundSSOProfileDisplaynameToName(ci, mode, profile, Ent.INBOUND_SSO_CREDENTIALS)
|
||||
if name:
|
||||
profiles.append(name)
|
||||
else:
|
||||
errors += 1
|
||||
if errors:
|
||||
return
|
||||
else:
|
||||
FJQC.GetFormatJSONQuoteChar(myarg, True)
|
||||
if not profiles:
|
||||
profiles = [p['name'] for p in _getInboundSSOProfiles(ci)]
|
||||
profiles = [p['name'] for p in _getInboundSSOProfiles(ci, mode)]
|
||||
count = len(profiles)
|
||||
i = 0
|
||||
for profile in profiles:
|
||||
@@ -46739,6 +47016,7 @@ def _getInboundSSOAssignmentByTarget(ci, cd, target):
|
||||
usageErrorExit(Msg.NO_SSO_PROFILE_ASSIGNED.format(targetType, target))
|
||||
|
||||
def _getInboundSSOAssignmentArguments(ci, cd, body):
|
||||
mode = None
|
||||
rank = 0
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
@@ -46746,9 +47024,19 @@ def _getInboundSSOAssignmentArguments(ci, cd, body):
|
||||
rank = getInteger(minVal=1)
|
||||
elif myarg == 'mode':
|
||||
body['ssoMode'] = getChoice(INBOUNDSSO_MODE_CHOICE_MAP, mapChoice=True)
|
||||
elif myarg == 'profile':
|
||||
body['samlSsoInfo'] = {'inboundSamlSsoProfile':
|
||||
_convertInboundSSOProfileDisplaynameToName(ci, getString(Cmd.OB_STRING))}
|
||||
if body['ssoMode'] == 'SAML_SSO':
|
||||
mode = 'saml'
|
||||
profile = 'inboundSamlSsoProfile'
|
||||
elif body['ssoMode'] == 'OIDC_SSO':
|
||||
mode = 'oidc'
|
||||
profile = 'inboundOidcSsoProfile'
|
||||
elif mode and myarg == 'profile':
|
||||
name = _convertInboundSSOProfileDisplaynameToName(ci, mode,
|
||||
getString(Cmd.OB_STRING),
|
||||
Ent.INBOUND_SSO_ASSIGNMENT)
|
||||
if not name:
|
||||
return None
|
||||
body['samlSsoInfo'] = {profile: name}
|
||||
elif myarg == 'neverredirect':
|
||||
body['signInBehavior'] = {'redirectCondition': 'NEVER'}
|
||||
elif myarg == 'group':
|
||||
@@ -46759,7 +47047,7 @@ def _getInboundSSOAssignmentArguments(ci, cd, body):
|
||||
unknownArgumentExit()
|
||||
if 'ssoMode' not in body:
|
||||
missingArgumentExit('mode')
|
||||
if body['ssoMode'] == 'SAML_SSO' and 'samlSsoInfo' not in body:
|
||||
if mode and 'samlSsoInfo' not in body:
|
||||
missingArgumentExit('profile')
|
||||
if 'targetGroup' in body:
|
||||
if 'targetOrgUnit' in body:
|
||||
@@ -46797,13 +47085,17 @@ def _processInboundSSOAssignmentResult(result, kvlist, ci, cd, function):
|
||||
else:
|
||||
entityActionPerformedMessage(kvlist, Msg.ACTION_IN_PROGRESS.format(f'{function} inboundssoassignment'))
|
||||
|
||||
# gam create inboundssoassignment (group <GroupItem> rank <Number>)|(ou|org|orgunit <OrgUnitItem>)
|
||||
# (mode sso_off)|(mode saml_sso profile <SSOProfileItem>)(mode domain_wide_saml_if_enabled) [neverredirect]
|
||||
# gam create inboundssoassignment
|
||||
# (group <GroupItem> rank <Number>)|(ou|org|orgunit <OrgUnitItem>)
|
||||
# (mode sso_off)|(mode saml_sso profile <SSOProfileItem>)|(mode oidc_sso profile <SSOProfileName>}|(mode domain_wide_saml_if_enabled)
|
||||
# [neverredirect]
|
||||
def doCreateInboundSSOAssignment():
|
||||
cd = buildGAPIObject(API.DIRECTORY)
|
||||
ci = buildGAPIObject(API.CLOUDIDENTITY_INBOUND_SSO)
|
||||
body = {'customer': normalizeChannelCustomerID(GC.Values[GC.CUSTOMER_ID])}
|
||||
body = _getInboundSSOAssignmentArguments(ci, cd, body)
|
||||
if not body:
|
||||
return
|
||||
kvlist = [Ent.INBOUND_SSO_ASSIGNMENT, body['customer']]
|
||||
try:
|
||||
result = callGAPI(ci.inboundSsoAssignments(), 'create',
|
||||
@@ -46817,8 +47109,10 @@ def doCreateInboundSSOAssignment():
|
||||
GAPI.systemError, GAPI.permissionDenied, GAPI.internalError, GAPI.serviceNotAvailable) as e:
|
||||
entityActionFailedWarning(kvlist, str(e))
|
||||
|
||||
# gam update inboundssoassignment [(group <GroupItem> rank <Number>)|(ou|org|orgunit <OrgUnitItem>)]
|
||||
# [(mode sso_off)|(mode saml_sso profile <SSOProfileItem>)(mode domain_wide_saml_if_enabled)] [neverredirect]
|
||||
# gam update inboundssoassignment <SSOAssignmentName>
|
||||
# [(group <GroupItem> rank <Number>)|(ou|org|orgunit <OrgUnitItem>)]
|
||||
# (mode sso_off)|(mode saml_sso profile <SSOProfileItem>)|(mode oidc_sso profile <SSOProfileName>}|(mode domain_wide_saml_if_enabled)
|
||||
# [neverredirect]
|
||||
def doUpdateInboundSSOAssignment():
|
||||
cd = buildGAPIObject(API.DIRECTORY)
|
||||
ci = buildGAPIObject(API.CLOUDIDENTITY_INBOUND_SSO)
|
||||
@@ -46875,14 +47169,20 @@ def doInfoInboundSSOAssignment():
|
||||
return
|
||||
name = assignment.get('samlSsoInfo', {}).get('inboundSamlSsoProfile')
|
||||
if name:
|
||||
profile = _getInboundSSOProfile(ci, name)
|
||||
profile = _getInboundSSOProfileByName(ci, 'saml', name)
|
||||
if profile:
|
||||
assignment['samlSsoInfo']['inboundSamlSsoProfile'] = profile
|
||||
else:
|
||||
name = assignment.get('oidcSsoInfo', {}).get('inboundOidcSsoProfile')
|
||||
if name:
|
||||
profile = _getInboundSSOProfileByName(ci, 'oidc', name)
|
||||
if profile:
|
||||
assignment['oidcSsoInfo']['inboundOidcSsoProfile'] = profile
|
||||
_showInboundSSOAssignment(assignment, FJQC, ci, cd)
|
||||
|
||||
# gam show inboundssoassignment
|
||||
# gam show inboundssoassignments
|
||||
# [formatjson]
|
||||
# gam print inboundssoassignment [todrive <ToDriveAttribute>*]
|
||||
# gam print inboundssoassignments [todrive <ToDriveAttribute>*]
|
||||
# [[formatjson [quotechar <Character>]]
|
||||
def doPrintShowInboundSSOAssignments():
|
||||
cd = buildGAPIObject(API.DIRECTORY)
|
||||
@@ -58013,11 +58313,11 @@ def printShowDrivelastModifications(users):
|
||||
DLP.Finalize(fileIdEntity)
|
||||
if csvPF:
|
||||
sortTitles = ['User', 'id', 'name'] if fileIdEntity.get('shareddrive') else ['User']
|
||||
if addCSVData:
|
||||
sortTitles.extend(sorted(addCSVData.keys()))
|
||||
sortTitles.extend(['lastModifiedFileId', 'lastModifiedFileName',
|
||||
'lastModifiedFileMimeType', 'lastModifiedFilePath',
|
||||
'lastModifyingUser', 'lastModifiedTime'])
|
||||
if addCSVData:
|
||||
sortTitles.extend(sorted(addCSVData.keys()))
|
||||
csvPF.SetTitles(sortTitles)
|
||||
csvPF.SetSortAllTitles()
|
||||
pagesFields = getItemFieldsFromFieldsList('files', fieldsList)
|
||||
@@ -70997,7 +71297,7 @@ def _processMessagesThreads(users, entityType):
|
||||
try:
|
||||
callGAPI(gmail.users().messages(), function,
|
||||
throwReasons=GAPI.GMAIL_THROW_REASONS+[GAPI.INVALID_MESSAGE_ID, GAPI.INVALID, GAPI.INVALID_ARGUMENT,
|
||||
GAPI.FAILED_PRECONDITION, GAPI.PERMISSION_DENIED],
|
||||
GAPI.FAILED_PRECONDITION, GAPI.PERMISSION_DENIED, GAPI.QUOTA_EXCEEDED],
|
||||
userId='me', body=body)
|
||||
for messageId in body['ids']:
|
||||
mcount += 1
|
||||
@@ -71007,7 +71307,7 @@ def _processMessagesThreads(users, entityType):
|
||||
csvPF.WriteRow({'User': user, entityHeader: messageId, 'action': Act.Performed()})
|
||||
except GAPI.serviceNotAvailable:
|
||||
mcount += bcount
|
||||
except (GAPI.invalid, GAPI.invalidArgument, GAPI.permissionDenied) as e:
|
||||
except (GAPI.invalid, GAPI.invalidArgument, GAPI.permissionDenied, GAPI.quotaExceeded) as e:
|
||||
_processMessageFailed(user, idsList, f'{str(e)} ({mcount+1}-{mcount+bcount}/{jcount})')
|
||||
mcount += bcount
|
||||
except GAPI.invalidMessageId:
|
||||
@@ -71020,7 +71320,8 @@ def _processMessagesThreads(users, entityType):
|
||||
|
||||
_GMAIL_ERROR_REASON_TO_MESSAGE_MAP = {GAPI.NOT_FOUND: Msg.DOES_NOT_EXIST,
|
||||
GAPI.INVALID_MESSAGE_ID: Msg.INVALID_MESSAGE_ID,
|
||||
GAPI.FAILED_PRECONDITION: Msg.FAILED_PRECONDITION}
|
||||
GAPI.FAILED_PRECONDITION: Msg.FAILED_PRECONDITION,
|
||||
GAPI.QUOTA_EXCEEDED: Msg.QUOTA_EXCEEDED}
|
||||
|
||||
def _callbackProcessMessage(request_id, _, exception):
|
||||
ri = request_id.splitlines()
|
||||
@@ -71031,7 +71332,9 @@ def _processMessagesThreads(users, entityType):
|
||||
csvPF.WriteRow({'User': ri[RI_ENTITY], entityHeader: ri[RI_ITEM], 'action': Act.Performed()})
|
||||
else:
|
||||
http_status, reason, message = checkGAPIError(exception)
|
||||
_processMessageFailed(ri[RI_ENTITY], ri[RI_ITEM], getHTTPError(_GMAIL_ERROR_REASON_TO_MESSAGE_MAP, http_status, reason, message), int(ri[RI_J]), int(ri[RI_JCOUNT]))
|
||||
_processMessageFailed(ri[RI_ENTITY], ri[RI_ITEM],
|
||||
getHTTPError(_GMAIL_ERROR_REASON_TO_MESSAGE_MAP, http_status, reason, message),
|
||||
int(ri[RI_J]), int(ri[RI_JCOUNT]))
|
||||
|
||||
def _batchProcessMessagesThreads(service, function, user, jcount, messageIds, **kwargs):
|
||||
svcargs = dict([('userId', 'me'), ('id', None), ('fields', '')]+list(kwargs.items())+GM.Globals[GM.EXTRA_ARGS_LIST])
|
||||
@@ -71129,7 +71432,9 @@ def _processMessagesThreads(users, entityType):
|
||||
continue
|
||||
if parameters['messageEntity'] is None:
|
||||
if parameters['maxToProcess'] and jcount > parameters['maxToProcess']:
|
||||
entityNumEntitiesActionNotPerformedWarning([Ent.USER, user], entityType, jcount, Msg.COUNT_N_EXCEEDS_MAX_TO_PROCESS_M.format(jcount, Act.ToPerform(), parameters['maxToProcess']), i, count)
|
||||
entityNumEntitiesActionNotPerformedWarning([Ent.USER, user], entityType, jcount,
|
||||
Msg.COUNT_N_EXCEEDS_MAX_TO_PROCESS_M.format(jcount, Act.ToPerform(), parameters['maxToProcess']),
|
||||
i, count)
|
||||
continue
|
||||
if not parameters['doIt']:
|
||||
entityNumEntitiesActionNotPerformedWarning([Ent.USER, user], entityType, jcount, Msg.USE_DOIT_ARGUMENT_TO_PERFORM_ACTION, i, count)
|
||||
@@ -77097,7 +77402,8 @@ MAIN_COMMANDS_WITH_OBJECTS = {
|
||||
),
|
||||
'clear':
|
||||
(Act.CLEAR,
|
||||
{Cmd.ARG_CONTACT: doClearDomainContacts,
|
||||
{Cmd.ARG_ALERTSETTINGS: doClearAlertSettings,
|
||||
Cmd.ARG_CONTACT: doClearDomainContacts,
|
||||
}
|
||||
),
|
||||
'close':
|
||||
@@ -77418,6 +77724,7 @@ MAIN_COMMANDS_WITH_OBJECTS = {
|
||||
Cmd.ARG_ADMIN: doPrintShowAdmins,
|
||||
Cmd.ARG_ALERT: doPrintShowAlerts,
|
||||
Cmd.ARG_ALERTFEEDBACK: doPrintShowAlertFeedback,
|
||||
Cmd.ARG_ALERTSETTINGS: doShowAlertSettings,
|
||||
Cmd.ARG_BROWSER: doPrintShowBrowsers,
|
||||
Cmd.ARG_BROWSERTOKEN: doPrintShowBrowserTokens,
|
||||
Cmd.ARG_BUILDING: doPrintShowBuildings,
|
||||
@@ -77510,6 +77817,7 @@ MAIN_COMMANDS_WITH_OBJECTS = {
|
||||
'update':
|
||||
(Act.UPDATE,
|
||||
{Cmd.ARG_ADMINROLE: doCreateUpdateAdminRoles,
|
||||
Cmd.ARG_ALERTSETTINGS: doUpdateAlertSettings,
|
||||
Cmd.ARG_ALIAS: doCreateUpdateAliases,
|
||||
Cmd.ARG_BROWSER: doUpdateBrowsers,
|
||||
Cmd.ARG_BUILDING: doUpdateBuilding,
|
||||
|
||||
@@ -119,6 +119,7 @@ USERINFO_PROFILE_SCOPE = 'https://www.googleapis.com/auth/userinfo.profile' # pr
|
||||
VAULT_SCOPES = ['https://www.googleapis.com/auth/ediscovery', 'https://www.googleapis.com/auth/ediscovery.readonly']
|
||||
REQUIRED_SCOPES = [USERINFO_EMAIL_SCOPE, USERINFO_PROFILE_SCOPE]
|
||||
REQUIRED_SCOPES_SET = set(REQUIRED_SCOPES)
|
||||
NUM_CLIENT_SCOPES_ERROR_LIMIT = 48
|
||||
#
|
||||
JWT_APIS = {
|
||||
ACCESSCONTEXTMANAGER: [CLOUD_PLATFORM_SCOPE],
|
||||
|
||||
@@ -411,6 +411,7 @@ class GamCLArgs():
|
||||
ARG_ALERTFEEDBACK = 'alertfeedback'
|
||||
ARG_ALERTFEEDBACKS = 'alertfeedbacks'
|
||||
ARG_ALERTSFEEDBACK = 'alertsfeedback'
|
||||
ARG_ALERTSETTINGS = 'alertsettings'
|
||||
ARG_ALIAS = 'alias'
|
||||
ARG_ALIASES = 'aliases'
|
||||
ARG_ALIASDOMAIN = 'aliasdomain'
|
||||
@@ -1013,6 +1014,7 @@ class GamCLArgs():
|
||||
OB_PROJECT_ID_ENTITY = 'ProjectIDEntity'
|
||||
OB_PROPERTY_KEY = 'PropertyKey'
|
||||
OB_PROPERTY_VALUE = 'PropertyValue'
|
||||
OB_PUBSUB_TOPIC_NAME = 'PubSubTopicName'
|
||||
OB_QUERY = 'Query'
|
||||
OB_QUERY_ITEM = 'QueryItem'
|
||||
OB_QUERY_LIST = 'QueryList'
|
||||
|
||||
@@ -54,6 +54,7 @@ class GamEntity():
|
||||
ALERT_ID = 'alri'
|
||||
ALERT_FEEDBACK = 'alfb'
|
||||
ALERT_FEEDBACK_ID = 'alfi'
|
||||
ALERT_SETTINGS = 'alrs'
|
||||
ALIAS = 'alia'
|
||||
ALIAS_EMAIL = 'alie'
|
||||
ALIAS_TARGET = 'alit'
|
||||
@@ -285,10 +286,11 @@ class GamEntity():
|
||||
MIMETYPE = 'mime'
|
||||
MOBILE_DEVICE = 'mobi'
|
||||
NAME = 'name'
|
||||
NONEDITABLE_ALIAS = 'neal'
|
||||
NOTE = 'note'
|
||||
NOTE_ACL = 'nota'
|
||||
NOTES_ACLS = 'naac'
|
||||
NONEDITABLE_ALIAS = 'neal'
|
||||
NOTIFICATION = 'noti'
|
||||
OAUTH2_TXT_FILE = 'oaut'
|
||||
OAUTH2SERVICE_JSON_FILE = 'oau2'
|
||||
ORGANIZATIONAL_UNIT = 'orgu'
|
||||
@@ -414,6 +416,7 @@ class GamEntity():
|
||||
ALERT_ID: ['Alert IDs', 'Alert ID'],
|
||||
ALERT_FEEDBACK: ['Alert Feedbacks', 'Alert Feedback'],
|
||||
ALERT_FEEDBACK_ID: ['Alert Feedback IDs', 'Alert Feedback ID'],
|
||||
ALERT_SETTINGS: ['Alert Settings', 'Alert Settings'],
|
||||
ALIAS: ['Aliases', 'Alias'],
|
||||
ALIAS_EMAIL: ['Alias Emails', 'Alias Email'],
|
||||
ALIAS_TARGET: ['Alias Targets', 'Alias Target'],
|
||||
@@ -645,10 +648,11 @@ class GamEntity():
|
||||
MIMETYPE: ['MIME Types', 'MIME Type'],
|
||||
MOBILE_DEVICE: ['Mobile Devices', 'Mobile Device'],
|
||||
NAME: ['Names', 'Name'],
|
||||
NONEDITABLE_ALIAS: ['Non-Editable Aliases', 'Non-Editable Alias'],
|
||||
NOTE: ['Notes', 'Note'],
|
||||
NOTE_ACL: ['Note ACLs', 'Note ACL'],
|
||||
NOTES_ACLS: ["'Note's ACLs", "Note's ACLs"],
|
||||
NONEDITABLE_ALIAS: ['Non-Editable Aliases', 'Non-Editable Alias'],
|
||||
NOTIFICATION: ['Notifications', 'Notification'],
|
||||
OAUTH2_TXT_FILE: ['Client OAuth2 File', 'Client OAuth2 File'],
|
||||
OAUTH2SERVICE_JSON_FILE: ['Service Account OAuth2 File', 'Service Account OAuth2 File'],
|
||||
ORGANIZATIONAL_UNIT: ['Organizational Units', 'Organizational Unit'],
|
||||
|
||||
@@ -433,6 +433,7 @@ NO_SVCACCT_ACCESS_ALLOWED = 'No Service Account Access allowed'
|
||||
NO_TRANSFER_LACK_OF_DISK_SPACE = 'Transfer not performed due to lack of target drive space.'
|
||||
NO_USAGE_PARAMETERS_DATA_AVAILABLE = 'No usage parameters data available.'
|
||||
NO_USER_COUNTS_DATA_AVAILABLE = 'No User counts data available.'
|
||||
NUM_SELECTED_CLIENT_SCOPES = '\n{0} scopes are selected, if more than {1} scopes are selected, Google will probably generate a "Something went wrong" error\n'
|
||||
OAUTH2_GO_TO_LINK_MESSAGE = """
|
||||
Go to the following link in a browser on this computer or on another computer:
|
||||
|
||||
@@ -464,6 +465,7 @@ PROCESSING_ITEM_N = '{0},0,Processing item {1}\n'
|
||||
PROCESSING_ITEM_N_OF_M = '{0},0,Processing item {1}/{2}\n'
|
||||
PROFILE_PHOTO_NOT_FOUND = 'Profile photo not found'
|
||||
PROFILE_PHOTO_IS_DEFAULT = 'Profile photo is default'
|
||||
QUOTA_EXCEEDED = 'Quota exceeded'
|
||||
REASON_ONLY_VALID_WITH_CONTENTRESTRICTIONS_READONLY_TRUE = 'reason only valid with contentrestrictions readonly true'
|
||||
REAUTHENTICATION_IS_NEEDED = 'Reauthentication is needed, please run\n\ngam oauth create'
|
||||
RECOMMEND_RUNNING_GAM_ROTATE_SAKEY = 'Recommend running "gam rotate sakey" to get a new key\n'
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
- [Display administrators](#display-administrators)
|
||||
- [Copy privileges from one role to a new role](#copy-privileges-from-one-role-to-a-new-role)
|
||||
- [Copy roles from one administrator to another](#copy-roles-from-one-administrator-to-another)
|
||||
- [Copy non-system admin roles from a source workspace to a target workspace](#copy-non-system-admin-roles-from-a-source-workspace-to-a-target-workspace)
|
||||
|
||||
## API documentation
|
||||
* [About Administrator roles](https://support.google.com/a/answer/33325?ref_topic=4514341)
|
||||
@@ -21,13 +22,16 @@
|
||||
<DomainName> ::= <String>(.<String>)+
|
||||
<EmailAddress> ::= <String>@<DomainName>
|
||||
<GroupItem> ::= <EmailAddress>|<UniqueID>|<String>
|
||||
<JSONData> ::= (json [charset <Charset>] <String>) | (json file <FileName> [charset <Charset>]) |
|
||||
<OrgUnitID> ::= id:<String>
|
||||
<OrgUnitPath> ::= /|(/<String)+
|
||||
<OrgUnitItem> ::= <OrgUnitID>|<OrgUnitPath>
|
||||
<Privilege> ::= <String>
|
||||
<PrivilegeList> ::= "<Privilege>(,<Privilege)*"
|
||||
<RoleAssignmentID> ::= <String>
|
||||
<RoleItem> ::= id:<String>|uid:<String>|<String>
|
||||
<RoleID> ::= <String>
|
||||
<RoleName> ::= <String>
|
||||
<RoleItem> ::= id:<RoleID>|<RoleName>
|
||||
<UniqueID> ::= id:<String>
|
||||
<UserItem> ::= <EmailAddress>|<UniqueID>|<String>
|
||||
```
|
||||
@@ -1383,9 +1387,11 @@ Show 111 Privileges
|
||||
## Manage administrative roles
|
||||
```
|
||||
gam create adminrole <String> [description <String>]
|
||||
privileges all|all_ou|<PrivilegeList>|(select <FileSelector>|<CSVFileSelector>>)
|
||||
privileges all|all_ou|<PrivilegeList>|(select <FileSelector>|<CSVFileSelector>>)|<JSONData>
|
||||
[csv [todrive <ToDriveAttribute>*] [formatjson [quotechar <Character>]] (addcsvdata <FieldName> <String>)*]
|
||||
gam update adminrole <RoleItem> [name <String>] [description <String>]
|
||||
[privileges all|all_ou|<PrivilegeList>|(select <FileSelector>|<CSVFileSelector>>)]
|
||||
[privileges all|all_ou|<PrivilegeList>|(select <FileSelector>|<CSVFileSelector>>)|<JSONData>]
|
||||
[csv [todrive <ToDriveAttribute>*] [formatjson [quotechar <Character>]] (addcsvdata <FieldName> <String>)*]
|
||||
gam delete adminrole <RoleItem>
|
||||
```
|
||||
* `privileges all` - All defined privileges
|
||||
@@ -1393,24 +1399,61 @@ gam delete adminrole <RoleItem>
|
||||
* `privileges <PrivilegeList>` - A specific list of privileges
|
||||
* `privileges select <FileSelector>|<CSVFileSelector>>` - A collection of privileges from a flat or CSV file
|
||||
|
||||
By default, when an admin role is created|update, GAM displays `<RoleName>(<RoleID>) created|updated`.
|
||||
* `csv [todrive <ToDriveAttribute>*] [formatjson [quotechar <Character>]]` - Output the admin roledetails in CSV format.
|
||||
|
||||
When `csv` is uused, Add additional columns of data from the command line to the output.
|
||||
* `addcsvdata <FieldName> <String>`
|
||||
|
||||
## Display administrative roles
|
||||
```
|
||||
gam info adminrole <RoleItem> [privileges]
|
||||
[formatjson]
|
||||
```
|
||||
* `privileges` - Display privileges associated with role
|
||||
|
||||
By default, Gam displays the information as an indented list of keys and values.
|
||||
* `formatjson` - Display the fields in JSON format.
|
||||
|
||||
```
|
||||
gam show adminroles|roles
|
||||
[role <RoleItem>] [privileges]
|
||||
[nosystemroles]
|
||||
[formatjson]
|
||||
```
|
||||
* `privileges` - Display privileges associated with each role
|
||||
|
||||
By default, all roles are displayed:
|
||||
* `role <RoleItem>` - Display a specific role.
|
||||
* `nosystemroles` - Display onnly non-system roles.
|
||||
|
||||
By default, Gam displays the information as an indented list of keys and values.
|
||||
* `formatjson` - Display the fields in JSON format.
|
||||
|
||||
```
|
||||
gam print adminroles|roles [todrive <ToDriveAttribute>*]
|
||||
[role <RoleItem>] [privileges] [oneitemperrow]
|
||||
gam show adminroles|roles
|
||||
[role <RoleItem>] [privileges]
|
||||
[nosystemroles]
|
||||
[formatjson [quotechar <Character>]]
|
||||
```
|
||||
By default, all roles are displayed, use `role <RoleItem>` to display a specific role.
|
||||
|
||||
* `privileges` - Display privileges associated with each role
|
||||
|
||||
By default, with `print`, all privileges for a role are shown on one row as a repeating item.
|
||||
By default, all privileges for a role are shown on one row as a repeating item.
|
||||
When `oneitemperrow` is specified, each privilege is output on a separate row/line with the other role fields.
|
||||
|
||||
By default, all roles are displayed:
|
||||
* `role <RoleItem>` - Display a specific role.
|
||||
* `nosystemroles` - Display onnly non-system roles.
|
||||
|
||||
By default, Gam displays the information as columns of fields; the following option causes the output to be in JSON format:
|
||||
* `formatjson` - Display the fields in JSON format.
|
||||
|
||||
By default, when writing CSV files, Gam uses a quote character of double quote `"`. The quote character is used to enclose columns that contain
|
||||
the quote character itself, the column delimiter (comma by default) and new-line characters. Any quote characters within the column are doubled.
|
||||
When using the `formatjson` option, double quotes are used extensively in the data resulting in hard to read/process output.
|
||||
The `quotechar <Character>` option allows you to choose an alternate quote character, single quote for instance, that makes for readable/processable output.
|
||||
`quotechar` defaults to `gam.cfg/csv_output_quote_char`. When uploading CSV files to Google, double quote `"` should be used.
|
||||
|
||||
## Create an administrator
|
||||
Add an administrator role to an administrator.
|
||||
```
|
||||
@@ -1469,3 +1512,15 @@ gam config csv_input_row_filter "scopeType:regex:CUSTOMER" redirect stdout ./Upd
|
||||
gam config csv_input_row_filter "scopeType:regex:ORG_UNIT" redirect stdout ./UpdateNewAdminOrgUnitRoles.txt multiprocess redirect stderr stdout csv CurrentAdminRoles.csv gam create admin newadmin@domain.com "id:~~roleId~~" org_unit "id:~~orgUnitId~~"
|
||||
```
|
||||
|
||||
## Copy non-system admin roles from a source workspace to a target workspace
|
||||
This requires GAM version 7.18.01 or higher.
|
||||
|
||||
In the source workspace to the following:
|
||||
```
|
||||
gam redirect csv ./SourceNonSystemRoles.csv print adminroles privileges nosystemroles formatjson quotechar "'"
|
||||
```
|
||||
|
||||
In the target workspacce do the following:
|
||||
```
|
||||
gam redirect csv ./TargetNonSystemRoles.csv multiprocess quotechar "'" redirect stderr - multiprocess csv SourceNonSystemRoles.csv quotechar "'" gam create adminrole "~roleName" description "~roleDescription" privileges json "~JSON" csv addcsvdata oldRoleId "~roleId" formatjson
|
||||
```
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
- [Display alerts](#display-alerts)
|
||||
- [Manage alert feedback](#manage-alert-feedback)
|
||||
- [Display alert feedback](#display-alert-feedback)
|
||||
- [Configuring settings](#configuring-settings)
|
||||
|
||||
## API documentation
|
||||
* [Alert Center API](https://developers.google.com/admin-sdk/alertcenter/reference/rest/)
|
||||
@@ -18,6 +19,7 @@
|
||||
## Definitions
|
||||
```
|
||||
<AlertID> ::= <String>
|
||||
<PubSubTopicName> ::= <String>
|
||||
<QueryAlert> ::= <String> See: https://developers.google.com/admin-sdk/alertcenter/guides/query-filters
|
||||
```
|
||||
## Introduction
|
||||
@@ -95,3 +97,15 @@ the quote character itself, the column delimiter (comma by default) and new-line
|
||||
When using the `formatjson` option, double quotes are used extensively in the data resulting in hard to read/process output.
|
||||
The `quotechar <Character>` option allows you to choose an alternate quote character, single quote for instance, that makes for readable/processable output.
|
||||
`quotechar` defaults to `gam.cfg/csv_output_quote_char`. When uploading CSV files to Google, double quote `"` should be used.
|
||||
|
||||
## Configuring settings
|
||||
|
||||
Alert Center can be configured to send notifications to a Google Cloud Pub/Sub topic, but it first requires configuration.
|
||||
* See https://developers.google.com/workspace/admin/alertcenter/guides/notifications for information.
|
||||
|
||||
Gam can be used to display or modify the settings:
|
||||
```
|
||||
gam show alertsettings
|
||||
gam update alertsettings <PubSubTopicName>
|
||||
gam clear alertsettings
|
||||
```
|
||||
@@ -436,6 +436,7 @@
|
||||
Must match this Python Regular Expression: [a-zA-Z0-9 '"!-]{4,30}
|
||||
<PropertyKey> ::= <String>
|
||||
<PropertyValue> ::= <String>
|
||||
<PubSubTopicName> ::= <String>
|
||||
<QueryAlert> ::= <String>
|
||||
See: https://developers.google.com/admin-sdk/alertcenter/guides/query-filters
|
||||
<QueryBrowser> ::= <String>
|
||||
|
||||
36
wiki/Business-Account-Management.md
Normal file
36
wiki/Business-Account-Management.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Users - Business Account Management
|
||||
- [API documentation](#api-documentation)
|
||||
- [Introduction](#introduction)
|
||||
- [Definitions](#definitions)
|
||||
- [Display Business Profile Accounts](#display-business-profile-accounts)
|
||||
|
||||
## API documentation
|
||||
* [Business Account Management](https://developers.google.com/my-business/reference/accountmanagement/rest)
|
||||
|
||||
|
||||
## Introduction
|
||||
These features were added in version 7.18.00.
|
||||
|
||||
To use these commands you add the 'Business Account Management API' to your project and update client authorization.
|
||||
```
|
||||
gam update project
|
||||
gam oauth create
|
||||
...
|
||||
[*] 0) Business Account Management API
|
||||
|
||||
```
|
||||
## Definitions
|
||||
* [`<UserTypeEntity>`](Collections-of-Users)
|
||||
|
||||
## Display Business Profile Accounts
|
||||
```
|
||||
gam <UserItem> show businessprofileaccounts
|
||||
[type locationgroup|organization|personal|usergroup]
|
||||
```
|
||||
Gam displays the information as an indented list of keys and values.
|
||||
|
||||
```
|
||||
gam <UserItem> print businessprofileaccounts [todrive <ToDriveAttribute>*]
|
||||
[type locationgroup|organization|personal|usergroup]
|
||||
```
|
||||
Gam displays the information as columns of fields.
|
||||
@@ -1,4 +1,4 @@
|
||||
# Chat Bot
|
||||
# Chat Bot Setup and Use
|
||||
- [Introduction](#introduction)
|
||||
- [Set up a Chat Bot](#set-up-a-chat-bot)
|
||||
- [API documentation](#api-documentation)
|
||||
@@ -62,15 +62,6 @@ To use the `crostelemetry` commands you must authorize an additional scope:
|
||||
gam oauth create
|
||||
```
|
||||
|
||||
Many commands come in two forms:
|
||||
```
|
||||
gam <CrOSTypeEntity> <Command> ...
|
||||
gam <Command> cros <CrOSEntity> ...
|
||||
```
|
||||
The first form allows more powerful selection of devices with `<CrOSTypeEntity>`.
|
||||
|
||||
The second form is backwards compatible with Legacy GAM and selection with `<CrOSEntity>` is limited.
|
||||
|
||||
## Definitions
|
||||
* [`<CrOSTypeEntity>`](Collections-of-ChromeOS-Devices)
|
||||
|
||||
@@ -332,7 +323,6 @@ gam select default config update_cros_ou_with_id true save
|
||||
|
||||
```
|
||||
gam <CrOSTypeEntity> update <CrOSAttribute>+ [quickcrosmove [<Boolean>]] [nobatchupdate]
|
||||
gam update cros <CrOSEntity> <CrOSAttribute>+ [quickcrosmove [<Boolean>]] [nobatchupdate]
|
||||
```
|
||||
|
||||
Google has introduced a new, faster method for moving CrOS devices to a new OU. The `quickcrosmove` option controls which method Gam uses.
|
||||
@@ -419,8 +409,6 @@ gam update ou csvkmd cros.csv keyfield OU datafield deviceId add croscsvdata dev
|
||||
|
||||
gam <CrOSTypeEntity> update action <CrOSAction> [acknowledge_device_touch_requirement]
|
||||
[actionbatchsize <Integer>]
|
||||
gam update cros <CrOSEntity> action <CrOSAction> [acknowledge_device_touch_requirement]
|
||||
[actionbatchsize <Integer>]
|
||||
```
|
||||
As of GAM version `6.67.00`, the new API function `batchChangeStatus` replaces the old API function `action`; ChromeOS devices are now processed in batches.
|
||||
The batch size defaults to 10, the `actionbatchsize <Integer>` option can be used to set a batch size between 10 and 250.
|
||||
@@ -457,21 +445,18 @@ is configurable from 0 to some large number. If the status reaches `EXPIRED`, `C
|
||||
wipe_users|
|
||||
take_a_screenshot
|
||||
|
||||
gam cros <CrOSTypeEntity> issuecommand command <CrOSCommand> [times_to_check_status <Integer>] [doit]
|
||||
gam issuecommand cros <CrOSEntity> command <CrOSCommand> [times_to_check_status <Integer>] [doit]
|
||||
gam <CrOSTypeEntity> issuecommand command <CrOSCommand> [times_to_check_status <Integer>] [doit]
|
||||
```
|
||||
If the final status is not reached before GAM exits, you can issue the following commands to continue checking the status.
|
||||
```
|
||||
gam cros <CrOSTypeEntity> getcommand commandid <CommandID> [times_to_check_status <Integer>]
|
||||
gam getcommand cros <CrOSEntity> commandid <CommandID> [times_to_check_status <Integer>]
|
||||
gam <CrOSTypeEntity> getcommand commandid <CommandID> [times_to_check_status <Integer>]
|
||||
```
|
||||
|
||||
### Action Examples
|
||||
Remove user profile data from the device; the device will remain enrolled and connected.
|
||||
User data not synced to the Cloud including Downloads, Android app data and Crostini Linux VMs will be permanently lost.
|
||||
Commands with issuecommand directly after gam will work with Legacy GAM & GAM7, whereas commands where the issuecommand is after the cros <CrOSTypeEntity> will work only with GAM7.
|
||||
```
|
||||
gam issuecommand cros dd1d659a-0ea4-4e94-905e-4726c7a5f1e9 command wipe_users doit
|
||||
gam cros dd1d659a-0ea4-4e94-905e-4726c7a5f1e9 issuecommand command wipe_users doit
|
||||
```
|
||||
Remove profiles using the annotatedAssetID, which is a user editable field, in this example the device has an asset ID of CB1234.
|
||||
```
|
||||
@@ -483,14 +468,12 @@ gam cros_queries "asset_id:CB1234,asset_id:CB5678" issuecommand command wipe_use
|
||||
```
|
||||
Powerwash the device with serial number 143040348.
|
||||
```
|
||||
gam issuecommand cros query:id:143040348 command remote_powerwash times_to_check_status 10 doit
|
||||
gam cros_sn 143040348 issuecommand command remote_powerwash times_to_check_status 10 doit
|
||||
```
|
||||
|
||||
Powerwash all devices in the /StudentCarts OrgUnit. Devices will need to be manually reconnected to WiFi which may mean entering a PSK.
|
||||
Use `wipe_users` if that's going to create too much work for you.
|
||||
```
|
||||
gam issuecommand cros "query:orgunitpath:/StudentCarts" command remote_powerwash times_to_check_status 0 doit
|
||||
gam cros_ou /StudentCarts issuecommand command remote_powerwash times_to_check_status 0 doit
|
||||
```
|
||||
## ChromeOS device lists
|
||||
@@ -829,7 +812,6 @@ The `quotechar <Character>` option allows you to choose an alternate quote chara
|
||||
|
||||
```
|
||||
gam <CrOSTypeEntity> info downloadfile latest|<Time> [targetfolder <FilePath>]
|
||||
gam info cros <CrOSEntity> downloadfile latest|<Time> [targetfolder <FilePath>]
|
||||
```
|
||||
|
||||
Select the device file to download by its timestamp.
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
* [Google Classroom API](https://developers.google.com/classroom/reference/rest)
|
||||
* [Google Classroom API - Courses Students](https://developers.google.com/classroom/reference/rest/v1/courses.students)
|
||||
* [Google Classroom API - Courses Teachers](https://developers.google.com/classroom/reference/rest/v1/courses.teachers)
|
||||
* [Classroom Membership Limits](https://support.google.com/edu/classroom/answer/7300976)
|
||||
|
||||
## Definitions
|
||||
```
|
||||
|
||||
@@ -10,6 +10,64 @@ Add the `-s` option to the end of the above commands to suppress creating the `g
|
||||
|
||||
See [Downloads-Installs-GAM7](https://github.com/GAM-team/GAM/wiki/Downloads-Installs) for Windows or other options, including manual installation
|
||||
|
||||
### 7.18.06
|
||||
|
||||
Updated `gam <UserTypeEntity> delete|modify messages` to improve the handling
|
||||
of the following error.
|
||||
```
|
||||
quotaExceeded - User-rate limit exceeded
|
||||
```
|
||||
|
||||
### 7.18.05
|
||||
|
||||
Added support for Inbound SSO OIDC profiles.
|
||||
|
||||
Currently, if you enter `gam select <SectionName>` and nothing else on the command line,
|
||||
GAM performs no action. Now, it will be treated as if you entered:
|
||||
`gam select <SectionName> save`
|
||||
|
||||
Updated to Python 3.13.7.
|
||||
|
||||
### 7.18.04
|
||||
|
||||
Added commands to display/manage Alert Center Pub/Sub notifications.
|
||||
* See: https://github.com/GAM-team/GAM/wiki/Alert-Center#configuring-settings
|
||||
|
||||
### 7.18.03
|
||||
|
||||
Updated `gam oauth create` to give a warning if the number of selected scopes will
|
||||
probably cause Google to generate a "Something went wrong" error.
|
||||
|
||||
### 7.18.02
|
||||
|
||||
Upgraded to OpenSSL 3.5.2.
|
||||
|
||||
### 7.18.01
|
||||
|
||||
Added option `nosystemroles` to `gam print|show adminroles` that causes GAM
|
||||
to only display non-system roles.
|
||||
|
||||
Added option `formatjson` to `gam info|print|show adminroles`; this will be most useful
|
||||
when the `privileges` option is used.
|
||||
|
||||
Updated `gam create|update adminrole` to allow specification of privileges with
|
||||
JSON data: `privileges <JSONData>`. These two updates make it easier to copy admin roles.
|
||||
|
||||
Updated `gam create|update adminrole` to allow output of the created/updated
|
||||
role data in CSV format; by default, GAM displays `<RoleName>(<RoleID>) created|updated`.
|
||||
```
|
||||
csv [todrive <ToDriveAttribute>*] [formatjson [quotechar <Character>]] (addcsvdata <FieldName> <String>)*
|
||||
```
|
||||
|
||||
### 7.18.00
|
||||
|
||||
Added commands to display Business Profile Accounts.
|
||||
These are special purpose commands and will not generally be used.
|
||||
```
|
||||
gam show businessprofileaccounts
|
||||
gam print businessprofileaccounts [todrive <ToDriveAttribute>*]
|
||||
```
|
||||
|
||||
### 7.17.03
|
||||
|
||||
Fixed bug in `gam <UserItem> print|show chatspaces asadmin fields <ChatSpaceFieldNameList>` that caused a trap
|
||||
|
||||
@@ -252,10 +252,10 @@ writes the credentials into the file oauth2.txt.
|
||||
admin@server:/Users/admin$ rm -f /Users/admin/GAMConfig/oauth2.txt
|
||||
admin@server:/Users/admin$ gam version
|
||||
WARNING: Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, Item: oauth2_txt, Value: /Users/admin/GAMConfig/oauth2.txt, Not Found
|
||||
GAM 7.17.03 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.18.06 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.13.5 64-bit final
|
||||
MacOS Sequoia 15.5 x86_64
|
||||
Python 3.13.7 64-bit final
|
||||
MacOS Sequoia 15.6 x86_64
|
||||
Path: /Users/admin/bin/gam7
|
||||
Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
||||
|
||||
@@ -990,9 +990,9 @@ writes the credentials into the file oauth2.txt.
|
||||
C:\>del C:\GAMConfig\oauth2.txt
|
||||
C:\>gam version
|
||||
WARNING: Config File: C:\GAMConfig\gam.cfg, Section: DEFAULT, Item: oauth2_txt, Value: C:\GAMConfig\oauth2.txt, Not Found
|
||||
GAM 7.17.03 - https://github.com/GAM-team/GAM - pythonsource
|
||||
GAM 7.18.06 - https://github.com/GAM-team/GAM - pythonsource
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.13.5 64-bit final
|
||||
Python 3.13.7 64-bit final
|
||||
Windows-10-10.0.17134 AMD64
|
||||
Path: C:\GAM7
|
||||
Config File: C:\GAMConfig\gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
# Inbound SSO
|
||||
- [Admin Console](#admin-console)
|
||||
- [Setup SSO](https://support.google.com/a/answer/12032922)
|
||||
- [Admin Console](https://admin.google.com/ac/security/sso)
|
||||
- [API documentation](#api-documentation)
|
||||
- [Definitions](#definitions)
|
||||
- [Setup SSO](#setupsso)
|
||||
- [Manage profiles](#manage-profiles)
|
||||
- [Display profiles](#display-profiles)
|
||||
- [Manage credentials](#manage-credentials)
|
||||
@@ -9,12 +11,10 @@
|
||||
- [Manage assignments](#manage-assignments)
|
||||
- [Display assignments](#display-assignments)
|
||||
|
||||
## Admin Console
|
||||
* https://admin.google.com/ac/security/sso
|
||||
|
||||
## API documentation
|
||||
* [Cloud Identity API - Inbound SAML SSO Profiles](https://cloud.google.com/identity/docs/reference/rest/v1beta1/inboundSamlSsoProfiles)
|
||||
* [Cloud Identity API - Inbound SAML SSO Profiles idp Credentials](https://cloud.google.com/identity/docs/reference/rest/v1beta1/inboundSamlSsoProfiles.idpCredentials)
|
||||
* [Cloud Identity API - Inbound OIDC SSO Profiles](https://cloud.google.com/identity/docs/reference/rest/v1beta1/inboundOidcSsoProfiles)
|
||||
* [Cloud Identity API - Inbound SSO Assignments](https://cloud.google.com/identity/docs/reference/rest/v1beta1/inboundSsoAssignments)
|
||||
|
||||
## Definitions
|
||||
@@ -41,46 +41,68 @@
|
||||
```
|
||||
## Manage profiles
|
||||
```
|
||||
gam create inboundssoprofile [name <SSOProfileDisplayName>]
|
||||
gam create inboundssoprofile [saml|oidc] [name <SSOProfileDisplayName>]
|
||||
[entityid <String>] [loginurl <URL>] [logouturl <URL>] [changepasswordurl <URL>]
|
||||
[returnnameonly]
|
||||
gam update inboundssoprofile <SSOProfileItem>
|
||||
gam update inboundssoprofile [saml|oidc] <SSOProfileItem>
|
||||
[entityid <String>] [loginurl <URL>] [logouturl <URL>] [changepasswordurl <URL>]
|
||||
[returnnameonly]
|
||||
```
|
||||
Select type of profile:
|
||||
* `saml` - SAML profile; this is the default
|
||||
* `oidc` - OIDC profile
|
||||
|
||||
By default, all fields of the created|updated profile are displayed;
|
||||
use the `returnnameonly` option to have GAM display just the profile name of the created|updated profile.
|
||||
This will be useful in scripts that create|update a profile and then want to perform subsequent GAM commands that
|
||||
reference the profile.
|
||||
|
||||
If `returnnameonly is specified, `inProgress` is returned if the API does not return a complete result.
|
||||
If `returnnameonly` is specified, `inProgress` is returned if the API does not return a complete result.
|
||||
|
||||
```
|
||||
gam delete inboundssoprofile <SSOProfileItem>
|
||||
gam delete inboundssoprofile [saml|oidc] <SSOProfileItem>
|
||||
```
|
||||
Select type of profile:
|
||||
* `saml` - SAML profile; this is the default
|
||||
* `oidc` - OIDC profile
|
||||
|
||||
## Display profiles
|
||||
Display a specific profile.
|
||||
```
|
||||
gam info inboundssoprofile <SSOProfileItem>
|
||||
gam info inboundssoprofile [all|saml|oidc] <SSOProfileItem>
|
||||
[formatjson]
|
||||
```
|
||||
Select type of profile:
|
||||
* `all` - All profiles are displayed; this is the default
|
||||
* `saml` - SAML profile
|
||||
* `oidc` - OIDC profile
|
||||
|
||||
By default, Gam displays the information as an indented list of keys and values.
|
||||
* `formatjson` - Display the fields in JSON format.
|
||||
|
||||
Display all profiles.
|
||||
Display profiles.
|
||||
```
|
||||
gam show inboundssoprofiles
|
||||
gam show inboundssoprofiles [all|saml|oidc]
|
||||
[formatjson]
|
||||
```
|
||||
Select profiles to display:
|
||||
* `all` - All profiles are displayed; this is the default
|
||||
* `saml` - Display SAML profiles
|
||||
* `oidc` - Display OIDC profiles
|
||||
|
||||
By default, Gam displays the information as an indented list of keys and values.
|
||||
* `formatjson` - Display the fields in JSON format.
|
||||
|
||||
Display all profiles in a CSV file.
|
||||
Display profiles in a CSV file.
|
||||
```
|
||||
gam print inboundssoprofiles [todrive <ToDriveAttribute>*]
|
||||
gam print inboundssoprofiles [all|saml|oidc] [todrive <ToDriveAttribute>*]
|
||||
[[formatjson [quotechar <Character>]]
|
||||
```
|
||||
Select profiles to display:
|
||||
* `all` - All profiles are displayed; this is the default
|
||||
* `saml` - Display SAML profiles
|
||||
* `oidc` - Display OIDC profiles
|
||||
|
||||
By default, Gam displays the information as columns of fields; the following option causes the output to be in JSON format,
|
||||
* `formatjson` - Display the fields in JSON format.
|
||||
|
||||
@@ -130,10 +152,14 @@ The `quotechar <Character>` option allows you to choose an alternate quote chara
|
||||
|
||||
## Manage assignments
|
||||
```
|
||||
gam create inboundssoassignment (group <GroupItem> rank <Number>)|(ou|org|orgunit <OrgUnitItem>)
|
||||
(mode sso_off)|(mode saml_sso profile <SSOProfileItem>)(mode domain_wide_saml_if_enabled) [neverredirect]
|
||||
gam update inboundssoassignment [(group <GroupItem> rank <Number>)|(ou|org|orgunit <OrgUnitItem>)]
|
||||
[(mode sso_off)|(mode saml_sso profile <SSOProfileItem>)(mode domain_wide_saml_if_enabled)] [neverredirect]
|
||||
gam create inboundssoassignment
|
||||
(group <GroupItem> rank <Number>)|(ou|org|orgunit <OrgUnitItem>)
|
||||
(mode sso_off)|(mode saml_sso profile <SSOProfileItem>)(mode domain_wide_saml_if_enabled)
|
||||
[neverredirect]
|
||||
gam update inboundssoassignment <SSOAssignmentName>
|
||||
[(group <GroupItem> rank <Number>)|(ou|org|orgunit <OrgUnitItem>)]
|
||||
[(mode sso_off)|(mode saml_sso profile <SSOProfileItem>)(mode domain_wide_saml_if_enabled)]
|
||||
[neverredirect]
|
||||
gam delete inboundssoassignment <SSOAssignmentSelector>
|
||||
```
|
||||
|
||||
|
||||
@@ -35,6 +35,9 @@ Select a section from gam.cfg and process a GAM command using values from that s
|
||||
- Print the variable values for the selected section
|
||||
- Values are determined in this order: Selected section, DEFAULT section, Program default
|
||||
|
||||
If you enter `gam select <SectionName>` and nothing else on the command line,
|
||||
it will be treated as if you entered: `gam select <SectionName> save`
|
||||
|
||||
### Display sections
|
||||
Display all of the sections in gam.cfg and mark the currently selected section with a *.
|
||||
```
|
||||
|
||||
@@ -392,7 +392,7 @@ Your command line will have: `embedimage file1.jpg image1 embedimage file2.jpg i
|
||||
|
||||
## Send an email to users
|
||||
```
|
||||
gam <UserTypeEntity> sendemail [from <EmailAddress>]
|
||||
gam <UserTypeEntity> sendemail from <EmailAddress>
|
||||
[replyto <EmailAddress>]
|
||||
[cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
|
||||
[subject <String>]
|
||||
@@ -406,8 +406,6 @@ gam <UserTypeEntity> sendemail [from <EmailAddress>]
|
||||
```
|
||||
Emails will be sent to the users in `<UserTypeEntity>`.
|
||||
|
||||
By default, emails will be sent from the admin user named in oauth2.txt, override this with the `from <EmailAddress>` option.
|
||||
|
||||
When using the Gmail API/SMTP, GAM gets no/little indication as to the status of the message delivery; the from user will get a non-delivery receipt if the message
|
||||
could not be sent to the specified recipients.
|
||||
|
||||
|
||||
@@ -464,7 +464,7 @@ gam <UserItem> delete chatmember asadmin <ChatSpace>
|
||||
|
||||
Delete members from a chat space by specifying chatmember names, asadmin
|
||||
```
|
||||
gam <UserItem> remove chatmember members asadmin <ChatMemberList>
|
||||
gam <UserItem> remove chatmember asadmin members <ChatMemberList>
|
||||
```
|
||||
|
||||
### Update a members role in a user's chat space
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
- [Move with ownership change](#move-with-ownership-change)
|
||||
- [Complex moves](#complex-moves)
|
||||
- [Move content of a Shared Drive to another Shared Drive](#move-content-of-a-shared-drive-to-another-shared-drive)
|
||||
- [Move content of a Shared Drive to a My Drive](#move-content-of-a-shared-drive-to-a-my-drive)
|
||||
|
||||
## API documentation
|
||||
* [Drive API - Files](https://developers.google.com/drive/api/v3/reference/files)
|
||||
@@ -673,8 +674,10 @@ gam user user@domain.com move drivefile teamdriveid 0AC_1AB teamdriveparentid 0A
|
||||
```
|
||||
|
||||
If you want the source Shared Drive with ID 0AC_1AB to be contained in a top level folder of the target Shared Drive with ID 0AE_9ZX, omit the `mergewithparent` argument.
|
||||
The folder on the target Shared Drive will have the same name as the name of the source Shared Drive; use the `newfilename <DriveFileName>` to use a different name.
|
||||
```
|
||||
gam user user@domain.com move drivefile teamdriveid 0AC_1AB teamdriveparentid 0AE_9ZX
|
||||
gam user user@domain.com move drivefile teamdriveid 0AC_1AB teamdriveparentid 0AE_9ZX newfilename "Copy of source Shared Drive"
|
||||
```
|
||||
|
||||
### Inter-workspace moves
|
||||
@@ -692,3 +695,21 @@ User: user@domaina.com, Move 1 Drive File/Folder
|
||||
User: user@domaina.com, Drive Folder: Shared Drive A(<SharedDriveAID>), Retained
|
||||
```
|
||||
To get this to work, you must check `Allow people outside of Domain A to access files` on Shared Drive A in domaina.com
|
||||
|
||||
## Move content of a Shared Drive to a My Drive
|
||||
Suppose you have a Shared Drive with ID 0AC_1AB with multiple files and folders, and want to move all of its content to the root of a My Drive.
|
||||
|
||||
The following command will change the parents of the top level files and folders from 0AC_1AB to the root of the My Drive; the sub files and folders will move along with their top level folder.
|
||||
|
||||
* No permissions are processed.
|
||||
```
|
||||
gam user user@domain.com move drivefile teamdriveid 0AC_1AB parentid root mergewithparent
|
||||
```
|
||||
|
||||
If you want the contents of Shared Drive with ID 0AC_1AB to be contained in a top level folder of the My Drive, omit the `mergewithparent` argument.
|
||||
The folder on the My Drive will have the same name as the name of the Shared Drive; use the `newfilename <DriveFileName>` to use a different name.
|
||||
```
|
||||
gam user user@domain.com move drivefile teamdriveid 0AC_1AB parentid root
|
||||
gam user user@domain.com move drivefile teamdriveid 0AC_1AB parentid root newfilename "Copy of Shared Drive"
|
||||
```
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
- [Delete all ACLs except owner from a user's My Drive](#delete-all-acls-except-owner-from-a-users-my-drive)
|
||||
- [Change shares to User1 to shares to User2](#change-shares-to-user1-to-shares-to-user2)
|
||||
- [Map All ACLs from an old domain to a new domain](#map-all-acls-from-an-old-domain-to-a-new-domain)
|
||||
- [Remove all ACLs for a specific user or group email address](#remove-all-ACLs-for-a-specific-user-or-group-email-address)
|
||||
- [Remove anyone-anyoneWithLink ACLs](#remove-anyone-anyonewithlink-acls)
|
||||
|
||||
## API documentation
|
||||
* [Drive API - Permissions](https://developers.google.com/drive/api/v3/reference/permissions)
|
||||
@@ -385,3 +387,83 @@ gam config csv_input_row_filter "permission.type:regex:user|group" redirect stdo
|
||||
gam config csv_input_row_filter "permission.type:regex:domain" redirect stdout ./AddNewDomainACLsDomainShares.txt multiprocess redirect stderr stdout csv ./allUsersFiles.csv gam user "~Owner" create drivefileacl "~id" "~permission.type" "~permission.domain" role "~permission.role" allowfilediscovery "~permission.allowFileDiscovery" mappermissionsdomain olddomain.com newdomain.com
|
||||
```
|
||||
|
||||
## Remove all ACLs for a specific user or group email address
|
||||
|
||||
### My Drives
|
||||
|
||||
Get My Drive ACLs sharing to that email address:
|
||||
* Replace `<Type>` with user or group
|
||||
* Replace `email@domain.com` with actual email address
|
||||
```
|
||||
gam config auto_batch_min 1 num_threads 20 redirect csv ./MyDriveShares.csv multiprocess redirect stderr - multiprocess all users print filelist fields id,name,mimetype,basicpermissions query "'email@domain.com' in readers or 'email@domain.com' in writers" pm notrole owner type <Type> emailaddress email@domain.com em pmfilter oneitemperrow
|
||||
```
|
||||
|
||||
Delete those My Drive ACLs.
|
||||
```
|
||||
gam config num_threads 20 redirect stdout ./DeleteMyDriveShares.txt multiprocess redirect stderr stdout csv MyDriveShares.csv gam user "~Owner" delete drivefleacl "~id" "id:~~permission.id~~"
|
||||
```
|
||||
|
||||
Add My Drive ACLs with a different email address and the same role.
|
||||
```
|
||||
gam config num_threads 20 redirect stdout ./AddMyDriveShares.txt multiprocess redirect stderr stdout csv MyDriveShares.csv gam user "~Owner" add drivefleacl "~id" "~permission.type" newemail@domain.rom role "~permission.role"
|
||||
```
|
||||
|
||||
### Shared Drives
|
||||
Get an organizer for each Shared Drive
|
||||
```
|
||||
gam redirect csv ./SharedDriveOrganizers.csv print shareddriveorganizers
|
||||
```
|
||||
|
||||
Get Shared Drive ACLs explicitly sharing to that email address:
|
||||
* Replace `<Type>` with user or group
|
||||
* Replace `email@domain.com` with actual email address
|
||||
```
|
||||
gam config num_threads 20 csv_input_row_filter "organizers:regex:^.+$" redirect csv ./SharedDriveShares.csv multiprocess redirect stderr - multiprocess csv SharedDriveOrganizers.csv gam user "~organizers" print filelist select shareddriveid "~id" fields id,name,mimetype,basicpermissions,driveid showdrivename query "'email@domain.com' in readers or 'email@domain.com' in writers" pm type <Type> emailaddress email@domain.com inherited false em pmfilter oneitemperrow
|
||||
```
|
||||
|
||||
Delete those Shared Drive ACLs.
|
||||
```
|
||||
gam config num_threads 20 redirect stdout ./DeleteSharedDriveShares.txt multiprocess redirect stderr stdout csv SharedDriveShares.csv gam user "~Owner" delete drivefleacl "~id" "id:~~permission.id~~"
|
||||
```
|
||||
|
||||
Add Shared Drive ACLs with a different email address and the same role.
|
||||
```
|
||||
gam config num_threads 20 redirect stdout ./ReplaceSharedDriveShares.txt multiprocess redirect stderr stdout csv SharedDriveShares.csv gam user "~Owner" add drivefleacl "~id" "~permission.type" newemail@domain.rom role "~permission.role"
|
||||
```
|
||||
|
||||
## Remove anyone-anyoneWithLink ACLs
|
||||
|
||||
Here are the queries that will be used in these commands:
|
||||
* anyone - query "visibility='anyoneCanFind'"
|
||||
* anyoneWithLink - query "visibility='anyoneWithLink'"
|
||||
* both - query "(visibility='anyoneCanFind' or visibility='anyoneWithLink')"
|
||||
|
||||
### My Drives
|
||||
|
||||
Get My Drive anyone/anyoneWithLink ACLs
|
||||
```
|
||||
gam config auto_batch_min 1 num_threads 20 redirect csv ./MyDriveShares.csv multiprocess redirect stderr - multiprocess all users print filelist fields id,name,mimetype,basicpermissions <Query> pm type anyone em pmfilter oneitemperrow
|
||||
```
|
||||
|
||||
Delete those My Drive ACLs.
|
||||
```
|
||||
gam config num_threads 20 redirect stdout ./DeleteMyDriveShares.txt multiprocess redirect stderr stdout csv MyDriveShares.csv gam user "~Owner" delete drivefleacl "~id" "id:~~permission.id~~"
|
||||
```
|
||||
|
||||
### Shared Drives
|
||||
Get an organizer for each Shared Drive
|
||||
```
|
||||
gam redirect csv ./SharedDriveOrganizers.csv print shareddriveorganizers
|
||||
```
|
||||
|
||||
Get Shared Drive anyone/anyoneWithLink ACLs
|
||||
```
|
||||
gam config num_threads 20 csv_input_row_filter "organizers:regex:^.+$" redirect csv ./SharedDriveShares.csv multiprocess redirect stderr - multiprocess csv SharedDriveOrganizers.csv gam user "~organizers" print filelist select shareddriveid "~id" fields id,name,mimetype,basicpermissions,driveid showdrivename <Query> pm type anyone inherited false em pmfilter oneitemperrow
|
||||
```
|
||||
|
||||
Delete those Shared Drive ACLs.
|
||||
```
|
||||
gam config num_threads 20 redirect stdout ./DeleteSharedDriveShares.txt multiprocess redirect stderr stdout csv SharedDriveShares.csv gam user "~Owner" delete drivefleacl "~id" "id:~~permission.id~~"
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -16,7 +16,10 @@ Delegated admin service accounts (DASA) are regular [GCP service accounts](https
|
||||
|
||||
## Disadvantages
|
||||
* DASA accounts can only be delegated admins. [If a task requires super admin rights to perform](https://support.google.com/a/answer/2405986#:~:text=Only%20super%20administrators%20can...), DASA accounts won’t be able to do it.
|
||||
Not all Google Admin APIs work with DASA right now. For example, Google Vault API calls will fail with a DASA account; Classroom API calls do not return data.
|
||||
Not all Google Admin APIs work with DASA right no:
|
||||
* Google Vault API calls will fail with a DASA account
|
||||
* Classroom API calls do not return data
|
||||
* Cloud Identity Policies are not available
|
||||
* DASA is a delegated admin and can make Workspace / Cloud Identity admin API calls, it does not replace domain-wide delegation (DwD) when using GAM7 commands that interact with Gmail, Drive and Calendar user data.
|
||||
* GAM7 support for DASA is still experimental and some things may fail. Please report your findings to the [GAM group](https://groups.google.com/g/google-apps-manager).
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
Print the current version of Gam with details
|
||||
```
|
||||
gam version
|
||||
GAM 7.17.03 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.18.06 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.13.5 64-bit final
|
||||
MacOS Sequoia 15.5 x86_64
|
||||
Python 3.13.7 64-bit final
|
||||
MacOS Sequoia 15.6 x86_64
|
||||
Path: /Users/Admin/bin/gam7
|
||||
Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
||||
Time: 2023-06-02T21:10:00-07:00
|
||||
@@ -15,10 +15,10 @@ Time: 2023-06-02T21:10:00-07:00
|
||||
Print the current version of Gam with details and time offset information
|
||||
```
|
||||
gam version timeoffset
|
||||
GAM 7.17.03 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.18.06 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.13.5 64-bit final
|
||||
MacOS Sequoia 15.5 x86_64
|
||||
Python 3.13.7 64-bit final
|
||||
MacOS Sequoia 15.6 x86_64
|
||||
Path: /Users/Admin/bin/gam7
|
||||
Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
||||
Your system time differs from www.googleapis.com by less than 1 second
|
||||
@@ -27,15 +27,15 @@ Your system time differs from www.googleapis.com by less than 1 second
|
||||
Print the current version of Gam with extended details and SSL information
|
||||
```
|
||||
gam version extended
|
||||
GAM 7.17.03 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.18.06 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.13.5 64-bit final
|
||||
MacOS Sequoia 15.5 x86_64
|
||||
Python 3.13.7 64-bit final
|
||||
MacOS Sequoia 15.6 x86_64
|
||||
Path: /Users/Admin/bin/gam7
|
||||
Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
||||
Time: 2023-06-02T21:10:00-07:00
|
||||
Your system time differs from admin.googleapis.com by less than 1 second
|
||||
OpenSSL 3.4.0 22 Oct Sep 2024
|
||||
OpenSSL 3.5.2 5 ASug 2025
|
||||
cryptography 43.0.3
|
||||
filelock 3.16.1
|
||||
google-api-python-client 2.149.0
|
||||
@@ -64,7 +64,7 @@ MacOS High Sierra 10.13.6 x86_64
|
||||
Path: /Users/Admin/bin/gam7
|
||||
Version Check:
|
||||
Current: 5.35.08
|
||||
Latest: 7.17.03
|
||||
Latest: 7.18.06
|
||||
echo $?
|
||||
1
|
||||
```
|
||||
@@ -72,7 +72,7 @@ echo $?
|
||||
Print the current version number without details
|
||||
```
|
||||
gam version simple
|
||||
7.17.03
|
||||
7.18.06
|
||||
```
|
||||
In Linux/MacOS you can do:
|
||||
```
|
||||
@@ -82,10 +82,10 @@ echo $VER
|
||||
Print the current version of Gam and address of this Wiki
|
||||
```
|
||||
gam help
|
||||
GAM 7.17.03 - https://github.com/GAM-team/GAM
|
||||
GAM 7.18.06 - https://github.com/GAM-team/GAM
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.13.5 64-bit final
|
||||
MacOS Sequoia 15.5 x86_64
|
||||
Python 3.13.7 64-bit final
|
||||
MacOS Sequoia 15.6 x86_64
|
||||
Path: /Users/Admin/bin/gam7
|
||||
Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
||||
Time: 2023-06-02T21:10:00-07:00
|
||||
|
||||
@@ -66,6 +66,7 @@ Client Access
|
||||
* [Administrators](Administrators)
|
||||
* [Alert Center](Alert-Center)
|
||||
* [Aliases](Aliases)
|
||||
* [Business Account Management](Business-Account-Management)
|
||||
* [Calendars](Calendars)
|
||||
* [Calendars - Access](Calendars-Access)
|
||||
* [Calendars - Events](Calendars-Events)
|
||||
@@ -128,7 +129,7 @@ Client Access
|
||||
* [Version and Help](Version-and-Help)
|
||||
|
||||
Special Service Account Access
|
||||
* [Chat Bot](Chat-Bot)
|
||||
* [Chat Bot Setup and Use](Chat-Bot-Setup-Use)
|
||||
|
||||
Service Account Access
|
||||
* [Users - Analytics Admin](Users-Analytics-Admin)
|
||||
|
||||
Reference in New Issue
Block a user