web-dev-qa-db-fra.com

Comment envoyer un e-mail via gmail sans activer «accès non sécurisé»?

Google nous pousse à améliorer la sécurité de l'accès des scripts à leurs serveurs smtp gmail. Ça ne me pose pas de problème. En fait, je suis heureux de vous aider.

Mais ils ne facilitent pas les choses. C'est bien beau de suggérer que nous Upgrade to a more secure app that uses the most up to date security measures, mais cela ne m'aide pas à déterminer comment mettre à niveau des morceaux de code qui ressemblent à ceci:

server = smtplib.SMTP("smtp.gmail.com", 587)
server.ehlo()
server.starttls()
server.login(GMAIL_USER, GMAIL_PASSWORD)
server.sendmail(FROM, TO, MESSAGE)
server.close()

Bien sûr, je vais activer "Accès aux applications moins sécurisées", mais si quelqu'un a trouvé quoi remplacer ce code, je vous en serais reconnaissant.

28
John Mee

Exemple mis à jour pour Python 3, et l'API actuelle de GMail, ci-dessous.

Notez que pour obtenir le credentials.json fichier ci-dessous, vous devrez créer un Oauth identifiant client ID ici , après avoir sélectionné le projet GCP approprié. Une fois que vous l'avez créé, vous afficher la clé client et le secret client. Fermez cette invite et cliquez sur la flèche vers le bas à côté du compte. Il s'agit du fichier dont vous aurez besoin.

enter image description here

import base64
import logging
import mimetypes
import os
import os.path
import pickle
from email.mime.text import MIMEText
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from googleapiclient import errors
from googleapiclient.discovery import build

def get_service():
    """Gets an authorized Gmail API service instance.

    Returns:
        An authorized Gmail API service instance..
    """    

    # If modifying these scopes, delete the file token.pickle.
    SCOPES = [
        'https://www.googleapis.com/auth/gmail.readonly',
        'https://www.googleapis.com/auth/gmail.send',
    ]

    creds = None
    # The file token.pickle stores the user's access and refresh tokens, and is
    # created automatically when the authorization flow completes for the first
    # time.
    if os.path.exists('token.pickle'):
        with open('token.pickle', 'rb') as token:
            creds = pickle.load(token)
    # If there are no (valid) credentials available, let the user log in.
    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(
                'credentials.json', SCOPES)
            creds = flow.run_local_server(port=0)
        # Save the credentials for the next run
        with open('token.pickle', 'wb') as token:
            pickle.dump(creds, token)

    service = build('gmail', 'v1', credentials=creds)
    return service

def send_message(service, sender, message):
  """Send an email message.

  Args:
    service: Authorized Gmail API service instance.
    user_id: User's email address. The special value "me"
    can be used to indicate the authenticated user.
    message: Message to be sent.

  Returns:
    Sent Message.
  """
  try:
    sent_message = (service.users().messages().send(userId=sender, body=message)
               .execute())
    logging.info('Message Id: %s', sent_message['id'])
    return sent_message
  except errors.HttpError as error:
    logging.error('An HTTP error occurred: %s', error)

def create_message(sender, to, subject, message_text):
  """Create a message for an email.

  Args:
    sender: Email address of the sender.
    to: Email address of the receiver.
    subject: The subject of the email message.
    message_text: The text of the email message.

  Returns:
    An object containing a base64url encoded email object.
  """
  message = MIMEText(message_text)
  message['to'] = to
  message['from'] = sender
  message['subject'] = subject
  s = message.as_string()
  b = base64.urlsafe_b64encode(s.encode('utf-8'))
  return {'raw': b.decode('utf-8')}

if __name__ == '__main__':
    logging.basicConfig(
        format="[%(levelname)s] %(message)s",
        level=logging.INFO
    )

    try:
        service = get_service()
        message = create_message("[email protected]", "[email protected]", "Test subject", "Test body")
        send_message(service, "[email protected]", message)

    except Exception as e:
        logging.error(e)
        raise
1
Steve Gore

C'était douloureux, mais il me semble que quelque chose se passe maintenant ...

