Skip to content

Commit

Permalink
Add SecretAttribute
Browse files Browse the repository at this point in the history
  • Loading branch information
matkoch committed Jun 13, 2020
1 parent 7963788 commit 9c3b788
Show file tree
Hide file tree
Showing 11 changed files with 271 additions and 15 deletions.
14 changes: 7 additions & 7 deletions build/Build.Announce.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@

partial class Build
{
[Parameter] readonly string GitHubToken;
[Parameter] readonly string GitterAuthToken;
[Parameter] readonly string SlackWebhook;
[Parameter] [Secret] readonly string GitHubToken;
[Parameter] [Secret] readonly string GitterAuthToken;
[Parameter] [Secret] readonly string SlackWebhook;

[Parameter] readonly string TwitterConsumerKey;
[Parameter] readonly string TwitterConsumerSecret;
[Parameter] readonly string TwitterAccessToken;
[Parameter] readonly string TwitterAccessTokenSecret;
[Parameter] [Secret] readonly string TwitterConsumerKey;
[Parameter] [Secret] readonly string TwitterConsumerSecret;
[Parameter] [Secret] readonly string TwitterAccessToken;
[Parameter] [Secret] readonly string TwitterAccessTokenSecret;

Target Announce => _ => _
.DependsOn(ReleaseImage)
Expand Down
2 changes: 2 additions & 0 deletions shell-completion.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ Host:
- TeamCity
- Travis
IgnoreFailedSources:
LoadProfile:
NoLogo:
Plan:
Root:
SaveProfile:
Skip:
- Analysis
- Announce
Expand Down
5 changes: 5 additions & 0 deletions source/Nuke.Common/EnvironmentInfo.Parameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,5 +110,10 @@ public static T GetVariable<T>(string parameterName, char? separator = null)
{
return (T) s_parameterService.GetEnvironmentVariable(parameterName, typeof(T), separator);
}

public static bool HasArgument(MemberInfo member)
{
return s_parameterService.HasCommandLineArgument(ParameterService.GetParameterMemberName(member));
}
}
}
3 changes: 3 additions & 0 deletions source/Nuke.Common/Execution/BuildManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Nuke.Common.CI;
using Nuke.Common.Tooling;
using Nuke.Common.Utilities;
using Nuke.Common.Utilities.Collections;
Expand Down Expand Up @@ -40,6 +41,8 @@ void ExecuteExtension<TExtension>(Action<TExtension> action)
.GetCustomAttributes()
.OfType<TExtension>()
.OrderBy(x => x.GetType() == typeof(HandleHelpRequestsAttribute))
.ThenBy(x => x.GetType() == typeof(HandleProfileManagementAttribute))
.ThenBy(x => x.GetType() == typeof(HandleConfigurationGenerationAttribute))
.ForEach(action);

try
Expand Down
43 changes: 43 additions & 0 deletions source/Nuke.Common/Execution/DelegateConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright 2020 Maintainers of NUKE.
// Distributed under the MIT License.
// https://github.com/nuke-build/nuke/blob/master/LICENSE

using System;
using System.Linq;
using Newtonsoft.Json;

