首页 >邮件操作 > 内容

Outlook 的可操作邮件

2022年11月7日 23:19

我喜欢电子邮件。工作时,我都是通过电子邮件,及时了解最新动态以及需要完成的工作。在电子邮件中,我可以看到自己团队提交新费用报表的通知、我的推文的新回复、拉取请求的新评论等。但电子邮件可以有更好的表现。我为什么需要先单击电子邮件中的链接,并等待财务系统网站在浏览器中加载后,才能审批费用报表呢? 我又为什么必须在头脑里更改上下文呢? 我应当能够在电子邮件客户端上下文中直接审批费用报表。


听起来是不是很熟悉? Outlook 旨在改善用户生活、节省用户时间并提高用户工作效率。


1
可操作邮件简介



使用可操作邮件,用户可以在电子邮件本身中完成任务。通过它,用户可以在 Outlook 桌面客户端和 Outlook Web Access (OWA) 中获得本机体验。在本文中,我将使用 Outlook 一词表示 Outlook 桌面客户端或 OWA。


在我要用的示例中,虚构公司 Contoso 有一个内部费用审批系统。每当有员工提交费用报表,经理就会收到供审批的电子邮件。我将逐步介绍如何在 Outlook 中使用可操作邮件。通过此类邮件,经理可以在电子邮件本身中批准请求。



2
我的首个可操作邮件



图 1 展示了可操作邮件的 HTML。虽然看起来可能很复杂,但请相信我,它并不复杂。我将在下面各部分中详细介绍标记。第一步是使用图 1 中的标记,将电子邮件发送到 Office 365 电子邮件帐户。


图1:Outlook 可操作邮件的 HTML


<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf8">
    <script type="application/ld+json">{      "@context": "http://schema.org/extensions",      "@type": "MessageCard",      "hideOriginalBody": "true",      "title": "Expense report is pending your approval",      "sections": [{        "text": "Please review the expense report below.",        "facts": [{          "name": "ID",          "value": "98432019"
        }, {          "name": "Amount",          "value": "83.27 USD"
        }, {          "name": "Submitter",          "value": "Kathrine Joseph"
        }, {          "name": "Description",          "value": "Dinner with client"
        }]
      }],      "potentialAction": [{        "@type": "HttpPost",        "name": "Approve",        "target": ""
      }, {        "@type": "OpenUri",        "name": "View Expense",        "targets": [ { "os": "default", 
        "uri": "https://expense.contoso.com/view?id=98432019"} ]
      }]
    }
    </script>
  </head>
  <body>
    <p>Please <a href="https://expense.contoso.com/view?id=98432019">approve</a> 
      expense report #98432019 for $83.27.</p>
  </body>
</html>


如图 2 所示,邮件本身中有一个邮件卡,其中包含两个可交互的按钮。如果单击“批准”按钮,暂时会生成错误,因为还没有指定操作 URL。稍后将添加 URL。如果单击“查看费用”按钮,将打开浏览器,并转到“费用审批”网站。



图 2:Outlook Web Access 中的可操作邮件



3
MessageCard 标记



电子邮件本身就是典型的 HTML 标记。若要在 Outlook 中实现可操作邮件,可以在 <script> 元素中插入 MessageCard 标记。这种方法的一大优点是,电子邮件可以继续照常在无法识别 MessageCard 标记的客户端上呈现。此标记的格式称为“JSON-LD”。这是一种标准格式,用于在 Internet 上创建计算机可读数据。现在,我们将详细探讨此标记。下面这两行代码在每个标记中都是必需的:


"@context": "http://schema.org/extensions",
"@type": "MessageCard",


将上下文设置为“http://schema.org/extensions”,并将类型设置为“MessageCard”。 MessageCard 类型表明此电子邮件为可操作邮件。


接下来是属性“hideOriginalBody”。 如果值设置为“True”,将隐藏电子邮件正文,而只显示邮件卡,如图 2 所示。如果邮件卡本身包含用户所需的全部信息,或邮件卡内容导致电子邮件正文内容变得多余,此设置尤为实用。如果在无法识别邮件卡的电子邮件客户端中查看邮件,将看到原始正文,而看不到邮件卡,无论“hideOriginalBody”值如何。 属性“title”的值是 MessageCard 的标题:


