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

(#3281) Add validation for cache folder permissions #3282

Merged
merged 2 commits into from
Jul 25, 2023
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
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