|
PROFESSIONAL NEWSGROUPS WINDEV, WEBDEV and WINDEV Mobile |
| | | | | |
Office 365 OAuth 2.0 avec les API Microsoft Graph |
Started by Jérémie, Oct., 17 2023 10:07 PM - 8 replies |
| |
| | | |
|
| |
Registered member 19 messages |
|
Posted on October, 17 2023 - 10:07 PM |
Bonjour,
Je recherche depuis deux jours sans trouver de solution qui me convienne et sur le forum il y a plusieurs discussions ouvertes sur le sujet, mais sans vraiment de réponse claire... et sans doute lié au différent changement du coter de Microsoft.
Je voudrais pouvoir envoyer des mails depuis mon application de bureau de manière automatique via une session SMTP, jusque-là pas bien compliquer avec le login et le mot de passe aucun problème, par contre pour passer à une authentification oAuth 2.0 c'est la galère.
Sur Microsoft Entra : - j'ai inscrit une nouvelle application - dans "API autorisées", j'ai activé les API du Microsoft Graph : -- SMTP.Send Type Déléguée -- offline_access Type Déléguée -- Mail.Send Type Déléguée -- Mail.Send Type Application - dans "Authentification" j'ai ajouté une URI de redirection : http://localhost:9874 - dans "Certificats & secrets" j'ai ajouté un secret client valide pour 2 ans (qui est la durée max)
Mon code : À noter que je récupère bien mon token, ma session SMTP s'ouvre sans erreurs, mais l'envoi du mail ne part pas.
ClientId est une chaîne = "111111-1111-11111" ClientSecret est une chaîne = "1111~111111111111" LocataireId est une chaîne = "11111111-1111-111"
MonTokenParam est un OAuth2Paramètres MonTokenParam.ClientID = ClientId MonTokenParam.ClientSecret = ClientSecret MonTokenParam.URLAuth = "https://login.microsoftonline.com/"+ LocataireId +"/oauth2/v2.0/authorize" MonTokenParam.URLToken = "https://login.microsoftonline.com/"+ LocataireId +"/oauth2/v2.0/token" MonTokenParam.URLRedirection = "http://localhost:9874/" MonTokenParam.Scope = "https://graph.microsoft.com/offline_access https://graph.microsoft.com/Mail.Send" MonTokenParam.ParamètresSupplémentaires = "force_reapprove=false" MonTokenParam.TypeRéponse = oauth2TypeRéponseCode
MaSessionSMTP est un emailSessionSMTP
MaSessionSMTP.AdresseServeur = "smtp.office365.com" MaSessionSMTP.Port = 587 MaSessionSMTP.Option = emailProtocoleSMTPS MaSessionSMTP.AuthToken = AuthIdentifie(MonTokenParam)
MonMessage est un Email MonMessage.Expediteur = "username@domain.com" Ajoute(MonMessage.Destinataire, "username@domain.com") MonMessage.Sujet = "Sujet de test" MonMessage.Message = "Titre Mon message de test" MonMessage.HTML = "<h1>Titre</h1><p>Mon message de <b>test</b></p>"
SI EmailOuvreSession(MaSessionSMTP) = Faux ALORS Erreur("Impossible d'ouvrir la session SMTP.", ErreurInfo()) RETOUR FIN
SI EmailEnvoieMessage(MaSessionSMTP, MonMessage, emailOptionEncodeEntête) = Faux ALORS Erreur(ErreurInfo(errRésumé)) FIN
EmailFermeSession(MaSessionSMTP)
Message d'erreur avec ce code : "Le contenu de Email.Expéditeur n'est pas reconnu par le serveur. La transaction est refusée."
Et si je commente, j'ai ce message d'erreur : "Vous avez appelé la fonction 'EmailEnvoieMessage'. L'envoi d'un message sans préciser l'expéditeur n'est pas autorisé."
Avez-vous déjà réussi à envoyer un email via SMTP et une authentification avec oAuth2.0 pour Office 365 ? si oui comment ?
Merci par avance |
| |
| |
| | | |
|
| | |
| |
Registered member 37 messages Popularité : +1 (1 vote) |
|
Posted on October, 19 2023 - 7:36 AM |
Bonjour, le champ expéditeur quand c'est un compte office doit forcément être renseigné avec l'adresse du titulaire du compte.
Pour ma part quand je passe par un SMTP avec office , je ne renseigne pas de Token , je rentre directement les informations IdUtilisateur et mot de passe du compte. Le token n'est utilisé que si j'utilise les appels à l'api pour faire partir des mails en direct.
-- Cordialement |
| |
| |
| | | |
|
| | |
| |
Registered member 19 messages |
|
Posted on November, 02 2023 - 2:16 PM |
Bonjour,
Je passe sur le forum pour un autre problème et je constate que j'ai reçu une réponse et une demande qui, pour moi, n'avaient pas été envoyées sur le forum car j'avais une erreur lorsque je postais le message... (Je pense que le forum a vraiment besoin d'une mise à jour !)
Entre-temps, j'ai trouvé la solution à mon problème sans avoir besoin de passer par les méthodes de Windev. J'ai dû reprendre le code d'un autre utilisateur sur le forum. Ensuite, j'ai créé une classe qui gère la génération du access_token et le refresh_token + autre classe qui gère le format attendu par l'API de Microsoft Graph et j'envoie mon mail de cette manière :
MonMail est un CLA_Mail MonMail.subject("Sujet de test") MonMail.content("<h1>Titre de test</h1><p>Mon contenu de test</p>") MonMail.from(JSONVersChaîne(OAuthOffice365.User().mail)) MonMail.sender(JSONVersChaîne(OAuthOffice365.User().mail)) MonMail.toRecipients(["dest1@contact.com"]) MonMail.ccRecipients(["dest2@contact.com"; "dest3@contact.com"]) MonMail.bccRecipients(["dest4@contact.com"]) MonMail.attachmentAdd("PATH_DE_LA_PJ") SI MonMail.send(OAuthOffice365.TokenSession.Valeur, Faux) ALORS Info("Mail envoyé !") SINON Erreur("Mail non envoyé !") FIN
La méthode send :
Procedure send(accessToken est une chaîne, saveToSentItems est un booléen = Vrai) <métier>
sMail est une chaîne
MonMail.saveToSentItems = saveToSentItems
Sérialise(MonMail,sMail,psdJSON)
maRequete est une httpRequête maReponse est une httpRéponse maRequete.Méthode = httpPost maRequete.ContentType = "application/json" maRequete.Entête["Authorization"] = "Bearer " + accessToken maRequete.URL = "https://graph.microsoft.com/v1.0/me/sendMail" maRequete.Contenu = sMail maReponse = maRequete.Envoie()
SI maReponse.CodeEtat = 200 _OU_ maReponse.CodeEtat = 202 ALORS RENVOYER Vrai SINON RENVOYER Faux FIN |
| |
| |
| | | |
|
| | |
| |
Posted on November, 17 2023 - 11:54 AM |
Bonjour;
j'ai essayé la même chose en créant une librairie externe mais j'ai des soucis avec les dépendances pouvez-vous me montre en plusieurs détails votre CLA_Mail ? le token est valid combien de temps ?
Cordialement
Geremi |
| |
| |
| | | |
|
| | |
| |
Registered member 19 messages |
|
Posted on November, 19 2023 - 10:59 AM |
Bonjour Gerem,
Code de la déclaration de ma classe CLA_Mail :
CLA_Mail est une Classe MonMail est un STMail
FIN
STCorps est une structure typeDucontenu est une chaîne <Sérialise="contentType"> content est une chaîne FIN
STAdresse est une structure Address est une chaîne FIN
STEmailAdresse est une structure EmailAddress est STAdresse FIN
STFichierJoint est une structure type est une chaîne <Sérialise="@odata.type"> name est une chaîne typeDucontenu est une chaîne <Sérialise="contentType"> contentBytes est une chaîne FIN
STMessage est une structure subject est une chaîne body est STCorps from est un STEmailAdresse sender est un STEmailAdresse toRecipients est un tableau de STEmailAdresse ccRecipients est un tableau de STEmailAdresse bccRecipients est un tableau de STEmailAdresse attachments est un tableau de STFichierJoint FIN
STMail est une structure monMessage est STMessage <Sérialise="message"> saveToSentItems est un booléen FIN
Ensuite j'ai ajouté 9 méthodes à ma classe
Code de la méthode content :
Procedure content(valeur est une chaîne, type est une chaîne = "html") <métier>
MonMail.monMessage.body.content = valeur MonMail.monMessage.body.typeDucontenu = type
Code de la méthode from :
Procedure from(valeur est une chaîne) <métier>
MonMail.monMessage.from.EmailAddress.Address = valeur
Code de la méthode sender :
Procedure sender(valeur est une chaîne) <métier>
MonMail.monMessage.sender.EmailAddress.Address = valeur
Code de la méthode toRecipients :
Procedure toRecipients(valeurs est un tableau de chaînes)
SI valeurs..Occurrence > 0 ALORS POUR TOUT valeur de valeurs SI valeur <> "" ALORS recipient est un STEmailAdresse recipient.EmailAddress.Address = valeur MonMail.monMessage.toRecipients.Ajoute(recipient) FIN FIN FIN
Code de la méthode ccRecipients :
Procedure ccRecipients(valeurs est un tableau de chaînes)
SI valeurs..Occurrence > 0 ALORS POUR TOUT valeur de valeurs SI valeur <> "" ALORS recipient est un STEmailAdresse recipient.EmailAddress.Address = valeur MonMail.monMessage.ccRecipients.Ajoute(recipient) FIN FIN FIN
Code de la méthode bccRecipients :
Procedure bccRecipients(valeurs est un tableau de chaînes)
SI valeurs..Occurrence > 0 ALORS POUR TOUT valeur de valeurs SI valeur <> "" ALORS recipient est un STEmailAdresse recipient.EmailAddress.Address = valeur MonMail.monMessage.bccRecipients.Ajoute(recipient) FIN FIN FIN
Code de la méthode bccRecipients :
Procedure attachmentAdd(pathFile est une chaîne, contentType est une chaîne = "application/pdf", name est une chaîne = "") <métier>
attachment est un STFichierJoint
SI pathFile <> "" OU Null ALORS attachment.type = "microsoft.graph.fileAttachment" SI name <> "" ALORS attachment.name = name SINON attachment.name = fExtraitChemin(pathFile, fFichier+fExtension) FIN attachment.typeDucontenu = contentType attachment.contentBytes = Encode(fChargeBuffer(pathFile), encodeBASE64) MonMail.monMessage.attachments.Ajoute(attachment) FIN
Code de la méthode subject :
Procedure subject(valeur est une chaîne) <métier>
MonMail.monMessage.subject = valeur
Code de la méthode send :
Procedure send(accessToken est une chaîne, saveToSentItems est un booléen = Vrai) <métier>
sMail est une chaîne
MonMail.saveToSentItems = saveToSentItems
Sérialise(MonMail,sMail,psdJSON)
maRequete est une httpRequête maReponse est une httpRéponse maRequete.Méthode = httpPost maRequete.ContentType = "application/json" maRequete.Entête["Authorization"] = "Bearer " + accessToken maRequete.URL = "https://graph.microsoft.com/v1.0/me/sendMail" maRequete.Contenu = sMail maReponse = maRequete.Envoie()
SI maReponse.CodeEtat = 200 _OU_ maReponse.CodeEtat = 202 ALORS RENVOYER Vrai SINON RENVOYER Faux FIN
Il est facilement possible de faire un code plus propre mais pour mon cas d'utilisation cela me convient parfaitement. Pour le token il est valide 2h mais je renouvelle le access_token avec le refresh_token si mon token expire du coup je n'ai pas besoin de me reconnecter à chaque fois. |
| |
| |
| | | |
|
| | |
| |
Registered member 19 messages |
|
Posted on November, 19 2023 - 11:09 AM |
Je vous poste également ma classe qui gère le token et son renouvellement :
Code de déclaration de ma classe CLA_OAuthOffice365 :
CLA_OAuthOffice365 est une Classe CONSTANTE LOCATAIRE_ID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" CLIENT_ID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" CLIENT_SECRET = "" URL_AUTH = "https://login.microsoftonline.com/"+ LOCATAIRE_ID +"/oauth2/v2.0/authorize" URL_TOKEN = "https://login.microsoftonline.com/"+ LOCATAIRE_ID +"/oauth2/v2.0/token" URL_REDIRECTION = "http://localhost:9874/" SCOPE = "offline_access https://graph.microsoft.com/Mail.Send" PARAMETRES_SUPPLEMENTAIRES = "force_reapprove=false" FIN MonTokenParam est un OAuth2Paramètres TokenSession est un AuthToken FichierPersistanceAuth est une chaîne UserInfo est une JSON FIN
Code du constructeur de la classe :
Procedure Constructeur()
FichierPersistanceAuth = fRepDonnées() + [fSep] + "AuthSession.bin"
MonTokenParam.ClientID = ::CLIENT_ID
MonTokenParam.URLAuth = ::URL_AUTH MonTokenParam.URLToken = ::URL_TOKEN MonTokenParam.URLRedirection = ::URL_REDIRECTION MonTokenParam.Scope = ::SCOPE MonTokenParam.ParamètresSupplémentaires = ::PARAMETRES_SUPPLEMENTAIRES MonTokenParam.TypeRéponse = oauth2TypeRéponseCode
Code de la méthode Connect :
Procedure Connect() : AuthToken
SI fFichierExiste(FichierPersistanceAuth) ALORS QUAND EXCEPTIONEXCEPTION DANS getTokenSession() FAIRE ToastAffiche("Session oAuth mémorisée est invalide") SINON SI (TokenSession.DateExpiration-1Min < DateHeureSys()) _ET_ TokenSession.Actualisation <> "" ALORS TokenSession = AuthRenouvelleToken(TokenSession) SI TokenSession.Valide ALORS setTokenSession(TokenSession) SINON Erreur("Échec du renouvellement de la session oAuth") FIN FIN FIN FIN
SI PAS TokenSession.Valide ALORS TokenSession = AuthIdentifie(MonTokenParam) SI TokenSession.Valide ALORS setTokenSession(TokenSession) FIN FIN
RENVOYER TokenSession
Code de la méthode getTokenSession :
Procedure getTokenSession() <métier> : AuthToken
SI fFichierExiste(FichierPersistanceAuth) ALORS bufToken est un Buffer bufToken = fChargeBuffer(FichierPersistanceAuth) Désérialise(TokenSession, bufToken, psdBinaire) FIN
RENVOYER TokenSession
Code de la méthode getTokenSession :
Procedure setTokenSession(AuthToken est un AuthToken) <métier> : booléen
bufToken est un Buffer
Sérialise(AuthToken, bufToken, psdBinaire)
RENVOYER fSauveBuffer(FichierPersistanceAuth, bufToken)
Code de la méthode AuthSessionExiste :
Procedure PUBLIQUE GLOBALE AuthSessionExiste() <métier> : booléen
FichierPersistanceAuth est une chaîne = fRepDonnées() + [fSep] + "AuthSession.bin"
SI fFichierExiste(FichierPersistanceAuth) = Vrai ALORS RENVOYER Vrai FIN
RENVOYER Faux
Même chose pour cette classe le code peut facilement être améliorer mais cela me convient pour mon cas d'utilisation.
Je précise également que finalement le secret client n'est pas utile pour l'envoie de mail. |
| |
| |
| | | |
|
| | |
| |
Registered member 417 messages Popularité : +6 (6 votes) |
|
Posted on November, 20 2023 - 8:20 AM |
| |
| |
| | | |
|
| | |
| |
Posted on November, 20 2023 - 12:01 PM |
Merci de votre réponse rapide cela m'aide beaucoup ! |
| |
| |
| | | |
|
| | |
| |
Registered member 24 messages Popularité : +1 (1 vote) |
|
Posted on March, 23 2024 - 7:13 AM |
Merci Jérémie, bel esprit de partage, bien rare en francophonie... C'est exactement ce dont j'avais besoin
-- otto.matic@outlook.frMessage modified, March, 23 2024 - 7:37 AM |
| |
| |
| | | |
|
| | | | |
| | |
| | |
| |
|
|
|