Skip to content

Commit c01c24e

Browse files
authored
Merge pull request #77 from jscarle/feature/v226
Added support for MoveItem and ShareItem.
2 parents 35b5fb0 + 0ca9595 commit c01c24e

12 files changed

Lines changed: 210 additions & 45 deletions

File tree

Banner.png

12.4 KB
Loading

Icon.png

404 KB
Loading

OnePassword.NET.Tests/Common/TestsBase.cs

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,16 @@ public class TestsBase
2525
private static readonly string ServiceAccountToken = GetEnv("OPT_SERVICE_ACCOUNT_TOKEN", "");
2626
private protected static readonly int TestUserConfirmTimeout = int.Parse(GetEnv("OPT_TEST_USER_CONFIRM_TIMEOUT", GetEnv("OPT_COMMAND_TIMEOUT", "2")), CultureInfo.InvariantCulture) * 60 * 1000;
2727
private static readonly SemaphoreSlim SemaphoreSlim = new(1, 1);
28-
private protected static readonly CancellationTokenSource SetUpCancellationTokenSource = new();
2928
private static readonly CancellationTokenSource TestCancellationTokenSource = new();
3029
private static readonly CancellationTokenSource TearDownCancellationTokenSource = new();
30+
private static readonly bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
31+
private static readonly Uri DownloadSource = IsLinux ?
32+
new Uri("https://cache.agilebits.com/dist/1P/op2/pkg/v2.26.0/op_linux_amd64_v2.26.0.zip") :
33+
new Uri("https://cache.agilebits.com/dist/1P/op2/pkg/v2.26.0/op_windows_amd64_v2.26.0.zip");
34+
private static readonly string ExecutableName = IsLinux ? "op" : "op.exe";
35+
private static bool _initialSetupDone;
36+
37+
private protected static readonly CancellationTokenSource SetUpCancellationTokenSource = new();
3138
private protected static OnePasswordManager OnePassword = null!;
3239
private protected static IUser TestUser = null!;
3340
private protected static IGroup TestGroup = null!;
@@ -36,19 +43,7 @@ public class TestsBase
3643
private protected static Template TestTemplate = null!;
3744
private protected static Item TestItem = null!;
3845
private protected static bool DoFinalTearDown;
39-
private static readonly bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
40-
private static readonly Uri DownloadSource = IsLinux ?
41-
new Uri("https://cache.agilebits.com/dist/1P/op2/pkg/v2.25.1/op_linux_amd64_v2.25.1.zip") :
42-
new Uri("https://cache.agilebits.com/dist/1P/op2/pkg/v2.25.1/op_windows_amd64_v2.25.1.zip");
43-
private static readonly string ExecutableName = IsLinux ? "op" : "op.exe";
4446
private protected static readonly string WorkingDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
45-
private static bool _initialSetupDone;
46-
47-
private static string GetEnv(string name, string value) =>
48-
Environment.GetEnvironmentVariable(name, EnvironmentVariableTarget.Machine)
49-
?? Environment.GetEnvironmentVariable(name, EnvironmentVariableTarget.User)
50-
?? Environment.GetEnvironmentVariable(name, EnvironmentVariableTarget.Process)
51-
?? value;
5247

5348
[OneTimeSetUp]
5449
public async Task Setup()
@@ -116,10 +111,14 @@ protected static void Run(RunType runType, Action action)
116111
}
117112
}
118113

114+
private static string GetEnv(string name, string value) =>
115+
Environment.GetEnvironmentVariable(name, EnvironmentVariableTarget.Machine)
116+
?? Environment.GetEnvironmentVariable(name, EnvironmentVariableTarget.User) ?? Environment.GetEnvironmentVariable(name, EnvironmentVariableTarget.Process) ?? value;
117+
119118
protected enum RunType
120119
{
121120
SetUp,
122121
Test,
123122
TearDown
124123
}
125-
}
124+
}

OnePassword.NET/Common/CommonExtensions.cs

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,18 @@
33

44
namespace OnePassword.Common;
55

