Skip to content

Commit

Permalink
Merge pull request #77 from jscarle/feature/v226
Browse files Browse the repository at this point in the history
Added support for MoveItem and ShareItem.
  • Loading branch information
jscarle committed Mar 19, 2024
2 parents 35b5fb0 + 0ca9595 commit c01c24e
Show file tree
Hide file tree
Showing 12 changed files with 210 additions and 45 deletions.
Binary file added Banner.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Icon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 13 additions & 14 deletions OnePassword.NET.Tests/Common/TestsBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,16 @@ public class TestsBase
private static readonly string ServiceAccountToken = GetEnv("OPT_SERVICE_ACCOUNT_TOKEN", "");
private protected static readonly int TestUserConfirmTimeout = int.Parse(GetEnv("OPT_TEST_USER_CONFIRM_TIMEOUT", GetEnv("OPT_COMMAND_TIMEOUT", "2")), CultureInfo.InvariantCulture) * 60 * 1000;
private static readonly SemaphoreSlim SemaphoreSlim = new(1, 1);
private protected static readonly CancellationTokenSource SetUpCancellationTokenSource = new();
private static readonly CancellationTokenSource TestCancellationTokenSource = new();
private static readonly CancellationTokenSource TearDownCancellationTokenSource = new();
private static readonly bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
private static readonly Uri DownloadSource = IsLinux ?
new Uri("https://cache.agilebits.com/dist/1P/op2/pkg/v2.26.0/op_linux_amd64_v2.26.0.zip") :
new Uri("https://cache.agilebits.com/dist/1P/op2/pkg/v2.26.0/op_windows_amd64_v2.26.0.zip");
private static readonly string ExecutableName = IsLinux ? "op" : "op.exe";
private static bool _initialSetupDone;

private protected static readonly CancellationTokenSource SetUpCancellationTokenSource = new();
private protected static OnePasswordManager OnePassword = null!;
private protected static IUser TestUser = null!;
private protected static IGroup TestGroup = null!;
Expand All @@ -36,19 +43,7 @@ public class TestsBase
private protected static Template TestTemplate = null!;
private protected static Item TestItem = null!;
private protected static bool DoFinalTearDown;
private static readonly bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
private static readonly Uri DownloadSource = IsLinux ?
new Uri("https://cache.agilebits.com/dist/1P/op2/pkg/v2.25.1/op_linux_amd64_v2.25.1.zip") :
new Uri("https://cache.agilebits.com/dist/1P/op2/pkg/v2.25.1/op_windows_amd64_v2.25.1.zip");
private static readonly string ExecutableName = IsLinux ? "op" : "op.exe";
private protected static readonly string WorkingDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
private static bool _initialSetupDone;

private static string GetEnv(string name, string value) =>
Environment.GetEnvironmentVariable(name, EnvironmentVariableTarget.Machine)
?? Environment.GetEnvironmentVariable(name, EnvironmentVariableTarget.User)
?? Environment.GetEnvironmentVariable(name, EnvironmentVariableTarget.Process)
?? value;

[OneTimeSetUp]
public async Task Setup()
Expand Down Expand Up @@ -116,10 +111,14 @@ protected static void Run(RunType runType, Action action)
}
}

private static string GetEnv(string name, string value) =>
Environment.GetEnvironmentVariable(name, EnvironmentVariableTarget.Machine)
?? Environment.GetEnvironmentVariable(name, EnvironmentVariableTarget.User) ?? Environment.GetEnvironmentVariable(name, EnvironmentVariableTarget.Process) ?? value;

protected enum RunType
{
SetUp,
Test,
TearDown
}
}
}
62 changes: 41 additions & 21 deletions OnePassword.NET/Common/CommonExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,18 @@

namespace OnePassword.Common;

/// <summary>
/// Common extensions methods.
/// </summary>
/// <summary>Common extensions methods.</summary>
internal static class CommonExtensions
{
/// <summary>
/// Converts an enum field to its string representation.
/// </summary>
/// <summary>Converts an enum field to its string representation.</summary>
/// <param name="field">The enum field.</param>
/// <typeparam name="TField">The type of enum field.</typeparam>
/// <returns>A string representation of the enum field.</returns>
/// <exception cref="ArgumentNullException">Thrown when field is null.</exception>
/// <exception cref="NotImplementedException">Thrown when field is not correctly annotated with a <see cref="EnumMemberAttribute"/>.</exception>
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2090:'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to 'target method'.", Justification = "https://github.com/dotnet/runtime/issues/97737")]
internal static string ToEnumString<TField>(this TField field)
where TField : Enum
/// <exception cref="NotImplementedException">Thrown when field is not correctly annotated with a <see cref="EnumMemberAttribute" />.</exception>
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2090:'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to 'target method'.",
Justification = "https://github.com/dotnet/runtime/issues/97737")]
internal static string ToEnumString<TField>(this TField field) where TField : Enum
{
var fieldInfo = typeof(TField).GetField(field.ToString(), BindingFlags.Public | BindingFlags.Static) ?? throw new ArgumentNullException(nameof(field));
var attributes = (EnumMemberAttribute[])fieldInfo.GetCustomAttributes(typeof(EnumMemberAttribute), false);
Expand All @@ -29,30 +25,54 @@ internal static string ToEnumString<TField>(this TField field)
return value;
}