"hideOriginalBody": "true","title": "Expense report is pending your approval",


接下来是“section”。 可以将分区看作是“活动”。 如果邮件卡有多个活动,绝对应使用多个分区,每个分区对应一个活动。图 3 展示了包含一个分区的标记。使用分区的 facts 属性(一组名称/值对)显示费用报表的详细信息。


图 3:包含一个分区的邮件卡


"sections": [{  "text": "Please review the expense report below.",  "facts": [{    "name": "ID",    "value": "98432019"
  }, {    "name": "Amount",    "value": "83.27 USD"
  }, {    "name": "Submitter",    "value": "Jonathan Kiev"
  }, {    "name": "Description",    "value": "Dinner with client"
  }]
}],


接下来是“potentialAction”。 这是可以对此邮件卡调用的一系列操作。目前,支持操作 OpenUri 和 HttpPOST:


"potentialAction": [{  "@type": "HttpPost",  "name": "Approve",  "target": ""}, {  "@type": "OpenUri",  "name": "View Expense",  "targets": [ { "os": "default",  "uri": "https://expense.contoso.com/view?id=98432019"} ]
}]


OpenUri 操作打开浏览器,并转到 target 属性中指定的 URL。target 属性是一个数组,可用于指定平台专属 URL。例如,可能希望 iOS 和 Android 上的用户转到不同的 URL。在此示例中,将 OS 设置为默认值。也就是说,所有平台的 URL 都是相同的。

HttpPOST 操作向 target 属性中指定的外部 Web 服务发出 HTTP POST 请求。值暂为空。正因如此,单击“批准”按钮时会看到错误消息。



4
MessageCard 样本应用



如果在创建标记时能够呈现邮件卡的外观,将会带来极大的帮助。Microsoft 提供具有此用途的 Web 应用。这就是 MessageCard 样本应用 (bit.ly/2s274S9)。

一律应先在此应用中设计邮件卡。对邮件卡的布局感到满意后,便可以对电子邮件使用标记了。



5
通过 HttpPOST 操作调用外部 Web 服务



至此,已有一张包含两个操作的邮件卡。OpenUri 打开浏览器,并转到操作中指定的 URL。对于 HttpPOST 操作,希望它调用可批准批费用报表的 REST API。可以将 HttpPOST 操作替换为下列代码:


{  "@type": "HttpPost",  "name": "Approve",  "target": "https://api.contoso.com/expense/approve",  "body": "{ \"id\": \"98432019\" }"}


当用户单击“批准”按钮时,Microsoft 服务器会发出如下 HTTP POST 请求:


POST api.contoso.com/expense/approve
Content-Type: application/json

{ "id": "98432019" }


target 是 Microsoft 服务器向其发出 POST 请求的 URL,而 body 是请求内容。body 内容始终假定为 JSON。


现在,将使用新标记向自己发送电子邮件。单击“批准”按钮后,操作成功完成。



6
ActionCard 操作



现在,让我们添加“拒绝”按钮,以便用户能够拒绝费用报表。若要提供“拒绝”按钮,需要另外获取用户输入,以说明费用报表遭拒原因。


ActionCard 操作专为此而设计。它包含一个或多个输入以及相关操作(可以是 OpenUri,也可以是 HttpPost)。在 HttpPOST 和 OpenUri 之间插入 ActionCard 操作,如图 4 所示。


图 4:ActionCard 操作


"potentialAction": [{  "@type": "HttpPost",
  ...
}, {  "@type": "ActionCard",  "name": "Reject",  "inputs": [{    "@type": "TextInput",    "id": "comment",    "isMultiline": true,    "title": "Explain why the expense report is rejected"
  }],  "actions": [{    "@type": "HttpPOST",    "name": "Reject",    "target": "https://api.contoso.com/expense/reject",    "body": "{ \"id\": \"98432019\", \"comment\": \"{{rejectComment.value}}\" }"
  }]
},{  "@type": "OpenUri",
  ...
}]


如果向自己发送更新后的标记,就会看到“批准”、“拒绝”和“查看费用”按钮。如果单击“拒绝”按钮,现在可以先输入注释,然后再拒绝费用报表。