6-
/// <summary>
7-
/// Common extensions methods.
8-
/// </summary>
6+
/// <summary>Common extensions methods.</summary>
97
internal static class CommonExtensions
108
{
11-
/// <summary>
12-
/// Converts an enum field to its string representation.
13-
/// </summary>
9+
/// <summary>Converts an enum field to its string representation.</summary>
1410
/// <param name="field">The enum field.</param>
1511
/// <typeparam name="TField">The type of enum field.</typeparam>
1612
/// <returns>A string representation of the enum field.</returns>
1713
/// <exception cref="ArgumentNullException">Thrown when field is null.</exception>
18-
/// <exception cref="NotImplementedException">Thrown when field is not correctly annotated with a <see cref="EnumMemberAttribute"/>.</exception>
19-
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2090:'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to 'target method'.", Justification = "https://github.com/dotnet/runtime/issues/97737")]
20-
internal static string ToEnumString<TField>(this TField field)
21-
where TField : Enum
14+
/// <exception cref="NotImplementedException">Thrown when field is not correctly annotated with a <see cref="EnumMemberAttribute" />.</exception>
15+
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2090:'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to 'target method'.",
16+
Justification = "https://github.com/dotnet/runtime/issues/97737")]
17+
internal static string ToEnumString<TField>(this TField field) where TField : Enum
2218
{
2319
var fieldInfo = typeof(TField).GetField(field.ToString(), BindingFlags.Public | BindingFlags.Static) ?? throw new ArgumentNullException(nameof(field));
2420
var attributes = (EnumMemberAttribute[])fieldInfo.GetCustomAttributes(typeof(EnumMemberAttribute), false);
@@ -29,30 +25,54 @@ internal static string ToEnumString<TField>(this TField field)
2925
return value;
3026
}
3127

32-
/// <summary>
33-
/// Converts enum fields to a comma separated list.
34-
/// </summary>
28+
/// <summary>Converts enum fields to a comma separated list.</summary>
3529
/// <param name="fields">The enum fields.</param>
36-
/// <param name="replaceUnderscoresWithSpaces">When <see langword="true"/>, replaces underscores in the field name with spaces.</param>
30+
/// <param name="replaceUnderscoresWithSpaces">When <see langword="true" />, replaces underscores in the field name with spaces.</param>
3731
/// <typeparam name="TField">The type of enum field.</typeparam>
3832
/// <returns>A comma separated list of the enum fields.</returns>
39-
internal static string ToCommaSeparated<TField>(this IEnumerable<TField> fields, bool replaceUnderscoresWithSpaces = false)
40-
where TField : struct, Enum
33+
internal static string ToCommaSeparated<TField>(this IEnumerable<TField> fields, bool replaceUnderscoresWithSpaces = false) where TField : struct, Enum
4134
{
4235
var values = fields.Select(field => field.ToEnumString()).ToList();
4336
var commaSeparated = string.Join(",", values);
4437
return replaceUnderscoresWithSpaces ? commaSeparated.Replace("_", " ", StringComparison.InvariantCulture) : commaSeparated;
4538
}
4639

47-
/// <summary>
48-
/// Converts string items to a comma separated list.
49-
/// </summary>
40+
/// <summary>Converts string items to a comma separated list.</summary>
5041
/// <param name="items">The string items.</param>
51-
/// <param name="replaceUnderscoresWithSpaces">When <see langword="true"/>, replaces underscores in the field name with spaces.</param>
42+
/// <param name="replaceUnderscoresWithSpaces">When <see langword="true" />, replaces underscores in the field name with spaces.</param>
5243
/// <returns>A comma separated list of the string items.</returns>
5344
internal static string ToCommaSeparated(this IEnumerable<string> items, bool replaceUnderscoresWithSpaces = false)
5445
{
5546
var commaSeparated = string.Join(",", items);
5647
return replaceUnderscoresWithSpaces ? commaSeparated.Replace("_", " ", StringComparison.InvariantCulture) : commaSeparated;
5748
}
58-
}
49+
50+
/// <summary>Converts a <see cref="TimeSpan" /> to a human readable string.</summary>
51+
/// <returns>A human readable string representing the time span.</returns>
52+
internal static string ToHumanReadable(this TimeSpan timeSpan)
53+
{
54+
if (timeSpan.TotalSeconds < 1)
55+
return "0s";
56+
57+
var result = "";
58+
if (timeSpan.Days > 7)
59+
{
60+
result += $"{timeSpan.Days / 7}w";
61+
timeSpan = TimeSpan.FromDays(timeSpan.Days % 7);
62+
}
63+
64+
if (timeSpan.Days > 0)
65+
result += $"{timeSpan.Days}d";
66+
67+
if (timeSpan.Hours > 0)
68+
result += $"{timeSpan.Hours}h";
69+
70+
if (timeSpan.Minutes > 0)
71+
result += $"{timeSpan.Minutes}m";
72+
73+
if (timeSpan.Seconds > 0)
74+
result += $"{timeSpan.Seconds}s";
75+
76+
return result.Length == 0 ? "0s" : result;
77+
}
78+
}