/// <summary>
/// Converts enum fields to a comma separated list.
/// </summary>
/// <summary>Converts enum fields to a comma separated list.</summary>
/// <param name="fields">The enum fields.</param>
/// <param name="replaceUnderscoresWithSpaces">When <see langword="true"/>, replaces underscores in the field name with spaces.</param>
/// <param name="replaceUnderscoresWithSpaces">When <see langword="true" />, replaces underscores in the field name with spaces.</param>
/// <typeparam name="TField">The type of enum field.</typeparam>
/// <returns>A comma separated list of the enum fields.</returns>
internal static string ToCommaSeparated<TField>(this IEnumerable<TField> fields, bool replaceUnderscoresWithSpaces = false)
where TField : struct, Enum
internal static string ToCommaSeparated<TField>(this IEnumerable<TField> fields, bool replaceUnderscoresWithSpaces = false) where TField : struct, Enum
{
var values = fields.Select(field => field.ToEnumString()).ToList();
var commaSeparated = string.Join(",", values);
return replaceUnderscoresWithSpaces ? commaSeparated.Replace("_", " ", StringComparison.InvariantCulture) : commaSeparated;
}

/// <summary>
/// Converts string items to a comma separated list.
/// </summary>
/// <summary>Converts string items to a comma separated list.</summary>
/// <param name="items">The string items.</param>
/// <param name="replaceUnderscoresWithSpaces">When <see langword="true"/>, replaces underscores in the field name with spaces.</param>
/// <param name="replaceUnderscoresWithSpaces">When <see langword="true" />, replaces underscores in the field name with spaces.</param>
/// <returns>A comma separated list of the string items.</returns>
internal static string ToCommaSeparated(this IEnumerable<string> items, bool replaceUnderscoresWithSpaces = false)
{
var commaSeparated = string.Join(",", items);
return replaceUnderscoresWithSpaces ? commaSeparated.Replace("_", " ", StringComparison.InvariantCulture) : commaSeparated;
}
}

/// <summary>Converts a <see cref="TimeSpan" /> to a human readable string.</summary>
/// <returns>A human readable string representing the time span.</returns>
internal static string ToHumanReadable(this TimeSpan timeSpan)
{
if (timeSpan.TotalSeconds < 1)
return "0s";

var result = "";
if (timeSpan.Days > 7)
{
result += $"{timeSpan.Days / 7}w";
timeSpan = TimeSpan.FromDays(timeSpan.Days % 7);
}

if (timeSpan.Days > 0)
result += $"{timeSpan.Days}d";

if (timeSpan.Hours > 0)
result += $"{timeSpan.Hours}h";

if (timeSpan.Minutes > 0)
result += $"{timeSpan.Minutes}m";

if (timeSpan.Seconds > 0)
result += $"{timeSpan.Seconds}s";

return result.Length == 0 ? "0s" : result;
}
}
50 changes: 50 additions & 0 deletions OnePassword.NET/IOnePasswordManager.Items.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,54 @@ public partial interface IOnePasswordManager
/// <param name="vaultId">The ID of the vault that contains the item to delete.</param>
/// <exception cref="ArgumentException">Thrown when there is an invalid argument.</exception>
public void DeleteItem(string itemId, string vaultId);

/// <summary>Moves an item.</summary>
/// <param name="item">The item to move.</param>
/// <param name="currentVault">The vault that contains the item to move.</param>
/// <param name="destinationVault">The destination vault to move the item to.</param>
/// <exception cref="ArgumentException">Thrown when there is an invalid argument.</exception>
public void MoveItem(IItem item, IVault currentVault, IVault destinationVault);

/// <summary>Moves an item.</summary>
/// <param name="itemId">The ID of the item to move.</param>
/// <param name="currentVaultId">The ID of the vault that contains the item to move.</param>
/// <param name="destinationVaultId">The ID of the destination vault to move the item to.</param>
/// <exception cref="ArgumentException">Thrown when there is an invalid argument.</exception>
public void MoveItem(string itemId, string currentVaultId, string destinationVaultId);

