Skip to content

Commit 007d44f

Browse files
committed
Add Function checks back to KeysController
1 parent a964749 commit 007d44f

File tree

8 files changed

+190
-63
lines changed

8 files changed

+190
-63
lines changed

src/WebJobs.Script.WebHost/App_Start/AutofacBootstrap.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the MIT License. See License.txt in the project root for license information.
33

4+
using System.IO.Abstractions;
45
using Autofac;
56
using Microsoft.Azure.WebJobs.Host;
67
using Microsoft.Azure.WebJobs.Script.Config;
@@ -16,6 +17,7 @@ internal static void Initialize(ScriptSettingsManager settingsManager, Container
1617
{
1718
builder.RegisterInstance(settingsManager);
1819
builder.RegisterInstance(settings);
20+
builder.Register<IFileSystem>(_ => FileUtility.Instance).SingleInstance();
1921

2022
builder.RegisterType<WebHostResolver>().SingleInstance();
2123
builder.RegisterType<DefaultSecretManagerFactory>().As<ISecretManagerFactory>().SingleInstance();

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

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.IO;
7+
using System.IO.Abstractions;
68
using System.Linq;
79
using System.Net;
810
using System.Threading.Tasks;
@@ -26,12 +28,16 @@ public class KeysController : ApiController
2628
private readonly ISecretManager _secretManager;
2729
private readonly TraceWriter _traceWriter;
2830
private readonly ILogger _logger;
31+
private readonly WebHostSettings _settings;
32+
private readonly IFileSystem _fileSystem;
2933

30-
public KeysController(ISecretManager secretManager, TraceWriter traceWriter, ILoggerFactory loggerFactory)
34+
public KeysController(WebHostSettings settings, ISecretManager secretManager, TraceWriter traceWriter, ILoggerFactory loggerFactory, IFileSystem fileSystem)
3135
{
36+
_settings = settings;
3237
_secretManager = secretManager;
3338
_traceWriter = traceWriter.WithDefaults($"{ScriptConstants.TraceSourceSecretManagement}.Api");
3439
_logger = loggerFactory?.CreateLogger(ScriptConstants.LogCategoryKeysController);
40+
_fileSystem = fileSystem;
3541
}
3642

3743
[HttpGet]
@@ -86,6 +92,11 @@ public async Task<IHttpActionResult> GetHostKey(string name)
8692

8793
private async Task<IDictionary<string, string>> GetFunctionKeys(string functionName)
8894
{
95+
if (!IsFunction(functionName))
96+
{
97+
return null;
98+
}
99+
89100
return await _secretManager.GetFunctionSecretsAsync(functionName);
90101
}
91102

@@ -155,6 +166,11 @@ private async Task<IHttpActionResult> PutKeyAsync(string keyName, Key key, strin
155166

156167
private async Task<IHttpActionResult> AddOrUpdateSecretAsync(string keyName, string value, string keyScope, ScriptSecretsType secretsType)
157168
{
169+
if (secretsType == ScriptSecretsType.Function && keyScope != null && !IsFunction(keyScope))
170+
{
171+
return NotFound();
172+
}
173+
158174
KeyOperationResult operationResult;
159175
if (secretsType == ScriptSecretsType.Host && string.Equals(keyName, MasterKeyName, StringComparison.OrdinalIgnoreCase))
160176
{
@@ -224,7 +240,8 @@ private async Task<IHttpActionResult> DeleteFunctionSecretAsync(string keyName,
224240
return BadRequest("Invalid key name.");
225241
}
226242

227-
if (!await _secretManager.DeleteSecretAsync(keyName, keyScope, secretsType))
243+
if ((secretsType == ScriptSecretsType.Function && keyScope != null && !IsFunction(keyScope)) ||
244+
!await _secretManager.DeleteSecretAsync(keyName, keyScope, secretsType))
228245
{
229246
// the key was not found
230247
return NotFound();
@@ -236,5 +253,12 @@ private async Task<IHttpActionResult> DeleteFunctionSecretAsync(string keyName,
236253

237254
return StatusCode(HttpStatusCode.NoContent);
238255
}
256+
257+
private bool IsFunction(string functionName)
258+
{
259+
string json = null;
260+
string functionPath = Path.Combine(_settings.ScriptPath, functionName);
261+
return ScriptHost.TryReadFunctionConfig(functionPath, out json, _fileSystem);
262+
}
239263
}
240264
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,9 @@
351351
<HintPath>..\..\packages\System.IO.4.3.0\lib\net462\System.IO.dll</HintPath>
352352
<Private>True</Private>
353353
</Reference>
354+
<Reference Include="System.IO.Abstractions, Version=2.0.0.140, Culture=neutral, processorArchitecture=MSIL">
355+
<HintPath>..\..\packages\System.IO.Abstractions.2.0.0.140\lib\net40\System.IO.Abstractions.dll</HintPath>
356+
</Reference>
354357
<Reference Include="System.IO.Compression, Version=4.1.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
355358
<HintPath>..\..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll</HintPath>
356359
<Private>True</Private>

src/WebJobs.Script.WebHost/packages.config

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
<package id="System.Globalization.Calendars" version="4.3.0" targetFramework="net471" />
108108
<package id="System.IdentityModel.Tokens.Jwt" version="5.1.3" targetFramework="net471" />
109109
<package id="System.IO" version="4.3.0" targetFramework="net471" />
110+
<package id="System.IO.Abstractions" version="2.0.0.140" targetFramework="net471" />
110111
<package id="System.IO.Compression" version="4.3.0" targetFramework="net471" />
111112
<package id="System.IO.Compression.ZipFile" version="4.3.0" targetFramework="net471" />
112113
<package id="System.IO.FileSystem" version="4.3.0" targetFramework="net471" />

src/WebJobs.Script/Extensions/FileUtility.cs

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,38 @@
33

44
using System;
55
using System.IO;
6+
using System.IO.Abstractions;
67
using System.Text;
78
using System.Threading.Tasks;
89

910
namespace Microsoft.Azure.WebJobs.Script
1011
{
1112
public static class FileUtility
1213
{
14+
private static IFileSystem _default = new FileSystem();
15+
private static IFileSystem _instance;
16+
17+
public static IFileSystem Instance
18+
{
19+
get { return _instance ?? _default; }
20+
set { _instance = value; }
21+
}
22+
1323
public static void EnsureDirectoryExists(string path)
1424
{
15-
if (!Directory.Exists(path))
25+
if (!Instance.Directory.Exists(path))
1626
{
17-
Directory.CreateDirectory(path);
27+
Instance.Directory.CreateDirectory(path);
1828
}
1929
}
2030

2131
public static Task DeleteDirectoryAsync(string path, bool recursive)
2232
{
2333
return Task.Run(() =>
2434
{
25-
if (Directory.Exists(path))
35+
if (Instance.Directory.Exists(path))
2636
{
27-
Directory.Delete(path, recursive);
37+
Instance.Directory.Delete(path, recursive);
2838
}
2939
});
3040
}
@@ -33,9 +43,9 @@ public static Task<bool> DeleteIfExistsAsync(string path)
3343
{
3444
return Task.Run(() =>
3545
{
36-
if (File.Exists(path))
46+
if (Instance.File.Exists(path))
3747
{
38-
File.Delete(path);
48+
Instance.File.Delete(path);
3949
return true;
4050
}
4151
return false;
@@ -55,7 +65,8 @@ public static async Task WriteAsync(string path, string contents, Encoding encod
5565
}
5666

5767
encoding = encoding ?? Encoding.UTF8;
58-
using (var writer = new StreamWriter(path, false, encoding, 4096))
68+
using (Stream fileStream = OpenFile(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete))
69+
using (var writer = new StreamWriter(fileStream, encoding, 4096))
5970
{
6071
await writer.WriteAsync(contents);
6172
}
@@ -69,12 +80,20 @@ public static async Task<string> ReadAsync(string path, Encoding encoding = null
6980
}
7081

7182
encoding = encoding ?? Encoding.UTF8;
72-
using (var reader = new StreamReader(path, encoding, true, 4096))
83+
using (var fileStream = OpenFile(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete))
84+
using (var reader = new StreamReader(fileStream, encoding, true, 4096))
7385
{
7486
return await reader.ReadToEndAsync();
7587
}
7688
}
7789

90+
public static string ReadAllText(string path) => Instance.File.ReadAllText(path);
91+
92+
public static Stream OpenFile(string path, FileMode mode, FileAccess access = FileAccess.ReadWrite, FileShare share = FileShare.None)
93+
{
94+
return Instance.File.Open(path, mode, access, share);
95+
}
96+
7897
public static string GetRelativePath(string path1, string path2)
7998
{
8099
if (path1 == null)
@@ -124,7 +143,7 @@ public static Task<string[]> GetFilesAsync(string path, string prefix)
124143

125144
return Task.Run(() =>
126145
{
127-
return Directory.GetFiles(path, prefix);
146+
return Instance.Directory.GetFiles(path, prefix);
128147
});
129148
}
130149
}

src/WebJobs.Script/Host/ScriptHost.cs

Lines changed: 70 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,59 +1093,66 @@ public static Collection<FunctionMetadata> ReadFunctionMetadata(IEnumerable<stri
10931093
traceWriter.Info(msg);
10941094
}
10951095

1096-
foreach (var scriptDir in functionDirectories)
1096+
foreach (var functionDirectory in functionDirectories)
10971097
{
1098-
string functionName = null;
1099-
try
1098+
var function = ReadFunctionMetadata(functionDirectory, traceWriter, logger, functionErrors, settingsManager, functionWhitelist);
1099+
if (function != null)
11001100
{
1101-
// read the function config
1102-
string functionConfigPath = Path.Combine(scriptDir, ScriptConstants.FunctionMetadataFileName);
1103-
string json = null;
1104-
try
1105-
{
1106-
json = File.ReadAllText(functionConfigPath);
1107-
}
1108-
catch (FileNotFoundException)
1109-
{
1110-
// not a function directory
1111-
continue;
1112-
}
1101+
functions.Add(function);
1102+
}
1103+
}
11131104

1114-
functionName = Path.GetFileName(scriptDir);
1115-
if (functionWhitelist != null &&
1116-
!functionWhitelist.Contains(functionName, StringComparer.OrdinalIgnoreCase))
1117-
{
1118-
// a functions filter has been specified and the current function is
1119-
// not in the filter list
1120-
continue;
1121-
}
1105+
return functions;
1106+
}
11221107

1123-
ValidateName(functionName);
1108+
public static FunctionMetadata ReadFunctionMetadata(string functionDirectory, TraceWriter traceWriter, ILogger logger, Dictionary<string, Collection<string>> functionErrors, ScriptSettingsManager settingsManager = null, IEnumerable<string> functionWhitelist = null)
1109+
{
1110+
string functionName = null;
11241111

1125-
JObject functionConfig = JObject.Parse(json);
1112+
try
1113+
{
1114+
// read the function config
1115+
string json = null;
1116+
if (!TryReadFunctionConfig(functionDirectory, out json))
1117+
{
1118+
// not a function directory
1119+
return null;
1120+
}
11261121

1127-
string functionError = null;
1128-
FunctionMetadata functionMetadata = null;
1129-
if (!TryParseFunctionMetadata(functionName, functionConfig, traceWriter, logger, scriptDir, settingsManager, out functionMetadata, out functionError))
1130-
{
1131-
// for functions in error, log the error and don't
1132-
// add to the functions collection
1133-
AddFunctionError(functionErrors, functionName, functionError);
1134-
continue;
1135-
}
1136-
else if (functionMetadata != null)
1137-
{
1138-
functions.Add(functionMetadata);
1139-
}
1122+
functionName = Path.GetFileName(functionDirectory);
1123+
if (functionWhitelist != null &&
1124+
!functionWhitelist.Contains(functionName, StringComparer.OrdinalIgnoreCase))
1125+
{
1126+
// a functions filter has been specified and the current function is
1127+
// not in the filter list
1128+
return null;
11401129
}
1141-
catch (Exception ex)
1130+
1131+
ValidateName(functionName);
1132+
1133+
JObject functionConfig = JObject.Parse(json);
1134+
1135+
string functionError = null;
1136+
FunctionMetadata functionMetadata = null;
1137+
if (!TryParseFunctionMetadata(functionName, functionConfig, traceWriter, logger, functionDirectory, settingsManager, out functionMetadata, out functionError))
11421138
{
1143-
// log any unhandled exceptions and continue
1144-
AddFunctionError(functionErrors, functionName, Utility.FlattenException(ex, includeSource: false), isFunctionShortName: true);
1139+
// for functions in error, log the error and don't
1140+
// add to the functions collection
1141+
AddFunctionError(functionErrors, functionName, functionError);
1142+
return null;
1143+
}
1144+
else if (functionMetadata != null)
1145+
{
1146+
return functionMetadata;
11451147
}
11461148
}
1149+
catch (Exception ex)
1150+
{
1151+
// log any unhandled exceptions and continue
1152+
AddFunctionError(functionErrors, functionName, Utility.FlattenException(ex, includeSource: false), isFunctionShortName: true);
1153+
}
11471154

1148-
return functions;
1155+
return null;
11491156
}
11501157

11511158
internal Collection<FunctionMetadata> ReadProxyMetadata(ScriptHostConfiguration config, ScriptSettingsManager settingsManager = null)
@@ -1177,6 +1184,27 @@ internal Collection<FunctionMetadata> ReadProxyMetadata(ScriptHostConfiguration
11771184
return null;
11781185
}
11791186

1187+
internal static bool TryReadFunctionConfig(string scriptDir, out string json, IFileSystem fileSystem = null)
1188+
{
1189+
json = null;
1190+
fileSystem = fileSystem ?? FileUtility.Instance;
1191+
1192+
// read the function config
1193+
string functionConfigPath = Path.Combine(scriptDir, ScriptConstants.FunctionMetadataFileName);
1194+
try
1195+
{
1196+
json = fileSystem.File.ReadAllText(functionConfigPath);
1197+
}
1198+
catch (IOException ex) when
1199+
(ex is FileNotFoundException || ex is DirectoryNotFoundException)
1200+
{
1201+
// not a function directory
1202+
return false;
1203+
}
1204+
1205+
return true;
1206+
}
1207+
11801208
private Collection<FunctionMetadata> LoadProxyRoutes(string proxiesJson)
11811209
{
11821210
var proxies = new Collection<FunctionMetadata>();

0 commit comments

Comments
 (0)