OnePassword.NET/IOnePasswordManager.Items.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,4 +128,54 @@ public ImmutableList<Item> SearchForItems(string? vaultId = null, bool? includeA
128128
/// <param name="vaultId">The ID of the vault that contains the item to delete.</param>
129129
/// <exception cref="ArgumentException">Thrown when there is an invalid argument.</exception>
130130
public void DeleteItem(string itemId, string vaultId);
131+
132+
/// <summary>Moves an item.</summary>
133+
/// <param name="item">The item to move.</param>
134+
/// <param name="currentVault">The vault that contains the item to move.</param>
135+
/// <param name="destinationVault">The destination vault to move the item to.</param>
136+
/// <exception cref="ArgumentException">Thrown when there is an invalid argument.</exception>
137+
public void MoveItem(IItem item, IVault currentVault, IVault destinationVault);
138+
139+
/// <summary>Moves an item.</summary>
140+
/// <param name="itemId">The ID of the item to move.</param>
141+
/// <param name="currentVaultId">The ID of the vault that contains the item to move.</param>
142+
/// <param name="destinationVaultId">The ID of the destination vault to move the item to.</param>
143+
/// <exception cref="ArgumentException">Thrown when there is an invalid argument.</exception>
144+
public void MoveItem(string itemId, string currentVaultId, string destinationVaultId);
145+
146+
/// <summary>Shares an item.</summary>
147+
/// <param name="item">The item to share.</param>
148+
/// <param name="vault">The vault that contains the item to share.</param>
149+
/// <param name="emailAddress">The email address to share the item with.</param>
150+
/// <param name="expiresIn">The delay before the link expires.</param>
151+
/// <param name="viewOnce">Expires the link after a single view.</param>
152+
/// <exception cref="ArgumentException">Thrown when there is an invalid argument.</exception>
153+
public void ShareItem(IItem item, IVault vault, string emailAddress, TimeSpan? expiresIn = null, bool? viewOnce = null);
154+
155+
/// <summary>Shares an item.</summary>
156+
/// <param name="itemId">The ID of the item to share.</param>
157+
/// <param name="vaultId">The ID of the vault that contains the item to share.</param>
158+
/// <param name="emailAddress">The email address to share the item with.</param>
159+
/// <param name="expiresIn">The delay before the link expires.</param>
160+
/// <param name="viewOnce">Expires the link after a single view.</param>
161+
/// <exception cref="ArgumentException">Thrown when there is an invalid argument.</exception>
162+
public void ShareItem(string itemId, string vaultId, string emailAddress, TimeSpan? expiresIn = null, bool? viewOnce = null);
163+
164+
/// <summary>Shares an item.</summary>
165+
/// <param name="item">The item to share.</param>
166+
/// <param name="vault">The vault that contains the item to share.</param>
167+
/// <param name="emailAddresses">The email address to share the item with.</param>
168+
/// <param name="expiresIn">The delay before the link expires.</param>
169+
/// <param name="viewOnce">Expires the link after a single view.</param>
170+
/// <exception cref="ArgumentException">Thrown when there is an invalid argument.</exception>
171+
public void ShareItem(IItem item, IVault vault, IReadOnlyCollection<string> emailAddresses, TimeSpan? expiresIn = null, bool? viewOnce = null);
172+
173+
/// <summary>Shares an item.</summary>
174+
/// <param name="itemId">The ID of the item to share.</param>
175+
/// <param name="vaultId">The ID of the vault that contains the item to share.</param>
176+
/// <param name="emailAddresses">The email address to share the item with.</param>
177+
/// <param name="expiresIn">The delay before the link expires.</param>
178+
/// <param name="viewOnce">Expires the link after a single view.</param>
179+
/// <exception cref="ArgumentException">Thrown when there is an invalid argument.</exception>
180+
public void ShareItem(string itemId, string vaultId, IReadOnlyCollection<string> emailAddresses, TimeSpan? expiresIn = null, bool? viewOnce = null);
131181
}