/// <summary>Shares an item.</summary>
/// <param name="item">The item to share.</param>
/// <param name="vault">The vault that contains the item to share.</param>
/// <param name="emailAddress">The email address to share the item with.</param>
/// <param name="expiresIn">The delay before the link expires.</param>
/// <param name="viewOnce">Expires the link after a single view.</param>
/// <exception cref="ArgumentException">Thrown when there is an invalid argument.</exception>
public void ShareItem(IItem item, IVault vault, string emailAddress, TimeSpan? expiresIn = null, bool? viewOnce = null);

/// <summary>Shares an item.</summary>
/// <param name="itemId">The ID of the item to share.</param>
/// <param name="vaultId">The ID of the vault that contains the item to share.</param>
/// <param name="emailAddress">The email address to share the item with.</param>
/// <param name="expiresIn">The delay before the link expires.</param>
/// <param name="viewOnce">Expires the link after a single view.</param>
/// <exception cref="ArgumentException">Thrown when there is an invalid argument.</exception>
public void ShareItem(string itemId, string vaultId, string emailAddress, TimeSpan? expiresIn = null, bool? viewOnce = null);

/// <summary>Shares an item.</summary>
/// <param name="item">The item to share.</param>
/// <param name="vault">The vault that contains the item to share.</param>
/// <param name="emailAddresses">The email address to share the item with.</param>
/// <param name="expiresIn">The delay before the link expires.</param>
/// <param name="viewOnce">Expires the link after a single view.</param>
/// <exception cref="ArgumentException">Thrown when there is an invalid argument.</exception>
public void ShareItem(IItem item, IVault vault, IReadOnlyCollection<string> emailAddresses, TimeSpan? expiresIn = null, bool? viewOnce = null);

/// <summary>Shares an item.</summary>
/// <param name="itemId">The ID of the item to share.</param>
/// <param name="vaultId">The ID of the vault that contains the item to share.</param>
/// <param name="emailAddresses">The email address to share the item with.</param>
/// <param name="expiresIn">The delay before the link expires.</param>
/// <param name="viewOnce">Expires the link after a single view.</param>
/// <exception cref="ArgumentException">Thrown when there is an invalid argument.</exception>
public void ShareItem(string itemId, string vaultId, IReadOnlyCollection<string> emailAddresses, TimeSpan? expiresIn = null, bool? viewOnce = null);
}
24 changes: 15 additions & 9 deletions OnePassword.NET/OnePassword.NET.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
<Company>Jean-Sebastien Carle</Company>
<Product>OnePassword.NET</Product>
<Description>1Password CLI Wrapper</Description>
<Version>2.4.0</Version>
<AssemblyVersion>2.4.0.0</AssemblyVersion>
<FileVersion>2.4.0.0</FileVersion>
<Version>2.4.1</Version>
<AssemblyVersion>2.4.1.0</AssemblyVersion>
<FileVersion>2.4.1.0</FileVersion>
<Copyright>Copyright © Jean-Sebastien Carle 2021-2024</Copyright>
<RepositoryUrl>https://github.com/jscarle/OnePassword.NET</RepositoryUrl>
<RepositoryType>git</RepositoryType>
Expand All @@ -21,13 +21,14 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageIcon>Icon.png</PackageIcon>
<PackageProjectUrl>https://github.com/jscarle/OnePassword.NET</PackageProjectUrl>
<Title>OnePassword.NET - 1Password CLI Wrapper</Title>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<AnalysisLevel>latest-All</AnalysisLevel>
<CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>
<WarningsAsErrors>true</WarningsAsErrors>
<WarningsAsErrors>true</WarningsAsErrors>
<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">true</IsAotCompatible>
<Optimize>true</Optimize>
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
Expand All @@ -42,22 +43,24 @@
<None Include="..\LICENSE.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
<Visible>false</Visible>
<Visible>False</Visible>
</None>
<None Include="..\README.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
<Visible>false</Visible>
<Visible>False</Visible>
</None>
<None Include="..\Icon.png">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
<Visible>False</Visible>
</None>
</ItemGroup>

<ItemGroup>
<Compile Update="IOnePasswordManagerOptions.cs">
<DependentUpon>OnePasswordManagerOptions.cs</DependentUpon>
</Compile>
<Compile Update="IOnePasswordManager.Accounts.cs">
<DependentUpon>OnePasswordManager.Accounts.cs</DependentUpon>
</Compile>
<Compile Update="IOnePasswordManager.Documents.cs">
<DependentUpon>OnePasswordManager.Documents.cs</DependentUpon>
</Compile>
Expand All @@ -79,6 +82,9 @@
<Compile Update="IOnePasswordManager.cs">
<DependentUpon>OnePasswordManager.cs</DependentUpon>
</Compile>
<Compile Update="IOnePasswordManager.Accounts.cs">
<DependentUpon>OnePasswordManager.Accounts.cs</DependentUpon>
</Compile>
</ItemGroup>