namespace Nuke.Common.Execution
{
internal class DelegateConverter : JsonConverter
{
private readonly Func<object, object> _convertToJson;
private readonly Func<object, object> _convertFromJson;

public DelegateConverter(
Func<object, object> convertToJson,
Func<object, object> convertFromJson)
{
_convertToJson = convertToJson;
_convertFromJson = convertFromJson;
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(_convertToJson(value));
}

public override object ReadJson(
JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
{
return _convertFromJson(reader.Value);
}

public override bool CanConvert(Type objectType)
{
return true;
}
}
}
138 changes: 138 additions & 0 deletions source/Nuke.Common/Execution/HandleProfileManagementAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// Copyright 2020 Maintainers of NUKE.
// Distributed under the MIT License.
// https://github.com/nuke-build/nuke/blob/master/LICENSE

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using DeviceId;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Nuke.Common.IO;
using Nuke.Common.Utilities.Collections;

namespace Nuke.Common.Execution
{
[AttributeUsage(AttributeTargets.Class)]
public class HandleProfileManagementAttribute : Attribute, IOnBeforeLogo
{
private const string DefaultProfile = "default";
private const char Separator = ':';

public static string[] GetLoadProfiles()
{
return new[] { DefaultProfile }
.Where(x => File.Exists(GetProfileFile(x)))
.Concat(EnvironmentInfo.GetParameter(() => NukeBuild.LoadedProfiles) ?? new string[0])
.ToArray();
}

public static string GetSaveProfile()
{
return EnvironmentInfo.GetParameter(() => NukeBuild.SaveProfile)
?? (EnvironmentInfo.GetParameter<bool>(() => NukeBuild.SaveProfile)
? DefaultProfile
: null);
}

private static string GetProfileName(string profile)
{
return profile.Split(Separator).First();
}

private static string GetProfileKey(string profile)
{
return profile.Split(Separator).ElementAtOrDefault(1) ??
new DeviceIdBuilder()
.AddMachineName()
.AddUserName()
.AddMotherboardSerialNumber().ToString();
}

private static string GetProfileFile(string profile)
{
return Path.ChangeExtension(NukeBuild.TemporaryDirectory / GetProfileName(profile), ".json");
}

public void OnBeforeLogo(
NukeBuild build,
IReadOnlyCollection<ExecutableTarget> executableTargets)
{
if (NukeBuild.SaveProfile != null)
SaveProfileAndExit(build, NukeBuild.SaveProfile);

NukeBuild.LoadedProfiles
.ForEach(x => LoadProfile(x, build));
}

private void LoadProfile(string profile, NukeBuild build)
{
var profileFile = GetProfileFile(profile);
var profileContent = TextTasks.ReadAllText(profileFile);
JsonConvert.PopulateObject(profileContent, build, GetSerializerSettings(profile));
}

private void SaveProfileAndExit(NukeBuild build, string profile)
{
var profileFile = GetProfileFile(profile);
var content = JsonConvert.SerializeObject(build, Formatting.Indented, GetSerializerSettings(profile));
File.WriteAllText(profileFile, content);

Environment.Exit(0);
}

private JsonSerializerSettings GetSerializerSettings(string profile)
{
return new JsonSerializerSettings
{
ContractResolver = new CustomContractResolver(GetProfileKey(profile), ShouldSerialize)
};
}

protected virtual bool ShouldSerialize(MemberInfo member)
{
return EnvironmentInfo.HasArgument(member);
}

private class CustomContractResolver : DefaultContractResolver
{
private readonly string _key;
private readonly Func<MemberInfo, bool> _shouldSerialize;

public CustomContractResolver(string key, Func<MemberInfo, bool> shouldSerialize)
{
_key = key;
_shouldSerialize = shouldSerialize;
}

protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
return type.GetFields(ReflectionService.All)
.Concat(type.GetProperties(ReflectionService.All).Cast<MemberInfo>())
.Where(x => x.HasCustomAttribute<ParameterAttribute>())
.Select(x => CreateProperty(x, memberSerialization))
.ToList();
}

protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
property.ShouldSerialize = x => _shouldSerialize(member);

var secretAttribute = member.GetCustomAttribute<SecretAttribute>();
if (secretAttribute != null)
property.Converter = secretAttribute.GetConverter(member, _key);

if (member is FieldInfo)
{
property.Writable = true;
property.Readable = true;
}

return property;
}
}
}
}
2 changes: 1 addition & 1 deletion source/Nuke.Common/Execution/ParameterService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ private object ConvertCommandLineArguments(
}
}

private bool HasCommandLineArgument(string argumentName)
public bool HasCommandLineArgument(string argumentName)
{
return GetCommandLineArgumentIndex(argumentName) != -1;
}
Expand Down
13 changes: 7 additions & 6 deletions source/Nuke.Common/Nuke.Common.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

<ItemGroup>
<PackageReference Include="Colorful.Console" Version="1.2.9" />
<PackageReference Include="DeviceId" Version="5.1.0" />
<PackageReference Include="Glob" Version="1.1.5" />
<PackageReference Include="JetBrains.Annotations" Version="2019.1.3" />
<PackageReference Include="Microsoft.Azure.KeyVault" Version="3.0.4" />
Expand All @@ -33,12 +34,12 @@
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == ''">
<None Include="$(MSBuildProjectName).props" PackagePath="build" Pack="true"/>
<None Include="$(MSBuildProjectName).targets" PackagePath="build" Pack="true"/>
<None Include="..\Nuke.MSBuildTasks\Nuke.MSBuildTasks.targets" PackagePath="build\netcore" Pack="true"/>
<None Include="..\Nuke.MSBuildTasks\Nuke.MSBuildTasks.targets" PackagePath="build\netfx" Pack="true"/>
<None Include="..\Nuke.MSBuildTasks\bin\$(Configuration)\netcoreapp2.1\publish\**\*.*" PackagePath="build\netcore" Pack="true"/>
<None Include="..\Nuke.MSBuildTasks\bin\$(Configuration)\net472\publish\**\*.*" PackagePath="build\netfx" Pack="true"/>
<None Include="$(MSBuildProjectName).props" PackagePath="build" Pack="true" />
<None Include="$(MSBuildProjectName).targets" PackagePath="build" Pack="true" />
<None Include="..\Nuke.MSBuildTasks\Nuke.MSBuildTasks.targets" PackagePath="build\netcore" Pack="true" />
<None Include="..\Nuke.MSBuildTasks\Nuke.MSBuildTasks.targets" PackagePath="build\netfx" Pack="true" />
<None Include="..\Nuke.MSBuildTasks\bin\$(Configuration)\netcoreapp2.1\publish\**\*.*" PackagePath="build\netcore" Pack="true" />
<None Include="..\Nuke.MSBuildTasks\bin\$(Configuration)\net472\publish\**\*.*" PackagePath="build\netfx" Pack="true" />
</ItemGroup>