OnePassword.NET/OnePassword.NET.csproj

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
<Company>Jean-Sebastien Carle</Company>
1010
<Product>OnePassword.NET</Product>
1111
<Description>1Password CLI Wrapper</Description>
12-
<Version>2.4.0</Version>
13-
<AssemblyVersion>2.4.0.0</AssemblyVersion>
14-
<FileVersion>2.4.0.0</FileVersion>
12+
<Version>2.4.1</Version>
13+
<AssemblyVersion>2.4.1.0</AssemblyVersion>
14+
<FileVersion>2.4.1.0</FileVersion>
1515
<Copyright>Copyright © Jean-Sebastien Carle 2021-2024</Copyright>
1616
<RepositoryUrl>https://github.com/jscarle/OnePassword.NET</RepositoryUrl>
1717
<RepositoryType>git</RepositoryType>
@@ -21,13 +21,14 @@
2121
<ImplicitUsings>enable</ImplicitUsings>
2222
<Nullable>enable</Nullable>
2323
<PackageReadmeFile>README.md</PackageReadmeFile>
24+
<PackageIcon>Icon.png</PackageIcon>
2425
<PackageProjectUrl>https://github.com/jscarle/OnePassword.NET</PackageProjectUrl>
2526
<Title>OnePassword.NET - 1Password CLI Wrapper</Title>
2627
<IncludeSymbols>true</IncludeSymbols>
2728
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
2829
<AnalysisLevel>latest-All</AnalysisLevel>
2930
<CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>
30-
<WarningsAsErrors>true</WarningsAsErrors>
31+
<WarningsAsErrors>true</WarningsAsErrors>
3132
<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">true</IsAotCompatible>
3233
<Optimize>true</Optimize>
3334
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
@@ -42,22 +43,24 @@
4243
<None Include="..\LICENSE.md">
4344
<Pack>True</Pack>
4445
<PackagePath>\</PackagePath>
45-
<Visible>false</Visible>
46+
<Visible>False</Visible>
4647
</None>
4748
<None Include="..\README.md">
4849
<Pack>True</Pack>
4950
<PackagePath>\</PackagePath>
50-
<Visible>false</Visible>
51+
<Visible>False</Visible>
52+
</None>
53+
<None Include="..\Icon.png">
54+
<Pack>True</Pack>
55+
<PackagePath>\</PackagePath>
56+
<Visible>False</Visible>
5157
</None>
5258
</ItemGroup>
5359

5460
<ItemGroup>
5561
<Compile Update="IOnePasswordManagerOptions.cs">
5662
<DependentUpon>OnePasswordManagerOptions.cs</DependentUpon>
5763
</Compile>
58-
<Compile Update="IOnePasswordManager.Accounts.cs">
59-
<DependentUpon>OnePasswordManager.Accounts.cs</DependentUpon>
60-
</Compile>
6164
<Compile Update="IOnePasswordManager.Documents.cs">
6265
<DependentUpon>OnePasswordManager.Documents.cs</DependentUpon>
6366
</Compile>
@@ -79,6 +82,9 @@
7982
<Compile Update="IOnePasswordManager.cs">
8083
<DependentUpon>OnePasswordManager.cs</DependentUpon>
8184
</Compile>
85+
<Compile Update="IOnePasswordManager.Accounts.cs">
86+
<DependentUpon>OnePasswordManager.Accounts.cs</DependentUpon>
87+
</Compile>
8288
</ItemGroup>
8389

8490
</Project>

