mirror of
https://github.com/GAM-team/GAM.git
synced 2026-07-04 12:51:36 +00:00
308 lines
13 KiB
Python
308 lines
13 KiB
Python
"""User photo and profile management.
|
|
|
|
Part of the _userop_tmp sub-package."""
|
|
|
|
"""GAM user operations: Looker Studio, user groups, licenses, photos, profile, sheets, tokens, deprovision."""
|
|
|
|
import re
|
|
import sys
|
|
|
|
import googleapiclient.http
|
|
import base64
|
|
import os
|
|
|
|
import google.auth
|
|
import google.auth.exceptions
|
|
|
|
from gamlib import glaction
|
|
from gamlib import glapi as API
|
|
from gamlib import glcfg as GC
|
|
from gamlib import glclargs
|
|
from gamlib import glentity
|
|
from gamlib import glgapi as GAPI
|
|
from gamlib import glglobals as GM
|
|
from gamlib import glindent
|
|
from gamlib import glmsgs as Msg
|
|
|
|
Act = glaction.GamAction()
|
|
Ent = glentity.GamEntity()
|
|
Ind = glindent.GamIndent()
|
|
Cmd = glclargs.GamCLArgs()
|
|
|
|
|
|
def _getMain():
|
|
return sys.modules['gam']
|
|
|
|
def __getattr__(name):
|
|
"""Fall back to gam module for any undefined names."""
|
|
main = _getMain()
|
|
try:
|
|
return getattr(main, name)
|
|
except AttributeError:
|
|
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
|
|
from tempfile import TemporaryFile
|
|
|
|
def updatePhoto(users):
|
|
cd = _getMain().buildGAPIObject(API.DIRECTORY)
|
|
baseFileIdEntity = drive = owner = None
|
|
sourceFolder = os.getcwd()
|
|
if Cmd.NumArgumentsRemaining() == 1:
|
|
filenamePattern = _getMain().getString(Cmd.OB_FILE_NAME_PATTERN)
|
|
else:
|
|
filenamePattern = '#email#.jpg'
|
|
while Cmd.ArgumentsRemaining():
|
|
myarg = _getMain().getArgument()
|
|
if myarg == 'drivedir':
|
|
sourceFolder = GC.Values[GC.DRIVE_DIR]
|
|
elif myarg == 'sourcefolder':
|
|
sourceFolder = _getMain().setFilePath(_getMain().getString(Cmd.OB_FILE_PATH), GC.INPUT_DIR)
|
|
if not os.path.isdir(sourceFolder):
|
|
_getMain().entityDoesNotExistExit(Ent.DIRECTORY, sourceFolder)
|
|
elif myarg == 'filename':
|
|
filenamePattern = _getMain().getString(Cmd.OB_FILE_NAME_PATTERN)
|
|
elif myarg == 'gphoto':
|
|
owner, drive = _getMain().buildGAPIServiceObject(API.DRIVE3, _getMain().getEmailAddress())
|
|
if not drive:
|
|
return
|
|
baseFileIdEntity = _getMain().getDriveFileEntity(queryShortcutsOK=False)
|
|
else:
|
|
_getMain().unknownArgumentExit()
|
|
p = re.compile('^(ht|f)tps?://.*$')
|
|
i, count, users = _getMain().getEntityArgument(users)
|
|
for user in users:
|
|
i += 1
|
|
user, userName, _ = _getMain().splitEmailAddressOrUID(user)
|
|
filename = _getMain()._substituteForUser(filenamePattern, user, userName)
|
|
if baseFileIdEntity is not None:
|
|
fileIdEntity = baseFileIdEntity.copy()
|
|
if fileIdEntity['query'] is not None:
|
|
fileIdEntity['query'] = _substituteForUser(fileIdEntity['query'], user, userName)
|
|
_, _, jcount = _getMain()._validateUserGetFileIDs(owner, 0, 0, fileIdEntity, drive=drive, entityType=None)
|
|
if jcount == 0:
|
|
_getMain().entityItemValueListActionNotPerformedWarning([Ent.USER, user], [Ent.OWNER, owner],
|
|
Msg.NO_ENTITIES_FOUND.format(Ent.Singular(Ent.DRIVE_FILE)), i, count)
|
|
continue
|
|
if jcount > 1:
|
|
_getMain().entityItemValueListActionNotPerformedWarning([Ent.USER, user], [Ent.OWNER, owner],
|
|
Msg.MULTIPLE_ENTITIES_FOUND.format(Ent.Plural(Ent.DRIVE_FILE), jcount, ','.join(fileIdEntity['list'])), i, count)
|
|
continue
|
|
fb = TemporaryFile(mode='wb+')
|
|
filename = fileIdEntity['list'][0]
|
|
request = drive.files().get_media(fileId=filename)
|
|
downloader = googleapiclient.http.MediaIoBaseDownload(fb, request)
|
|
done = False
|
|
while not done:
|
|
_, done = downloader.next_chunk()
|
|
fb.seek(0)
|
|
image_data = fb.read()
|
|
fb.close()
|
|
elif p.match(filename):
|
|
try:
|
|
status, image_data = _getMain().getHttpObj().request(filename, 'GET')
|
|
if status['status'] != '200':
|
|
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.PHOTO, filename], Msg.NOT_ALLOWED, i, count)
|
|
continue
|
|
except (httplib2.HttpLib2Error, google.auth.exceptions.TransportError, RuntimeError) as e:
|
|
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.PHOTO, filename], str(e), i, count)
|
|
continue
|
|
else:
|
|
filename = os.path.join(sourceFolder, filename)
|
|
try:
|
|
with open(filename, 'rb') as f:
|
|
image_data = f.read()
|
|
except (OSError, IOError) as e:
|
|
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.PHOTO, filename], str(e), i, count)
|
|
continue
|
|
body = {'photoData': base64.urlsafe_b64encode(image_data).decode(_getMain().UTF8)}
|
|
try:
|
|
try:
|
|
_getMain().callGAPI(cd.users().photos(), 'delete',
|
|
bailOnInternalError=True,
|
|
throwReasons=[GAPI.USER_NOT_FOUND, GAPI.FORBIDDEN, GAPI.PHOTO_NOT_FOUND, GAPI.INTERNAL_ERROR],
|
|
userKey=user)
|
|
except (GAPI.photoNotFound, GAPI.internalError):
|
|
pass
|
|
_getMain().callGAPI(cd.users().photos(), 'update',
|
|
throwReasons=[GAPI.USER_NOT_FOUND, GAPI.FORBIDDEN, GAPI.INVALID_INPUT, GAPI.CONDITION_NOT_MET],
|
|
userKey=user, body=body, fields='')
|
|
_getMain().entityActionPerformed([Ent.USER, user, Ent.PHOTO, filename], i, count)
|
|
except (GAPI.invalidInput, GAPI.conditionNotMet) as e:
|
|
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.PHOTO, filename], str(e), i, count)
|
|
except (GAPI.userNotFound, GAPI.forbidden):
|
|
_getMain().entityUnknownWarning(Ent.USER, user, i, count)
|
|
|
|
# gam <UserTypeEntity> delete photo
|
|
def deletePhoto(users):
|
|
cd = _getMain().buildGAPIObject(API.DIRECTORY)
|
|
_getMain().checkForExtraneousArguments()
|
|
i, count, users = _getMain().getEntityArgument(users)
|
|
for user in users:
|
|
i += 1
|
|
user = _getMain().normalizeEmailAddressOrUID(user)
|
|
try:
|
|
_getMain().callGAPI(cd.users().photos(), 'delete',
|
|
bailOnInternalError=True,
|
|
throwReasons=[GAPI.USER_NOT_FOUND, GAPI.FORBIDDEN, GAPI.PHOTO_NOT_FOUND, GAPI.INTERNAL_ERROR],
|
|
userKey=user)
|
|
_getMain().entityActionPerformed([Ent.USER, user, Ent.PHOTO, ''], i, count)
|
|
except (GAPI.photoNotFound, GAPI.internalError) as e:
|
|
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.PHOTO, ''], str(e), i, count)
|
|
except (GAPI.userNotFound, GAPI.forbidden):
|
|
_getMain().entityUnknownWarning(Ent.USER, user, i, count)
|
|
|
|
def getPhoto(users, profileMode):
|
|
cd = _getMain().buildGAPIObject(API.DIRECTORY)
|
|
targetFolder = os.getcwd()
|
|
filenamePattern = '#email#.#ext#'
|
|
noDefault = returnURLonly = False
|
|
writeFileData = showPhotoData = True
|
|
size = ''
|
|
while Cmd.ArgumentsRemaining():
|
|
myarg = _getMain().getArgument()
|
|
if myarg == 'drivedir':
|
|
targetFolder = GC.Values[GC.DRIVE_DIR]
|
|
elif myarg == 'targetfolder':
|
|
targetFolder = _getMain().setFilePath(_getMain().getString(Cmd.OB_FILE_PATH), GC.DRIVE_DIR)
|
|
if not os.path.isdir(targetFolder):
|
|
os.makedirs(targetFolder)
|
|
elif myarg == 'filename':
|
|
filenamePattern = _getMain().getString(Cmd.OB_FILE_NAME_PATTERN)
|
|
elif myarg == 'nofile':
|
|
writeFileData = False
|
|
elif myarg == 'noshow':
|
|
showPhotoData = False
|
|
elif profileMode and myarg == 'returnurlonly':
|
|
returnURLonly = True
|
|
elif myarg == 'nodefault':
|
|
noDefault = True
|
|
elif profileMode and myarg == 'size':
|
|
size = _getMain().getInteger(minVal=50)
|
|
else:
|
|
_getMain().unknownArgumentExit()
|
|
i, count, users = _getMain().getEntityArgument(users)
|
|
for user in users:
|
|
i += 1
|
|
if profileMode:
|
|
user, people = _getMain().buildGAPIServiceObject(API.PEOPLE, user, i, count)
|
|
if not people:
|
|
continue
|
|
else:
|
|
user = _getMain().normalizeEmailAddressOrUID(user)
|
|
_, userName, _ = _getMain().splitEmailAddressOrUID(user)
|
|
filename = os.path.join(targetFolder, _getMain()._substituteForUser(filenamePattern, user, userName))
|
|
try:
|
|
if not showPhotoData:
|
|
_getMain().entityPerformActionNumItems([Ent.USER, user], 1, Ent.PHOTO, i, count)
|
|
if not profileMode:
|
|
photo = _getMain().callGAPI(cd.users().photos(), 'get',
|
|
throwReasons=[GAPI.USER_NOT_FOUND, GAPI.FORBIDDEN, GAPI.PHOTO_NOT_FOUND],
|
|
userKey=user)
|
|
if showPhotoData:
|
|
_getMain().writeStdout(photo['photoData']+'\n')
|
|
photo_data = base64.urlsafe_b64decode(photo['photoData'])
|
|
else:
|
|
result = _getMain().callGAPI(people.people(), 'get',
|
|
throwReasons=[GAPI.NOT_FOUND],
|
|
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
|
resourceName='people/me', personFields='photos')
|
|
default = False
|
|
url = None
|
|
for photo in result.get('photos', []):
|
|
if photo['metadata']['source']['type'] == 'PROFILE':
|
|
default = photo.get('default', False)
|
|
url = photo['url']
|
|
break
|
|
if not url:
|
|
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.PHOTO, None], Msg.PROFILE_PHOTO_NOT_FOUND, i, count)
|
|
continue
|
|
if noDefault and default:
|
|
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.PHOTO, None], Msg.PROFILE_PHOTO_IS_DEFAULT, i, count)
|
|
continue
|
|
if returnURLonly:
|
|
_getMain().writeStdout(f'{url}\n')
|
|
continue
|
|
if size:
|
|
url = re.sub(r"=s\d+$", f"=s{size}", url)
|
|
try:
|
|
status, photo_data = _getMain().getHttpObj().request(url, 'GET')
|
|
if status['status'] != '200':
|
|
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.PHOTO, filename], Msg.NOT_ALLOWED, i, count)
|
|
continue
|
|
if showPhotoData:
|
|
_getMain().writeStdout(base64.encodebytes(photo_data).decode(_getMain().UTF8))
|
|
except (httplib2.HttpLib2Error, google.auth.exceptions.TransportError, RuntimeError) as e:
|
|
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.PHOTO, filename], str(e), i, count)
|
|
continue
|
|
if writeFileData:
|
|
if photo_data[:3] == b'\xff\xd8\xff':
|
|
extension = 'jpg'
|
|
elif photo_data[:8] == b'\x89\x50\x4e\x47\x0d\x0a\x1a\x0a':
|
|
extension = 'png'
|
|
elif photo_data[:6] == b'\x47\x49\x46\x38\x37\x61' or photo_data[:6] == b'\x47\x49\x46\x38\x39\x61':
|
|
extension = 'gif'
|
|
elif photo_data[:2] == b'\x42\x4d':
|
|
extension= 'bmp'
|
|
elif photo_data[:4] == b'\x49\x49\x2A\x00' or photo_data[:4] == b'\x4D\x4D\x00\x2A':
|
|
extension= 'tif'
|
|
else:
|
|
extension = 'img'
|
|
filenameExt = filename.replace('#ext#', extension)
|
|
status, e = _getMain().writeFileReturnError(filenameExt, photo_data, mode='wb')
|
|
if status:
|
|
if not showPhotoData:
|
|
_getMain().entityActionPerformed([Ent.USER, user, Ent.PHOTO, filenameExt], i, count)
|
|
else:
|
|
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.PHOTO, filenameExt], str(e), i, count)
|
|
except (GAPI.notFound, GAPI.photoNotFound) as e:
|
|
_getMain().entityActionFailedWarning([Ent.USER, user, Ent.PHOTO, None], str(e), i, count)
|
|
except (GAPI.userNotFound, GAPI.forbidden):
|
|
_getMain().entityUnknownWarning(Ent.USER, user, i, count)
|
|
|
|
# gam <UserTypeEntity> get photo [drivedir|(targetfolder <FilePath>)] [filename <FileNamePattern>]]
|
|
# [noshow] [nofile]
|
|
def getUserPhoto(users):
|
|
getPhoto(users, False)
|
|
|
|
# gam <UserTypeEntity> get profilephoto [drivedir|(targetfolder <FilePath>)] [filename <FileNamePattern>]
|
|
# [noshow] [nofile] [returnurlonly] [nodefault] [size <Integer>]
|
|
def getProfilePhoto(users):
|
|
getPhoto(users, True)
|
|
|
|
PROFILE_SHARING_CHOICE_MAP = {
|
|
'share': True,
|
|
'shared': True,
|
|
'unshare': False,
|
|
'unshared': False,
|
|
}
|
|
|
|
def _setShowProfile(users, function, **kwargs):
|
|
cd = _getMain().buildGAPIObject(API.DIRECTORY)
|
|
_getMain().checkForExtraneousArguments()
|
|
i, count, users = _getMain().getEntityArgument(users)
|
|
for user in users:
|
|
i += 1
|
|
user = _getMain().normalizeEmailAddressOrUID(user)
|
|
try:
|
|
result = _getMain().callGAPI(cd.users(), function,
|
|
throwReasons=[GAPI.USER_NOT_FOUND, GAPI.FORBIDDEN],
|
|
userKey=user, fields='includeInGlobalAddressList', **kwargs)
|
|
_getMain().printEntity([Ent.USER, user, Ent.PROFILE_SHARING_ENABLED, result.get('includeInGlobalAddressList', _getMain().UNKNOWN)], i, count)
|
|
except (GAPI.userNotFound, GAPI.forbidden):
|
|
_getMain().entityUnknownWarning(Ent.USER, user, i, count)
|
|
|
|
# gam <UserTypeEntity> profile share|shared|unshare|unshared
|
|
def setProfile(users):
|
|
body = {'includeInGlobalAddressList': _getMain().getChoice(PROFILE_SHARING_CHOICE_MAP, mapChoice=True)}
|
|
_setShowProfile(users, 'update', body=body)
|
|
|
|
# gam <UserTypeEntity> show profile
|
|
def showProfile(users):
|
|
_setShowProfile(users, 'get')
|
|
|
|
# gam <UserTypeEntity> create sheet
|
|
# ((json [charset <Charset>] <SpreadsheetJSONCreateRequest>) |
|
|
# (json file <FileName> [charset <Charset>]))
|
|
# [<DriveFileParentAttribute>]
|
|
# [formatjson] [returnidonly]
|