让我们来看看如何实际使用 ActionCard 操作标记。除了类型和名称属性之外,它还包含一系列输入和操作。在此示例中,使用多行 TextInput,方便用户输入文本。支持的其他输入包括 DateInput 和 Multichoice­Input。有关详细信息,请访问 bit.ly/2t3bLJN


使用 HttpPOST 操作,以便调用外部 Web 服务来拒绝费用报表。这类似于用于批准操作的 HttpPOST 操作。一个主要区别是,要将用户输入的注释传递给 Web 服务调用。可以使用 {{rejectComment.value}} 引用文本输入值,其中 rejectComment 是文本输入 ID。



7
可操作邮件的 Web 服务



至此,已了解 Outlook 可操作邮件的标记及其工作原理。在本文的剩余部分中,我将介绍 Web 服务应如何处理 Outlook 可操作邮件发出的请求。


可操作邮件与可以处理 HTTP POST 请求的任何 Web 服务协同工作。在此示例中,Web 服务是 ASP.NET MVC 中的 API 控制器。图 5 展示了 API 控制器。


图 5:费用 API 控制器


[RoutePrefix("expense")]public class ExpenseController : ApiController
{
  [HttpPost]
  [Route("approve")]  public HttpResponseMessage Approve([FromBody]JObject jBody)
  {    string expenseId = jBody["id"].ToString();    // Process and approve the expense report.
    HttpResponseMessage response = this.Request.CreateResponse(HttpStatusCode.OK);
    response.Headers.Add("CARD-ACTION-STATUS", "The expense was approved.");      return response;    
  }

  [HttpPost]
  [Route("reject")]  public HttpResponseMessage Reject([FromBody]JObject jBody)
  {    string expenseId = jBody["id"].ToString();    string comment = jBody["comment"].ToString();    // Process and reject the expense report.
    HttpResponseMessage response = this.Request.CreateResponse(HttpStatusCode.OK);
    response.Headers.Add("CARD-ACTION-STATUS", "The expense was rejected.");    return response;    
  }
}


此 API 控制器有两种方法,一种用于批准,另一种用于拒绝。若要指明操作成功,Web 服务必须返回 HTTP 状态代码 2xx。Web 服务还可以在响应中添加 CARD-ACTION-STATUS 标头。此标头的值在邮件卡保留区域中向用户显示。如果将 Web 服务部署到 https://api.contoso.com,并单击“批准”按钮,将会收到指明操作已成功完成的通知,如图6所示。


图 6:成功批准费用报表的通知


现在,可以端到端运行可操作邮件了。可以发送可操作邮件,当用户单击“批准”按钮时,向 Web 服务发送 HTTP POST 请求。Web 服务将处理请求,并返回“200 OK”。然后,Outlook 将操作标记为“完成”。接下来,我将介绍如何保护 Web 服务。



8
专用令牌



因为费用 ID 通常遵循一定的格式,所以存在一定风险,即攻击者可以使用不同的费用 ID 发布大量请求,从而进行攻击。如果攻击者成功猜出费用 ID,就可以批准或拒绝费用报表。Microsoft 建议开发者在操作目标 URL 或请求正文中使用“专用令牌”。攻击者应该很难猜出专用令牌。例如,我使用 128 位数字的 GUID 作为专用令牌。此令牌可用于将服务 URL 与特定的请求和用户相关联。还可用于保护 Web 服务免受重播攻击 (bit.ly/2sBQmdn)。将标记更新为在正文中添加 GUID:


{  "@type": "HttpPost",  "name": "Approve",  "target": "https://api.contoso.com/expense/approve",  "body": "{ \"id\": \"98432019\", \"token\": \  "d8a0bf4f-ae70-4df6-b129-5999b41f4b7f\" }"}



9
持有者令牌



虽然专用令牌加大了攻击者伪造请求的难度,但仍并非十全十美。理想情况下,Web 服务应该能够判断 HTTP POST 请求是否来自 Microsoft 服务器,而不是一些未经授权的潜在恶意服务器。


为解决此问题,Microsoft 在发送给 Web 服务的每个 HTTP POST 请求中添加持有者令牌。持有者令牌是 JSON Web 令牌 (JWT),包含在请求的授权标头中。在用户单击“批准”按钮后,Web 服务会收到如下请求:


