Es cierto que, a priori, el título del post no levanta grandes pasiones, pero si por el camino aprendemos a usar MailKit, configuramos permisos en Azure y lo comparamos con el SDK de Microsoft Graph API, el tema algo mejora.
Aparentemente, leer los mensajes de un buzón de Office 365 debería ser una tarea sencilla. Lógicamente, al final todo se complica o, simplemente, no es tan directo como prometía inicialmente.
Para los ejemplos, el programa se ejecutará como una aplicación en segundo plano, es decir, no habrá delegación de permisos por parte de un usuario, luego los permisos en Azure serán del tipo Application en vez de Delegated.
Inicialmente, probaremos a usar MailKit con el protocolo IMAP. Para ello, nuestra primera parada será la documentación oficial Authenticate an IMAP, POP or SMTP connection using OAuth.
Los pasos a seguir son:
- Crear un App registration, un secreto, darle el permiso Office 365 Exchange Online | IMAP.AccessAsApp y dar consentimiento de administrador con Grand admin consent for…
- Configurar en el centro de administración de Exchange la delegación de uno o más buzones para el App registration recién creado.
La delegación del buzón la haremos con PowerShell, pero antes un par de aclaraciones:
YOUR_TENANT_ID
es Directory (tenant) ID como aparece en App registrations.YOUR_APPLICATION_ID
es Application (client) ID como aparece en App registrations.YOUR_OBJECT_ID
es Object ID como aparece en Entreprise applications.YOUR_EMAIL
es… ¡exacto!, el email que queremos leer.
Install-Module -Name ExchangeOnlineManagement
Connect-ExchangeOnline -Organization YOUR_TENANT_ID
New-ServicePrincipal -AppId YOUR_APPLICATION_ID -ObjectId YOUR_OBJECT_ID
Add-MailboxPermission -Identity "YOUR_EMAIL" -User YOUR_OBJECT_ID -AccessRights FullAccess
Podemos confirmar que todo fue bien desde el portal de administración de Exchange.
Aquí aparecera YOUR_OBJECT_ID
:
También podemos ejecutar Get-MailboxPermission -Identity "YOUR_EMAIL"
para verlo desde línea de comandos.
Es poco probable que no lo esté, pero confirma también que IMAP esté activado para el buzón.
En este momento, ya podemos empezar a escribir nuestro programa en C# usando MailKit.
Los paquetes necesarios son:
Microsoft.Identity.Client
.MailKit
.
using MailKit;
using MailKit.Net.Imap;
using MailKit.Security;
using Microsoft.Identity.Client;
Console.WriteLine(await GetCountAsync());
async Task<int> GetCountAsync(CancellationToken cancellationToken = default)
{
var authToken = await GetAuthToken(cancellationToken);
var mechanism =
new SaslMechanismOAuth2("YOUR_EMAIL", authToken.AccessToken);
using var imapClient = new ImapClient();
await imapClient.ConnectAsync("outlook.office365.com", 993,
SecureSocketOptions.SslOnConnect, cancellationToken);
await imapClient.AuthenticateAsync(mechanism, cancellationToken);
var inbox = imapClient.Inbox;
await inbox.OpenAsync(FolderAccess.ReadOnly, cancellationToken);
var count = inbox.Count;
await inbox.CloseAsync(cancellationToken: cancellationToken);
await imapClient.DisconnectAsync(quit: true, cancellationToken);
return count;
}
async Task<AuthenticationResult> GetAuthToken(CancellationToken cancellationToken)
{
var scopes = new[]
{
"https://outlook.office365.com/.default"
};
var confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create("YOUR_APPLICATION_ID")
.WithClientSecret("YOUR_SECRET")
.WithAuthority(new Uri($"https://login.microsoftonline.com/YOUR_TENANT_ID/"))
.Build();
return await confidentialClientApplication.AcquireTokenForClient(scopes)
.ExecuteAsync(cancellationToken);
}
Si lo que queremos en usar Microsoft Graph API, una API REST muy completa y, probablemente, la opción recomendada, habría que instalar estos paquetes:
Azure.Identity
Microsoft.Graph
En cuanto a la configuración del App registration, esta vez hay que darle el permiso Microsoft Graph | Mail.Read. Este permiso es más bestia que el anterior (si el administrador no configura nada para poner algún límite). Ahora no habrá que delegar buzones porque el permiso permitirá leer todos los buzones.
Console.WriteLine(await GetCountAsync());
async Task<int> GetCountAsync(CancellationToken cancellationToken = default)
{
var credentials = new ClientSecretCredential("YOUR_TENANT_ID",
"YOUR_APPLICATION_ID",
"YOUR_SECRET",
new TokenCredentialOptions { AuthorityHost = AzureAuthorityHosts.AzurePublicCloud });
var graphServiceClient = new GraphServiceClient(credentials);
var user = graphServiceClient.Users["YOUR_EMAIL"];
var inbox = (await user.MailFolders["Inbox"].GetAsync(cancellationToken: cancellationToken))!;
return (int)inbox.TotalItemCount!;
}
Un saludo!