Skip to content

Commit

Permalink
Merge pull request #3282 from AdmiringWorm/http-cache-locked
Browse files Browse the repository at this point in the history
(#3281) Add validation for cache folder permissions
  • Loading branch information
gep13 authored Jul 25, 2023
2 parents a408007 + 2217d63 commit ca3080f
Show file tree
Hide file tree
Showing 7 changed files with 345 additions and 21 deletions.
80 changes: 80 additions & 0 deletions src/chocolatey/StringExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,15 @@
namespace chocolatey
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Web.UI;
using infrastructure.app;
using infrastructure.logging;

Expand Down Expand Up @@ -50,6 +55,81 @@ public static string FormatWith(this string input, params object[] formatting)
}
}

/// <summary>
/// Splits any Newline elements and ensures that each line is no longer than the configured <paramref name="maxLineLength"/>.
/// Lines longer than the specified line length will be split on the last non-letter or digit before the max length.
/// </summary>
/// <param name="input">The input to split any lines on.</param>
/// <param name="linePrefix">The line prefix used for all lines not being the first line.</param>
/// <param name="maxLineLength">Maximum length of the line.</param>
/// <returns>The splitted formatted lines.</returns>
/// <remarks>Not recommended to be used in hot paths.</remarks>
public static string SplitOnSpace(this string input, string linePrefix = "", int maxLineLength = 70)
{
if (string.IsNullOrWhiteSpace(input))
{
return string.Empty;
}

var sb = new StringBuilder(input.Length);
var firstLine = true;
var stack = new Stack<string>(input.Split('\n').Reverse());

while (stack.Count > 0)
{
var currentLine = stack.Pop();

if (currentLine.Length <= maxLineLength)
{
if (!firstLine && !string.IsNullOrEmpty(currentLine))
{
sb.Append(linePrefix);
}

sb.AppendLine(currentLine.TrimEnd());
}
else
{
var index = 70 - 1;

for (; index >= 0; index--)
{
if (char.IsWhiteSpace(currentLine[index]) || !char.IsLetterOrDigit(currentLine[index]))
{
break;
}
}

if (index <= 0)
{
index = maxLineLength;
}

if (!firstLine)
{
sb.Append(linePrefix);
}

var subLine = currentLine.Substring(0, index);
sb.AppendLine(subLine.TrimEnd());

if (stack.Count > 0)
{
var nextLine = currentLine.Substring(index + 1).TrimStart() + stack.Pop();
stack.Push(nextLine);
}
else
{
stack.Push(currentLine.Substring(index + 1).TrimStart());
}
}

firstLine = false;
}

return sb.ToString();
}

/// <summary>
/// Performs a trim only if the item is not null
/// </summary>
Expand Down
3 changes: 2 additions & 1 deletion src/chocolatey/chocolatey.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\Microsoft.CodeAnalysis.BannedApiAnalyzers.3.3.3\build\Microsoft.CodeAnalysis.BannedApiAnalyzers.props" Condition="Exists('..\packages\Microsoft.CodeAnalysis.BannedApiAnalyzers.3.3.3\build\Microsoft.CodeAnalysis.BannedApiAnalyzers.props')" />
<PropertyGroup>
Expand Down Expand Up @@ -241,6 +241,7 @@
<Compile Include="infrastructure.app\rules\ServicableMetadataRule.cs" />
<Compile Include="infrastructure.app\rules\VersionMetadataRule.cs" />
<Compile Include="infrastructure.app\services\RuleService.cs" />
<Compile Include="infrastructure.app\validations\CacheFolderValidationLockdown.cs" />
<Compile Include="infrastructure\commands\ExitCodeDescription.cs" />
<Compile Include="infrastructure\cryptography\DefaultEncryptionUtility.cs" />
<Compile Include="infrastructure\adapters\IEncryptionUtility.cs" />
Expand Down
7 changes: 4 additions & 3 deletions src/chocolatey/infrastructure.app/ApplicationParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ public static class ApplicationParameters
System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile, System.Environment.SpecialFolderOption.DoNotVerify)
: CommonAppDataChocolatey;
public static readonly string HttpCacheUserLocation = _fileSystem.CombinePaths(UserProfilePath, ".chocolatey", "http-cache");
// CommonAppDataChocolatey is always set to ProgramData\Chocolatey.
// So we append HttpCache to that name if it is possible.
public static readonly string HttpCacheSystemLocation = CommonAppDataChocolatey + "HttpCache";
public static readonly string HttpCacheLocation = GetHttpCacheLocation();

