Microsoft Graph API-以其他用户身份发送电子邮件

时间:2018-10-04 13:13:54

标签: c# asp.net-core azure-active-directory microsoft-graph

在我们的应用程序中,我们需要通过电子邮件将各种事件触发器的通知发送给用户。

如果我以“我”的身份发送当前用户,我可以发送电子邮件,但是尝试以其他用户帐户的身份发送会返回错误消息,如果通知不是用户本人发出的,并且希望包含我们不想在“已发送”文件夹中四处移动的信息。

有效方法:

await graphClient.Me.SendMail(email, SaveToSentItems: false).Request().PostAsync();

什么不起作用:

string FromUserEmail = "notifications@contoso.com";
await graphClient.Users[FromUserEmail].SendMail(email, SaveToSentItems: false).Request().PostAsync();

还尝试直接使用用户对象ID:

await graphClient.Users["cd8cc59c-0815-46ed-aa45-4d46c8a89d72"].SendMail(email, SaveToSentItems: false).Request().PostAsync();

我的应用程序拥有Graph API的权限,该权限由所有者/管理员启用和授予。“ Send mail as any user”。

API返回的错误消息:

  

代码:ErrorFolderNotFound消息:无法指定文件夹   在商店中找到。

我认为此错误可能是由于通知帐户没有已发送的文件夹,所以我将SaveToSentItems的值设置为false,但是仍然遇到相同的错误。

我是否需要检查帐户本身的任何设置以允许应用程序通过该帐户发送邮件?

我已经在这里签出了文档: https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_sendmail

哪个似乎支持我要执行的操作,但是除了已发送的项目文件夹(我告诉API无论如何都不保存)之外,未引用任何文件夹。

我们不打算在这里模拟任何实际用户,只是从该特定帐户(我知道这在技术上是模拟,但不是真实实体)从应用程序内发送通知电子邮件。

3 个答案:

答案 0 :(得分:2)

无论何时您使用委派权限(即,当用户登录时),即使您的管理员同意Mail.Send.Shared,它也不会授予对租户中所有邮箱的访问权限。这些OAuth权限不会覆盖用户使用的权限(和限制)。

如果尚未为用户配置权限,使其能够“发送为” notifications@contoso.com用户,则将看到此错误。

要使其正常运行,您实际上需要向将使用您的应用程序的所有用户授予“发送为”权限。

这是一个微妙的事情,并且确实有点令人困惑。在Azure门户中,权限的描述稍有不同,具体取决于您要查看的是应用程序权限还是委派的权限

  • 应用程序:Send mail as any user
  • 已委派:Send mail on behalf of others

由于您使用的是委派,该权限不允许您以任何用户的身份发送,只能代表已登录用户有权发送的任何人发送。

您可以在此处使用的另一种方法是避免将这些权限授予所有用户(这将允许他们通过Outlook等发送),这将使您的后端应用使用客户端凭据流来获取仅应用令牌。在这种情况下,应用程序本身将具有以任何用户身份发送的权限。

答案 1 :(得分:0)

我不知道其他人会怎么做,但是我就这种确切情况与Microsoft联系:我想以固定用户(noreply@mycompany.com)的身份发送邮件,该邮件在Azure中具有邮箱。我想从其他应用程序或服务发送此邮件。

那里的人告诉我,只有使用已授权的用户令牌,才能在没有用户登录的情况下发送邮件。

因此,我们像移动应用程序一样,将应用程序配置为Azure中的本机应用程序。在设置阶段通过技术用户登录此应用程序后,会得到该特定用户的委派用户令牌,该令牌可以存储在邮件服务或组件中。此令牌不会过期(至少要等到用户的安全性发生更改(如密码或类似的东西)之后),并且在您授予该帐户从其发送邮件的权限时,可以使用该令牌来调用graph api发送邮件。

此外,我们甚至将其他共享邮箱关联到该帐户,以便也能够为这些邮箱发送邮件。

文档:

首先,您需要在Azure中注册本机应用程序(而不是Web API):

https://docs.microsoft.com/en-us/azure/active-directory/develop/native-app

此应用仅需要一次性登录并获得用户的批准即可获得可以不确定地代表该用户的令牌。我们设置了一个用于此目的的邮件用户帐户。然后,该令牌用于获取对Graph Api的访问令牌以发送邮件等