POST https://api.contoso.com/expenses/approveContent-Type: application/json
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJ­SUzI1NiIsIng1dCI6I­jhx­Z3A4­VER­CbDJINkp5­RkU0WjM0­ZDJoYS1rR­SIsImtpZCI6I­jhxZ3A4V­ERCbDJINkp5RkU0WjM0ZD­JoYS1rRSJ9.eyJpYXQiOjE0ODQwODkyNzksInZlciI6IlNUSS5FeHRlcm­5hbEFjY2Vzc1Rva2­VuLlYxIiwiYXBw aWQiOiI0OGFmMD­hkYy1mN­mQyLTQzNWYtYjJhNy0wN­jlhYmQ5OWMwODYiLCJzd­WIiOiJk­YXZpZEBj­ b250b3NvLmN­vbSIsImFwcGlk­YWNyIjoiMiIsIm­FjciI6IjAi­LCJzZW5kZ­XIiOiJleHB­lbnNlYXBw... (truncated for brevity)

{  "id": "98432019",  "token": "d8a0bf4f-ae70-4df6-b129-5999b41f4b7f"}


在授权标头中,“Bearer”后面是一个采用 Base64 编码的长字符串,即 JSON Web 令牌 (JWT)。可以在 jwt.calebb.net 中解码 JWT。图 7 展示了解码后的示例令牌。


图 7:示例持有者 JSON Web 令牌


{
  typ: "JWT",
  alg: "RS256",
  x5t: "8qgp8TDBl2H6JyFE4Z34d2ha-kE",
  kid: "8qgp8TDBl2H6JyFE4Z34d2ha-kE"}.
{
  iat: 1484089279,
  ver: "STI.ExternalAccessToken.V1",
  appid: "48af08dc-f6d2-435f-b2a7-069abd99c086",
  sub: "david@contoso.com",
  appidacr: "2",
  acr: "0",
  sender: "expenseapproval@contoso.com",
  iss: "https://substrate.office.com/sts/",
  aud: "https://api.contoso.com",
  exp: 1484090179,
  nbf: 1484089279
}.
[signature]


每个 JWT 都分为三段,分别用点号 (.) 隔开。第一段是标头,描述了应用于 JWT 的加密操作。在此示例中,用于执行令牌签名的算法 (alg) 是 RS256。也就是说,RSA 使用 SHA-256 哈希算法。x5t 值指定用于执行令牌签名的密钥指纹。


第二段是有效负载本身。其中包含令牌断言的声明列表。Web 服务应使用这些声明来验证请求。图 8 中的表格介绍了这些声明。


图 8:有关有效负载中声明的介绍


声明说明
iss令牌颁发者。值应始终为 https://substrate.office.om/sts/。如果值不一致,Web 服务应拒绝令牌和请求。
appid颁发令牌的应用程序 ID。值应始终为 48af08dc-f6d2-435f-b2a7-069abd99c086。如果值不一致,Web 服务应拒绝令牌和请求。
aud令牌受众。应与 Web 服务 URL 的主机名一致。如果值不一致,Web 服务应拒绝令牌和请求。
sub执行过操作的使用者。值为执行过操作的用户的电子邮件地址,前提是“收件人:”行中有此电子邮件地址或任何代理电子邮件地址。如果没有匹配的电子邮件地址,那么值为使用者的用户主体名称 (UPN) 哈希值。保证同一 UPN 的哈希值相同。
sender原始邮件发件人的电子邮件地址。
tid令牌颁发者的租户 ID。


第三段是令牌的数字签名。通过验证签名,Web 服务可以确信令牌是由 Microsoft 发送,并信任令牌中的声明。


验证数字签名是一项非常复杂的任务。幸运的是,可以借助 NuGet 库轻松执行验证任务。可从 bit.ly/2stq90c 获取 Microsoft 创建的库。Microsoft 还发布了有关如何验证令牌的其他语言代码示例。本文末尾收录了这些代码示例的链接。


在 Web 服务项目中添加 NuGet 包之后,可以使用 VerifyBearerToken 方法(如图 9 所示),验证请求中的持有者令牌。


图 9:VerifyBearerToken 方法


