Skip to content

Commit fccdf51

Browse files
committed
Disabled functions are now registered, but only invokable via admin API
1 parent 7d69a7b commit fccdf51

File tree

8 files changed

+86
-40
lines changed

8 files changed

+86
-40
lines changed

WebJobs.Script.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebHook-Generic-CSharp", "W
208208
sample\WebHook-Generic-CSharp\run.csx = sample\WebHook-Generic-CSharp\run.csx
209209
EndProjectSection
210210
EndProject
211+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HttpTrigger-Disabled", "HttpTrigger-Disabled", "{97B9DB7D-8014-46A0-9E51-6C02A99E4D38}"
212+
ProjectSection(SolutionItems) = preProject
213+
sample\HttpTrigger-Disabled\function.json = sample\HttpTrigger-Disabled\function.json
214+
sample\HttpTrigger-Disabled\index.js = sample\HttpTrigger-Disabled\index.js
215+
EndProjectSection
216+
EndProject
211217
Global
212218
GlobalSection(SolutionConfigurationPlatforms) = preSolution
213219
Debug|Any CPU = Debug|Any CPU
@@ -274,5 +280,6 @@ Global
274280
{B5FA5BC0-F929-4B85-BC9E-E5C7754D8FC7} = {FF9C0818-30D3-437A-A62D-7A61CA44F459}
275281
{ACEE40C6-DA72-422E-99B6-2FA3824733CE} = {FF9C0818-30D3-437A-A62D-7A61CA44F459}
276282
{DC1805CF-39CE-40E3-9F3A-E7C7DAD52B7E} = {FF9C0818-30D3-437A-A62D-7A61CA44F459}
283+
{97B9DB7D-8014-46A0-9E51-6C02A99E4D38} = {FF9C0818-30D3-437A-A62D-7A61CA44F459}
277284
EndGlobalSection
278285
EndGlobal
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"disabled": true,
3+
"bindings": [
4+
{
5+
"type": "httpTrigger",
6+
"direction": "in",
7+
"methods": [ "get" ]
8+
},
9+
{
10+
"type": "http",
11+
"direction": "out"
12+
}
13+
]
14+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module.exports = function (context, req) {
2+
context.log('Node.js HTTP trigger function invoked!');
3+
4+
context.res = {
5+
status: 200,
6+
body: 'Hello World!'
7+
};
8+
9+
context.done();
10+
}