public static readonly string UserLicenseFileLocation = _fileSystem.CombinePaths(UserProfilePath, "chocolatey.license.xml");
Expand Down Expand Up @@ -112,9 +115,7 @@ private static string GetHttpCacheLocation()
{
if (ProcessInformation.IsElevated() || string.IsNullOrEmpty(System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile, System.Environment.SpecialFolderOption.DoNotVerify)))
{
// CommonAppDataChocolatey is always set to ProgramData\Chocolatey.
// So we append HttpCache to that name if it is possible.
return CommonAppDataChocolatey + "HttpCache";
return HttpCacheSystemLocation;
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ public void RegisterDependencies(IContainerRegistrator registrator, ChocolateyCo

registrator.RegisterService<IValidation>(
typeof(GlobalConfigurationValidation),
typeof(SystemStateValidation));
typeof(SystemStateValidation),
typeof(CacheFolderLockdownValidation));

// Rule registrations
registrator.RegisterService<IRuleService, RuleService>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Copyright © 2017 - 2023 Chocolatey Software, Inc
// Copyright © 2011 - 2017 RealDimensions Software, LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
//
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace chocolatey.infrastructure.app.validations
{
using System;
using System.Collections.Generic;
using chocolatey.infrastructure.app.configuration;
using chocolatey.infrastructure.filesystem;
using chocolatey.infrastructure.information;
using chocolatey.infrastructure.validations;

public sealed class CacheFolderLockdownValidation : IValidation
{
private readonly IFileSystem _fileSystem;

public CacheFolderLockdownValidation(IFileSystem fileSystem)
{
_fileSystem = fileSystem;
}

public ICollection<ValidationResult> Validate(ChocolateyConfiguration config)
{
this.Log().Debug("Cache Folder Lockdown Checks:");

var result = new List<ValidationResult>();

if (!ProcessInformation.IsElevated() && !string.IsNullOrEmpty(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile, Environment.SpecialFolderOption.DoNotVerify)))
{
this.Log().Debug(" - Elevated State = Failed");

result.Add(new ValidationResult
{
ExitCode = 0,
Message = "User Cache directory is valid.",
Status = ValidationStatus.Success
});

return result;
}

this.Log().Debug(" - Elevated State = Checked");

var cacheFolderPath = ApplicationParameters.HttpCacheLocation;

if (_fileSystem.DirectoryExists(cacheFolderPath))
{
this.Log().Debug(" - Folder Exists = Checked");

if (_fileSystem.IsLockedDirectory(cacheFolderPath))
{
this.Log().Debug(" - Folder lockdown = Checked");

result.Add(new ValidationResult
{
ExitCode = 0,
Message = "System Cache directory is locked down to administrators.",
Status = ValidationStatus.Success
});
}
else
{
this.Log().Debug(" - Folder lockdown = Failed");

result.Add(new ValidationResult
{
ExitCode = 0,
Message = "System Cache directory is not locked down to administrators.\nRemove the directory '{0}' to have Chocolatey CLI create it with the proper permissions.".FormatWith(cacheFolderPath).SplitOnSpace(linePrefix: " "),
Status = ValidationStatus.Warning
});
}

return result;
}

this.Log().Debug(" - Folder Exists = Failed");

if (_fileSystem.LockDirectory(cacheFolderPath))
{
this.Log().Debug(" - Folder lockdown update = Success");

result.Add(new ValidationResult
{
ExitCode = 0,
Message = "System Cache directory successfullly created and locked down to administrators.",
Status = ValidationStatus.Success,
});
}
else
{
this.Log().Debug(" - Folder lockdown update = Failed");

result.Add(new ValidationResult
{
ExitCode = 1, // Should we error?
Message = "System Cache directory was not created, or could not be locked down to administrators.".SplitOnSpace(linePrefix: " "),
Status = ValidationStatus.Error
});
}

return result;
}

#pragma warning disable IDE1006

[Obsolete("This overload is deprecated and will be removed in v3.")]
public ICollection<ValidationResult> validate(ChocolateyConfiguration config)
{
return Validate(config);
}

#pragma warning restore IDE1006
}
}
Loading

0 comments on commit ca3080f

Please sign in to comment.