I have a section of my software that has recently stopped working correctly. The program should allow a user to upload a pdf to our team google drive account and then the program will send an email to some other people in our office for review. This worked until Monday, when uploads and emails from my users started coming from my google account rather than their own. It doesn't happen to everyone, maybe 1/3rd to 1/2 of the users are experiencing this issue. Sometimes the program will send out the email as me and other times it throws an <HttpError 403 when requesting https://gmail.googleapis.com/gmail/v1/users/username/messages/send?alt=json returned 'Delegation denied for 'my.email@email.com'>. Since nothing has changed with my upload and send email functions, I think it has to do with permissions or how the program is distributed? Any insight would be appreciated.
I've written everything in python3 and compile an exe to distribute to my team with pyinstaller.
pyinstaller --onefile path/to/code.py
Things I have tired to troubleshoot: run as admin had users restart their PC I had someone on my team run py file directly and that worked correctly Originally I had a --hidden-import='googleapiclient' for pyinstaller that I removed Originally in the email portion I had the userId='me' and I changed it to pull in the users actual email address. (The idea being pyinstaller was locking in 'me' as my personal email. That code runs after the upload, so that does not seem to be the issue.)
The relevant upload code in case someone can spot an issue:
self.SCOPES = ['https://www.googleapis.com/auth/gmail.modify',
'https://www.googleapis.com/auth/spreadsheets',
'https://www.googleapis.com/auth/drive']
credentials.json:
{
"installed":{
"client_id":"client_id.apps.googleusercontent.com",
"project_id":"project_ID",
"auth_uri":"https://accounts.google.com/o/oauth2/auth",
"token_uri":"https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs",
"client_secret":"secret_code",
"redirect_uris":[
"urn:ietf:wg:oauth:2.0:oob",
"http://localhost"
]
}
}
def upload(self, letterfile, plansfile, parentdrive):
# Check for json credentials for google drive and API access
try:
x = os.stat(os.path.join(self.prefs['data'], 'token.json'))
result = time.time() - x.st_mtime
if result > 129600:
os.remove(os.path.join(self.prefs['data'], 'token.json'))
except:
pass
creds = None
jsonpath = os.path.join(self.prefs['data'], 'token.json')
if os.path.exists(jsonpath):
creds = Credentials.from_authorized_user_file(jsonpath,
self.SCOPES)
# If there are no (valid) credentials available, let the user log in.
try:
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
os.path.join(self.prefs['data'], 'credentials.json'),
self.SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open(os.path.join(self.prefs['data'], 'token.json'), 'w') as token:
token.write(creds.to_json())
except BaseException as e:
print(f'Error with credentials.json check: {e}')
# Build the services to access the Gmail API and Drive API
driveService = build('drive', 'v3', credentials=creds)
file = letterfile
folderLink = f'https://drive.google.com/drive/folders/{parentdrive}'
# Work with different attachment formats (Text, Image, or PDF). This
# will need to be a FOR loop to iterate through all imported files
# from the app
content_type, encoding = mimetypes.guess_type(file)
if content_type is None or encoding is not None:
content_type = 'application/octet-stream'
main_type, sub_type = content_type.split('/', 1)
if main_type == 'text':
with open(file, 'rb') as fp:
msg = MIMEText(fp.read(), _subtype=sub_type)
fp.close()
elif main_type == 'image':
with open(file, 'rb') as fp:
msg = MIMEImage(fp.read(), _subtype=sub_type)
fp.close()
else:
with open(file, 'rb') as fp:
msg = MIMEBase(main_type, sub_type)
msg.set_payload(fp.read())
fp.close()
calcfile = os.path.basename(letterfile)
try:
planname = os.path.basename(plansfile)
except BaseException as e:
planname = []
for planfile in plansfile:
planname.append(os.path.basename(planfile))
# Upload file to Drive Project Folder
file_metadata = {'name': calcfile, 'parents': [parentdrive]}
media = MediaFileUpload(letterfile,
mimetype=content_type,
resumable=True)
tempcalcfile = driveService.files().create(body=file_metadata,
media_body=media,
supportsAllDrives=True).execute()
try:
plan_metadata = {'name': planname, 'parents': [parentdrive]}
media = MediaFileUpload(plansfile,
mimetype=content_type,
resumable=True)
tempplansfile = driveService.files().create(body=plan_metadata,
media_body=media,
supportsAllDrives=True).execute()
except BaseException as e:
for i in range(len(planname)):
plan_metadata = {'name': planname[i], 'parents': [parentdrive]}
media = MediaFileUpload(plansfile[i],
mimetype=content_type,
resumable=True)
tempplansfile = driveService.files().create(body=plan_metadata,
media_body=media,
supportsAllDrives=True).execute()
print('Files sent to Drive Folder through API')
I had a folder that housed the credentials.json, my token.json file, and some other files that were distributed to my team. I accidentally share MY token.json file with them, so when google Oauth2 ran, it read the file as me instead of as the user.
I had my team delete the token.json file, which forced a reauthorization by the user, the creation of a new token.json and fixed the problem.