From 3fc2aeed4d1d99f5a65b427dc1d63d7f6487d185 Mon Sep 17 00:00:00 2001 From: Ross Scroggs Date: Sat, 10 Aug 2019 12:37:44 -0700 Subject: [PATCH] Fix group settings (#990) * Fix group settings Clean up md5MatchesFile * Simplify doGetCustomerInfo Avoid trap when doPrintDomains returned a domain creationTime with no fraction. Traceback (most recent call last): File "gam.py", line 14761, in File "gam.py", line 14159, in ProcessGAMCommand File "gam.py", line 2053, in doGetDomainInfo File "gam.py", line 2088, in doGetCustomerInfo File "_strptime.py", line 577, in _strptime_datetime File "_strptime.py", line 359, in _strptime ValueError: time data '2019-04-23 16:14:56' does not match format '%Y-%m-%d %H:% M:%S.%f' lansa.co.uk,True,2019-04-23 16:14:13.041000,secondary, lansa.com.au,True,2019-04-23 16:14:56,secondary --- src/GamCommands.txt | 2 +- src/gam.py | 94 +++++++++++++++++++----------------- src/var.py | 114 +++++++++++++++++++++++++++++++------------- 3 files changed, 131 insertions(+), 79 deletions(-) diff --git a/src/GamCommands.txt b/src/GamCommands.txt index d84837c4..f8c13d81 100644 --- a/src/GamCommands.txt +++ b/src/GamCommands.txt @@ -874,7 +874,7 @@ gam info resoldsubscriptions [customer_auth_token ] ::= "(,)*" gam report users|user [todrive] [date ] [fulldatarequired all|] - [(user all|)|(orgunit|org|ou )] [filter|filters ] [fields|parameters ] + [(user )|(orgunit|org|ou )] [filter|filters ] [fields|parameters ] gam report customers|customer|domain [todrive] [date ] [fulldatarequired all|] [fields|parameters ] gam report admin|calendar|calendars|drive|docs|doc|groups|group|logins|login|mobile|tokens|token [todrive] diff --git a/src/gam.py b/src/gam.py index 22ab7067..0c241210 100755 --- a/src/gam.py +++ b/src/gam.py @@ -2082,13 +2082,14 @@ def doGetCustomerInfo(): # If customer has changed primary domain customerCreationTime is date # of current primary being added, not customer create date. # We should also get all domains and use oldest date - domains = doPrintDomains(return_results=True) oldest = datetime.datetime.strptime(customer_info['customerCreationTime'], '%Y-%m-%dT%H:%M:%S.%fZ') + domains = callGAPIitems(cd.domains(), 'list', 'domains', + customer=GC_Values[GC_CUSTOMER_ID], fields='domains(creationTime)') for domain in domains: - domain_creation = datetime.datetime.strptime(domain['creationTime'], '%Y-%m-%d %H:%M:%S.%f') + domain_creation = datetime.datetime.fromtimestamp(int(domain['creationTime'])/1000) if domain_creation < oldest: oldest = domain_creation - print('Customer Creation Time: %s' % oldest) + print('Customer Creation Time: %s' % oldest.strftime('%Y-%m-%dT%H:%M:%SZ')) print('Default Language: %s' % customer_info.get('language', 'Unset (defaults to en)')) if 'postalAddress' in customer_info: print('Address:') @@ -2167,7 +2168,7 @@ def doDelDomainAlias(): domainAliasName = sys.argv[3] callGAPI(cd.domainAliases(), 'delete', customer=GC_Values[GC_CUSTOMER_ID], domainAliasName=domainAliasName) -def doPrintDomains(return_results=False): +def doPrintDomains(): cd = buildGAPIObject('directory') todrive = False titles = ['domainName',] @@ -2208,8 +2209,6 @@ def doPrintDomains(return_results=False): titles.append(attr) aliasdomain_attributes[attr] = aliasdomain[attr] csvRows.append(aliasdomain_attributes) - if return_results: - return csvRows writeCSVfile(csvRows, titles, 'Domains', todrive) def doPrintDomainAliases(): @@ -8431,11 +8430,10 @@ def _getCloudStorageObject(s, bucket, object_, local_file=None, expectedMd5=None if expectedMd5: sys.stdout.write('Verifying %s hash...' % expectedMd5) sys.stdout.flush() - if md5MatchesFile(local_file, expectedMd5): + if md5MatchesFile(local_file, expectedMd5, False): print('VERIFIED') return - else: - print('not verified. Downloading again and over-writing...') + print('not verified. Downloading again and over-writing...') else: return # nothing to verify, just assume we're good. print('saving to %s' % local_file) @@ -8460,19 +8458,18 @@ def _getCloudStorageObject(s, bucket, object_, local_file=None, expectedMd5=None f = openFile(local_file, 'rb') sys.stdout.write(' Verifying file hash is %s...' % expectedMd5) sys.stdout.flush() - if md5MatchesFile(local_file, expectedMd5): - print('VERIFIED') - else: - print('ERROR: actual hash was %s. Exiting on corrupt file.' % actual_hash) - sys.exit(6) + md5MatchesFile(local_file, expectedMd5, True) + print('VERIFIED') closeFile(f) -def md5MatchesFile(local_file, expected_md5): +def md5MatchesFile(local_file, expected_md5, exitOnError): f = openFile(local_file, 'rb') hash_md5 = hashlib.md5() for chunk in iter(lambda: f.read(4096), b""): hash_md5.update(chunk) actual_hash = hash_md5.hexdigest() + if exitOnError and actual_hash != expected_md5: + systemErrorExit(6, 'actual hash was %s. Exiting on corrupt file.' % actual_hash) return actual_hash == expected_md5 def doDownloadCloudStorageBucket(): @@ -8541,11 +8538,8 @@ def doDownloadVaultExport(): expected_hash = s_file['md5Hash'] sys.stdout.write(' Verifying file hash is %s...' % expected_hash) sys.stdout.flush() - if md5MatchesFile(filename, expected_hash): - print('VERIFIED') - else: - print('ERROR: actual hash was %s. Exiting on corrupt file.' % actual_hash) - sys.exit(6) + md5MatchesFile(filename, expected_hash, True) + print('VERIFIED') if extractFiles and re.search(r'\.zip$', filename): extract_nested_zip(filename, targetFolder) @@ -8869,21 +8863,11 @@ def doCreateUser(): def GroupIsAbuseOrPostmaster(emailAddr): return emailAddr.startswith('abuse@') or emailAddr.startswith('postmaster@') -def mapCollaborativeACL(myarg, value): - value = value.lower().replace('_', '') - if value in COLLABORATIVE_ACL_CHOICES: - return COLLABORATIVE_ACL_CHOICES[value] - systemErrorExit(3, 'allowed choices for %s are %s, got %s' % (myarg, ', '.join(sorted(COLLABORATIVE_ACL_CHOICES)), value)) +GROUP_SETTINGS_LIST_PATTERN = re.compile(r'([A-Z][A-Z_]+[A-Z]?):') def getGroupAttrValue(myarg, value, gs_object, gs_body, function): if myarg == 'collaborative': - value = mapCollaborativeACL(myarg, value) - for attrName, attrValue in list(COLLABORATIVE_INBOX_ATTRIBUTES.items()): - if attrValue == 'acl': - gs_body[attrName] = value - else: - gs_body[attrName] = attrValue - return + myarg = 'enablecollaborativeinbox' for (attrib, params) in list(gs_object['schemas']['Groups']['properties'].items()): if attrib in ['kind', 'etag', 'email']: continue @@ -8905,19 +8889,19 @@ def getGroupAttrValue(myarg, value, gs_object, gs_body, function): value = value.replace('\\n', '\n') elif attrib == 'primaryLanguage': value = LANGUAGE_CODES_MAP.get(value.lower(), value) - elif COLLABORATIVE_INBOX_ATTRIBUTES.get(attrib) == 'acl': - value = mapCollaborativeACL(myarg, value) - elif params['description'].find(value.upper()) != -1: # ugly hack because API wants some values uppercased. + elif attrib in GROUP_SETTINGS_LIST_ATTRIBUTES: value = value.upper() - elif value.lower() in true_values: - value = 'true' - elif value.lower() in false_values: - value = 'false' - # Another ugly hack because Groups Settings API doesn't have proper enumerator values set in discovery file. - if 'description' in params and params['description'].find('Possible values are: ') != -1: - possible_values = params['description'][params['description'].find('Possible values are: ')+21:].split(' ') - if value not in possible_values: - systemErrorExit(2, 'value for %s must be one of %s. Got %s.' % (attrib, ', '.join(possible_values), value)) + possible_values = GROUP_SETTINGS_LIST_PATTERN.findall(params['description']) + if value not in possible_values: + systemErrorExit(2, 'value for %s must be one of %s. Got %s.' % (attrib, ', '.join(possible_values), value)) + elif attrib in GROUP_SETTINGS_BOOLEAN_ATTRIBUTES: + value = value.lower() + if value in true_values: + value = 'true' + elif value in false_values: + value = 'false' + else: + systemErrorExit(2, 'value for %s must be true|false. Got %s.' % (attrib, value)) gs_body[attrib] = value return systemErrorExit(2, '%s is not a valid argument for "gam %s group"' % (myarg, function)) @@ -9697,7 +9681,7 @@ def doUpdateMobile(): doit = False if resourceIds[:6] == 'query:': query = resourceIds[6:] - fields='nextPageToken,mobiledevices(resourceId,email)' + fields = 'nextPageToken,mobiledevices(resourceId,email)' page_message = 'Got %%total_items%% mobile devices...\n' devices = callGAPIpages(cd.mobiledevices(), 'list', page_message=page_message, customerId=GC_Values[GC_CUSTOMER_ID], items='mobiledevices', query=query, fields=fields) else: @@ -11639,11 +11623,14 @@ GROUP_ARGUMENT_TO_PROPERTY_TITLE_MAP = { GROUP_ATTRIBUTES_ARGUMENT_TO_PROPERTY_MAP = { 'allowexternalmembers': 'allowExternalMembers', + 'allowgooglecommunication': 'allowGoogleCommunication', 'allowwebposting': 'allowWebPosting', 'archiveonly': 'archiveOnly', 'customfootertext': 'customFooterText', 'customreplyto': 'customReplyTo', 'defaultmessagedenynotificationtext': 'defaultMessageDenyNotificationText', + 'enablecollaborativeinbox': 'enableCollaborativeInbox', + 'favoriterepliesontop': 'favoriteRepliesOnTop', 'gal': 'includeInGlobalAddressList', 'includecustomfooter': 'includeCustomFooter', 'includeinglobaladdresslist': 'includeInGlobalAddressList', @@ -11656,16 +11643,33 @@ GROUP_ATTRIBUTES_ARGUMENT_TO_PROPERTY_MAP = { 'showingroupdirectory': 'showInGroupDirectory', 'spammoderationlevel': 'spamModerationLevel', 'whocanadd': 'whoCanAdd', + 'whocanapprovemembers': 'whoCanApproveMembers', + 'whocanapprovemessages': 'whoCanApproveMessages', 'whocanassigntopics': 'whoCanAssignTopics', + 'whocanassistcontent': 'whoCanAssistContent', + 'whocanbanusers': 'whoCanBanUsers', 'whocancontactowner': 'whoCanContactOwner', + 'whocandeleteanypost': 'whoCanDeleteAnyPost', + 'whocandeletetopics': 'whoCanDeleteTopics', + 'whocandiscovergroup': 'whoCanDiscoverGroup', 'whocanenterfreeformtags': 'whoCanEnterFreeFormTags', + 'whocanhideabuse': 'whoCanHideAbuse', 'whocaninvite': 'whoCanInvite', 'whocanjoin': 'whoCanJoin', 'whocanleavegroup': 'whoCanLeaveGroup', + 'whocanlocktopics': 'whoCanLockTopics', + 'whocanmaketopicssticky': 'whoCanMakeTopicsSticky', 'whocanmarkduplicate': 'whoCanMarkDuplicate', 'whocanmarkfavoritereplyonanytopic': 'whoCanMarkFavoriteReplyOnAnyTopic', + 'whocanmarkfavoritereplyonowntopic': 'whoCanMarkFavoriteReplyOnOwnTopic', 'whocanmarknoresponseneeded': 'whoCanMarkNoResponseNeeded', + 'whocanmoderatecontent': 'whoCanModerateContent', + 'whocanmoderatemembers': 'whoCanModerateMembers', + 'whocanmodifymembers': 'whoCanModifyMembers', 'whocanmodifytagsandcategories': 'whoCanModifyTagsAndCategories', + 'whocanmovetopicsin': 'whoCanMoveTopicsIn', + 'whocanmovetopicsout': 'whoCanMoveTopicsOut', + 'whocanpostannouncements': 'whoCanPostAnnouncements', 'whocanpostmessage': 'whoCanPostMessage', 'whocantaketopics': 'whoCanTakeTopics', 'whocanunassigntopic': 'whoCanUnassignTopic', diff --git a/src/var.py b/src/var.py index 85d8a9fa..672f0064 100644 --- a/src/var.py +++ b/src/var.py @@ -11,10 +11,10 @@ gam_license = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)' GAM_URL = 'https://git.io/gam' GAM_INFO = 'GAM {0} - {1} / {2} / Python {3}.{4}.{5} {6} / {7} {8} /'.format(gam_version, GAM_URL, - gam_author, - sys.version_info[0], sys.version_info[1], - sys.version_info[2], sys.version_info[3], - platform.platform(), platform.machine()) + gam_author, + sys.version_info[0], sys.version_info[1], + sys.version_info[2], sys.version_info[3], + platform.platform(), platform.machine()) GAM_RELEASES = 'https://github.com/jay0lee/GAM/releases' GAM_WIKI = 'https://github.com/jay0lee/GAM/wiki' @@ -157,13 +157,13 @@ API_VER_MAPPING = { API_SCOPE_MAPPING = { 'alertcenter': ['https://www.googleapis.com/auth/apps.alerts',], 'appsactivity': ['https://www.googleapis.com/auth/activity', - 'https://www.googleapis.com/auth/drive',], + 'https://www.googleapis.com/auth/drive',], 'calendar': ['https://www.googleapis.com/auth/calendar',], 'drive': ['https://www.googleapis.com/auth/drive',], 'drive3': ['https://www.googleapis.com/auth/drive',], 'gmail': ['https://mail.google.com/', - 'https://www.googleapis.com/auth/gmail.settings.basic', - 'https://www.googleapis.com/auth/gmail.settings.sharing',], + 'https://www.googleapis.com/auth/gmail.settings.basic', + 'https://www.googleapis.com/auth/gmail.settings.sharing',], 'sheets': ['https://www.googleapis.com/auth/spreadsheets',], } @@ -403,7 +403,7 @@ DOCUMENT_FORMATS_MAP = { 'mht': [{'mime': 'message/rfc822', 'ext': 'mht'}], 'odp': [{'mime': 'application/vnd.oasis.opendocument.presentation', 'ext': '.odp'}], 'ods': [{'mime': 'application/x-vnd.oasis.opendocument.spreadsheet', 'ext': '.ods'}, - {'mime': 'application/vnd.oasis.opendocument.spreadsheet', 'ext': '.ods'}], + {'mime': 'application/vnd.oasis.opendocument.spreadsheet', 'ext': '.ods'}], 'odt': [{'mime': 'application/vnd.oasis.opendocument.text', 'ext': '.odt'}], 'pdf': [{'mime': 'application/pdf', 'ext': '.pdf'}], 'png': [{'mime': 'image/png', 'ext': '.png'}], @@ -414,7 +414,7 @@ DOCUMENT_FORMATS_MAP = { 'rtf': [{'mime': 'application/rtf', 'ext': '.rtf'}], 'svg': [{'mime': 'image/svg+xml', 'ext': '.svg'}], 'tsv': [{'mime': 'text/tab-separated-values', 'ext': '.tsv'}, - {'mime': 'text/tsv', 'ext': '.tsv'}], + {'mime': 'text/tsv', 'ext': '.tsv'}], 'txt': [{'mime': 'text/plain', 'ext': '.txt'}], 'xls': [{'mime': 'application/vnd.ms-excel', 'ext': '.xls'}], 'xlt': [{'mime': 'application/vnd.ms-excel', 'ext': '.xlt'}], @@ -425,9 +425,9 @@ DOCUMENT_FORMATS_MAP = { 'microsoft': _MICROSOFT_FORMATS_LIST, 'micro$oft': _MICROSOFT_FORMATS_LIST, 'openoffice': [{'mime': 'application/vnd.oasis.opendocument.presentation', 'ext': '.odp'}, - {'mime': 'application/x-vnd.oasis.opendocument.spreadsheet', 'ext': '.ods'}, - {'mime': 'application/vnd.oasis.opendocument.spreadsheet', 'ext': '.ods'}, - {'mime': 'application/vnd.oasis.opendocument.text', 'ext': '.odt'}], + {'mime': 'application/x-vnd.oasis.opendocument.spreadsheet', 'ext': '.ods'}, + {'mime': 'application/vnd.oasis.opendocument.spreadsheet', 'ext': '.ods'}, + {'mime': 'application/vnd.oasis.opendocument.text', 'ext': '.odt'}], } DNS_ERROR_CODES_MAP = { @@ -612,28 +612,76 @@ CROS_END_ARGUMENTS = ['end', 'enddate'] CROS_TPM_VULN_VERSIONS = ['41f', '420', '628', '8520',] CROS_TPM_FIXED_VERSIONS = ['422', '62b', '8521',] -COLLABORATIVE_ACL_CHOICES = { - 'members': 'ALL_MEMBERS', - 'managersonly': 'MANAGERS_ONLY', - 'managers': 'OWNERS_AND_MANAGERS', - 'owners': 'OWNERS_ONLY', - 'none': 'NONE', - } +COLLABORATIVE_INBOX_ATTRIBUTES = [ + 'whoCanAddReferences', + 'whoCanAssignTopics', + 'whoCanEnterFreeFormTags', + 'whoCanMarkDuplicate', + 'whoCanMarkFavoriteReplyOnAnyTopic', + 'whoCanMarkFavoriteReplyOnOwnTopic', + 'whoCanMarkNoResponseNeeded', + 'whoCanModifyTagsAndCategories', + 'whoCanTakeTopics', + 'whoCanUnassignTopic', + 'whoCanUnmarkFavoriteReplyOnAnyTopic', + 'favoriteRepliesOnTop', + ] -COLLABORATIVE_INBOX_ATTRIBUTES = { - 'whoCanAddReferences': 'acl', - 'whoCanAssignTopics': 'acl', - 'whoCanEnterFreeFormTags': 'acl', - 'whoCanMarkDuplicate': 'acl', - 'whoCanMarkFavoriteReplyOnAnyTopic': 'acl', - 'whoCanMarkFavoriteReplyOnOwnTopic': 'acl', - 'whoCanMarkNoResponseNeeded': 'acl', - 'whoCanModifyTagsAndCategories': 'acl', - 'whoCanTakeTopics': 'acl', - 'whoCanUnassignTopic': 'acl', - 'whoCanUnmarkFavoriteReplyOnAnyTopic': 'acl', - 'favoriteRepliesOnTop': True, - } +GROUP_SETTINGS_LIST_ATTRIBUTES = set([ + # ACL choices + 'whoCanAdd', + 'whoCanApproveMembers', + 'whoCanApproveMessages', + 'whoCanAssignTopics', + 'whoCanAssistContent', + 'whoCanBanUsers', + 'whoCanContactOwner', + 'whoCanDeleteAnyPost', + 'whoCanDeleteTopics', + 'whoCanDiscoverGroup', + 'whoCanEnterFreeFormTags', + 'whoCanHideAbuse', + 'whoCanInvite', + 'whoCanJoin', + 'whoCanLeaveGroup', + 'whoCanLockTopics', + 'whoCanMakeTopicsSticky', + 'whoCanMarkDuplicate', + 'whoCanMarkFavoriteReplyOnAnyTopic', + 'whoCanMarkFavoriteReplyOnOwnTopic', + 'whoCanMarkNoResponseNeeded', + 'whoCanModerateContent', + 'whoCanModerateMembers', + 'whoCanModifyMembers', + 'whoCanModifyTagsAndCategories', + 'whoCanMoveTopicsIn', + 'whoCanMoveTopicsOut', + 'whoCanPostAnnouncements', + 'whoCanPostMessage', + 'whoCanTakeTopics', + 'whoCanUnassignTopic', + 'whoCanUnmarkFavoriteReplyOnAnyTopic', + 'whoCanViewGroup', + 'whoCanViewMembership', + # Miscellaneous hoices + 'messageModerationLevel', + 'replyTo', + 'spamModerationLevel', + ]) +GROUP_SETTINGS_BOOLEAN_ATTRIBUTES = set([ + 'allowExternalMembers', + 'allowGoogleCommunication', + 'allowWebPosting', + 'archiveOnly', + 'enableCollaborativeInbox', + 'favoriteRepliesOnTop', + 'includeCustomFooter', + 'includeInGlobalAddressList', + 'isArchived', + 'membersCanPostAsTheGroup', + 'sendMessageDenyNotification', + 'showInGroupDirectory', + ]) # # Global variables