令牌处理示例:

https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/token-cache-serialization

存储了身份令牌(通常是某个地方的.cache文件)后,您可以请求访问令牌:

身份客户端:

https://docs.microsoft.com/en-us/dotnet/api/microsoft.identity.client.publicclientapplication?view=azure-dotnet

 _clientApp = new PublicClientApplication(ClientId, "https://login.microsoftonline.com/{xxx-xxx-xx}, usertoken,...

authResult =等待_clientApp.AcquireTokenSilentAsync(scopes,...

        private static string graphAPIEndpoint = "https://graph.microsoft.com/v1.0/me";

    //Set the scope for API call to user.read
    private static string[] scopes = new string[] { "user.read", "mail.send" };
    private const string GraphApi = "https://graph.microsoft.com/";

            var graphclient = new GraphServiceClient($"{GraphApi}/beta",
                       new DelegateAuthenticationProvider(
                           (requestMessage) =>
                           {                                  
                                   // inject bearer token for auth
                                   requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", authResult.AccessToken);
                               return Task.FromResult(0);
                           }));

        var sendmail = graphclient.Users[User].SendMail(mail), true);
        try
        {
            await sendmail.Request().PostAsync();
        }
        catch (Exception e)
        {

答案 2 :(得分:0)

因此,像Schwarzie2478一样,我们使用了noreply@ourcompany.com地址。但是我们的广告是联合身份的,这意味着您不能使用Username \ Password身份验证,并且我们不想使用Application Mail.Send许可,因为它实际上可以以任何人的身份发送,并且IT Security无法让它飞速发展。因此,我们改为使用Windows身份验证。

这要求您通过访问https://login.microsoftonline.com/ {tenantId} /oauth2/v2.0/authorize?client_id= {clientId}&response_type来同意应用程序使用mail.send和user.read委托权限= code&scope = user.read%20mail.send并使用运行该应用程序的Windows用户登录。

有关在此处使用Windows身份验证的更多信息:https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/Integrated-Windows-Authentication

// method call
var t = SendEmailUsingGraphAPI();
t.Wait();

// method
static async Task<Boolean> SendEmailUsingGraphAPI() {

    // AUTHENTICATION
    var tenantID = "YOUR_TENANT_ID"; //azure ad tenant/directory id
    var clientID = "YOUR_APPS_CLIENT_ID"; // registered app clientID
    var scopes = "user.read mail.send";  // DELEGATE permissions that the request will need

    string authority = $"https://login.microsoftonline.com/{tenantID}";
    string[] scopesArr = new string[] { scopes };

    try {
        IPublicClientApplication app = PublicClientApplicationBuilder
                .Create(clientID)
                .WithAuthority(authority)
                .Build();

        var accounts = await app.GetAccountsAsync();

        AuthenticationResult result = null;
        if (accounts.Any()) {
            result = await app.AcquireTokenSilent(scopesArr, accounts.FirstOrDefault())
                .ExecuteAsync();
        }
        else {
            // you could acquire a token by username/password authentication if you aren't federated.
            result = await app.AcquireTokenByIntegratedWindowsAuth(scopesArr)
                //.WithUsername(fromAddress)
                .ExecuteAsync(CancellationToken.None);
        }

        Console.WriteLine(result.Account.Username);


        // SEND EMAIL
        var toAddress = "EMAIL_OF_RECIPIENT";
        var message = "{'message': {'subject': 'Hello from Microsoft Graph API', 'body': {'contentType': 'Text', 'content': 'Hello, World!'}, 'toRecipients': [{'emailAddress': {'address': '" + result.Account.Username + "'} } ]}}";

        var restClient = new RestClient("https://graph.microsoft.com/v1.0/users/" + result.Account.Username + "/sendMail");
        var request = new RestRequest(Method.POST);
        request.AddHeader("Content-Type", "application/json");

        request.AddHeader("Authorization", "Bearer " + result.AccessToken);
        request.AddParameter("", message, ParameterType.RequestBody);
        IRestResponse response = restClient.Execute(request);
        Console.WriteLine(response.Content);

    }
    catch (Exception e) {
        Console.WriteLine(e.Message);
        throw e;
    }

    return true;
}