diff --git a/.travis.yml b/.travis.yml index 90cbb6ab..900e2a5b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,8 @@ env: - BUILD_OPENSSL_VERSION=1.1.1k - MIN_OPENSSL_VERSION=1.1.1k - PATCHELF_VERSION=0.12 - - PYINSTALLER_COMMIT=e5dbb051bd3d53d6c2c70cbd87270eec1765da2e +# PYINSTALLER_VERSION can be full commit hash or version like v4.20 + - PYINSTALLER_VERSION=000275e409640320cdd995a7f077abfdece86749 cache: directories: diff --git a/src/GamCommands.txt b/src/GamCommands.txt index df1bed5f..09b8a59c 100644 --- a/src/GamCommands.txt +++ b/src/GamCommands.txt @@ -2860,7 +2860,7 @@ gam show licenses [(products|product )|(skus|sku )|all ::= devices/ ::= devices//deviceUsers/ - ::= android|chrome_os|google_sync|linux|mac_os|windows + ::= android|chrome_os|google_sync|ios|linux|mac_os|windows ::= androidspecificattributes| @@ -3119,26 +3119,26 @@ gam show orgtree [fromparent ] [batchsuborgs []] gam delete domaincontacts gam info domaincontacts - [allfields|(fields )] + [allfields|(fields )] [showmetadata] [formatjson] gam print domaincontacts [todrive *] [query ] [mergesources ] - [allfields|(fields )] + [allfields|(fields )] [showmetadata] [formatjson [quotechar ]] gam show domaincontacts [query ] [mergesources ] - [allfields|(fields )] + [allfields|(fields )] [showmetadata] [formatjson] gam info domainprofiles - [allfields|(fields )] + [allfields|(fields )] [showmetadata] [formatjson] gam print domainprofiles [todrive *] [query ] [mergesources ] - [allfields|(fields )] + [allfields|(fields )] [showmetadata] [formatjson [quotechar ]] gam show domainprofiles [query ] @@ -3150,13 +3150,13 @@ gam print people [todrive *] [sources ] [query ] [mergesources ] - [allfields|(fields )] + [allfields|(fields )] [showmetadata] [formatjson [quotechar ]] gam show people [sources ] [query ] [mergesources ] - [allfields|(fields )] + [allfields|(fields )] [showmetadata] [formatjson] # Printers @@ -5440,7 +5440,9 @@ gam update sendas [name ] gam delete sendas gam info sendas [compact|format|html] gam show sendas [compact|format|html] -gam print sendas [compact] [todrive *] + [primary] [default] [verifyonly] +gam print sendas [compact] + [primary] [default] [verifyonly] [todrive *] gam create|add smime file [password ] [sendas|sendasemail ] [default] @@ -5458,8 +5460,10 @@ gam signature|sig (replace )* [html []] [name ] [replyto ] [default] [primary] [treatasalias ] -gam show signature|sig [compact|format|html] [primary] -gam print signature [compact] [todrive *] +gam show signature|sig [compact|format|html] + [primary] [default] [verifyonly] +gam print signature [compact] + [primary] [default] [verifyonly] [todrive *] gam vacation subject [message|htmlmessage |(file|htmlfile [charset ])|(gdoc|ghtml )] @@ -5564,47 +5568,47 @@ gam sync license [product|productid ] [addon ::= "(,)*" gam show peopleprofiles - [allfields|(fields )] + [allfields|(fields )] [showmetadata] [formatjson] gam print peopleprofiles [todrive *] - [allfields|(fields )] + [allfields|(fields )] [showmetadata] [formatjson [quotechar ]] gam show othercontacts [query ] - [allfields|(fields )] + [allfields|(fields )] [showmetadata] [formatjson] gam print othercontacts [todrive *] [query ] - [allfields|(fields )] + [allfields|(fields )] [showmetadata] [formatjson [quotechar ]] gam delete peoplecontacts gam info peoplecontacts - [allfields|(fields )] + [allfields|(fields )] [showmetadata] [formatjson] gam show peoplecontacts [query ] [orderby firstname|lastname|(lastmodified ascending)|(lastnodified descending) - [allfields|(allfields|(fields ))] + [allfields|(allfields|(fields ))] [showmetadata] [formatjson] gam print peoplecontacts [todrive *] [query ] [orderby firstname|lastname|(lastmodified ascending)|(lastnodified descending) - [allfields|(allfields|(fields ))] + [allfields|(allfields|(fields ))] [showmetadata] [formatjson [quotechar ]] gam print people [todrive *] [sources ] [query ] [mergesources ] - [allfields|(fields )] + [allfields|(fields )] [showmetadata] [formatjson [quotechar ]] gam show people [sources ] [query ] [mergesources ] - [allfields|(fields )] + [allfields|(fields )] [showmetadata] [formatjson] ::= diff --git a/src/GamUpdate.txt b/src/GamUpdate.txt index 8aeaa928..b584c3df 100644 --- a/src/GamUpdate.txt +++ b/src/GamUpdate.txt @@ -1,3 +1,43 @@ +6.04.07 + +Changed `config csv_input_row_filter|csv_output_row_filter` processing of blank fields +for `count` and `boolean:`. Previously, a blank field was +always treated a mismatch; now a blank field will be interpreted as `False` for `` +and `0` for `` and the match test will be performed. + +Added options `primary` and `default`` to the following commands +so that only the primary and/or default signature/sendas is displayed +rather than all signatures/sendas. + +Added option `verifyonly` to the following commands; it causes a Boolean +to be displayed in the `signature` field rather that the signature text; +this simplifies checking for users with undefined signatures. +``` +gam show sendas [compact|format|html] + [primary] [default] [verifyonly] +gam print sendas [compact] + [primary] [default] [verifyonly] [todrive *] +gam show signature|sig [compact|format|html] + [primary] [default] [verifyonly] +gam print signature [compact] + [primary] [default] [verifyonly] [todrive *] +``` + +For example, this command will display a list of users without a primary email address signature (wrapped for readability): +``` +gam config csv_output_row_filter "signature:boolean:false" csv_output_header_filter "User,displayName,signature" + auto_batch_min 1 num_threads 10 redirect csv ./NoPrimarySignature.txt multiprocess + all users print signature primary verifyonly + +Explanation: +config csv_output_row_filter "signature:boolean:false" - Output rows that indicate no signature +csv_output_header_filter "User,displayName,signature" - Output basic headers +auto_batch_min 1 num_threads 10 - Turn on parallel processing +redirect csv ./NoPrimarySignature.txt multiprocess - Intelligently combine output from all processes +all users - Process all non-suspended users +print signature primary verifyonly - Display state of primary email address signature +``` + 6.04.06 Eliminated `ios` from ` ::= android|chrome_os|google_sync|linux|mac_os|windows` diff --git a/src/gam/__init__.py b/src/gam/__init__.py index 4205ca9c..ac3d9e7f 100755 --- a/src/gam/__init__.py +++ b/src/gam/__init__.py @@ -23,7 +23,7 @@ """ __author__ = 'Ross Scroggs ' -__version__ = '6.04.06' +__version__ = '6.04.07' __license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)' import base64 @@ -6235,7 +6235,10 @@ def checkMatch(rowDate): def rowCountFilterMatch(op, filterCount): def checkMatch(rowCount): if isinstance(rowCount, str): - if not rowCount.isdigit(): +##### Blank = 0 + if not rowCount: + rowCount = '0' + elif not rowCount.isdigit(): return False rowCount = int(rowCount) elif not isinstance(rowCount, int): @@ -6288,8 +6291,12 @@ def rowBooleanFilterMatch(filterBoolean): def checkMatch(rowBoolean): if isinstance(rowBoolean, bool): return rowBoolean == filterBoolean - if isinstance(rowBoolean, str) and rowBoolean.lower() in TRUE_FALSE: - return rowBoolean.capitalize() == str(filterBoolean) + if isinstance(rowBoolean, str): + if rowBoolean.lower() in TRUE_FALSE: + return rowBoolean.capitalize() == str(filterBoolean) +##### Blank = False + if not rowBoolean: + return not filterBoolean return False if anyMatch: @@ -7371,7 +7378,8 @@ def _flatten(structure, key, path): # Show a json object def showJSON(showName, showValue, skipObjects=None, timeObjects=None, - simpleLists=None, dictObjectsKey=None, sortDictKeys=True): + simpleLists=None, dictObjectsKey=None, sortDictKeys=True, + noIndents=False): def _show(objectName, objectValue, subObjectKey, level): if objectName in allSkipObjects: return @@ -7403,7 +7411,7 @@ def _show(objectName, objectValue, subObjectKey, level): if objectName is not None: printBlankLine() Ind.Increment() - elif level > 0: + elif (level > 0) and not noIndents: indentAfterFirst = unindentAfterLast = True subObjects = sorted(objectValue) if sortDictKeys else objectValue.keys() if subObjectKey and (subObjectKey in subObjects): @@ -33586,16 +33594,31 @@ def doDeletePeopleDomainContacts(): def deletePeopleContacts(users): _deletePeople(users, Ent.USER) -def _showPerson(userEntityType, user, entityType, person, i, count, FJQC): +def _stripPersonMetadata(person): + metadata = person.pop('metadata', None) + if metadata is not None and 'primary' in metadata: + person['primary'] = metadata['primary'] + for _, v in iter(person.items()): + if isinstance(v, list): + for entry in v: + metadata = entry.pop('metadata', None) + if metadata is not None and 'primary' in metadata: + entry['primary'] = metadata['primary'] + +def _showPerson(userEntityType, user, entityType, person, i, count, FJQC, stripMetadata): + if stripMetadata: + _stripPersonMetadata(person) if not FJQC.formatJSON: printEntity([userEntityType, user, entityType, person['resourceName']], i, count) Ind.Increment() - showJSON(None, person) + showJSON(None, person, noIndents=True) Ind.Decrement() else: printLine(json.dumps(cleanJSON(person), ensure_ascii=False, sort_keys=True)) -def _printPerson(entityTypeName, user, person, csvPF, FJQC): +def _printPerson(entityTypeName, user, person, csvPF, FJQC, stripMetadata): + if stripMetadata: + _stripPersonMetadata(person) row = flattenJSON(person, flattened={entityTypeName: user}) if not FJQC.formatJSON: csvPF.WriteRowTitles(row) @@ -33604,7 +33627,7 @@ def _printPerson(entityTypeName, user, person, csvPF, FJQC): 'JSON': json.dumps(cleanJSON(person), ensure_ascii=False, sort_keys=True)}) -def _printPersonEntityList(entityType, entityList, userEntityType, user, i, count, csvPF, FJQC): +def _printPersonEntityList(entityType, entityList, userEntityType, user, i, count, csvPF, FJQC, stripMetadata): if not csvPF: jcount = len(entityList) if not FJQC.formatJSON: @@ -33613,17 +33636,19 @@ def _printPersonEntityList(entityType, entityList, userEntityType, user, i, coun j = 0 for person in entityList: j += 1 - _showPerson(userEntityType, user, entityType, person, j, jcount, FJQC) + _showPerson(userEntityType, user, entityType, person, j, jcount, FJQC, stripMetadata) Ind.Decrement() else: entityTypeName = Ent.Singular(userEntityType) for person in entityList: - _printPerson(entityTypeName, user, person, csvPF, FJQC) + _printPerson(entityTypeName, user, person, csvPF, FJQC, stripMetadata) PEOPLE_FIELDS_CHOICE_MAP = { 'addresses': 'addresses', 'ageranges': 'ageRanges', + 'biography': 'biographies', 'biographies': 'biographies', + 'birthday': 'birthdays', 'birthdays': 'birthdays', 'calendarurls': 'calendarUrls', 'clientdata': 'clientData', @@ -33631,6 +33656,7 @@ def _printPersonEntityList(entityType, entityList, userEntityType, user, i, coun 'emailaddresses': 'emailAddresses', 'events': 'events', 'externalids': 'externalIds', + 'gender': 'genders', 'genders': 'genders', 'imclients': 'imClients', 'interests': 'interests', @@ -33639,6 +33665,7 @@ def _printPersonEntityList(entityType, entityList, userEntityType, user, i, coun 'memberships': 'memberships', 'metadata': 'metadata', 'misckeywords': 'miscKeywords', + 'name': 'names', 'names': 'names', 'nicknames': 'nicknames', 'occupations': 'occupations', @@ -33653,16 +33680,17 @@ def _printPersonEntityList(entityType, entityList, userEntityType, user, i, coun } # gam print peopleprofile [todrive *] -# [allfields|(fields )] +# [allfields|(fields )] [showmetadata] # [formatjson [quotechar ]] # gam show peopleprofile -# [allfields|(fields )] +# [allfields|(fields )] [showmetadata] # [formatjson] def printShowPeopleProfile(users): entityType = Ent.USER entityTypeName = Ent.Singular(entityType) csvPF = CSVPrintFile([entityTypeName, 'resourceName']) if Act.csvFormat() else None FJQC = FormatJSONQuoteChar(csvPF) + stripMetadata = True fieldsList = [] while Cmd.ArgumentsRemaining(): myarg = getArgument() @@ -33673,6 +33701,8 @@ def printShowPeopleProfile(users): addFieldToFieldsList(field, PEOPLE_FIELDS_CHOICE_MAP, fieldsList) elif getFieldsList(myarg, PEOPLE_FIELDS_CHOICE_MAP, fieldsList): pass + elif myarg == 'showmetadata': + stripMetadata = False elif myarg == 'peoplelookupuser': deprecatedArgument(myarg) else: @@ -33696,9 +33726,9 @@ def printShowPeopleProfile(users): except (GAPI.serviceNotAvailable, GAPI.forbidden, GAPI.permissionDenied): ClientAPIAccessDeniedExit() if not csvPF: - _showPerson(entityType, user, Ent.PEOPLE_PROFILE, result, i, count, FJQC) + _showPerson(entityType, user, Ent.PEOPLE_PROFILE, result, i, count, FJQC, stripMetadata) else: - _printPerson(entityTypeName, user, result, csvPF, FJQC) + _printPerson(entityTypeName, user, result, csvPF, FJQC, stripMetadata) if csvPF: csvPF.writeCSVfile('People Profiles') @@ -33733,6 +33763,7 @@ def _infoPeople(users, entityType, source): entityList = getEntityList(Cmd.OB_CONTACT_ENTITY) resourceNameLists = entityList if isinstance(entityList, dict) else None FJQC = FormatJSONQuoteChar() + stripMetadata = True fieldsList = [] while Cmd.ArgumentsRemaining(): myarg = getArgument() @@ -33741,6 +33772,8 @@ def _infoPeople(users, entityType, source): addFieldToFieldsList(field, PEOPLE_FIELDS_CHOICE_MAP, fieldsList) elif getFieldsList(myarg, PEOPLE_FIELDS_CHOICE_MAP, fieldsList): pass + elif myarg == 'showmetadata': + stripMetadata = False else: FJQC.GetFormatJSON(myarg) personFields = ','.join(set(fieldsList)) if fieldsList else 'names,emailAddresses' @@ -33773,50 +33806,59 @@ def _infoPeople(users, entityType, source): continue except (GAPI.serviceNotAvailable, GAPI.forbidden, GAPI.permissionDenied): ClientAPIAccessDeniedExit() - _showPerson(entityType, user, peopleEntityType, result, j, jcount, FJQC) + _showPerson(entityType, user, peopleEntityType, result, j, jcount, FJQC, stripMetadata) Ind.Decrement() # gam info domaincontacts -# [allfields|(fields )] [formatjson] +# [allfields|(fields )] [showmetadata] +# [formatjson] def doInfoPeopleDomainContacts(): _infoPeople([GC.Values[GC.DOMAIN]], Ent.DOMAIN, 'domaincontact') # gam info domainprofiles -# [allfields|(fields )] [formatjson] +# [allfields|(fields )] [showmetadata] +# [formatjson] def doInfoPeopleDomainProfiles(): _infoPeople([GC.Values[GC.DOMAIN]], Ent.DOMAIN, 'profile') # gam info peoplecontacts -# [allfields|(fields )] [formatjson] +# [allfields|(fields )] [showmetadata] +# [formatjson] def infoPeopleContacts(users): _infoPeople(users, Ent.USER, 'contact') # gam print domaincontacts [todrive *] # [query ] # [mergesources ] -# [allfields|(fields )] [formatjson [quotechar ]] +# [allfields|(fields )] [showmetadata] +# [formatjson [quotechar ]] # gam show domaincontacts # [query ] # [mergesources ] -# [allfields|(fields )] [formatjson] +# [allfields|(fields )] [showmetadata] +# [formatjson] # gam print domainprofiles [todrive *] # [query ] # [mergesources ] -# [allfields|(fields )] [formatjson [quotechar ]] +# [allfields|(fields )] [showmetadata] +# [formatjson [quotechar ]] # gam show domainprofiles # [query ] # [mergesources ] -# [allfields|(fields )] [formatjson] +# [allfields|(fields )] [showmetadata] +# [formatjson] # gam [] print people [todrive *] # [sources ] # [query ] # [mergesources ] -# [allfields|(fields )] [formatjson [quotechar ]] +# [allfields|(fields )] [showmetadata] +# formatjson [quotechar ]] # gam [] show people # [sources ] # [query ] # [mergesources ] -# [allfields|(fields )] [formatjson] +# [allfields|(fields )] [showmetadata] +# [formatjson] def _printShowPeople(users, entityType, source): if entityType == Ent.DOMAIN: people = buildGAPIObject(API.PEOPLE_DIRECTORY) @@ -33824,6 +33866,7 @@ def _printShowPeople(users, entityType, source): function = 'listDirectoryPeople' csvPF = CSVPrintFile([entityTypeName, 'resourceName']) if Act.csvFormat() else None FJQC = FormatJSONQuoteChar(csvPF) + stripMetadata = True sources = [] if source is None else [PEOPLE_DIRECTORY_SOURCES_CHOICE_MAP[source]] mergeSources = [] fieldsList = [] @@ -33836,11 +33879,15 @@ def _printShowPeople(users, entityType, source): sources = [getChoice(PEOPLE_DIRECTORY_SOURCES_CHOICE_MAP, mapChoice=True)] elif myarg in {'mergesource', 'mergesources'}: mergeSources = [getChoice(PEOPLE_DIRECTORY_MERGE_SOURCES_CHOICE_MAP, mapChoice=True)] + elif myarg == 'showmetadata': + stripMetadata = False elif myarg == 'allfields': for field in PEOPLE_FIELDS_CHOICE_MAP: addFieldToFieldsList(field, PEOPLE_FIELDS_CHOICE_MAP, fieldsList) elif getFieldsList(myarg, PEOPLE_FIELDS_CHOICE_MAP, fieldsList): pass + elif myarg == 'showmetadata': + stripMetadata = False elif myarg == 'query': kwargs['query'] = getString(Cmd.OB_QUERY) function = 'searchDirectoryPeople' @@ -33871,7 +33918,7 @@ def _printShowPeople(users, entityType, source): readMask=fields, fields='nextPageToken,people', **kwargs) except (GAPI.serviceNotAvailable, GAPI.forbidden, GAPI.permissionDenied): ClientAPIAccessDeniedExit() - _printPersonEntityList(peopleEntityType, entityList, entityType, user, i, count, csvPF, FJQC) + _printPersonEntityList(peopleEntityType, entityList, entityType, user, i, count, csvPF, FJQC, stripMetadata) if csvPF: csvPF.writeCSVfile(CSVTitle) @@ -33896,16 +33943,19 @@ def doPrintShowPeopleDomainProfiles(): # gam print peoplecontacts [todrive *] # [query ] # [orderby firstname|lastname|(lastmodified ascending)|(lastnodified descending) -# [allfields|(fields )] [formatjson [quotechar ]] +# [allfields|(fields )] [showmetadata] +# [formatjson [quotechar ]] # gam show peoplecontacts # [query ] # [orderby firstname|lastname|(lastmodified ascending)|(lastnodified descending) -# [allfields|(fields )] [formatjson] +# [allfields|(fields )] [showmetadata] +# [formatjson] def printShowUserPeopleContacts(users): entityType = Ent.USER entityTypeName = Ent.Singular(entityType) csvPF = CSVPrintFile([entityTypeName, 'resourceName']) if Act.csvFormat() else None FJQC = FormatJSONQuoteChar(csvPF) + stripMetadata = True sources = [PEOPLE_READ_SOURCES_CHOICE_MAP['contact']] fieldsList = [] query = None @@ -33919,6 +33969,8 @@ def printShowUserPeopleContacts(users): addFieldToFieldsList(field, PEOPLE_FIELDS_CHOICE_MAP, fieldsList) elif getFieldsList(myarg, PEOPLE_FIELDS_CHOICE_MAP, fieldsList): pass + elif myarg == 'showmetadata': + stripMetadata = False elif myarg == 'query': query = getString(Cmd.OB_QUERY) elif myarg == 'orderby': @@ -33949,7 +34001,7 @@ def printShowUserPeopleContacts(users): entityList = results.get('results', []) except (GAPI.serviceNotAvailable, GAPI.forbidden, GAPI.permissionDenied): ClientAPIAccessDeniedExit() - _printPersonEntityList(Ent.PEOPLE_CONTACT, entityList, entityType, user, i, count, csvPF, FJQC) + _printPersonEntityList(Ent.PEOPLE_CONTACT, entityList, entityType, user, i, count, csvPF, FJQC, stripMetadata) if csvPF: csvPF.writeCSVfile('People Contacts') @@ -33961,15 +34013,18 @@ def printShowUserPeopleContacts(users): # gam print othercontacts [todrive *] # [query ] -# [fields ] [formatjson [quotechar ]] +# [fields ] [showmetadata] +# [formatjson [quotechar ]] # gam show othercontacts # [query ] -# [fields ] [formatjson] +# [fields ] [showmetadata] +# [formatjson] def printShowUserOtherContacts(users): entityType = Ent.USER entityTypeName = Ent.Singular(entityType) csvPF = CSVPrintFile([entityTypeName, 'resourceName']) if Act.csvFormat() else None FJQC = FormatJSONQuoteChar(csvPF) + stripMetadata = True fieldsList = [] query = None while Cmd.ArgumentsRemaining(): @@ -33981,6 +34036,8 @@ def printShowUserOtherContacts(users): addFieldToFieldsList(field, PEOPLE_OTHER_CONTACTS_FIELDS_CHOICE_MAP, fieldsList) elif getFieldsList(myarg, PEOPLE_OTHER_CONTACTS_FIELDS_CHOICE_MAP, fieldsList): pass + elif myarg == 'showmetadata': + stripMetadata = False elif myarg == 'query': query = getString(Cmd.OB_QUERY) else: @@ -34006,7 +34063,7 @@ def printShowUserOtherContacts(users): entityList = results.get('results', []) except (GAPI.serviceNotAvailable, GAPI.forbidden, GAPI.permissionDenied): ClientAPIAccessDeniedExit() - _printPersonEntityList(Ent.OTHER_CONTACT, entityList, entityType, user, i, count, csvPF, FJQC) + _printPersonEntityList(Ent.OTHER_CONTACT, entityList, entityType, user, i, count, csvPF, FJQC, stripMetadata) if csvPF: csvPF.writeCSVfile('Other Contacts') @@ -34019,14 +34076,17 @@ def printShowUserOtherContacts(users): } # gam print peoplecontactgroups [todrive *] -# [fields ] [formatjson [quotechar ]] +# [fields ] [showmetadata] +# [formatjson [quotechar ]] # gam show peoplacontactgroups -# [fields ] [formatjson] +# [fields ] [showmetadata] +# [formatjson] def printShowUserPeopleContactGroups(users): entityType = Ent.USER entityTypeName = Ent.Singular(entityType) csvPF = CSVPrintFile([entityTypeName, 'resourceName']) if Act.csvFormat() else None FJQC = FormatJSONQuoteChar(csvPF) + stripMetadata = True fieldsList = [] while Cmd.ArgumentsRemaining(): myarg = getArgument() @@ -34037,6 +34097,8 @@ def printShowUserPeopleContactGroups(users): addFieldToFieldsList(field, PEOPLE_CONTACTGROUPS_FIELDS_CHOICE_MAP, fieldsList) elif getFieldsList(myarg, PEOPLE_CONTACTGROUPS_FIELDS_CHOICE_MAP, fieldsList): pass + elif myarg == 'showmetadata': + stripMetadata = False else: FJQC.GetFormatJSONQuoteChar(myarg, True) fields = ','.join(set(fieldsList)) if fieldsList else None @@ -34054,7 +34116,7 @@ def printShowUserPeopleContactGroups(users): groupFields=fields, fields='nextPageToken,contactGroups') except (GAPI.serviceNotAvailable, GAPI.forbidden, GAPI.permissionDenied): ClientAPIAccessDeniedExit() - _printPersonEntityList(Ent.PEOPLE_CONTACTGROUP, entityList, entityType, user, i, count, csvPF, FJQC) + _printPersonEntityList(Ent.PEOPLE_CONTACTGROUP, entityList, entityType, user, i, count, csvPF, FJQC, stripMetadata) if csvPF: csvPF.writeCSVfile('People Contact Groupss') @@ -53130,7 +53192,7 @@ def printShowLanguage(users): SIG_REPLY_OPTIONS_MAP = {'html': SIG_REPLY_HTML, 'compact': SIG_REPLY_COMPACT, 'format': SIG_REPLY_FORMAT} SMTPMSA_DISPLAY_FIELDS = ['host', 'port', 'securityMode'] -def _showSendAs(result, j, jcount, sigReplyFormat): +def _showSendAs(result, j, jcount, sigReplyFormat, verifyOnly=False): if result['displayName']: printEntity([Ent.SENDAS_ADDRESS, f'{result["displayName"]} <{result["sendAsEmail"]}>'], j, jcount) else: @@ -53149,9 +53211,9 @@ def _showSendAs(result, j, jcount, sigReplyFormat): if 'verificationStatus' in result: printKeyValueList(['Verification Status', result['verificationStatus']]) signature = result.get('signature') - if not signature: - signature = 'None' - if sigReplyFormat == SIG_REPLY_HTML: + if verifyOnly: + printKeyValueList(['Signature', bool(signature)]) + elif sigReplyFormat == SIG_REPLY_HTML: printKeyValueList(['Signature', None]) Ind.Increment() printKeyValueList([Ind.MultiLineText(signature)]) @@ -53175,7 +53237,8 @@ def _processSignature(tagReplacements, signature, html): return signature # Process SendAs functions -def _processSendAs(user, i, count, entityType, emailAddress, j, jcount, gmail, function, sigReplyFormat, **kwargs): +def _processSendAs(user, i, count, entityType, emailAddress, j, jcount, gmail, function, + sigReplyFormat, verifyOnly=False, **kwargs): userDefined = True try: result = callGAPI(gmail.users().settings().sendAs(), function, @@ -53184,7 +53247,7 @@ def _processSendAs(user, i, count, entityType, emailAddress, j, jcount, gmail, f GAPI.FAILED_PRECONDITION], userId='me', **kwargs) if function == 'get': - _showSendAs(result, j, jcount, sigReplyFormat) + _showSendAs(result, j, jcount, sigReplyFormat, verifyOnly) else: entityActionPerformed([Ent.USER, user, entityType, emailAddress], j, jcount) except (GAPI.notFound, GAPI.alreadyExists, GAPI.duplicate, @@ -53307,17 +53370,26 @@ def deleteInfoSendAs(users): break Ind.Decrement() -# gam print sendas [compact] [todrive *] +# gam print sendas [compact] +# [default] [primary] [verifyonly] [todrive *] # gam show sendas [compact|format|html] +# [default] [primary] [verifyonly] def printShowSendAs(users): csvPF = CSVPrintFile(['User', 'displayName', 'sendAsEmail', 'replyToAddress', 'isPrimary', 'isDefault', 'treatAsAlias', 'verificationStatus'], 'sortall') if Act.csvFormat() else None sigReplyFormat = SIG_REPLY_HTML + default = primary = verifyOnly = False while Cmd.ArgumentsRemaining(): myarg = getArgument() if csvPF and myarg == 'todrive': csvPF.GetTodriveParameters() + elif myarg == 'primary': + primary = True + elif myarg == 'default': + default = True + elif myarg == 'verifyonly': + verifyOnly = True elif (not csvPF and myarg in SIG_REPLY_OPTIONS_MAP) or (csvPF and myarg == 'compact'): sigReplyFormat = SIG_REPLY_OPTIONS_MAP[myarg] else: @@ -53339,24 +53411,35 @@ def printShowSendAs(users): j = 0 for sendas in results: j += 1 - _showSendAs(sendas, j, jcount, sigReplyFormat) + if ((not primary and not default) or + (primary and sendas.get('isPrimary', False)) or + (default and sendas.get('isDefault', False))): + _showSendAs(sendas, j, jcount, sigReplyFormat, verifyOnly) Ind.Decrement() else: printGettingEntityItemForWhom(Ent.SENDAS_ADDRESS, user, i, count) if results: for sendas in results: - row = {'User': user, 'isPrimary': False} - for item in sendas: - if item != 'smtpMsa': - if item != 'signature' or sigReplyFormat != SIG_REPLY_COMPACT: - row[item] = sendas[item] + if ((not primary and not default) or + (primary and sendas.get('isPrimary', False)) or + (default and sendas.get('isDefault', False))): + row = {'User': user, 'isPrimary': False} + for item in sendas: + if item != 'smtpMsa': + if item == 'signature': + if verifyOnly: + row[item] = bool(sendas[item]) + elif sigReplyFormat != SIG_REPLY_COMPACT: + row[item] = sendas[item] + else: + row[item] = sendas[item].replace('\r', '').replace('\n', '') + else: + row[item] = sendas[item] else: - row[item] = sendas[item].replace('\r', '').replace('\n', '') - else: - for field in SMTPMSA_DISPLAY_FIELDS: - if field in sendas[item]: - row[f'smtpMsa.{field}'] = sendas[item][field] - csvPF.WriteRowTitles(row) + for field in SMTPMSA_DISPLAY_FIELDS: + if field in sendas[item]: + row[f'smtpMsa.{field}'] = sendas[item][field] + csvPF.WriteRowTitles(row) elif GC.Values[GC.CSV_OUTPUT_USERS_AUDIT]: csvPF.WriteRowNoFilter({'User': user}) except (GAPI.serviceNotAvailable, GAPI.badRequest): @@ -53648,14 +53731,18 @@ def setSignature(users): else: _processSendAs(user, i, count, Ent.SIGNATURE, user, i, count, gmail, 'patch', False, body=body, sendAsEmail=user, fields='') -# gam show signature|sig [compact|format|html] [primary] +# gam show signature|sig [compact|format|html] [primary] [default] [verifyonly] def showSignature(users): sigReplyFormat = SIG_REPLY_HTML - primary = False + default = primary = verifyOnly = False while Cmd.ArgumentsRemaining(): myarg = getArgument() if myarg == 'primary': primary = True + elif myarg == 'default': + default = True + elif myarg == 'verifyonly': + verifyOnly = True elif myarg in SIG_REPLY_OPTIONS_MAP: sigReplyFormat = SIG_REPLY_OPTIONS_MAP[myarg] else: @@ -53666,7 +53753,7 @@ def showSignature(users): user, gmail = buildGAPIServiceObject(API.GMAIL, user, i, count) if not gmail: continue - if primary: + if primary or default: try: result = callGAPI(gmail.users().settings().sendAs(), 'list', throwReasons=GAPI.GMAIL_THROW_REASONS, @@ -53674,14 +53761,17 @@ def showSignature(users): printEntity([Ent.USER, user, Ent.SIGNATURE, ''], i, count) Ind.Increment() for sendas in result['sendAs']: - if sendas.get('isPrimary', False): - _showSendAs(sendas, 0, 0, sigReplyFormat) + if ((not primary and not default) or + (primary and sendas.get('isPrimary', False)) or + (default and sendas.get('isDefault', False))): + _showSendAs(sendas, 0, 0, sigReplyFormat, verifyOnly) break Ind.Decrement() except (GAPI.serviceNotAvailable, GAPI.badRequest): entityServiceNotApplicableWarning(Ent.USER, user, i, count) else: - _processSendAs(user, i, count, Ent.SIGNATURE, user, i, count, gmail, 'get', sigReplyFormat, sendAsEmail=user) + _processSendAs(user, i, count, Ent.SIGNATURE, user, i, count, gmail, 'get', + sigReplyFormat, verifyOnly=verifyOnly, sendAsEmail=user) VACATION_START_STARTED = 'Started' VACATION_END_NOT_SPECIFIED = 'NotSpecified' diff --git a/src/gam/gamlib/glentity.py b/src/gam/gamlib/glentity.py index 6ad9dbbc..d74b0b33 100644 --- a/src/gam/gamlib/glentity.py +++ b/src/gam/gamlib/glentity.py @@ -216,7 +216,7 @@ class GamEntity(): PARENT_ORGANIZATIONAL_UNIT = 'porg' PARTICIPANT = 'part' PEOPLE_CONTACT = 'peco' - PEOPLE_CONTACTGROUP = 'pecg' + PEOPLE_CONTACT_GROUP = 'pecg' PEOPLE_PHOTO = 'peph' PEOPLE_PROFILE = 'pepr' PERMISSION = 'perm' @@ -481,7 +481,7 @@ class GamEntity(): PARENT_ORGANIZATIONAL_UNIT: ['Parent Organizational Units', 'Parent Organizational Unit'], PARTICIPANT: ['Participants', 'Participant'], PEOPLE_CONTACT: ['People Contacts', 'Person Contact'], - PEOPLE_CONTACTGROUP: ['People Contact Groups', 'People Contact Group'], + PEOPLE_CONTACT_GROUP: ['People Contact Groups', 'People Contact Group'], PEOPLE_PHOTO: ['People Photos', 'Person Photo'], PEOPLE_PROFILE: ['People Profiles', 'People Profile'], PERMISSION: ['Permissions', 'Permission'], diff --git a/src/gam/gamlib/glmsgs.py b/src/gam/gamlib/glmsgs.py index 7cd788bb..08ae19be 100644 --- a/src/gam/gamlib/glmsgs.py +++ b/src/gam/gamlib/glmsgs.py @@ -268,6 +268,7 @@ MISSING_FIELDS = 'Missing fields: {0}\n' MULTIPLE_BUILDINGS_SAME_NAME = '{0} {1} with the same (case-insensitive) name exist' MULTIPLE_ENTITIES_FOUND = 'Multiple {0} ({1}) found, {2}' +MULTIPLE_ITEMS_SPECIFIED = 'Multiple {0} are specfied, only one is allowed' MULTIPLE_ITEMS_MARKED_PRIMARY = 'Multiple {0} are marked primary, only one can be primary' MULTIPLE_PARENTS_SPECIFIED = 'Multiple parents ({0}) specified, only one is allowed' MULTIPLE_SEARCH_METHODS_SPECIFIED = 'Multiple search methods ({0}) specified, only one is allowed' diff --git a/src/travis/linux-before-install.sh b/src/travis/linux-before-install.sh index fe282423..96889bdd 100755 --- a/src/travis/linux-before-install.sh +++ b/src/travis/linux-before-install.sh @@ -119,7 +119,7 @@ else $pip install staticx fi - $pip install --upgrade git+git://github.com/pyinstaller/pyinstaller.git@$PYINSTALLER_COMMIT + $pip install --upgrade git+git://github.com/pyinstaller/pyinstaller.git@$PYINSTALLER_VERSION cd $whereibelong fi diff --git a/src/travis/macos-before-install.sh b/src/travis/macos-before-install.sh index c681dddd..73ea58a6 100755 --- a/src/travis/macos-before-install.sh +++ b/src/travis/macos-before-install.sh @@ -48,4 +48,4 @@ cd $whereibelong $pip install --upgrade pip $pip list --outdated --format=freeze | grep -v '^\-e' | cut -d = -f 1 | xargs -n1 $pip install -U $pip install --upgrade -r src/requirements.txt -$pip install --upgrade git+git://github.com/pyinstaller/pyinstaller.git@$PYINSTALLER_COMMIT +$pip install --upgrade git+git://github.com/pyinstaller/pyinstaller.git@$PYINSTALLER_VERSION diff --git a/src/travis/windows-before-install.sh b/src/travis/windows-before-install.sh index 07ce4b5f..67dc5b12 100755 --- a/src/travis/windows-before-install.sh +++ b/src/travis/windows-before-install.sh @@ -63,26 +63,22 @@ cd $mypath $pip install --upgrade pip $pip list --outdated --format=freeze | grep -v '^\-e' | cut -d = -f 1 | xargs -n1 $pip install -U $pip install --upgrade -r src/requirements.txt -$pip install --upgrade pyinstaller $pip install wheel -# Install PyInstaller from source and build bootloader -# to try and avoid getting flagged as malware since -# lots of malware uses PyInstaller default bootloader -# https://stackoverflow.com/questions/53584395/how-to-recompile-the-bootloader-of-pyinstaller -#echo "Downloading PyInstaller..." -#wget --quiet https://github.com/pyinstaller/pyinstaller/archive/$PYINSTALLER_COMMIT.tar.gz -#tar xf $PYINSTALLER_COMMIT.tar.gz -#mv pyinstaller-$PYINSTALLER_COMMIT pyinstaller -#cd pyinstaller/bootloader -#echo "bootloader before:" -#md5sum ../PyInstaller/bootloader/Windows-${BITS}bit/* -# -#$python ./waf all --target-arch=${BITS}bit --msvc_version "msvc 14.0" -# -#echo "bootloader after:" -#md5sum ../PyInstaller/bootloader/Windows-${BITS}bit/* -#echo "PATH: $PATH" -#cd .. -#$python setup.py install +export url="https://codeload.github.com/pyinstaller/pyinstaller/tar.gz/${PYINSTALLER_VERSION}" +echo "Downloading ${url}" +curl -o pyinstaller.tar.gz --compressed "${url}" +tar xf pyinstaller.tar.gz +cd "pyinstaller-${PYINSTALLER_VERSION}/" +# remove pre-compiled bootloaders so we fail if bootloader compile fails +rm -rf PyInstaller/bootloader/*bit +cd bootloader +if [ "${PLATFORM}" == "x86" ]; then + TARGETARCH="--target-arch=32bit" +else + TARGETARCH="" +fi +$python ./waf all $TARGETARCH +cd .. +$python setup.py install echo "cd to $mypath" cd $mypath