Skip to content

Commit d15e9ad

Browse files
committed
Allow 'code' value to be specified via headers for WebHooks (#1098)
1 parent e1e5373 commit d15e9ad

File tree

4 files changed

+121
-0
lines changed

4 files changed

+121
-0
lines changed

src/WebJobs.Script.WebHost/WebHooks/WebHookReceiverManager.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Linq;
88
using System.Net.Http;
99
using System.Threading.Tasks;
10+
using System.Web;
1011
using System.Web.Http;
1112
using System.Web.Http.Controllers;
1213
using Autofac;
@@ -15,6 +16,7 @@
1516
using Microsoft.AspNet.WebHooks.Config;
1617
using Microsoft.Azure.WebJobs.Host;
1718
using Microsoft.Azure.WebJobs.Script.Description;
19+
using Microsoft.Azure.WebJobs.Script.WebHost.Filters;
1820

1921
namespace Microsoft.Azure.WebJobs.Script.WebHost.WebHooks
2022
{
@@ -67,6 +69,10 @@ public async Task<HttpResponseMessage> HandleRequestAsync(FunctionDescriptor fun
6769
return new HttpResponseMessage(System.Net.HttpStatusCode.InternalServerError);
6870
}
6971

72+
// if the code value is specified via header rather than query string
73+
// promote it to the query string (that's what the WebHook library expects)
74+
ApplyHeaderValuesToQuery(request);
75+
7076
HttpRequestContext context = new HttpRequestContext
7177
{
7278
Configuration = _httpConfiguration
@@ -92,6 +98,25 @@ public async Task<HttpResponseMessage> HandleRequestAsync(FunctionDescriptor fun
9298
return await receiver.ReceiveAsync(webhookId, context, request);
9399
}
94100

101+
internal static void ApplyHeaderValuesToQuery(HttpRequestMessage request)
102+
{
103+
var query = HttpUtility.ParseQueryString(request.RequestUri.Query);
104+
IEnumerable<string> values = null;
105+
if (request.Headers.TryGetValues(AuthorizationLevelAttribute.FunctionsKeyHeaderName, out values) &&
106+
string.IsNullOrEmpty(query.Get("code")))
107+
{
108+
string value = values.FirstOrDefault();
109+
if (!string.IsNullOrEmpty(value))
110+
{
111+
query["code"] = value;
112+
}
113+
114+
UriBuilder builder = new UriBuilder(request.RequestUri);
115+
builder.Query = query.ToString();
116+
request.RequestUri = builder.Uri;
117+
}
118+
}
119+
95120
private static string GetClientID(HttpRequestMessage request)
96121
{
97122
string keyValue = null;

test/WebJobs.Script.Tests.Integration/SamplesEndToEndTests.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,26 @@ public async Task GenericWebHook_Post_NamedKey_Succeeds()
613613
Assert.Equal("Value: Foobar", jsonObject["result"]);
614614
}
615615

616+
[Fact]
617+
public async Task GenericWebHook_Post_NamedKey_Headers_Succeeds()
618+
{
619+
// Authenticate using values specified via headers,
620+
// rather than URI query params
621+
string uri = "api/webhook-generic";
622+
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, uri);
623+
request.Headers.Add("x-functions-key", "1388a6b0d05eca2237f10e4a4641260b0a08f3a6");
624+
request.Headers.Add("x-functions-clientid", "testclient");
625+
request.Content = new StringContent("{ 'value': 'Foobar' }");
626+
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
627+
628+
HttpResponseMessage response = await this._fixture.HttpClient.SendAsync(request);
629+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
630+
Assert.Equal("application/json", response.Content.Headers.ContentType.MediaType);
631+
string body = await response.Content.ReadAsStringAsync();
632+
JObject jsonObject = JObject.Parse(body);
633+
Assert.Equal("Value: Foobar", jsonObject["result"]);
634+
}
635+
616636
[Fact]
617637
public async Task GenericWebHook_Post_NamedKeyInHeader_Succeeds()
618638
{
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System.Net.Http;
5+
using Microsoft.Azure.WebJobs.Script.WebHost.Filters;
6+
using Microsoft.Azure.WebJobs.Script.WebHost.WebHooks;
7+
using Xunit;
8+
9+
namespace Microsoft.Azure.WebJobs.Script.Tests.WebHooks
10+
{
11+
public class WebHookReceiverManagerTests
12+
{
13+
private const string TestKey = "1388a6b0d05eca2237f10e4a4641260b0a08f3a6";
14+
private const string TestId = "testclient";
15+
16+
[Fact]
17+
public void ApplyHeaderValuesToQuery_NoHeadersPresent_ReturnsExpectedValue()
18+
{
19+
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "http://test.com/api/test");
20+
WebHookReceiverManager.ApplyHeaderValuesToQuery(request);
21+
Assert.Equal($"http://test.com/api/test", request.RequestUri.ToString());
22+
23+
request = new HttpRequestMessage(HttpMethod.Post, $"http://test.com/api/test?code={TestKey}&clientid={TestId}");
24+
WebHookReceiverManager.ApplyHeaderValuesToQuery(request);
25+
Assert.Equal($"http://test.com/api/test?code={TestKey}&clientid={TestId}", request.RequestUri.ToString());
26+
}
27+
28+
[Fact]
29+
public void ApplyHeaderValuesToQuery_CodeInHeaders_ReturnsExpectedValue()
30+
{
31+
var request = new HttpRequestMessage(HttpMethod.Post, $"http://test.com/api/test?clientid={TestId}");
32+
request.Headers.Add(AuthorizationLevelAttribute.FunctionsKeyHeaderName, TestKey);
33+
WebHookReceiverManager.ApplyHeaderValuesToQuery(request);
34+
Assert.Equal($"http://test.com/api/test?clientid={TestId}&code={TestKey}", request.RequestUri.ToString());
35+
}
36+
37+
[Fact]
38+
public void ApplyHeaderValuesToQuery_CodeAndClientIdInHeadesr_ReturnsExpectedValue()
39+
{
40+
var request = new HttpRequestMessage(HttpMethod.Post, "http://test.com/api/test");
41+
request.Headers.Add(AuthorizationLevelAttribute.FunctionsKeyHeaderName, TestKey);
42+
request.Headers.Add(WebHookReceiverManager.FunctionsClientIdHeaderName, TestId);
43+
WebHookReceiverManager.ApplyHeaderValuesToQuery(request);
44+
Assert.Equal($"http://test.com/api/test?code={TestKey}", request.RequestUri.ToString());
45+
}
46+
47+
[Fact]
48+
public void ApplyHeaderValuesToQuery_ValuesInHeadersAndQuery_ReturnsExpectedValue()
49+
{
50+
// query string value takes precedence
51+
var request = new HttpRequestMessage(HttpMethod.Post, "http://test.com/api/test?code=foo&clientid=bar");
52+
request.Headers.Add(AuthorizationLevelAttribute.FunctionsKeyHeaderName, TestKey);
53+
WebHookReceiverManager.ApplyHeaderValuesToQuery(request);
54+
Assert.Equal($"http://test.com/api/test?code=foo&clientid=bar", request.RequestUri.ToString());
55+
56+
// case insensitive query param lookups
57+
request = new HttpRequestMessage(HttpMethod.Post, "http://test.com/api/test?CODE=foo&clientid=bar");
58+
request.Headers.Add(AuthorizationLevelAttribute.FunctionsKeyHeaderName, TestKey);
59+
WebHookReceiverManager.ApplyHeaderValuesToQuery(request);
60+
Assert.Equal($"http://test.com/api/test?CODE=foo&clientid=bar", request.RequestUri.ToString());
61+
62+
// code via query param, id via header
63+
request = new HttpRequestMessage(HttpMethod.Post, $"http://test.com/api/test?code={TestKey}");
64+
request.Headers.Add(WebHookReceiverManager.FunctionsClientIdHeaderName, TestId);
65+
WebHookReceiverManager.ApplyHeaderValuesToQuery(request);
66+
Assert.Equal($"http://test.com/api/test?code={TestKey}", request.RequestUri.ToString());
67+
68+
// id via query param, code via header
69+
request = new HttpRequestMessage(HttpMethod.Post, $"http://test.com/api/test?clientid={TestId}");
70+
request.Headers.Add(AuthorizationLevelAttribute.FunctionsKeyHeaderName, TestKey);
71+
WebHookReceiverManager.ApplyHeaderValuesToQuery(request);
72+
Assert.Equal($"http://test.com/api/test?clientid={TestId}&code={TestKey}", request.RequestUri.ToString());
73+
}
74+
}
75+
}

test/WebJobs.Script.Tests/WebJobs.Script.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,7 @@
486486
<Compile Include="WebApiApplicationTests.cs" />
487487
<Compile Include="WebHooks\DynamicWebHookReceiverConfigTests.cs" />
488488
<Compile Include="Handlers\WebScriptHostHandlerTests.cs" />
489+
<Compile Include="WebHooks\WebHookReceiverManagerTests.cs" />
489490
<None Include="app.config">
490491
<SubType>Designer</SubType>
491492
</None>

0 commit comments

Comments
 (0)