</Project>
12 changes: 11 additions & 1 deletion source/Nuke.Common/NukeBuild.Statics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using Nuke.Common.CI.Jenkins;
using Nuke.Common.CI.TeamCity;
using Nuke.Common.CI.TravisCI;
using Nuke.Common.Execution;
using Nuke.Common.IO;
using Nuke.Common.Utilities;
using Nuke.Common.Utilities.Collections;
Expand All @@ -40,6 +41,9 @@ static NukeBuild()
Plan = EnvironmentInfo.GetParameter(() => Plan);
Help = EnvironmentInfo.GetParameter(() => Help);
NoLogo = EnvironmentInfo.GetParameter(() => NoLogo);

LoadedProfiles = HandleProfileManagementAttribute.GetLoadProfiles();
SaveProfile = HandleProfileManagementAttribute.GetSaveProfile();
}

/// <summary>
Expand Down Expand Up @@ -99,7 +103,13 @@ static NukeBuild()
/// Gets a value whether to display the NUKE logo.
/// </summary>
[Parameter("Disables displaying the NUKE logo.")]
public static bool NoLogo { get; }
public static bool NoLogo { get; set; }

[Parameter("Defines the profiles to load.", Name = "LoadProfile")]
public static string[] LoadedProfiles { get; }

[Parameter("Defines the profile to save to.")]
public static string SaveProfile { get; }

public static bool IsLocalBuild => Host == HostType.Console;
public static bool IsServerBuild => Host != HostType.Console;
Expand Down
1 change: 1 addition & 0 deletions source/Nuke.Common/NukeBuild.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ namespace Nuke.Common
[PublicAPI]
[HandleHelpRequests]
[HandleShellCompletion]
[HandleProfileManagement]
[HandleVisualStudioDebugging]
[HandleConfigurationGeneration]
public abstract partial class NukeBuild : INukeBuild
Expand Down
53 changes: 53 additions & 0 deletions source/Nuke.Common/ParameterAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using JetBrains.Annotations;
using Newtonsoft.Json;
using Nuke.Common.Execution;

namespace Nuke.Common
Expand Down Expand Up @@ -69,4 +73,53 @@ public override object GetValue(MemberInfo member, object instance)
public class RequiredAttribute : Attribute
{
}

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class SecretAttribute : Attribute
{
public virtual JsonConverter GetConverter(MemberInfo member, string key)
{
ControlFlow.Assert(member.GetMemberType() == typeof(string),
$"Member '{member.Name}' must be of type 'string' when using '{nameof(SecretAttribute)}'.");

return new DelegateConverter(
x => Encrypt(x as string, key),
x => Decrypt(x as string, key));
}

protected virtual string Decrypt(string cipherText, string key)
{
var cipherBytes = Convert.FromBase64String(cipherText.Replace(" ", "+"));

using var ms = new MemoryStream();
using var cs = GetCryptoStream(ms, key, x => x.CreateDecryptor());
cs.Write(cipherBytes, 0, cipherBytes.Length);
cs.Close();

return Encoding.Unicode.GetString(ms.ToArray());
}

protected virtual string Encrypt(string clearText, string key)
{
var clearBytes = Encoding.Unicode.GetBytes(clearText);

using var ms = new MemoryStream();
using var cs = GetCryptoStream(ms, key, x => x.CreateEncryptor());
cs.Write(clearBytes, 0, clearBytes.Length);
cs.Close();

return Convert.ToBase64String(ms.ToArray());
}

private Stream GetCryptoStream(Stream stream, string key, Func<SymmetricAlgorithm, ICryptoTransform> transformSelector)
{
var salt = new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 };
var pdb = new Rfc2898DeriveBytes(key, salt);
using var symmetricAlgorithm = Aes.Create().NotNull();
symmetricAlgorithm.Key = pdb.GetBytes(32);
symmetricAlgorithm.IV = pdb.GetBytes(16);

return new CryptoStream(stream, transformSelector(symmetricAlgorithm), CryptoStreamMode.Write);
}
}
}

0 comments on commit 9c3b788

Please sign in to comment.