Skip to content

Commit 86f45f7

Browse files
committed
Refactoring AuthorizationLevelAttribute to split system level authorization with named keys
1 parent 029f15a commit 86f45f7

File tree

7 files changed

+146
-67
lines changed

7 files changed

+146
-67
lines changed

src/WebJobs.Script.WebHost/Filters/AuthorizationLevelAttribute.cs

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,18 @@
1414

1515
namespace Microsoft.Azure.WebJobs.Script.WebHost.Filters
1616
{
17-
public sealed class AuthorizationLevelAttribute : AuthorizationFilterAttribute
17+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
18+
public class AuthorizationLevelAttribute : AuthorizationFilterAttribute
1819
{
1920
public const string FunctionsKeyHeaderName = "x-functions-key";
2021

21-
public AuthorizationLevelAttribute(AuthorizationLevel level, string keyName = null)
22+
public AuthorizationLevelAttribute(AuthorizationLevel level)
2223
{
2324
Level = level;
24-
KeyName = keyName;
2525
}
2626

2727
public AuthorizationLevel Level { get; }
2828

29-
public string KeyName { get; }
30-
3129
public async override Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
3230
{
3331
if (actionContext == null)
@@ -39,7 +37,7 @@ public async override Task OnAuthorizationAsync(HttpActionContext actionContext,
3937
// as a request property
4038
var secretManager = actionContext.ControllerContext.Configuration.DependencyResolver.GetService<ISecretManager>();
4139
var settings = actionContext.ControllerContext.Configuration.DependencyResolver.GetService<WebHostSettings>();
42-
var requestAuthorizationLevel = await GetAuthorizationLevelAsync(actionContext.Request, secretManager, keyName: KeyName);
40+
var requestAuthorizationLevel = await GetAuthorizationLevelAsync(actionContext.Request, secretManager, EvaluateKeyMatch);
4341
actionContext.Request.Properties[ScriptConstants.AzureFunctionsHttpRequestAuthorizationLevel] = requestAuthorizationLevel;
4442

4543
if (settings.IsAuthDisabled ||
@@ -55,7 +53,15 @@ public async override Task OnAuthorizationAsync(HttpActionContext actionContext,
5553
}
5654
}
5755

58-
internal static async Task<AuthorizationLevel> GetAuthorizationLevelAsync(HttpRequestMessage request, ISecretManager secretManager, string functionName = null, string keyName = null)
56+
protected virtual bool EvaluateKeyMatch(IDictionary<string, string> secrets, string keyValue) => HasMatchingKey(secrets, keyValue);
57+
58+
internal static Task<AuthorizationLevel> GetAuthorizationLevelAsync(HttpRequestMessage request, ISecretManager secretManager, string functionName = null)
59+
{
60+
return GetAuthorizationLevelAsync(request, secretManager, HasMatchingKey, functionName);
61+
}
62+
63+
internal static async Task<AuthorizationLevel> GetAuthorizationLevelAsync(HttpRequestMessage request, ISecretManager secretManager,
64+
Func<IDictionary<string, string>, string, bool> matchEvaluator, string functionName = null)
5965
{
6066
// first see if a key value is specified via headers or query string (header takes precedence)
6167
IEnumerable<string> values;
@@ -80,13 +86,13 @@ internal static async Task<AuthorizationLevel> GetAuthorizationLevelAsync(HttpRe
8086
return AuthorizationLevel.Admin;
8187
}
8288

83-
if (HasMatchingKey(hostSecrets.SystemKeys, keyValue, keyName))
89+
if (matchEvaluator(hostSecrets.SystemKeys, keyValue))
8490
{
8591
return AuthorizationLevel.System;
8692
}
8793

8894
// see if the key specified matches the host function key
89-
if (HasMatchingKey(hostSecrets.FunctionKeys, keyValue, keyName))
95+
if (matchEvaluator(hostSecrets.FunctionKeys, keyValue))
9096
{
9197
return AuthorizationLevel.Function;
9298
}
@@ -95,7 +101,7 @@ internal static async Task<AuthorizationLevel> GetAuthorizationLevelAsync(HttpRe
95101
if (functionName != null)
96102
{
97103
IDictionary<string, string> functionSecrets = await secretManager.GetFunctionSecretsAsync(functionName);
98-
if (HasMatchingKey(functionSecrets, keyValue, keyName))
104+
if (matchEvaluator(functionSecrets, keyValue))
99105
{
100106
return AuthorizationLevel.Function;
101107
}
@@ -105,9 +111,8 @@ internal static async Task<AuthorizationLevel> GetAuthorizationLevelAsync(HttpRe
105111
return AuthorizationLevel.Anonymous;
106112
}
107113

108-
private static bool HasMatchingKey(IDictionary<string, string> secrets, string keyValue, string keyName)
109-
=> secrets != null &&
110-
secrets.Any(kvp => (keyName == null || string.Equals(kvp.Key, keyName, StringComparison.OrdinalIgnoreCase)) && Key.SecretValueEquals(kvp.Value, keyValue));
114+
private static bool HasMatchingKey(IDictionary<string, string> secrets, string keyValue)
115+
=> secrets != null && secrets.Values.Any(s => Key.SecretValueEquals(s, keyValue));
111116

112117
internal static bool SkipAuthorization(HttpActionContext actionContext)
113118
{
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
8+
namespace Microsoft.Azure.WebJobs.Script.WebHost.Filters
9+
{
10+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
11+
public sealed class SystemAuthorizationLevelAttribute : AuthorizationLevelAttribute
12+
{
13+
public SystemAuthorizationLevelAttribute(string keyName)
14+
: base(AuthorizationLevel.System)
15+
{
16+
KeyName = keyName;
17+
}
18+
19+
public string KeyName { get; }
20+
21+
protected override bool EvaluateKeyMatch(IDictionary<string, string> secrets, string keyValue)
22+
=> EvaluateKeyMatch(secrets, keyValue, KeyName);
23+
24+
internal static bool EvaluateKeyMatch(IDictionary<string, string> secrets, string keyValue, string keyName)
25+
=> secrets != null &&
26+
secrets.Any(kvp => (keyName == null || string.Equals(kvp.Key, keyName, StringComparison.OrdinalIgnoreCase)) && Key.SecretValueEquals(kvp.Value, keyValue));
27+
}
28+
}

src/WebJobs.Script.WebHost/GlobalSuppressions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,5 @@
9191
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.WebHost.Controllers.AdminController.#Ping()")]
9292
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.WebHost.HostSecretsInfo.#SystemKeys")]
9393
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.WebHost.FunctionSecrets.#Keys")]
94-
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.WebHost.HostSecrets.#SystemKeys")]
94+
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Scope = "member", Target = "Microsoft.Azure.WebJobs.Script.WebHost.HostSecrets.#SystemKeys")]
95+
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Scope = "type", Target = "Microsoft.Azure.WebJobs.Script.WebHost.Filters.AuthorizationLevelAttribute")]

src/WebJobs.Script.WebHost/WebJobs.Script.WebHost.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@
418418
<Compile Include="Diagnostics\WebHostMetricsLogger.cs" />
419419
<Compile Include="Extensions\DependencyResolverExtensions.cs" />
420420
<Compile Include="Filters\AuthorizationLevelAttribute.cs" />
421+
<Compile Include="Filters\SystemAuthorizationLevelAttribute.cs" />
421422
<Compile Include="Formatting\PlaintextMediaTypeFormatter.cs" />
422423
<Compile Include="Global.asax.cs">
423424
<DependentUpon>Global.asax</DependentUpon>

0 commit comments

Comments
 (0)