src/WebJobs.Script.WebHost/Controllers/FunctionsController.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@ public override async Task<HttpResponseMessage> ExecuteAsync(HttpControllerConte
4545
SecretManager secretManager = (SecretManager)controllerContext.Configuration.DependencyResolver.GetService(typeof(SecretManager));
4646
AuthorizationLevel authorizationLevel = AuthorizationLevelAttribute.GetAuthorizationLevel(request, secretManager, functionName: function.Name);
4747

48+
if (function.Metadata.IsDisabled &&
49+
authorizationLevel != AuthorizationLevel.Admin)
50+
{
51+
// disabled functions are not publically addressable w/o Admin level auth
52+
return new HttpResponseMessage(HttpStatusCode.NotFound);
53+
}
54+
4855
// Dispatch the request
4956
HttpTriggerBindingMetadata httpFunctionMetadata = (HttpTriggerBindingMetadata)function.Metadata.InputBindings.FirstOrDefault(p => p.Type == BindingType.HttpTrigger);
5057
bool isWebHook = !string.IsNullOrEmpty(httpFunctionMetadata.WebHookType);

src/WebJobs.Script/Description/CSharp/CSharpFunctionDescriptionProvider.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ protected override Collection<ParameterDescriptor> GetFunctionParameters(IFuncti
8888

8989
try
9090
{
91+
ApplyMethodLevelAttributes(functionMetadata, triggerMetadata, methodAttributes);
92+
9193
MethodInfo functionTarget = csharpInvoker.GetFunctionTargetAsync().Result;
9294
ParameterInfo[] parameters = functionTarget.GetParameters();
9395
Collection<ParameterDescriptor> descriptors = new Collection<ParameterDescriptor>();
@@ -97,7 +99,7 @@ protected override Collection<ParameterDescriptor> GetFunctionParameters(IFuncti
9799
// Is it the trigger parameter?
98100
if (string.Compare(parameter.Name, triggerMetadata.Name, StringComparison.Ordinal) == 0)
99101
{
100-
descriptors.Add(CreateTriggerParameterDescriptor(parameter, triggerMetadata, methodAttributes));
102+
descriptors.Add(CreateTriggerParameterDescriptor(parameter, triggerMetadata));
101103
}
102104
else
103105
{
@@ -152,8 +154,7 @@ protected override Collection<ParameterDescriptor> GetFunctionParameters(IFuncti
152154
return base.GetFunctionParameters(functionInvoker, functionMetadata, triggerMetadata, methodAttributes, inputBindings, outputBindings);
153155
}
154156

155-
private ParameterDescriptor CreateTriggerParameterDescriptor(ParameterInfo parameter, BindingMetadata triggerMetadata,
156-
Collection<CustomAttributeBuilder> methodAttributes)
157+
private ParameterDescriptor CreateTriggerParameterDescriptor(ParameterInfo parameter, BindingMetadata triggerMetadata)
157158
{
158159
ParameterDescriptor triggerParameter = null;
159160
switch (triggerMetadata.Type)
@@ -174,10 +175,10 @@ private ParameterDescriptor CreateTriggerParameterDescriptor(ParameterInfo param
174175
triggerParameter = ParseTimerTrigger((TimerBindingMetadata)triggerMetadata, parameter.ParameterType);
175176
break;
176177
case BindingType.HttpTrigger:
177-
triggerParameter = ParseHttpTrigger((HttpTriggerBindingMetadata)triggerMetadata, methodAttributes, parameter.ParameterType);
178+
triggerParameter = ParseHttpTrigger((HttpTriggerBindingMetadata)triggerMetadata, parameter.ParameterType);
178179
break;
179180
case BindingType.ManualTrigger:
180-
triggerParameter = ParseManualTrigger(triggerMetadata, methodAttributes, parameter.ParameterType);
181+
triggerParameter = ParseManualTrigger(triggerMetadata, parameter.ParameterType);
181182
break;
182183
}
183184

src/WebJobs.Script/Description/FunctionDescriptorProvider.cs

Lines changed: 21 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,6 @@ public virtual bool TryCreate(FunctionMetadata functionMetadata, out FunctionDes
3939

4040
functionDescriptor = null;
4141

42-
if (functionMetadata.IsDisabled)
43-
{
44-
return false;
45-
}
46-
4742
// Default the trigger binding name if a name hasn't
4843
// been specified
4944
// TODO: Remove this logic and always require it to be explicitly
@@ -135,13 +130,15 @@ protected virtual Collection<ParameterDescriptor> GetFunctionParameters(IFunctio
135130
triggerParameter = ParseTimerTrigger((TimerBindingMetadata)triggerMetadata, typeof(TimerInfo));
136131
break;
137132
case BindingType.HttpTrigger:
138-
triggerParameter = ParseHttpTrigger((HttpTriggerBindingMetadata)triggerMetadata, methodAttributes, typeof(HttpRequestMessage));
133+
triggerParameter = ParseHttpTrigger((HttpTriggerBindingMetadata)triggerMetadata, typeof(HttpRequestMessage));
139134
break;
140135
case BindingType.ManualTrigger:
141-
triggerParameter = ParseManualTrigger(triggerMetadata, methodAttributes);
136+
triggerParameter = ParseManualTrigger(triggerMetadata);
142137
break;
143138
}
144139

140+
ApplyMethodLevelAttributes(functionMetadata, triggerMetadata, methodAttributes);
141+
145142
Collection<ParameterDescriptor> parameters = new Collection<ParameterDescriptor>();
146143
triggerParameter.IsTrigger = true;
147144
parameters.Add(triggerParameter);
@@ -158,6 +155,19 @@ protected virtual Collection<ParameterDescriptor> GetFunctionParameters(IFunctio
158155
return parameters;
159156
}
160157

158+
protected static void ApplyMethodLevelAttributes(FunctionMetadata functionMetadata, BindingMetadata triggerMetadata, Collection<CustomAttributeBuilder> methodAttributes)
159+
{
160+
if (functionMetadata.IsDisabled ||
161+
(triggerMetadata.Type == BindingType.HttpTrigger || triggerMetadata.Type == BindingType.ManualTrigger))
162+
{
163+
// the function can be run manually, but there will be no automatic
164+
// triggering
165+
ConstructorInfo ctorInfo = typeof(NoAutomaticTriggerAttribute).GetConstructor(new Type[0]);
166+
CustomAttributeBuilder attributeBuilder = new CustomAttributeBuilder(ctorInfo, new object[0]);
167+
methodAttributes.Add(attributeBuilder);
168+
}
169+
}
170+
161171
protected abstract IFunctionInvoker CreateFunctionInvoker(string scriptFilePath, BindingMetadata triggerMetadata, FunctionMetadata functionMetadata, Collection<FunctionBinding> inputBindings, Collection<FunctionBinding> outputBindings);
162172

163173
protected ParameterDescriptor ParseEventHubTrigger(EventHubBindingMetadata trigger, Type triggerParameterType = null)
@@ -314,58 +324,34 @@ protected ParameterDescriptor ParseTimerTrigger(TimerBindingMetadata trigger, Ty
314324
return new ParameterDescriptor(parameterName, triggerParameterType, attributes);
315325
}
316326

317-
protected ParameterDescriptor ParseHttpTrigger(HttpTriggerBindingMetadata trigger, Collection<CustomAttributeBuilder> methodAttributes, Type triggerParameterType = null)
327+
protected ParameterDescriptor ParseHttpTrigger(HttpTriggerBindingMetadata trigger, Type triggerParameterType = null)
318328
{
319329
if (trigger == null)
320330
{
321331
throw new ArgumentNullException("trigger");
322332
}
323333

324-
if (methodAttributes == null)
325-
{
326-
throw new ArgumentNullException("methodAttributes");
327-
}
328-
329334
if (triggerParameterType == null)
330335
{
331336
triggerParameterType = typeof(string);
332337
}
333338

334-
ConstructorInfo ctorInfo = typeof(NoAutomaticTriggerAttribute).GetConstructor(new Type[0]);
335-
CustomAttributeBuilder attributeBuilder = new CustomAttributeBuilder(ctorInfo, new object[0]);
336-
methodAttributes.Add(attributeBuilder);
337-
338-
ctorInfo = typeof(TraceLevelAttribute).GetConstructor(new Type[] { typeof(TraceLevel) });
339-
attributeBuilder = new CustomAttributeBuilder(ctorInfo, new object[] { TraceLevel.Off });
340-
methodAttributes.Add(attributeBuilder);
341-
342-
string parameterName = trigger.Name;
343-
return new ParameterDescriptor(parameterName, triggerParameterType);
339+
return new ParameterDescriptor(trigger.Name, triggerParameterType);
344340
}
345341

346-
protected ParameterDescriptor ParseManualTrigger(BindingMetadata trigger, Collection<CustomAttributeBuilder> methodAttributes, Type triggerParameterType = null)
342+
protected ParameterDescriptor ParseManualTrigger(BindingMetadata trigger, Type triggerParameterType = null)
347343
{
348344
if (trigger == null)
349345
{
350346
throw new ArgumentNullException("trigger");
351347
}
352348

353-
if (methodAttributes == null)
354-
{
355-
throw new ArgumentNullException("methodAttributes");
356-
}
357-
358349
if (triggerParameterType == null)
359350
{
360351
triggerParameterType = typeof(string);
361352
}
362353

363-
ConstructorInfo ctorInfo = typeof(NoAutomaticTriggerAttribute).GetConstructor(new Type[0]);
364-
CustomAttributeBuilder attributeBuilder = new CustomAttributeBuilder(ctorInfo, new object[0]);
365-
methodAttributes.Add(attributeBuilder);
366-
367-
string parameterName = trigger.Name;
368-
return new ParameterDescriptor(parameterName, triggerParameterType);
354+
return new ParameterDescriptor(trigger.Name, triggerParameterType);
369355
}
370356
}
371357
}

src/WebJobs.Script/GlobalSuppressions.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,6 @@
5757
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.ScriptHost.#ApplyConfiguration(Newtonsoft.Json.Linq.JObject,Microsoft.Azure.WebJobs.Script.ScriptHostConfiguration)")]
5858
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.ScriptHost.#Initialize()")]
5959
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "metadata", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.Binding.FunctionBinding.#.ctor(Microsoft.Azure.WebJobs.Script.ScriptHostConfiguration,Microsoft.Azure.WebJobs.Script.Description.BindingMetadata,System.IO.FileAccess)")]
60+
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.Description.FunctionDescriptorProvider.#ParseHttpTrigger(Microsoft.Azure.WebJobs.Script.Description.HttpTriggerBindingMetadata,System.Type)")]
61+
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.Description.FunctionDescriptorProvider.#ParseHttpTrigger(Microsoft.Azure.WebJobs.Script.Description.HttpTriggerBindingMetadata,System.Type)")]
62+
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.Description.FunctionDescriptorProvider.#ParseManualTrigger(Microsoft.Azure.WebJobs.Script.Description.BindingMetadata,System.Type)")]

test/WebJobs.Script.Tests/SamplesEndToEndTests.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,24 @@ public async Task HttpTrigger_Get_Succeeds()
5050
Assert.Equal("Hello Mathew", body);
5151
}
5252

53+
[Fact]
54+
public async Task HttpTrigger_Disabled_SucceedsWithAdminKey()
55+
{
56+
// first try with function key only - expect 404
57+
string uri = "api/httptrigger-disabled?code=zlnu496ve212kk1p84ncrtdvmtpembduqp25ajjc";
58+
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri);
59+
HttpResponseMessage response = await this._fixture.HttpClient.SendAsync(request);
60+
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
61+
62+
// now try with admin key
63+
uri = "api/httptrigger-disabled?code=t8laajal0a1ajkgzoqlfv5gxr4ebhqozebw4qzdy";
64+
request = new HttpRequestMessage(HttpMethod.Get, uri);
65+
response = await this._fixture.HttpClient.SendAsync(request);
66+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
67+
string body = await response.Content.ReadAsStringAsync();
68+
Assert.Equal("Hello World!", body);
69+
}
70+
5371
[Fact]
5472
public async Task GenericWebHook_Post_Succeeds()
5573
{

0 commit comments

Comments
 (0)