OnePassword.NET/OnePasswordManager.Items.cs

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,4 +219,80 @@ public void DeleteItem(string itemId, string vaultId)
219219
var command = $"item delete {itemId} --vault {vaultId}";
220220
Op(command);
221221
}
222-
}
222+
223+
/// <inheritdoc />
224+
public void MoveItem(IItem item, IVault currentVault, IVault destinationVault)
225+
{
226+
if (item is null || item.Id.Length == 0)
227+
throw new ArgumentException($"{nameof(item.Id)} cannot be empty.", nameof(item));
228+
if (currentVault is null || currentVault.Id.Length == 0)
229+
throw new ArgumentException($"{nameof(currentVault.Id)} cannot be empty.", nameof(currentVault));
230+
if (destinationVault is null || destinationVault.Id.Length == 0)
231+
throw new ArgumentException($"{nameof(destinationVault.Id)} cannot be empty.", nameof(destinationVault));
232+
233+
MoveItem(item.Id, currentVault.Id, destinationVault.Id);
234+
}
235+
236+
/// <inheritdoc />
237+
public void MoveItem(string itemId, string currentVaultId, string destinationVaultId)
238+
{
239+
if (itemId is null || itemId.Length == 0)
240+
throw new ArgumentException($"{nameof(itemId)} cannot be empty.", nameof(itemId));
241+
if (currentVaultId is null || currentVaultId.Length == 0)
242+
throw new ArgumentException($"{nameof(currentVaultId)} cannot be empty.", nameof(currentVaultId));
243+
if (destinationVaultId is null || destinationVaultId.Length == 0)
244+
throw new ArgumentException($"{nameof(destinationVaultId)} cannot be empty.", nameof(destinationVaultId));
245+
246+
var command = $"item move {itemId} --current-vault {{currentVaultId}} --destination-vault {{destinationVaultId}}";
247+
Op(command);
248+
}
249+
250+
/// <inheritdoc />
251+
public void ShareItem(IItem item, IVault vault, string emailAddress, TimeSpan? expiresIn = null, bool? viewOnce = null)
252+
{
253+
if (item is null || item.Id.Length == 0)
254+
throw new ArgumentException($"{nameof(item.Id)} cannot be empty.", nameof(item));
255+
if (vault is null || vault.Id.Length == 0)
256+
throw new ArgumentException($"{nameof(vault.Id)} cannot be empty.", nameof(vault));
257+
258+
ShareItem(item.Id, vault.Id, [emailAddress], expiresIn, viewOnce);
259+
}
260+
261+
/// <inheritdoc />
262+
public void ShareItem(string itemId, string vaultId, string emailAddress, TimeSpan? expiresIn = null, bool? viewOnce = null)
263+
{
264+
if (itemId is null || itemId.Length == 0)
265+
throw new ArgumentException($"{nameof(itemId)} cannot be empty.", nameof(itemId));
266+
if (vaultId is null || vaultId.Length == 0)
267+
throw new ArgumentException($"{nameof(vaultId)} cannot be empty.", nameof(vaultId));
268+
269+
ShareItem(itemId, vaultId, [emailAddress], expiresIn, viewOnce);
270+
}
271+
272+
/// <inheritdoc />
273+
public void ShareItem(IItem item, IVault vault, IReadOnlyCollection<string> emailAddresses, TimeSpan? expiresIn = null, bool? viewOnce = null)
274+
{
275+
if (item is null || item.Id.Length == 0)
276+
throw new ArgumentException($"{nameof(item.Id)} cannot be empty.", nameof(item));
277+
if (vault is null || vault.Id.Length == 0)
278+
throw new ArgumentException($"{nameof(vault.Id)} cannot be empty.", nameof(vault));
279+
280+
ShareItem(item.Id, vault.Id, emailAddresses, expiresIn, viewOnce);
281+
}
282+
283+
/// <inheritdoc />
284+
public void ShareItem(string itemId, string vaultId, IReadOnlyCollection<string> emailAddresses, TimeSpan? expiresIn = null, bool? viewOnce = null)
285+
{
286+
if (itemId is null || itemId.Length == 0)
287+
throw new ArgumentException($"{nameof(itemId)} cannot be empty.", nameof(itemId));
288+
if (vaultId is null || vaultId.Length == 0)
289+
throw new ArgumentException($"{nameof(vaultId)} cannot be empty.", nameof(vaultId));
290+
291+
var command = $"item share {itemId} --vault {vaultId}";
292+
if (expiresIn is not null)
293+
command += $" --expires-in {expiresIn.Value.ToHumanReadable()}";
294+
if (viewOnce is not null && viewOnce.Value)
295+
command += " --view-once";
296+
Op(command);
297+
}
298+
}

0 commit comments

Comments
 (0)