</Project>
78 changes: 77 additions & 1 deletion OnePassword.NET/OnePasswordManager.Items.cs
Original file line number Diff line number Diff line change
Expand Up @@ -219,4 +219,80 @@ public void DeleteItem(string itemId, string vaultId)
var command = $"item delete {itemId} --vault {vaultId}";
Op(command);
}
}

/// <inheritdoc />
public void MoveItem(IItem item, IVault currentVault, IVault destinationVault)
{
if (item is null || item.Id.Length == 0)
throw new ArgumentException($"{nameof(item.Id)} cannot be empty.", nameof(item));
if (currentVault is null || currentVault.Id.Length == 0)
throw new ArgumentException($"{nameof(currentVault.Id)} cannot be empty.", nameof(currentVault));
if (destinationVault is null || destinationVault.Id.Length == 0)
throw new ArgumentException($"{nameof(destinationVault.Id)} cannot be empty.", nameof(destinationVault));

MoveItem(item.Id, currentVault.Id, destinationVault.Id);
}

/// <inheritdoc />
public void MoveItem(string itemId, string currentVaultId, string destinationVaultId)
{
if (itemId is null || itemId.Length == 0)
throw new ArgumentException($"{nameof(itemId)} cannot be empty.", nameof(itemId));
if (currentVaultId is null || currentVaultId.Length == 0)
throw new ArgumentException($"{nameof(currentVaultId)} cannot be empty.", nameof(currentVaultId));
if (destinationVaultId is null || destinationVaultId.Length == 0)
throw new ArgumentException($"{nameof(destinationVaultId)} cannot be empty.", nameof(destinationVaultId));

var command = $"item move {itemId} --current-vault {{currentVaultId}} --destination-vault {{destinationVaultId}}";
Op(command);
}

/// <inheritdoc />
public void ShareItem(IItem item, IVault vault, string emailAddress, TimeSpan? expiresIn = null, bool? viewOnce = null)
{
if (item is null || item.Id.Length == 0)
throw new ArgumentException($"{nameof(item.Id)} cannot be empty.", nameof(item));
if (vault is null || vault.Id.Length == 0)
throw new ArgumentException($"{nameof(vault.Id)} cannot be empty.", nameof(vault));

ShareItem(item.Id, vault.Id, [emailAddress], expiresIn, viewOnce);
}

/// <inheritdoc />
public void ShareItem(string itemId, string vaultId, string emailAddress, TimeSpan? expiresIn = null, bool? viewOnce = null)
{
if (itemId is null || itemId.Length == 0)
throw new ArgumentException($"{nameof(itemId)} cannot be empty.", nameof(itemId));
if (vaultId is null || vaultId.Length == 0)
throw new ArgumentException($"{nameof(vaultId)} cannot be empty.", nameof(vaultId));

ShareItem(itemId, vaultId, [emailAddress], expiresIn, viewOnce);
}

/// <inheritdoc />
public void ShareItem(IItem item, IVault vault, IReadOnlyCollection<string> emailAddresses, TimeSpan? expiresIn = null, bool? viewOnce = null)
{
if (item is null || item.Id.Length == 0)
throw new ArgumentException($"{nameof(item.Id)} cannot be empty.", nameof(item));
if (vault is null || vault.Id.Length == 0)
throw new ArgumentException($"{nameof(vault.Id)} cannot be empty.", nameof(vault));

ShareItem(item.Id, vault.Id, emailAddresses, expiresIn, viewOnce);
}

/// <inheritdoc />
public void ShareItem(string itemId, string vaultId, IReadOnlyCollection<string> emailAddresses, TimeSpan? expiresIn = null, bool? viewOnce = null)
{
if (itemId is null || itemId.Length == 0)
throw new ArgumentException($"{nameof(itemId)} cannot be empty.", nameof(itemId));
if (vaultId is null || vaultId.Length == 0)
throw new ArgumentException($"{nameof(vaultId)} cannot be empty.", nameof(vaultId));

var command = $"item share {itemId} --vault {vaultId}";
if (expiresIn is not null)
command += $" --expires-in {expiresIn.Value.ToHumanReadable()}";
if (viewOnce is not null && viewOnce.Value)
command += " --view-once";
Op(command);
}
}

0 comments on commit c01c24e

Please sign in to comment.