Phase 2 (partial): Move constants and calendar ACL helpers

This commit is contained in:
Jay Lee
2026-07-03 17:58:42 -04:00
parent d992288d67
commit f41d60f116
4 changed files with 169 additions and 8 deletions

View File

@@ -93,12 +93,55 @@ from gam.util.errors import (
unknownArgumentExit,
)
from gam.util.fileio import UNKNOWN
from gam.util.output import executeBatch, setSysExitRC
from gam.util.output import executeBatch, formatKeyValueList, setSysExitRC
from gam.constants import DAYS_OF_WEEK, GOOGLE_MEETID_FORMAT_REQUIRED, GOOGLE_MEETID_PATTERN, NO_ENTITIES_FOUND_RC
Act = glaction.GamAction()
Ent = glentity.GamEntity()
Ind = glindent.GamIndent()
# ACL utility functions (moved from gam/__init__.py)
def ACLRuleDict(rule):
if rule['scope']['type'] != 'default':
return {'Scope': f'{rule["scope"]["type"]}:{rule["scope"]["value"]}', 'Role': rule['role']}
return {'Scope': f'{rule["scope"]["type"]}', 'Role': rule['role']}
def ACLRuleKeyValueList(rule):
if rule['scope']['type'] != 'default':
return ['Scope', f'{rule["scope"]["type"]}:{rule["scope"]["value"]}', 'Role', rule['role']]
return ['Scope', f'{rule["scope"]["type"]}', 'Role', rule['role']}
def formatACLRule(rule):
return formatKeyValueList('(', ACLRuleKeyValueList(rule), ')')
def formatACLScopeRole(scope, role):
if role:
return formatKeyValueList('(', ['Scope', scope, 'Role', role], ')')
return formatKeyValueList('(', ['Scope', scope], ')')
def normalizeRuleId(ruleId):
ruleIdParts = ruleId.split(':', 1)
if (len(ruleIdParts) == 1) or not ruleIdParts[1]:
if ruleIdParts[0] == 'default':
return ruleId
if ruleIdParts[0] == 'domain':
return f'domain:{GC.Values[GC.DOMAIN]}'
return f'user:{normalizeEmailAddressOrUID(ruleIdParts[0], noUid=True)}'
if ruleIdParts[0] in {'user', 'group'}:
return f'{ruleIdParts[0]}:{normalizeEmailAddressOrUID(ruleIdParts[1], noUid=True)}'
return ruleId
def makeRoleRuleIdBody(role, ruleId):
ruleIdParts = ruleId.split(':', 1)
if len(ruleIdParts) == 1:
if ruleIdParts[0] == 'default':
return {'role': role, 'scope': {'type': ruleIdParts[0]}}
if ruleIdParts[0] == 'domain':
return {'role': role, 'scope': {'type': ruleIdParts[0], 'value': GC.Values[GC.DOMAIN]}}
return {'role': role, 'scope': {'type': 'user', 'value': ruleIdParts[0]}}
return {'role': role, 'scope': {'type': ruleIdParts[0], 'value': ruleIdParts[1]}}
Cmd = glclargs.GamCLArgs()
@@ -234,7 +277,6 @@ def _normalizeCalIdGetRuleIds(origUser, user, origCal, calId, j, jcount, ACLScop
return (calId, cal, ruleIds, kcount)
def _processCalendarACLs(cal, function, entityType, calId, j, jcount, k, kcount, role, ruleId, sendNotifications):
from gam import formatACLScopeRole, makeRoleRuleIdBody
result = True
if function == 'insert':
kwargs = {'body': makeRoleRuleIdBody(role, ruleId), 'fields': '', 'sendNotifications': sendNotifications}
@@ -264,7 +306,6 @@ def _processCalendarACLs(cal, function, entityType, calId, j, jcount, k, kcount,
return result
def _createCalendarACLs(cal, entityType, calId, j, jcount, role, ruleIds, kcount, sendNotifications):
from gam import normalizeRuleId
Ind.Increment()
k = 0
for ruleId in ruleIds:
@@ -294,7 +335,6 @@ def doCalendarsCreateACLs(calIds):
_doCalendarsCreateACLs(None, None, None, calIds, len(calIds), role, ACLScopeEntity, sendNotifications)
def _updateDeleteCalendarACLs(cal, function, entityType, calId, j, jcount, role, ruleIds, kcount, sendNotifications):
from gam import normalizeRuleId
Ind.Increment()
k = 0
for ruleId in ruleIds:
@@ -334,7 +374,6 @@ def doCalendarsDeleteACLs(calIds):
_doUpdateDeleteCalendarACLs(None, None, None, 'delete', calIds, len(calIds), ACLScopeEntity, role, False)
def _showCalendarACL(user, entityType, calId, acl, k, kcount, FJQC):
from gam import ACLRuleKeyValueList
if FJQC.formatJSON:
if entityType == Ent.CALENDAR:
if user:
@@ -350,7 +389,6 @@ def _showCalendarACL(user, entityType, calId, acl, k, kcount, FJQC):
printKeyValueListWithCount(ACLRuleKeyValueList(acl), k, kcount)
def _infoCalendarACLs(cal, user, entityType, calId, j, jcount, ruleIds, kcount, FJQC):
from gam import formatACLScopeRole, normalizeRuleId
Ind.Increment()
k = 0
for ruleId in ruleIds:

View File

@@ -673,7 +673,7 @@ RESOURCE_FIELDS_WITH_CRS_NLS = {'resourceDescription'}
def _showResource(cd, resource, i, count, FJQC, acls=None, noSelfOwner=False):
from gam import ACLRuleKeyValueList
from gam.cmd.calendar import ACLRuleKeyValueList
def _showResourceField(title, resource, field):
if field in resource:
if field not in RESOURCE_FIELDS_WITH_CRS_NLS:

View File

@@ -610,7 +610,7 @@ def _getCalendarAttributes(body, returnOnUnknownArgument=False):
unknownArgumentExit()
def _showCalendar(calendar, j, jcount, FJQC, acls=None):
from gam import ACLRuleKeyValueList
from gam.cmd.calendar import ACLRuleKeyValueList
if FJQC.formatJSON:
if acls:
calendar['acls'] = [{'id': rule['id'], 'role': rule['role']} for rule in acls]

View File

@@ -146,6 +146,129 @@ YUBIKEY_VALUE_ERROR_RC = 85
YUBIKEY_MULTIPLE_CONNECTED_RC = 86
YUBIKEY_NOT_FOUND_RC = 87
# Boolean constants
TRUE = 'true'
FALSE = 'false'
TRUE_VALUES = [TRUE, 'on', 'yes', 'enabled', '1']
FALSE_VALUES = [FALSE, 'off', 'no', 'disabled', '0']
TRUE_FALSE = [TRUE, FALSE]
# Error/warning prefixes
ERROR = 'ERROR'
ERROR_PREFIX = ERROR + ': '
WARNING = 'WARNING'
WARNING_PREFIX = WARNING + ': '
# Byte sizes (powers of 10)
ONE_KILO_10_BYTES = 1000
ONE_MEGA_10_BYTES = ONE_KILO_10_BYTES * ONE_KILO_10_BYTES
ONE_GIGA_10_BYTES = ONE_KILO_10_BYTES * ONE_MEGA_10_BYTES
ONE_TERA_10_BYTES = ONE_KILO_10_BYTES * ONE_GIGA_10_BYTES
# Time durations in seconds
SECONDS_PER_MINUTE = 60
SECONDS_PER_HOUR = 3600
SECONDS_PER_DAY = 86400
SECONDS_PER_WEEK = 604800
# Google limits
MAX_GOOGLE_SHEET_CELLS = 10000000 # See https://support.google.com/drive/answer/37603
SHARED_DRIVE_MAX_FILES_FOLDERS = 500000
# Encoding
UTF8 = 'utf-8'
UTF8_SIG = 'utf-8-sig'
# Environment variable names
EV_GAMCFGDIR = 'GAMCFGDIR'
EV_GAMCFGSECTION = 'GAMCFGSECTION'
EV_OLDGAMPATH = 'OLDGAMPATH'
# Config file names
FN_GAM_CFG = 'gam.cfg'
FN_LAST_UPDATE_CHECK_TXT = 'lastupdatecheck.txt'
FN_GAMCOMMANDS_TXT = 'GamCommands.txt'
# Drive path constants
ROOTID = 'rootid'
ORPHANS = 'Orphans'
SHARED_WITHME = 'SharedWithMe'
SHARED_DRIVES = 'SharedDrives'
# Additional character sets
URL_SAFE_CHARS = ALPHANUMERIC_CHARS + '-._~'
FILENAME_SAFE_CHARS = ALPHANUMERIC_CHARS + "-_.() "
CHAT_MESSAGEID_CHARS = string.ascii_lowercase + string.digits + '-'
# File mode constants
DEFAULT_CSV_READ_MODE = 'r'
DEFAULT_FILE_APPEND_MODE = 'a'
DEFAULT_FILE_READ_MODE = 'r'
DEFAULT_FILE_WRITE_MODE = 'w'
# Application URLs
GAM_URL = f'https://github.com/{GIT_USER}/{GAM}'
GAM_RELEASES = f'https://github.com/{GIT_USER}/{GAM}/releases'
GAM_WIKI = f'https://github.com/{GIT_USER}/{GAM}/wiki'
GAM_LATEST_RELEASE = f'https://api.github.com/repos/{GIT_USER}/{GAM}/releases/latest'
# Additional Google API MIME types
MIMETYPE_GA_DOCUMENT = f'{APPLICATION_VND_GOOGLE_APPS}document'
MIMETYPE_GA_DRAWING = f'{APPLICATION_VND_GOOGLE_APPS}drawing'
MIMETYPE_GA_FILE = f'{APPLICATION_VND_GOOGLE_APPS}file'
MIMETYPE_GA_FORM = f'{APPLICATION_VND_GOOGLE_APPS}form'
MIMETYPE_GA_FUSIONTABLE = f'{APPLICATION_VND_GOOGLE_APPS}fusiontable'
MIMETYPE_GA_JAM = f'{APPLICATION_VND_GOOGLE_APPS}jam'
MIMETYPE_GA_MAP = f'{APPLICATION_VND_GOOGLE_APPS}map'
MIMETYPE_GA_PRESENTATION = f'{APPLICATION_VND_GOOGLE_APPS}presentation'
MIMETYPE_GA_SCRIPT = f'{APPLICATION_VND_GOOGLE_APPS}script'
MIMETYPE_GA_SCRIPT_JSON = f'{APPLICATION_VND_GOOGLE_APPS}script+json'
MIMETYPE_GA_3P_SHORTCUT = f'{APPLICATION_VND_GOOGLE_APPS}drive-sdk'
MIMETYPE_GA_SITE = f'{APPLICATION_VND_GOOGLE_APPS}site'
MIMETYPE_GA_SPREADSHEET = f'{APPLICATION_VND_GOOGLE_APPS}spreadsheet'
MIMETYPE_TEXT_CSV = 'text/csv'
MIMETYPE_TEXT_HTML = 'text/html'
MIMETYPE_TEXT_PLAIN = 'text/plain'
# Google infrastructure
GOOGLE_NAMESERVERS = ['8.8.8.8', '8.8.4.4']
# Date/time sentinel values
NEVER_DATE = '1970-01-01'
NEVER_DATETIME = '1970-01-01 00:00'
NEVER_TIME = '1970-01-01T00:00:00.000Z'
NEVER_TIME_NOMS = '1970-01-01T00:00:00Z'
NEVER_END_DATE = '1969-12-31'
NEVER_START_DATE = NEVER_DATE
REFRESH_EXPIRY = '1970-01-01T00:00:01Z'
UNKNOWN = 'Unknown'
REPLACE_GROUP_PATTERN = re.compile(r'\\(\d+)')
# Additional Drive query fragments
MY_FOLDERS = ME_IN_OWNERS_AND + ANY_FOLDERS
WITH_ANY_FILE_NAME = "name = '{0}'"
WITH_MY_FILE_NAME = ME_IN_OWNERS_AND + WITH_ANY_FILE_NAME
WITH_OTHER_FILE_NAME = NOT_ME_IN_OWNERS_AND + WITH_ANY_FILE_NAME
# Debug redaction patterns
DEBUG_REDACTION_PATTERNS = [
# Positional patterns that redact sensitive credentials based on their location
(r'(Bearer\s+)\S+', r'\1*****'), # access tokens and JWTs in auth header
(r'([?&]refresh_token=)[^&]*', r'\1*****'), # refresh token URL parameter
(r'([?&]client_secret=)[^&]*', r'\1*****'), # client secret URL parameter
(r'([?&]key=)[^&]*', r'\1*****'), # API key URL parameter
(r'([?&]code=)[^&]*', r'\1*****'), # auth code URL parameter
# Pattern match patterns that redact sensitive credentials based on known credential pattern
(r'ya29.[0-9A-Za-z-_]+', '*****'), # Access token
(r'1%2F%2F[0-9A-Za-z-_]{100}|1%2F%2F[0-9A-Za-z-_]{64}|1%2F%2F[0-9A-Za-z-_]{43}', '*****'), # Refresh token
(r'4/[0-9A-Za-z-_]+', '*****'), # Auth code
(r'GOCSPX-[0-9a-zA-Z-_]{28}', '*****'), # Client secret
(r'AIza[0-9A-Za-z-_]{35}', '*****'), # API key
(r'eyJ[a-zA-Z0-9\-_]+\.eyJ[a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]*', '*****'), # JWT
]
# Building address field map
BUILDING_ADDRESS_FIELD_MAP = {
'address': 'addressLines',