Compare commits

..

2 Commits

Author SHA1 Message Date
Ross Scroggs
1dc7868078 Add option notifyrecoveryemail to create|update user 2025-09-17 19:52:13 -07:00
Jay Lee
c0dc8ae790 Create GAM-Release-Process.md
Some checks failed
Push wiki / pushwiki (push) Has been cancelled
2025-09-17 16:50:35 -04:00
4 changed files with 48 additions and 14 deletions

View File

@@ -5710,7 +5710,7 @@ gam create|add user <EmailAddress> [ignorenullpassword] <UserAttribute>*
(groups [<GroupRole>] [[delivery] <DeliverySetting>] <GroupEntity>)* (groups [<GroupRole>] [[delivery] <DeliverySetting>] <GroupEntity>)*
[alias|aliases <EmailAddressList>] [alias|aliases <EmailAddressList>]
[license <SKUIDList> [product|productid <ProductID>]] [license <SKUIDList> [product|productid <ProductID>]]
[notify <EmailAddressList> [[notify <EmailAddressList>] [notifyrecoveryemail]
[subject <String>] [subject <String>]
[notifypassword <String>] [notifypassword <String>]
[from <EmailAaddress>] [from <EmailAaddress>]
@@ -5736,7 +5736,7 @@ gam update user <UserItem> [ignorenullpassword] <UserAttribute>*
[createifnotfound] [notfoundpassword (random [<Integer>])|blocklogin|<Password>] [createifnotfound] [notfoundpassword (random [<Integer>])|blocklogin|<Password>]
(groups [<GroupRole>] [[delivery] <DeliverySetting>] <GroupEntity>)* (groups [<GroupRole>] [[delivery] <DeliverySetting>] <GroupEntity>)*
[alias|aliases <EmailAddressList>] [alias|aliases <EmailAddressList>]
[notify <EmailAddressList> [[notify <EmailAddressList>] [notifyrecoveryemail]
[subject <String>] [subject <String>]
[notifypassword <String>] [notifypassword <String>]
[from <EmailAaddress>] [from <EmailAaddress>]
@@ -5774,7 +5774,7 @@ gam update users <UserTypeEntity> [ignorenullpassword] <UserAttribute>*
[createifnotfound] [notfoundpassword (random [<Integer>])|blocklogin|<Password>] [createifnotfound] [notfoundpassword (random [<Integer>])|blocklogin|<Password>]
(groups [<GroupRole>] [[delivery] <DeliverySetting>] <GroupEntity>)* (groups [<GroupRole>] [[delivery] <DeliverySetting>] <GroupEntity>)*
[alias|aliases <EmailAddressList>] [alias|aliases <EmailAddressList>]
[notify <EmailAddressList> [[notify <EmailAddressList>] [notifyrecoveryemail]
[subject <String>] [subject <String>]
[notifypassword <String>] [notifypassword <String>]
[from <EmailAddress>] [from <EmailAddress>]
@@ -5811,7 +5811,7 @@ gam <UserTypeEntity> update users [ignorenullpassword] <UserAttribute>*
[createifnotfound] [notfoundpassword (random [<Integer>])|blocklogin|<Password>] [createifnotfound] [notfoundpassword (random [<Integer>])|blocklogin|<Password>]
(groups [<GroupRole>] [[delivery] <DeliverySetting>] <GroupEntity>)* (groups [<GroupRole>] [[delivery] <DeliverySetting>] <GroupEntity>)*
[alias|aliases <EmailAddressList>] [alias|aliases <EmailAddressList>]
[notify <EmailAddressList> [[notify <EmailAddressList>] [notifyrecoveryemail]
[subject <String>] [subject <String>]
[notifypassword <String>] [notifypassword <String>]
[from <EmailAaddress>] [from <EmailAaddress>]

View File

@@ -1,3 +1,8 @@
7.21.03
Added option `notifyrecoveryemail` to `gam create user` and `gam <UserTypeEntity> update user password <String>`
that sends the passsword notification email to the user's recovery email address (if defined).
7.21.02 7.21.02
GAM now builds on macOS 26 Tahoe and properly identifies the OS. GAM now builds on macOS 26 Tahoe and properly identifies the OS.

View File

@@ -25,7 +25,7 @@ https://github.com/GAM-team/GAM/wiki
""" """
__author__ = 'GAM Team <google-apps-manager@googlegroups.com>' __author__ = 'GAM Team <google-apps-manager@googlegroups.com>'
__version__ = '7.21.02' __version__ = '7.21.03'
__license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)' __license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
#pylint: disable=wrong-import-position #pylint: disable=wrong-import-position
@@ -43947,6 +43947,7 @@ def getUserAttributes(cd, updateCmd, noUid=False):
invalidArgumentExit(Cmd.OB_SCHEMA_NAME_FIELD_NAME) invalidArgumentExit(Cmd.OB_SCHEMA_NAME_FIELD_NAME)
parameters = { parameters = {
'notifyRecoveryEmail': False,
'verifyNotInvitable': False, 'verifyNotInvitable': False,
'createIfNotFound': False, 'createIfNotFound': False,
'noActionIfAlias': False, 'noActionIfAlias': False,
@@ -43963,7 +43964,7 @@ def getUserAttributes(cd, updateCmd, noUid=False):
body = {'name': {'givenName': UNKNOWN, 'familyName': UNKNOWN}} body = {'name': {'givenName': UNKNOWN, 'familyName': UNKNOWN}}
body['primaryEmail'] = getEmailAddress(noUid=noUid) body['primaryEmail'] = getEmailAddress(noUid=noUid)
notFoundBody = {} notFoundBody = {}
notify = {'subject': '', 'message': '', 'html': False, 'charset': UTF8, 'password': ''} notify = {'recipients': [], 'subject': '', 'message': '', 'html': False, 'charset': UTF8, 'password': ''}
primary = {} primary = {}
updatePrimaryEmail = {} updatePrimaryEmail = {}
groupOrgUnitMap = None groupOrgUnitMap = None
@@ -43975,7 +43976,9 @@ def getUserAttributes(cd, updateCmd, noUid=False):
while Cmd.ArgumentsRemaining(): while Cmd.ArgumentsRemaining():
myarg = getArgument() myarg = getArgument()
if myarg == 'notify': if myarg == 'notify':
notify['recipients'] = getNormalizedEmailAddressEntity(shlexSplit=True, noLower=True) notify['recipients'].extend(getNormalizedEmailAddressEntity(shlexSplit=True, noLower=True))
elif myarg == 'notifyrecoveryemail':
parameters['notifyRecoveryEmail'] = True
elif myarg == 'subject': elif myarg == 'subject':
notify['subject'] = getString(Cmd.OB_STRING) notify['subject'] = getString(Cmd.OB_STRING)
elif myarg in SORF_MSG_FILE_ARGUMENTS: elif myarg in SORF_MSG_FILE_ARGUMENTS:
@@ -44391,7 +44394,7 @@ def createUserAddAliases(cd, user, aliasList, i, count):
# (groups [<GroupRole>] [[delivery] <DeliverySetting>] <GroupEntity>)* # (groups [<GroupRole>] [[delivery] <DeliverySetting>] <GroupEntity>)*
# [alias|aliases <EmailAddressList>] # [alias|aliases <EmailAddressList>]
# [license <SKUID> [product|productid <ProductID>]] # [license <SKUID> [product|productid <ProductID>]]
# [notify <EmailAddressList> # [[notify <EmailAddressList>] [notifyrecoveryemail]
# [subject <String>] # [subject <String>]
# [notifypassword <String>] # [notifypassword <String>]
# [from <EmailAaddress>] # [from <EmailAaddress>]
@@ -44409,7 +44412,7 @@ def doCreateUser():
suffix = 0 suffix = 0
originalEmail = body['primaryEmail'] originalEmail = body['primaryEmail']
atLoc = originalEmail.find('@') atLoc = originalEmail.find('@')
fields = '*' if tagReplacements['subs'] else 'primaryEmail,name' fields = '*' if tagReplacements['subs'] else 'primaryEmail,name,recoveryEmail'
while True: while True:
user = body['primaryEmail'] user = body['primaryEmail']
if parameters['verifyNotInvitable']: if parameters['verifyNotInvitable']:
@@ -44450,7 +44453,9 @@ def doCreateUser():
createUserAddToGroups(cd, result['primaryEmail'], addGroups, 0, 0) createUserAddToGroups(cd, result['primaryEmail'], addGroups, 0, 0)
if addAliases: if addAliases:
createUserAddAliases(cd, result['primaryEmail'], addAliases, 0, 0) createUserAddAliases(cd, result['primaryEmail'], addAliases, 0, 0)
if notify.get('recipients'): if (notify.get('recipients') or (parameters['notifyRecoveryEmail'] and result.get('recoveryEmail'))):
if parameters['notifyRecoveryEmail'] and result.get('recoveryEmail'):
notify['recipients'].append(result['recoveryEmail'])
sendCreateUpdateUserNotification(result, notify, tagReplacements) sendCreateUpdateUserNotification(result, notify, tagReplacements)
for productSku in parameters[LICENSE_PRODUCT_SKUIDS]: for productSku in parameters[LICENSE_PRODUCT_SKUIDS]:
productId = productSku[0] productId = productSku[0]
@@ -44493,7 +44498,7 @@ def verifyUserPrimaryEmail(cd, user, createIfNotFound, i, count):
# [createifnotfound] [notfoundpassword (random [<Integer>])|blocklogin|<Password>] # [createifnotfound] [notfoundpassword (random [<Integer>])|blocklogin|<Password>]
# (groups [<GroupRole>] [[delivery] <DeliverySetting>] <GroupEntity>)* # (groups [<GroupRole>] [[delivery] <DeliverySetting>] <GroupEntity>)*
# [alias|aliases <EmailAddressList>] # [alias|aliases <EmailAddressList>]
# [notify <EmailAddressList> # [[notify <EmailAddressList>] [notifyrecoveryemail]
# [subject <String>] # [subject <String>]
# [notifypassword <String>] # [notifypassword <String>]
# [from <EmailAaddress>] # [from <EmailAaddress>]
@@ -44522,7 +44527,7 @@ def updateUsers(entityList):
else: else:
checkImmutableOUs = False checkImmutableOUs = False
i, count, entityList = getEntityArgument(entityList) i, count, entityList = getEntityArgument(entityList)
fields = '*' if tagReplacements['subs'] else 'primaryEmail,name' fields = '*' if tagReplacements['subs'] else 'primaryEmail,name,recoveryEmail'
for user in entityList: for user in entityList:
i += 1 i += 1
user = userKey = normalizeEmailAddressOrUID(user) user = userKey = normalizeEmailAddressOrUID(user)
@@ -44598,7 +44603,10 @@ def updateUsers(entityList):
entityActionPerformed([Ent.USER, user], i, count) entityActionPerformed([Ent.USER, user], i, count)
if PwdOpts.filename and PwdOpts.password: if PwdOpts.filename and PwdOpts.password:
writeFile(PwdOpts.filename, f'{userKey},{PwdOpts.password}\n', mode='a', continueOnError=True) writeFile(PwdOpts.filename, f'{userKey},{PwdOpts.password}\n', mode='a', continueOnError=True)
if parameters['notifyOnUpdate'] and notify.get('recipients') and notify['password']: if (parameters['notifyOnUpdate'] and notify['password'] and
(notify.get('recipients') or (parameters['notifyRecoveryEmail'] and result.get('recoveryEmail')))):
if parameters['notifyRecoveryEmail'] and result.get('recoveryEmail'):
notify['recipients'].append(result['recoveryEmail'])
sendCreateUpdateUserNotification(result, notify, tagReplacements, i, count, createMessage=False) sendCreateUpdateUserNotification(result, notify, tagReplacements, i, count, createMessage=False)
break break
except GAPI.conditionNotMet as e: except GAPI.conditionNotMet as e:
@@ -44632,8 +44640,10 @@ def updateUsers(entityList):
createUserAddToGroups(cd, result['primaryEmail'], addGroups, i, count) createUserAddToGroups(cd, result['primaryEmail'], addGroups, i, count)
if addAliases: if addAliases:
createUserAddAliases(cd, result['primaryEmail'], addAliases, i, count) createUserAddAliases(cd, result['primaryEmail'], addAliases, i, count)
if notify.get('recipients'): if (notify.get('recipients') or (parameters['notifyRecoveryEmail'] and result.get('recoveryEmail'))):
notify['password'] = notify['notFoundPassword'] notify['password'] = notify['notFoundPassword']
if parameters['notifyRecoveryEmail'] and result.get('recoveryEmail'):
notify['recipients'].append(result['recoveryEmail'])
sendCreateUpdateUserNotification(result, notify, tagReplacements, i, count) sendCreateUpdateUserNotification(result, notify, tagReplacements, i, count)
except GAPI.duplicate: except GAPI.duplicate:
duplicateAliasGroupUserWarning(cd, [Ent.USER, body['primaryEmail']], i, count) duplicateAliasGroupUserWarning(cd, [Ent.USER, body['primaryEmail']], i, count)

View File

@@ -0,0 +1,19 @@
# Steps to release a new GAM version
1. In a final commit before release:
- [src/gam/__init.py](https://github.com/GAM-team/GAM/blob/main/src/gam/__init__.py) `__version___` value should be updated to the new version.
- [src/GamUpdate.txt](https://github.com/GAM-team/GAM/blob/main/src/GamUpdate.txt) should be updated with a high-level changelog.
- [wiki/GamUpdates.md](https://github.com/GAM-team/GAM/blob/main/wiki/GamUpdates.md) should be updated with same high-level changelog.
2. The [build.yaml](https://github.com/GAM-team/GAM/blob/main/.github/workflows/build.yml) Github Action for final commit should complete successfully and creating a new dated Draft release.
- We should *NEVER* upload release files manually. Only release files automatically created and [attested](https://github.com/GAM-team/GAM/wiki/Verifying-a-GAM7-Build-is-Legitimate-and-Official#github-attestation-linuxmacoswindows) as created by the Github Action should be used.
3. Edit the Draft release:
- Create a new tag with the format: `vN.NN.NN` where N.NN.NN is the GAM release version.
- name the release "GAM N.NN.NN" where N.NN.NN is the GAM release version.
- Include the changelog details for the new version in details.
- leave "Set as pre-release" unchecked and "Set as the latest release" checked.
- Publish the release.
# TODO: Release Process Improvements
- copying changelog between GamUpdate.txt, GamUpdates.md and release description is manual and tedious. Automate it.
- copying version string from gam/__init__.py, changelogs and release details and tag in manual and tedious. Automate it.
- See if we can block releases with binaries not uploaded by GitHub Actions to further secure release pipelines.