Python3 n'est pas encore pris en charge

Je ne pense pas que ce sera trop difficile à atteindre, car je suis tombé sur la conversion de packages sans toucher à rien de massif: juste les choses 2to3 habituelles. Pourtant, après quelques heures, je me suis lassé de nager en amont. Au moment de la rédaction, je n'ai pas pu trouver de package publié pour la consommation publique pour Python 3. L'expérience python 2 était simple (en comparaison) .

Naviguer sur le site Web de Google est la moitié de la bataille

Sans doute, avec le temps, cela va changer. En fin de compte, vous devez télécharger un client_secret.json fichier. Vous pouvez uniquement (probablement) effectuer cette configuration via un navigateur Web:

  1. Vous avez besoin d'un compte Google - soit des applications Google, soit Gmail. Donc, si vous n'en avez pas, allez en chercher un.
  2. Accédez à la console des développeurs
  3. Créez un nouveau projet et attendez 4 ou 400 secondes pour qu'il se termine.
  4. Aller vers API's and Auth -> Credentials
  5. Sous OAuth sélectionnez Create New Client ID
  6. Choisissez Installed Application comme type d'application et Autre
  7. Vous devriez maintenant avoir un bouton Download JSON. Faites ça. C'est ton client_secret.json— les mots de passe pour ainsi dire

Mais attendez, ce n'est pas tout!

