Added variable oauth2_txt_lock_mode to gam.cfg

This commit is contained in:
Ross Scroggs
2026-01-21 09:55:50 -08:00
parent cd34c3d1e2
commit 613cae987f
5 changed files with 38 additions and 8 deletions

View File

@@ -1,3 +1,9 @@
7.32.02
Added variable `oauth2_txt_lock_mode` to `gam.cfg`, the default is 644 and valid values are: 644, 664, 666.
This value is used to set the file permissions on the `oauth2.txt.lock` file. In very special cases where
daemon processes, e.g. Apache/httpd, are running GAM, the value 666 may have to be used.
7.32.01 7.32.01
Added option `(addcsvdata <FieldName> <String>)*` to `gam <CrOSTypeEntity> issuecommand command <CrOSCommand> csv` Added option `(addcsvdata <FieldName> <String>)*` to `gam <CrOSTypeEntity> issuecommand command <CrOSCommand> csv`

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.32.01' __version__ = '7.32.02'
__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
@@ -4748,7 +4748,7 @@ def getClientCredentials(forceRefresh=False, forceWrite=False, filename=None, ap
"""Gets OAuth2 credentials which are guaranteed to be fresh and valid. """Gets OAuth2 credentials which are guaranteed to be fresh and valid.
Locks during read and possible write so that only one process will Locks during read and possible write so that only one process will
attempt refresh/write when running in parallel. """ attempt refresh/write when running in parallel. """
lock = FileLock(GM.Globals[GM.OAUTH2_TXT_LOCK]) lock = FileLock(GM.Globals[GM.OAUTH2_TXT_LOCK], mode=GC.Values[GC.OAUTH2_TXT_LOCK_MODE])
with lock: with lock:
writeCreds, credentials = getOauth2TxtCredentials(api=api, noDASA=noDASA, refreshOnly=refreshOnly, noScopes=noScopes) writeCreds, credentials = getOauth2TxtCredentials(api=api, noDASA=noDASA, refreshOnly=refreshOnly, noScopes=noScopes)
if not credentials: if not credentials:
@@ -10760,7 +10760,7 @@ Continue to authorization by entering a 'c'
menu = oauth2_menu % tuple(range(numScopes)) menu = oauth2_menu % tuple(range(numScopes))
selectedScopes = ['*'] * numScopes selectedScopes = ['*'] * numScopes
if currentScopes is None and clientAccess: if currentScopes is None and clientAccess:
lock = FileLock(GM.Globals[GM.OAUTH2_TXT_LOCK]) lock = FileLock(GM.Globals[GM.OAUTH2_TXT_LOCK], mode=GC.Values[GC.OAUTH2_TXT_LOCK_MODE])
with lock: with lock:
_, credentials = getOauth2TxtCredentials(exitOnError=False) _, credentials = getOauth2TxtCredentials(exitOnError=False)
if credentials and credentials.scopes is not None: if credentials and credentials.scopes is not None:
@@ -11258,7 +11258,7 @@ def doOAuthRequest(currentScopes, login_hint, verifyScopes=False):
access_type='offline', access_type='offline',
login_hint=login_hint, login_hint=login_hint,
open_browser=not GC.Values[GC.NO_BROWSER]) open_browser=not GC.Values[GC.NO_BROWSER])
lock = FileLock(GM.Globals[GM.OAUTH2_TXT_LOCK]) lock = FileLock(GM.Globals[GM.OAUTH2_TXT_LOCK], mode=GC.Values[GC.OAUTH2_TXT_LOCK_MODE])
with lock: with lock:
writeClientCredentials(credentials, GC.Values[GC.OAUTH2_TXT]) writeClientCredentials(credentials, GC.Values[GC.OAUTH2_TXT])
entityActionPerformed([Ent.OAUTH2_TXT_FILE, GC.Values[GC.OAUTH2_TXT]]) entityActionPerformed([Ent.OAUTH2_TXT_FILE, GC.Values[GC.OAUTH2_TXT]])
@@ -11308,7 +11308,7 @@ def exitIfNoOauth2Txt():
def doOAuthDelete(): def doOAuthDelete():
checkForExtraneousArguments() checkForExtraneousArguments()
exitIfNoOauth2Txt() exitIfNoOauth2Txt()
lock = FileLock(GM.Globals[GM.OAUTH2_TXT_LOCK], timeout=10) lock = FileLock(GM.Globals[GM.OAUTH2_TXT_LOCK], mode=GC.Values[GC.OAUTH2_TXT_LOCK_MODE], timeout=10)
with lock: with lock:
_, credentials = getOauth2TxtCredentials(noScopes=True) _, credentials = getOauth2TxtCredentials(noScopes=True)
if not credentials: if not credentials:
@@ -11392,7 +11392,7 @@ def doOAuthUpdate():
login_hint = getEmailAddress(noUid=True, optional=True) login_hint = getEmailAddress(noUid=True, optional=True)
checkForExtraneousArguments() checkForExtraneousArguments()
exitIfNoOauth2Txt() exitIfNoOauth2Txt()
lock = FileLock(GM.Globals[GM.OAUTH2_TXT_LOCK]) lock = FileLock(GM.Globals[GM.OAUTH2_TXT_LOCK], mode=GC.Values[GC.OAUTH2_TXT_LOCK_MODE])
with lock: with lock:
jsonData = readFile(GC.Values[GC.OAUTH2_TXT], continueOnError=True, displayError=False) jsonData = readFile(GC.Values[GC.OAUTH2_TXT], continueOnError=True, displayError=False)
if not jsonData: if not jsonData:
@@ -45905,6 +45905,20 @@ def verifyUserPrimaryEmail(cd, user, createIfNotFound, i, count):
entityUnknownWarning(Ent.USER, user, i, count) entityUnknownWarning(Ent.USER, user, i, count)
return False return False
# gam create guestuser <EmailAddress>
def doCreateGuestUser():
cd = buildGAPIObject(API.DIRECTORY)
body = {'primaryGuestEmail': getEmailAddress(noUid=True),
'customer': GC.Values[GC.CUSTOMER_ID]}
checkForExtraneousArguments()
try:
result = callGAPI(cd.users(), 'createGuest',
throwReasons=[GAPI.FAILED_PRECONDITION],
body=body)
entityActionPerformed([Ent.GUEST_USER, result['primaryGuestEmail']])
except (GAPI.failedPrecondition) as e:
entityActionFailedExit([Ent.GUEST_USER, body['primaryGuestEmail']], str(e))
# gam <UserTypeEntity> update user <UserAttribute>* # gam <UserTypeEntity> update user <UserAttribute>*
# [verifynotinvitable|alwaysevict] [noactionifalias] # [verifynotinvitable|alwaysevict] [noactionifalias]
# [updateprimaryemail <RESEarchPattern> <RESubstitution>] # [updateprimaryemail <RESEarchPattern> <RESubstitution>]
@@ -79485,6 +79499,7 @@ MAIN_ADD_CREATE_FUNCTIONS = {
Cmd.ARG_GROUP: doCreateGroup, Cmd.ARG_GROUP: doCreateGroup,
Cmd.ARG_GUARDIAN: doInviteGuardian, Cmd.ARG_GUARDIAN: doInviteGuardian,
Cmd.ARG_GUARDIANINVITATION: doInviteGuardian, Cmd.ARG_GUARDIANINVITATION: doInviteGuardian,
Cmd.ARG_GUESTUSER: doCreateGuestUser,
Cmd.ARG_INBOUNDSSOASSIGNMENT: doCreateInboundSSOAssignment, Cmd.ARG_INBOUNDSSOASSIGNMENT: doCreateInboundSSOAssignment,
Cmd.ARG_INBOUNDSSOCREDENTIAL: doCreateInboundSSOCredential, Cmd.ARG_INBOUNDSSOCREDENTIAL: doCreateInboundSSOCredential,
Cmd.ARG_INBOUNDSSOPROFILE: doCreateInboundSSOProfile, Cmd.ARG_INBOUNDSSOPROFILE: doCreateInboundSSOProfile,
@@ -81218,6 +81233,7 @@ USER_COMMANDS_OBJ_ALIASES = {
Cmd.ARG_GUARDIANINVITATIONS: Cmd.ARG_GUARDIANINVITATION, Cmd.ARG_GUARDIANINVITATIONS: Cmd.ARG_GUARDIANINVITATION,
Cmd.ARG_GUARDIANINVITE: Cmd.ARG_GUARDIANINVITATION, Cmd.ARG_GUARDIANINVITE: Cmd.ARG_GUARDIANINVITATION,
Cmd.ARG_GUARDIANS: Cmd.ARG_GUARDIAN, Cmd.ARG_GUARDIANS: Cmd.ARG_GUARDIAN,
Cmd.ARG_GUESTUSERS: Cmd.ARG_GUESTUSER,
Cmd.ARG_HOLD: Cmd.ARG_VAULTHOLD, Cmd.ARG_HOLD: Cmd.ARG_VAULTHOLD,
Cmd.ARG_HOLDS: Cmd.ARG_VAULTHOLD, Cmd.ARG_HOLDS: Cmd.ARG_VAULTHOLD,
Cmd.ARG_IMAP4: Cmd.ARG_IMAP, Cmd.ARG_IMAP4: Cmd.ARG_IMAP,

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (C) 2025 Ross Scroggs All Rights Reserved. # Copyright (C) 2026 Ross Scroggs All Rights Reserved.
# #
# All Rights Reserved. # All Rights Reserved.
# #
@@ -218,6 +218,8 @@ NUM_TBATCH_THREADS = 'num_tbatch_threads'
NUM_THREADS = 'num_threads' NUM_THREADS = 'num_threads'
# Path to oauth2.txt # Path to oauth2.txt
OAUTH2_TXT = 'oauth2_txt' OAUTH2_TXT = 'oauth2_txt'
# File permissions for oauth2.txt.lock
OAUTH2_TXT_LOCK_MODE = 'oauth2_txt_lock_mode'
# Path to oauth2service.json # Path to oauth2service.json
OAUTH2SERVICE_JSON = 'oauth2service_json' OAUTH2SERVICE_JSON = 'oauth2service_json'
# Output date format, empty defalts to ISOFormat # Output date format, empty defalts to ISOFormat
@@ -416,6 +418,7 @@ Defaults = {
NUM_TBATCH_THREADS: '2', NUM_TBATCH_THREADS: '2',
NUM_THREADS: '5', NUM_THREADS: '5',
OAUTH2_TXT: FN_OAUTH2_TXT, OAUTH2_TXT: FN_OAUTH2_TXT,
OAUTH2_TXT_LOCK_MODE: '644',
OAUTH2SERVICE_JSON: FN_OAUTH2SERVICE_JSON, OAUTH2SERVICE_JSON: FN_OAUTH2SERVICE_JSON,
OUTPUT_DATEFORMAT: '', OUTPUT_DATEFORMAT: '',
OUTPUT_TIMEFORMAT: '', OUTPUT_TIMEFORMAT: '',
@@ -587,6 +590,7 @@ VAR_INFO = {
NUM_TBATCH_THREADS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 1000)}, NUM_TBATCH_THREADS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 1000)},
NUM_THREADS: {VAR_TYPE: TYPE_INTEGER, VAR_ENVVAR: 'GAM_THREADS', VAR_LIMITS: (1, 1000)}, NUM_THREADS: {VAR_TYPE: TYPE_INTEGER, VAR_ENVVAR: 'GAM_THREADS', VAR_LIMITS: (1, 1000)},
OAUTH2_TXT: {VAR_TYPE: TYPE_FILE, VAR_ENVVAR: 'OAUTHFILE', VAR_ACCESS: os.R_OK | os.W_OK}, OAUTH2_TXT: {VAR_TYPE: TYPE_FILE, VAR_ENVVAR: 'OAUTHFILE', VAR_ACCESS: os.R_OK | os.W_OK},
OAUTH2_TXT_LOCK_MODE: {VAR_TYPE: TYPE_CHOICE, VAR_CHOICES: {'644': 0o644, '664': 0o664, '666': 0o666}},
OAUTH2SERVICE_JSON: {VAR_TYPE: TYPE_FILE, VAR_ENVVAR: 'OAUTHSERVICEFILE', VAR_ACCESS: os.R_OK | os.W_OK}, OAUTH2SERVICE_JSON: {VAR_TYPE: TYPE_FILE, VAR_ENVVAR: 'OAUTHSERVICEFILE', VAR_ACCESS: os.R_OK | os.W_OK},
OUTPUT_DATEFORMAT: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)}, OUTPUT_DATEFORMAT: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)},
OUTPUT_TIMEFORMAT: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)}, OUTPUT_TIMEFORMAT: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)},

