Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Files configuration endpoint #143

Merged
merged 6 commits into from
Oct 11, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ after_build:
Push-Location $securityScript.Directory;
.\security.ps1 Add-SelfRights -Path $appsettings.FullName;
Pop-Location;
[System.IO.File]::ReadAllText("$env:iis_admin_solution_dir\test\appsettings.test.json").replace("IIS Administrators", [System.Security.Principal.WindowsIdentity]::GetCurrent().Name.Replace('\', '\\')) | Out-File $appsettings.FullName;
[System.IO.File]::ReadAllText("$env:iis_admin_solution_dir\test\appsettings.test.json").replace("`"Administrator`"", "`"$([System.Security.Principal.WindowsIdentity]::GetCurrent().Name.Replace('\', '\\'))`"") | Out-File $appsettings.FullName;
Restart-Service "Microsoft IIS Administration";
test_script:
- ps: dotnet test "$env:iis_admin_solution_dir\test\Microsoft.IIS.Administration.Tests\Microsoft.IIS.Administration.Tests.csproj"
Expand Down
11 changes: 11 additions & 0 deletions src/Microsoft.IIS.Administration.Core/IConfigurationWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.


namespace Microsoft.IIS.Administration.Core
{
public interface IConfigurationWriter
{
void WriteSection(string name, object value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.


namespace Microsoft.IIS.Administration.Core.Utils
{
using Microsoft.Extensions.Primitives;
using System;
using System.Threading;
using System.Threading.Tasks;

public static class ChangeTokenExtensions
{
public static async Task WaitForChange(this IChangeToken changeToken, int millisecondTimeout)
{
var tcs = new TaskCompletionSource<IChangeToken>();
IDisposable waitForChange = null;
CancellationTokenSource ct = null;

ct = new CancellationTokenSource(millisecondTimeout);

ct.Token.Register(() => tcs.TrySetException(new TimeoutException()), useSynchronizationContext: false);

waitForChange = changeToken.RegisterChangeCallback(_ => tcs.TrySetResult(changeToken), null);

await tcs.Task;

if (ct != null) {
ct.Dispose();
ct = null;
}

if (waitForChange != null) {
waitForChange.Dispose();
waitForChange = null;
}
}
}
}
4 changes: 3 additions & 1 deletion src/Microsoft.IIS.Administration.Files.Core/IOErrors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

namespace Microsoft.IIS.Administration.Files
{
static class HResults
public static class HResults
{
public const int FileInUse = unchecked((int)0x80070020);
public const int FileNotFound = unchecked((int)0x80070003);
public const int PathNotFound = unchecked((int)0x80070035);
}

static class Win32Errors
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
namespace Microsoft.IIS.Administration.Files
{
using Extensions.Configuration;
using Microsoft.Extensions.Primitives;
using Serilog;
using System;
using System.Collections.Generic;
using System.Linq;

public class FileOptions : IFileOptions
public class FileOptions : IFileOptions, IChangeToken
{
private ConfigurationReloadToken _changeToken = new ConfigurationReloadToken();
private List<ILocation> _locations;

private FileOptions() { }
Expand All @@ -26,32 +28,23 @@ public IEnumerable<ILocation> Locations {

public static IFileOptions FromConfiguration(IConfiguration configuration)
{
FileOptions options = EmptyOptions();

if (configuration.GetSection("files").GetChildren().Count() > 0) {
foreach (var child in configuration.GetSection("files:locations").GetChildren()) {
var location = Location.FromSection(child);
if (location != null) {
options.AddLocation(location);
}
}
FileOptions options = new FileOptions() {
_locations = new List<ILocation>()
};

options.SkipResolvingSymbolicLinks = configuration.GetSection("files").GetValue("skip_resolving_symbolic_links", false);
}
options.Set(configuration);

configuration.GetReloadToken().RegisterChangeCallback(_ => {
options.HandleConfigurationChange(configuration);
}, null);

return options;
}

private static FileOptions EmptyOptions()
{
return new FileOptions()
{
_locations = new List<ILocation>()
};
}

public void AddLocation(ILocation location)
{
string rawPath = (location as IRawPath)?.RawPath ?? location.Path;

try {
var p = PathUtil.GetFullPath(location.Path);
location.Path = p;
Expand All @@ -73,7 +66,10 @@ public void AddLocation(ILocation location)
// Only add the location if it doesn't exist
if (!_locations.Any(loc => loc.Path.ToLowerInvariant().TrimEnd(PathUtil.SEPARATORS)
.Equals(location.Path.ToLowerInvariant().TrimEnd(PathUtil.SEPARATORS)))) {
_locations.Add(location);

Location resolved = Location.Clone(location);
resolved.RawPath = rawPath;
_locations.Add(resolved);
}

//
Expand All @@ -82,5 +78,52 @@ public void AddLocation(ILocation location)
return item2.Path.Length - item1.Path.Length;
});
}

#region IChangeToken
public bool HasChanged {
get {
return _changeToken.HasChanged;
}
}

public bool ActiveChangeCallbacks {
get {
return _changeToken.ActiveChangeCallbacks;
}
}

public IDisposable RegisterChangeCallback(Action<object> callback, object state)
{
return this._changeToken.RegisterChangeCallback(callback, state);
}

#endregion

private void Set(IConfiguration configuration)
{
_locations.Clear();

if (configuration.GetSection("files").GetChildren().Count() > 0) {
foreach (var child in configuration.GetSection("files:locations").GetChildren()) {
var location = Location.FromSection(child);
if (location != null) {
AddLocation(location);
}
}

SkipResolvingSymbolicLinks = configuration.GetSection("files").GetValue("skip_resolving_symbolic_links", false);
}
}

private void HandleConfigurationChange(IConfiguration configuration)
{
Set(configuration);
configuration.GetReloadToken().RegisterChangeCallback(_ => {
HandleConfigurationChange(configuration);
}, null);

_changeToken.OnReload();
_changeToken = new ConfigurationReloadToken();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.


namespace Microsoft.IIS.Administration.Files
{
public interface IRawPath
{
string RawPath { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ namespace Microsoft.IIS.Administration.Files
using Extensions.Configuration;
using System.Collections.Generic;

class Location : ILocation
class Location : ILocation, IRawPath
{
public string Alias { get; set; }
public string Path { get; set; }
public string RawPath { get; set; }
public IEnumerable<string> Claims { get; set; }

public static Location FromSection(IConfigurationSection section)
Expand All @@ -26,11 +27,22 @@ public static Location FromSection(IConfigurationSection section)
location = new Location() {
Alias = alias,
Path = path,
RawPath = path,
Claims = claims
};
}

return location;
}

public static Location Clone(ILocation location)
{
return new Location {
Alias = location.Alias,
Path = location.Path,
RawPath = location.Path,
Claims = location.Claims
};
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.


namespace Microsoft.IIS.Administration.Files
{
using AspNetCore.Mvc;
using Core;
using Core.Http;
using Microsoft.AspNetCore.Authorization;
using System;
using System.Linq;
using System.Net;
using System.Threading.Tasks;

[Authorize(Policy = "System")]
public class LocationsController : ApiBaseController
{
LocationsHelper _helper;

public LocationsController(IFileOptions options, IConfigurationWriter configurationWriter)
{
_helper = new LocationsHelper(options, configurationWriter);
}

[HttpGet]
[ResourceInfo(Name = Defines.LocationsName)]
public object Get()
{
return new {
locations = _helper.Options.Locations.Select(l => _helper.ToJsonModel(l))
};
}

[HttpGet]
[ResourceInfo(Name = Defines.LocationsName)]
public object Get(string id)
{
FileId fileId = FileId.FromUuid(id);

ILocation location = _helper.Options.Locations.FirstOrDefault(l => l.Path.Equals(fileId.PhysicalPath, StringComparison.OrdinalIgnoreCase));

if (location == null) {
return NotFound();
}

return _helper.ToJsonModel(location);
}

[HttpPatch]
[ResourceInfo(Name = Defines.LocationsName)]
[Audit(AuditAttribute.ALL)]
public async Task<object> Patch([FromBody] dynamic model, string id)
{
FileId fileId = FileId.FromUuid(id);

ILocation location = _helper.Options.Locations.FirstOrDefault(l => l.Path.Equals(fileId.PhysicalPath, StringComparison.OrdinalIgnoreCase));

if (location == null) {
return NotFound();
}

location = await _helper.UpdateLocation(model, location);

dynamic locModel = _helper.ToJsonModel(location);

if (locModel.id != id) {
return LocationChanged(_helper.GetLocationPath(locModel.id), locModel);
}

return locModel;
}

[HttpPost]
[ResourceInfo(Name = Defines.LocationsName)]
[Audit(AuditAttribute.ALL)]
public async Task<object> Post([FromBody] dynamic model)
{
ILocation location = _helper.CreateLocation(model);

//
// Refresh location to newly added version
location = await _helper.AddLocation(location);

dynamic locModel = _helper.ToJsonModel(location);
return Created(_helper.GetLocationPath(locModel.id), locModel);
}

[HttpDelete]
[Audit(AuditAttribute.ALL)]
public async Task Delete(string id)
{
FileId fileId = FileId.FromUuid(id);

ILocation location = _helper.Options.Locations.FirstOrDefault(l => l.Path.Equals(fileId.PhysicalPath, StringComparison.OrdinalIgnoreCase));

if (location != null) {
await _helper.RemoveLocation(location);
}

Context.Response.StatusCode = (int)HttpStatusCode.NoContent;
}
}
}
5 changes: 5 additions & 0 deletions src/Microsoft.IIS.Administration.Files/Defines.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public class Defines
private const string DOWNLOADS_ENDPOINT = "downloads";
private const string COPY_ENDPOINT = "copy";
private const string MOVE_ENDPOINT = "move";
private const string LOCATIONS_ENDPOINT = "locations";

public const string FilesName = "Microsoft.IIS.Administration.Files";
public const string FileName = "Microsoft.IIS.Administration.File";
Expand All @@ -37,5 +38,9 @@ public class Defines

public static readonly string DOWNLOAD_PATH = $"{DOWNLOADS_ENDPOINT}";
public static readonly ResDef DownloadResource = new ResDef("downloads", new Guid("9DAF09F0-197B-4164-81D5-B6A25154883A"), DOWNLOADS_ENDPOINT);

public const string LocationsName = "Microsoft.IIS.Administration.Files.Locations";
public static readonly string LOCATIONS_PATH = $"{FILES_PATH}/{LOCATIONS_ENDPOINT}";
public static readonly ResDef LocationsResource = new ResDef("locations", new Guid("5A3F8A3F-CF8F-4371-B640-92370A37E1EE"), LOCATIONS_ENDPOINT);
}
}
Loading