Vous devez donner à votre application un "nom de produit" pour éviter certaines erreurs étranges. (voyez combien j'ai souffert pour vous donner ça ;-)

  1. Aller vers API's & auth -> Consent Screen
  2. Choisissez votre email
  3. Saisissez un NOM DE PRODUIT. Peu importe ce que c'est. "Foobar" fera l'affaire.
  4. Sauver

Flash info! Whoa. Maintenant, il y a encore plus!

  1. Accédez aux API et à l'authentification -> API -> API Gmail
  2. Cliquez sur le bouton Activer l'API

Yay. Nous pouvons maintenant mettre à jour le script d'e-mailing.

Python 2

Vous devez exécuter le script de manière interactive la première fois. Il ouvrira un navigateur Web sur votre machine et vous accorderez des autorisations (appuyez sur un bouton). Cet exercice va enregistrer un fichier sur votre ordinateur gmail.storage qui contient un jeton réutilisable.

[Je n'ai pas eu de chance de transférer le jeton sur une machine qui n'a pas de fonctionnalité de navigateur graphique - renvoie une erreur HTTPE. J'ai essayé de passer au travers du navigateur graphique lynx. Cela a également échoué, car Google a défini le dernier bouton "Accepter" sur "désactivé"!? Je vais soulever une autre question pour sauter cet obstacle (plus de grognements)]

Vous avez d'abord besoin de quelques bibliothèques:

pip install --upgrade google-api-python-client
pip install --upgrade python-gflags
  • vous devez modifier les adresses de et vers
  • assurez-vous d'avoir le fichier client_token.json là où les instructions Storage l'attendent
  • le répertoire doit être accessible en écriture pour pouvoir enregistrer le gmail.storage fichier

Enfin du code:

import base64
import httplib2

from email.mime.text import MIMEText

from apiclient.discovery import build
from oauth2client.client import flow_from_clientsecrets
from oauth2client.file import Storage
from oauth2client.tools import run


# Path to the client_secret.json file downloaded from the Developer Console
CLIENT_SECRET_FILE = 'client_secret.json'

# Check https://developers.google.com/gmail/api/auth/scopes for all available scopes
OAUTH_SCOPE = 'https://www.googleapis.com/auth/gmail.compose'

# Location of the credentials storage file
STORAGE = Storage('gmail.storage')

# Start the OAuth flow to retrieve credentials
flow = flow_from_clientsecrets(CLIENT_SECRET_FILE, scope=OAUTH_SCOPE)
http = httplib2.Http()

# Try to retrieve credentials from storage or run the flow to generate them
credentials = STORAGE.get()
if credentials is None or credentials.invalid:
  credentials = run(flow, STORAGE, http=http)

# Authorize the httplib2.Http object with our credentials
http = credentials.authorize(http)

# Build the Gmail service from discovery
gmail_service = build('gmail', 'v1', http=http)

# create a message to send
message = MIMEText("Message goes here.")
message['to'] = "[email protected]"
message['from'] = "[email protected]"
message['subject'] = "your subject goes here"
body = {'raw': base64.b64encode(message.as_string())}

# send it
try:
  message = (gmail_service.users().messages().send(userId="me", body=body).execute())
  print('Message Id: %s' % message['id'])
  print(message)
except Exception as error:
  print('An error occurred: %s' % error)

J'espère que cela nous fera commencer. Pas aussi simple que l'ancienne, mais ça a l'air beaucoup moins compliqué maintenant je peux le voir dans la chair.

30
John Mee

Il semble que la réponse de John Mee soit obsolète. Cela ne fonctionne pas en juillet 2016. Peut-être en raison de la mise à jour de l'API de Gmail. Je mets à jour son code (python 2) comme ci-dessous:

    """Send an email message from the user's account.
"""

import base64
from email.mime.audio import MIMEAudio
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import mimetypes
import os

#from __future__ import print_function
import httplib2
import os

from apiclient import discovery
import oauth2client
from oauth2client import client
from oauth2client import tools

from apiclient import errors

SCOPES = 'https://www.googleapis.com/auth/gmail.compose'
CLIENT_SECRET_FILE = 'client_secret.json'
APPLICATION_NAME = 'Gmail API Python Quickstart'

try:
    import argparse
    flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args()
except ImportError:
    flags = None

def SendMessage(service, user_id, message):
  """Send an email message.

  Args:
    service: Authorized Gmail API service instance.
    user_id: User's email address. The special value "me"
    can be used to indicate the authenticated user.
    message: Message to be sent.

  Returns:
    Sent Message.
  """
  try:
    message = (service.users().messages().send(userId=user_id, body=message)
               .execute())
    print 'Message Id: %s' % message['id']
    return message
  except errors.HttpError, error:
    print 'An error occurred: %s' % error


def CreateMessage(sender, to, subject, message_text):
  """Create a message for an email.

  Args:
    sender: Email address of the sender.
    to: Email address of the receiver.
    subject: The subject of the email message.
    message_text: The text of the email message.

  Returns:
    An object containing a base64url encoded email object.
  """
  message = MIMEText(message_text)
  message['to'] = to
  message['from'] = sender
  message['subject'] = subject
  return {'raw': base64.urlsafe_b64encode(message.as_string())}


def get_credentials():
    """Gets valid user credentials from storage.

    If nothing has been stored, or if the stored credentials are invalid,
    the OAuth2 flow is completed to obtain the new credentials.

    Returns:
        Credentials, the obtained credential.
    """
    home_dir = os.path.expanduser('~')
    credential_dir = os.path.join(home_dir, '.credentials')
    if not os.path.exists(credential_dir):
        os.makedirs(credential_dir)
    credential_path = os.path.join(credential_dir,
                                   'sendEmail.json')

    store = oauth2client.file.Storage(credential_path)
    credentials = store.get()
    if not credentials or credentials.invalid:
        flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES)
        flow.user_agent = APPLICATION_NAME
        if flags:
            credentials = tools.run_flow(flow, store, flags)
        else: # Needed only for compatibility with Python 2.6
            credentials = tools.run(flow, store)
        print('Storing credentials to ' + credential_path)
    return credentials

if __name__ == "__main__":

    try:
        credentials = get_credentials()
        http = credentials.authorize(httplib2.Http())
        service = discovery.build('gmail', 'v1', http=http)
        SendMessage(service, "me", CreateMessage("[email protected]", "[email protected]", "Test gmail automation", "Hello world"))

    except Exception, e:
        print e
        raise

Notez que si vous rencontrez l'erreur Insufficient Permission, une des raisons possibles est que la portée du programme n'est pas définie correctement. L'autre raison possible peut être que vous devez supprimer le fichier json de stockage ("sendEmail.json" dans ce programme) et actualiser votre programme. Plus de détails peuvent être vus dans ce post .

16
ccy

Avez-vous envisagé d'utiliser l'API Gmail? L'API a des fonctionnalités de sécurité intégrées et est optimisée spécifiquement pour Gmail. Vous pouvez trouver la documentation de l'API sur http://developers.google.com - par exemple, voici la documentation de l'appel de l'API d'envoi:

https://developers.google.com/gmail/api/v1/reference/users/messages/send

5
Amber

J'inclus du code qui a été mis à jour pour python 3 utilisation - il semble envoyer des e-mails une fois que vous avez obtenu les autorisations nécessaires et que les OAuth jetons fonctionnent. C'est principalement basé sur les échantillons de site Web de Google api

    from __future__ import print_function

import base64
import os
from email.mime.text import MIMEText

import httplib2
from apiclient import discovery
from googleapiclient import errors
from oauth2client import client
from oauth2client import tools
from oauth2client.file import Storage

try:
    import argparse

    flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args()
except ImportError:
    flags = None

# If modifying these scopes, delete your previously saved credentials
# at ~/.credentials/gmail-python-quickstart.json
SCOPES = 'https://www.googleapis.com/auth/gmail.send'
CLIENT_SECRET_FILE = 'client_secret.json'
APPLICATION_NAME = 'Gmail API Python Quickstart'


def get_credentials():
    """Gets valid user credentials from storage.

    If nothing has been stored, or if the stored credentials are invalid,
    the OAuth2 flow is completed to obtain the new credentials.

    Returns:
        Credentials, the obtained credential.
    """
    home_dir = os.path.expanduser('~')
    credential_dir = os.path.join(home_dir, '.credentials')
    if not os.path.exists(credential_dir):
        os.makedirs(credential_dir)
    credential_path = os.path.join(credential_dir,
                                   'gmail-python-quickstart.json')

    store = Storage(credential_path)
    credentials = store.get()
    if not credentials or credentials.invalid:
        flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES)
        flow.user_agent = APPLICATION_NAME
        if flags:
            credentials = tools.run_flow(flow, store, flags)
        else:  # Needed only for compatibility with Python 2.6
            credentials = tools.run(flow, store)
        print('Storing credentials to ' + credential_path)
    return credentials