private async Task<HttpStatusCode> VerifyBearerToken(
  HttpRequestMessage request, string serviceBaseUrl, string expectedSender)
{  
if (request.Headers.Authorization == null || !string.Equals(request.Headers.Authorization.Scheme, "bearer", StringComparison.OrdinalIgnoreCase) || string.IsNullOrEmpty(request.Headers.Authorization.Parameter)) {
return HttpStatusCode.Unauthorized ; }

string bearerToken = request.Headers.Authorization.Parameter; ActionableMessageTokenValidator validator = new ActionableMessageTokenValidator(); ActionableMessageTokenValidationResult result = await validator.ValidateTokenAsync(bearerToken, serviceBaseUrl); if (!result.ValidationSucceeded) { return HttpStatusCode.Unauthorized; }
if (!string.Equals(result.Sender, expectedSender, StringComparison.OrdinalIgnoreCase) || !result.ActionPerformer.EndsWith("@contoso.com", StringComparison.OrdinalIgnoreCase)) {
return HttpStatusCode.Forbidden; } return HttpStatusCode.OK; } [HttpPost] [Route("approve")]
public async Task<HttpResponseMessage> Approve([FromBody]JObject jBody) { HttpRequestMessage request = this.ActionContext.Request; HttpStatusCode result = await VerifyBearerToken( request, "https://api.contoso.com", "expenseapproval@contoso.com");
switch (result) { case HttpStatusCode.Unauthorized:
return request.CreateErrorResponse( HttpStatusCode.Unauthorized, new HttpError());
case HttpStatusCode.Forbidden: HttpResponseMessage errorResponse = this.Request.CreateErrorResponse(HttpStatusCode.Forbidden, new HttpError()); errorResponse.Headers.Add("CARD-ACTION-STATUS", "Invalid sender or the action performer is not allowed."); return errorResponse; default: break; } string expenseId = jBody["id"].ToString(); // Process and approve the expense report. HttpResponseMessage response = this.Request.CreateResponse(HttpStatusCode.OK); response.Headers.Add("CARD-ACTION-STATUS", "The expense was approved."); return response; }


首先,此方法验证授权标头中是否有持有者令牌。然后,它会初始化 ActionableMessageTokenValidator 的新实例,并调用 ValidateToken­Async 方法。此方法需要使用两个参数。第一个参数是持有者令牌本身。第二个参数是 Web 服务基 URL。如果查看解码后的 JWT,这就是 aud(受众)声明值。基本上是说,令牌是针对目标受众(用户的 Web 服务,而不是其他任何 Web 服务)颁发。在此示例中,要调用的 API 是 http://api.contoso.com/expense/approve。声明中的值为基 URL,即 https://api.contoso.com。


此方法返回 ActionableMessage­TokenValidationResult 实例。首先,检查属性 ValidationSucceeded。如果验证成功,则值为 True;否则,值为 False。


结果还包括另外两个对第三方有用的属性。第一个是 Sender。此为令牌中的发件人声明值。这是发送可操作邮件的帐户的电子邮件地址。第二个是 ActionPerformer,也就是子声明值。这是执行过操作的用户的电子邮件地址。在此示例中,只有电子邮件地址包含 @contoso.com 的用户,才能批准或拒绝费用报表。可以将代码替换为自己编写的更复杂验证代码。



10
刷新操作卡



目前,向用户提供反馈的唯一方法是通过 CARD-ACTION-STATUS 标头。此标头的值在邮件卡保留区域中向用户显示。另一种方法是向用户返回刷新操作卡。我们的想法是将当前操作卡替换为其他操作卡。需要这样做的原因有一些。例如,不希望用户再次批准或拒绝已获准的费用报表。相反,应告诉用

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时候联系我们修改或删除,在此表示感谢。

特别提醒:

1、请用户自行保存原始数据,为确保安全网站使用完即被永久销毁,如何人将无法再次获取。

2、如果上次文件较大或者涉及到复杂运算的数据,可能需要一定的时间,请耐心等待一会。

3、请按照用户协议文明上网,如果发现用户存在恶意行为,包括但不限于发布不合适言论妄图

     获取用户隐私信息等行为,网站将根据掌握的情况对用户进行限制部分行为、永久封号等处罚。

4、如果文件下载失败可能是弹出窗口被浏览器拦截,点击允许弹出即可,一般在网址栏位置设置

5、欢迎将网站推荐给其他人,网站持续更新更多功能敬请期待,收藏网站高效办公不迷路。

      



登录后回复

共有0条评论