From 16636c34b5d66712e27e891dc209af6989b302d8 Mon Sep 17 00:00:00 2001 From: Ross Scroggs Date: Wed, 28 Mar 2018 15:01:49 -0700 Subject: [PATCH] Multiple fixes (#713) * - Update new create drive options * - Add gender, keyword, languages attributes to user --- src/GamCommands.txt | 66 ++++++++++++++++-------------- src/gam.py | 97 +++++++++++++++++++++++++++++++++++++-------- src/var.py | 30 +++++++++++++- 3 files changed, 144 insertions(+), 49 deletions(-) diff --git a/src/GamCommands.txt b/src/GamCommands.txt index a27f832c..54e8897f 100644 --- a/src/GamCommands.txt +++ b/src/GamCommands.txt @@ -436,6 +436,7 @@ Named items email|emails|otheremail|otheremails| externalids|externalid| familyname|firstname|fullname|givenname|lastname|name| + gender| id| ims|im| includeinglobaladdresslist|gal| @@ -444,6 +445,8 @@ Named items ismailboxsetup| isenforcedin2sv|is2svenforced| isenrolledin2sv|is2svenrolled| + keyword|keywords| + language|languages| lastlogintime| locations|location| noneditablealiases|aliases|nicknames| @@ -463,11 +466,18 @@ Named items familyname|lastname|givenname|firstname|email Named Lists -Lists can be in the following formats -Items, separated by commas, without spaces or commas in the items themselves: item(,item)* -Items, separated by spaces, without spaces or commas in the items themselves: "item( item)*" -Items, separated by commas, with spaces or commas in the items themselves: "'it em'(,'it,em')*" -Items, separated by spaces, with spaces or commas in the items themselves: "'it em'( 'it,em')*" + +Items in a list can be separated by commas or spaces; if an item itself contains a comma, a space or a single quote, special quoting must be used. +Typically, you will enclose the entire list in double quotes and quote each item in the list as detailed below. + +Items, separated by commas, without spaces, commas or single quotes in the items themselves + "item,item,item" +Items, separated by spaces, without spaces, commas or single quotes in the items themselves + "item item item" +Items, separated by commas, with spaces, commas or single quotes in the items themselves + "'it em','it,em',\"it'em\"" +Items, separated by spaces, with spaces, commas or single quotes in the items themselves + "'it em' 'it,em' \"it'em\"" ::= "(,)*" ::= "(,)*" @@ -507,29 +517,29 @@ Items, separated by spaces, with spaces or commas in the items themselves: "'it Specify a collection of ChromeOS devices by directly specifying them ::= - (all cros)| - (cros )| + (all cros)| + (cros )| Specify a collection of Users by directly specifying them or by specifiying items that will yield a list of users ::= - (all users)| - (user )| - (users )| - (group )| - (courseparticipants )| - (students )| - (teachers )| - (file )| - (csvfile :)| - (license|licenses|licence|licences )| - (query ) + (all users)| + (user )| + (users )| + (group )| + (courseparticipants )| + (students )| + (teachers )| + (file )| + (csvfile :)| + (license|licenses|licence|licences )| + (query ) Item attributes ::= - (buildingid )| (description )| (floors )| + (id )| (latitude )| (longitude )| (name ) @@ -621,7 +631,7 @@ Item attributes ::= (buildingid )| (capacity )| - (category other|room|conference_room)| + (category other|room|conference_room|unknown_category)| (description )| (features )| (floor )| @@ -646,8 +656,11 @@ Item attributes (externalid clear|(account|customer|login_id|network|organization| ))| (firstname|givenname )| (gal|includeinglobaladdresslist )| + (gender clear|(female|male|unknown|(other ) [addressmeas ]))| (im clear|(type work|home|other|(custom ) protocol aim|gtalk|icq|jabber|msn|net_meeting|qq|skype|yahoo|(custom_protocol ) [notprimary|primary]))| (ipwhitelisted )| + (keyword clear|(occupation|outlook|(custom ) ))| + (language clear|)| (lastname|familyname )| (location clear|(type default|desk| area [building|buildingid ] [floor|floorname ] [section|floorsection ] [desk|deskcode ] endlocation))| (note clear|([text_plain|text_html] |(file [charset ])))| @@ -821,13 +834,6 @@ Prints no header row and deviceId for specified CrOS devices. gam print cros ... basic|full Prints a header row and selected fields for specified CrOS devices. -Print a header row and selected fields for specified CrOS devices; deviceId is the always the first column. -If you specify allfields|basic|full, all other column headers are sorted. -If you specify * | fields , the column headers appear in the order specified. -gam [] print cros [todrive []] [query ]|[select ] [limittoou ] - [orderby [ascending|descending]] [nolists|recentusers|timeranges|devicefiles] [listlimit ] [start ] [end ] - [basic|full|allfields] * [fields ] - The basic argument outputs these column headers: deviceId,annotatedAssetId,annotatedLocation,annotatedUser,lastSync,notes,serialNumber,status The allfields/full arguments output all column headers including three headers, recentUsers, activeTimeRanges and deviceFiles, that repeat with two/four/two subvalues each, yielding a large number of columns that make the output hard to process. @@ -1039,7 +1045,7 @@ gam show fileinfo [allfields|*] gam show filerevisions gam show filetree [anyowner] (orderby [ascending|descending])* -gam create|add drivefile [drivefilename ] * +gam create|add drivefile [drivefilename ] * [csv] [todrive] gam update drivefile (id )|(query ] * gam get drivefile (id )|(drivefilename )|(query ) [format ] [targetfolder ] [revision ] gam delete|del drivefile ||(query:) [purge|untrash] diff --git a/src/gam.py b/src/gam.py index 975433f0..ffc21204 100755 --- a/src/gam.py +++ b/src/gam.py @@ -4346,7 +4346,7 @@ def getDriveFileAttribute(i, body, parameters, myarg, update=False): parameters[DFA_OCR] = True i += 1 elif myarg == u'ocrlanguage': - parameters[DFA_OCRLANGUAGE] = sys.argv[i+1] + parameters[DFA_OCRLANGUAGE] = LANGUAGE_CODES_MAP.get(sys.argv[i+1].lower(), sys.argv[i+1]) i += 2 elif myarg in DRIVEFILE_LABEL_CHOICES_MAP: body.setdefault(u'labels', {}) @@ -4461,9 +4461,9 @@ def doUpdateDriveFile(users): print u'Successfully copied %s to %s' % (fileId, result[u'id']) def createDriveFile(users): - csv_output = None + csv_output = to_drive = False csv_rows = [] - csv_titles = [u'title',u'id'] + csv_titles = [u'User', u'title', u'id'] media_body = None body, parameters = initializeDriveFileAttributes() i = 5 @@ -4475,6 +4475,9 @@ def createDriveFile(users): elif myarg == u'csv': csv_output = True i += 1 + elif myarg == u'todrive': + to_drive = True + i += 1 else: i = getDriveFileAttribute(i, body, parameters, myarg, False) for user in users: @@ -4495,17 +4498,14 @@ def createDriveFile(users): supportsTeamDrives=True) titleInfo = u'{0}({1})'.format(result[u'title'], result[u'id']) if csv_output: - csv_rows.append({ - u'title': result[u'title'], - u'id': result[u'id'] - }) + csv_rows.append({u'User': user, u'title': result[u'title'], u'id': result[u'id']}) else: if parameters[DFA_LOCALFILENAME]: print u'Successfully uploaded %s to Drive File %s' % (parameters[DFA_LOCALFILENAME], titleInfo) else: print u'Successfully created Drive %s %s' % ([u'Folder', u'File'][result[u'mimeType'] != MIMETYPE_GA_FOLDER], titleInfo) if csv_output: - writeCSVfile(csv_rows, csv_titles, u'Files', False) + writeCSVfile(csv_rows, csv_titles, u'Files', to_drive) def downloadDriveFile(users): i = 5 @@ -5475,7 +5475,7 @@ def doDeleteLabel(users): bcount = 0 j = 0 del_me_count = len(del_labels) - dbatch = gmail.new_batch_http_request() + dbatch = gmail.new_batch_http_request() for del_me in del_labels: j += 1 print u' deleting label %s (%s/%s)' % (del_me[u'name'], j, del_me_count) @@ -6423,7 +6423,7 @@ def doGetUserSchema(): _showSchema(schema) def getUserAttributes(i, cd, updateCmd=False): - def getEntryType(i, entry, entryTypes, setTypeCustom=True): + def getEntryType(i, entry, entryTypes, setTypeCustom=True, customKeyword=u'custom', customTypeKeyword=u'customType'): """ Get attribute entry type entryTypes is list of pre-defined types, a|b|c Allow a|b|c|, a|b|c|custom @@ -6435,26 +6435,25 @@ def getUserAttributes(i, cd, updateCmd=False): """ utype = sys.argv[i] ltype = utype.lower() - if ltype == u'custom': + if ltype == customKeyword: i += 1 utype = sys.argv[i] ltype = utype.lower() if ltype in entryTypes: entry[u'type'] = ltype - entry.pop(u'customType', None) + entry.pop(customTypeKeyword, None) else: - entry[u'customType'] = utype + entry[customTypeKeyword] = utype if setTypeCustom: - entry[u'type'] = u'custom' + entry[u'type'] = customKeyword else: entry.pop(u'type', None) return i+1 def checkClearBodyList(i, body, itemName): if sys.argv[i].lower() == u'clear': - if itemName in body: - del body[itemName] - body.setdefault(itemName, None) + body.pop(itemName, None) + body[itemName] = None return True return False @@ -6553,6 +6552,28 @@ def getUserAttributes(i, cd, updateCmd=False): if body[u'orgUnitPath'][0] != u'/': body[u'orgUnitPath'] = u'/%s' % body[u'orgUnitPath'] i += 2 + elif myarg in [u'language', u'languages']: + i += 1 + if checkClearBodyList(i, body, u'languages'): + i += 1 + continue + for language in sys.argv[i].replace(u',', u' ').split(): + if language.lower() in LANGUAGE_CODES_MAP: + appendItemToBodyList(body, u'languages', {u'languageCode': LANGUAGE_CODES_MAP[language.lower()]}) + else: + appendItemToBodyList(body, u'languages', {u'customLanguage': language}) + i += 1 + elif myarg == u'gender': + i += 1 + if checkClearBodyList(i, body, u'gender'): + i += 1 + continue + gender = {} + i = getEntryType(i, gender, USER_GENDER_TYPES, customKeyword=u'other', customTypeKeyword=u'customGender') + if (i < len(sys.argv)) and (sys.argv[i].lower() == u'addressmeas'): + gender[u'addressMeAs'] = getString(i+1, u'String') + i += 2 + body[u'gender'] = gender elif myarg in [u'address', u'addresses']: i += 1 if checkClearBodyList(i, body, u'addresses'): @@ -6846,6 +6867,16 @@ def getUserAttributes(i, cd, updateCmd=False): else: systemErrorExit(3, '%s is not a valid argument for user posix details. Make sure user posix details end with an endposix argument') appendItemToBodyList(body, u'posixAccounts', posix, checkSystemId=True) + elif myarg in [u'keyword', u'keywords']: + i += 1 + if checkClearBodyList(i, body, u'keywords'): + i += 1 + continue + keyword = {} + i = getEntryType(i, keyword, USER_KEYWORD_TYPES, customKeyword=u'custom', customTypeKeyword=u'customType') + keyword[u'value'] = sys.argv[i] + i += 1 + appendItemToBodyList(body, u'keywords', keyword) elif myarg == u'clearschema': if not updateCmd: systemErrorExit(2, '%s is not a valid create user argument.' % sys.argv[i]) @@ -7650,6 +7681,8 @@ def getGroupAttrValue(myarg, value, gs_object, gs_body, function): elif params[u'type'] == u'string': if attrib == u'description': value = value.replace(u'\\n', u'\n') + elif attrib == u'primaryLanguage': + value = LANGUAGE_CODES_MAP.get(value.lower(), value) elif params[u'description'].find(value.upper()) != -1: # ugly hack because API wants some values uppercased. value = value.upper() elif value.lower() in true_values: @@ -8683,6 +8716,15 @@ def doGetUserInfo(user_email=None): print utils.convertUTF8(u'First Name: %s' % user[u'name'][u'givenName']) if u'name' in user and u'familyName' in user[u'name']: print utils.convertUTF8(u'Last Name: %s' % user[u'name'][u'familyName']) + if u'languages' in user: + up = u'languageCode' + languages = [row[up] for row in user[u'languages'] if up in row] + if languages: + print u'Languages: %s' % u','.join(languages) + up = u'customLanguage' + languages = [row[up] for row in user[u'languages'] if up in row] + if languages: + print u'Custom Languages: %s' % u','.join(languages) if u'isAdmin' in user: print u'Is a Super Admin: %s' % user[u'isAdmin'] if u'isDelegatedAdmin' in user: @@ -8733,6 +8775,22 @@ def doGetUserInfo(user_email=None): else: print utils.convertUTF8(utils.indentMultiLineText(u' value: {0}'.format(notes), n=2)) print u'' + if u'gender' in user: + print u'Gender' + gender = user[u'gender'] + for key in gender: + if key == u'customGender' and not gender[key]: + continue + print utils.convertUTF8(u' %s: %s' % (key, gender[key])) + print u'' + if u'keywords' in user: + print u'Keywords:' + for keyword in user[u'keywords']: + for key in keyword: + if key == u'customType' and not keyword[key]: + continue + print utils.convertUTF8(u' %s: %s' % (key, keyword[key])) + print u'' if u'ims' in user: print u'IMs:' for im in user[u'ims']: @@ -9891,6 +9949,7 @@ USER_ARGUMENT_TO_PROPERTY_MAP = { u'firstname': [u'name.givenName',], u'fullname': [u'name.fullName',], u'gal': [u'includeInGlobalAddressList',], + u'gender': [u'gender.type', u'gender.customGender', u'gender.addressMeAs',], u'givenname': [u'name.givenName',], u'id': [u'id',], u'im': [u'ims',], @@ -9904,6 +9963,10 @@ USER_ARGUMENT_TO_PROPERTY_MAP = { u'is2svenforced': [u'isEnforcedIn2Sv',], u'is2svenrolled': [u'isEnrolledIn2Sv',], u'ismailboxsetup': [u'isMailboxSetup',], + u'keyword': [u'keywords',], + u'keywords': [u'keywords',], + u'language': [u'languages',], + u'languages': [u'languages',], u'lastlogintime': [u'lastLoginTime',], u'lastname': [u'name.familyName',], u'location': [u'locations',], diff --git a/src/var.py b/src/var.py index d7cc6a3c..d77654af 100644 --- a/src/var.py +++ b/src/var.py @@ -784,9 +784,10 @@ GAPI_MEMBERS_RETRY_REASONS = [GAPI_SYSTEM_ERROR] USER_ADDRESS_TYPES = [u'home', u'work', u'other'] USER_EMAIL_TYPES = [u'home', u'work', u'other'] -USER_EXTERNALID_TYPES = [u'account', u'customer', u'login_id', u'network', - u'organization'] +USER_EXTERNALID_TYPES = [u'account', u'customer', u'login_id', u'network', u'organization'] +USER_GENDER_TYPES = [u'female', u'male', u'unknown'] USER_IM_TYPES = [u'home', u'work', u'other'] +USER_KEYWORD_TYPES = [u'occupation', u'outlook'] USER_LOCATION_TYPES = [u'default', u'desk'] USER_ORGANIZATION_TYPES = [u'domain_only', u'school', u'unknown', u'work'] USER_PHONE_TYPES = [u'assistant', u'callback', u'car', u'company_main', @@ -952,3 +953,28 @@ WEBCOLOR_MAP = { u'yellow': u'#ffff00', u'yellowgreen': u'#9acd32', } +# Valid language codes +LANGUAGE_CODES_MAP = { + u'ach': u'ach', u'af': u'af', u'ag': u'ga', u'ak': u'ak', u'am': u'am', u'ar': u'ar', u'az': u'az', #Luo, Afrikaans, Irish, Akan, Amharic, Arabica, Azerbaijani + u'be': u'be', u'bem': u'bem', u'bg': u'bg', u'bn': u'bn', u'br': u'br', u'bs': u'bs', u'ca': u'ca', #Belarusian, Bemba, Bulgarian, Bengali, Breton, Bosnian, Catalan + u'chr': u'chr', u'ckb': u'ckb', u'co': u'co', u'crs': u'crs', u'cs': u'cs', u'cy': u'cy', u'da': u'da', #Cherokee, Kurdish (Sorani), Corsican, Seychellois Creole, Czech, Welsh, Danish + u'de': u'de', u'ee': u'ee', u'el': u'el', u'en': u'en', u'en-gb': u'en-GB', u'en-us': u'en-US', u'eo': u'eo', #German, Ewe, Greek, English, English (UK), English (US), Esperanto + u'es': u'es', u'es-419': u'es-419', u'et': u'et', u'eu': u'eu', u'fa': u'fa', u'fi': u'fi', u'fo': u'fo', #Spanish, Spanish (Latin American), Estonian, Basque, Persian, Finnish, Faroese + u'fr': u'fr', u'fr-ca': u'fr-ca', u'fy': u'fy', u'ga': u'ga', u'gaa': u'gaa', u'gd': u'gd', u'gl': u'gl', #French, French (Canada), Frisian, Irish, Ga, Scots Gaelic, Galician + u'gn': u'gn', u'gu': u'gu', u'ha': u'ha', u'haw': u'haw', u'he': u'he', u'hi': u'hi', u'hr': u'hr', #Guarani, Gujarati, Hausa, Hawaiian, Hebrew, Hindi, Croatian + u'ht': u'ht', u'hu': u'hu', u'hy': u'hy', u'ia': u'ia', u'id': u'id', u'ig': u'ig', u'in': u'in', #Haitian Creole, Hungarian, Armenian, Interlingua, Indonesian, Igbo, in + u'is': u'is', u'it': u'it', u'iw': u'iw', u'ja': u'ja', u'jw': u'jw', u'ka': u'ka', u'kg': u'kg', #Icelandic, Italian, Hebrew, Japanese, Javanese, Georgian, Kongo + u'kk': u'kk', u'km': u'km', u'kn': u'kn', u'ko': u'ko', u'kri': u'kri', u'ku': u'ku', u'ky': u'ky', #Kazakh, Khmer, Kannada, Korean, Krio (Sierra Leone), Kurdish, Kyrgyz + u'la': u'la', u'lg': u'lg', u'ln': u'ln', u'lo': u'lo', u'loz': u'loz', u'lt': u'lt', u'lua': u'lua', #Latin, Luganda, Lingala, Laothian, Lozi, Lithuanian, Tshiluba + u'lv': u'lv', u'mfe': u'mfe', u'mg': u'mg', u'mi': u'mi', u'mk': u'mk', u'ml': u'ml', u'mn': u'mn', #Latvian, Mauritian Creole, Malagasy, Maori, Macedonian, Malayalam, Mongolian + u'mo': u'mo', u'mr': u'mr', u'ms': u'ms', u'mt': u'mt', u'my': u'my', u'ne': u'ne', u'nl': u'nl', #Moldavian, Marathi, Malay, Maltese, Burmese, Nepali, Dutch + u'nn': u'nn', u'no': u'no', u'nso': u'nso', u'ny': u'ny', u'nyn': u'nyn', u'oc': u'oc', u'om': u'om', #Norwegian (Nynorsk), Norwegian, Northern Sotho, Chichewa, Runyakitara, Occitan, Oromo + u'or': u'or', u'pa': u'pa', u'pcm': u'pcm', u'pl': u'pl', u'ps': u'ps', u'pt-br': u'pt-BR', u'pt-pt': u'pt-PT', #Oriya, Punjabi, Nigerian Pidgin, Polish, Pashto, Portuguese (Brazil), Portuguese (Portugal) + u'qu': u'qu', u'rm': u'rm', u'rn': u'rn', u'ro': u'ro', u'ru': u'ru', u'rw': u'rw', u'sd': u'sd', #Quechua, Romansh, Kirundi, Romanian, Russian, Kinyarwanda, Sindhi + u'sh': u'sh', u'si': u'si', u'sk': u'sk', u'sl': u'sl', u'sn': u'sn', u'so': u'so', u'sq': u'sq', #Serbo-Croatian, Sinhalese, Slovak, Slovenian, Shona, Somali, Albanian + u'sr': u'sr', u'sr-me': u'sr-ME', u'st': u'st', u'su': u'su', u'sv': u'sv', u'sw': u'sw', u'ta': u'ta', #Serbian, Montenegrin, Sesotho, Sundanese, Swedish, Swahili, Tamil + u'te': u'te', u'tg': u'tg', u'th': u'th', u'ti': u'ti', u'tk': u'tk', u'tl': u'tl', u'tn': u'tn', #Telugu, Tajik, Thai, Tigrinya, Turkmen, Tagalog, Setswana + u'to': u'to', u'tr': u'tr', u'tt': u'tt', u'tum': u'tum', u'tw': u'tw', u'ug': u'ug', u'uk': u'uk', #Tonga, Turkish, Tatar, Tumbuka, Twi, Uighur, Ukrainian + u'ur': u'ur', u'uz': u'uz', u'vi': u'vi', u'wo': u'wo', u'xh': u'xh', u'yi': u'yi', u'yo': u'yo', #Urdu, Uzbek, Vietnamese, Wolof, Xhosa, Yiddish, Yoruba + u'zh-cn': u'zh-CN', u'zh-hk': u'zh-HK', u'zh-tw': u'zh-TW', u'zu': u'zu', #Chinese (Simplified), Chinese (Hong Kong/Traditional), Chinese (Taiwan/Traditional), Zulu + }