View File

@@ -928,6 +928,8 @@ class GamCLArgs():
ARG_GUARDIANINVITE = 'guardianinvite' ARG_GUARDIANINVITE = 'guardianinvite'
ARG_GUARDIANINVITATION = 'guardianinvitation' ARG_GUARDIANINVITATION = 'guardianinvitation'
ARG_GUARDIANINVITATIONS = 'guardianinvitations' ARG_GUARDIANINVITATIONS = 'guardianinvitations'
ARG_GUESTUSER = 'guestuser'
ARG_GUESTUSERS = 'guestusers'
ARG_HOLD = 'hold' ARG_HOLD = 'hold'
ARG_HOLDS = 'holds' ARG_HOLDS = 'holds'
ARG_IMAP = 'imap' ARG_IMAP = 'imap'

View File

@@ -253,6 +253,7 @@ class GamEntity():
GUARDIAN = 'guar' GUARDIAN = 'guar'
GUARDIAN_INVITATION = 'gari' GUARDIAN_INVITATION = 'gari'
GUARDIAN_AND_INVITATION = 'gani' GUARDIAN_AND_INVITATION = 'gani'
GUEST_USER = 'gstu'
IAM_POLICY = 'iamp' IAM_POLICY = 'iamp'
IMAP_ENABLED = 'imap' IMAP_ENABLED = 'imap'
INBOUND_SSO_ASSIGNMENT = 'insa' INBOUND_SSO_ASSIGNMENT = 'insa'
@@ -358,7 +359,7 @@ class GamEntity():
SNIPPETS_ENABLED = 'snip' SNIPPETS_ENABLED = 'snip'
SSO_KEY = 'ssok' SSO_KEY = 'ssok'
SSO_SETTINGS = 'ssos' SSO_SETTINGS = 'ssos'
SOURCE_USER = 'src' SOURCE_USER = 'srcu'
SPREADSHEET = 'sprd' SPREADSHEET = 'sprd'
SPREADSHEET_RANGE = 'ssrn' SPREADSHEET_RANGE = 'ssrn'
START_TIME = 'strt' START_TIME = 'strt'
@@ -620,6 +621,7 @@ class GamEntity():
GROUP_MEMBERSHIP_TREE: ['Group Membership Trees', 'Group Membership Tree'], GROUP_MEMBERSHIP_TREE: ['Group Membership Trees', 'Group Membership Tree'],
GROUP_SETTINGS: ['Group Settings', 'Group Settings'], GROUP_SETTINGS: ['Group Settings', 'Group Settings'],
GROUP_TREE: ['Group Trees', 'Group Tree'], GROUP_TREE: ['Group Trees', 'Group Tree'],
GUEST_USER: ['Guest Users', 'Guest User'],
GUARDIAN: ['Guardians', 'Guardian'], GUARDIAN: ['Guardians', 'Guardian'],
GUARDIAN_INVITATION: ['Guardian Invitations', 'Guardian Invitation'], GUARDIAN_INVITATION: ['Guardian Invitations', 'Guardian Invitation'],
GUARDIAN_AND_INVITATION: ['Guardians and Invitations', 'Guardian and Invitation'], GUARDIAN_AND_INVITATION: ['Guardians and Invitations', 'Guardian and Invitation'],