to = '[email protected]'
sender = '[email protected]'
subject = 'test emails'
message_text = 'hello this is a text test message'
user_id = 'me'

def create_message(sender, to, subject, message_text):
    """Create a message for an email.

    Args:
      sender: Email address of the sender.
      to: Email address of the receiver.
      subject: The subject of the email message.
      message_text: The text of the email message.

    Returns:
      An object containing a base64url encoded email object.
    """
    message = MIMEText(message_text)
    message['to'] = to
    message['from'] = sender
    message['subject'] = subject
    return {'raw': (base64.urlsafe_b64encode(message.as_bytes()).decode())}


def send_message(service, user_id, message):
    """Send an email message.

    Args:
      service: Authorized Gmail API service instance.
      user_id: User's email address. The special value "me"
      can be used to indicate the authenticated user.
      message: Message to be sent.

    Returns:
      Sent Message.
    """
    try:
        message = (service.users().messages().send(userId=user_id, body=message)
                   .execute())
        print('Message Id: {}'.format(message['id']))
        return message
    except errors.HttpError as error:
        print('An error occurred: {}'.format(error))


def main():
    """Shows basic usage of the Gmail API.

    Creates a Gmail API service object and outputs a list of label names
    of the user's Gmail account.
    """
    credentials = get_credentials()
    http = credentials.authorize(httplib2.Http())
    service = discovery.build('gmail', 'v1', http=http)

    msg = create_message(sender,to,subject,message_text)
    message = (service.users().messages().send(userId=user_id, body=msg)
               .execute())
    print('Message Id: {}'.format(message['id']))
    results = service.users().messages().list(userId='me').execute()
    labels = results.get('labels', [])

    if not labels:
        print('No labels found.')
    else:
        print('Labels:')
        for label in labels:
            print(label['name'])


if __name__ == '__main__':
    main()
4
jjisnow