mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-18 13:28:12 -04:00
Add 1Password importer (#542)
This commit is contained in:
committed by
Leendert de Borst
parent
43f5e0c647
commit
6f5ae7c17e
@@ -0,0 +1,21 @@
|
||||
@using AliasVault.ImportExport.Models
|
||||
@using AliasVault.ImportExport.Importers
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject GlobalNotificationService GlobalNotificationService
|
||||
@inject ILogger<ImportService1Password> Logger
|
||||
|
||||
<ImportServiceCard
|
||||
ServiceName="1Password"
|
||||
Description="Import passwords from your 1Password vault"
|
||||
LogoUrl="img/importers/1password.svg"
|
||||
ProcessFileCallback="ProcessFile">
|
||||
<p class="text-gray-700 dark:text-gray-300 mb-4">In order to import your 1Password vault, you need to export it as a CSV file. You can do this by logging into your 1Password account in the 1Password 8 desktop app (Windows / MacOS / Linux), going to the 'File' menu and selecting 'Export' (to CSV).</p>
|
||||
<p class="text-gray-700 dark:text-gray-300 mb-4">Once you have exported the file, you can upload it below.</p>
|
||||
</ImportServiceCard>
|
||||
|
||||
@code {
|
||||
private async Task<List<ImportedCredential>> ProcessFile(string fileContents)
|
||||
{
|
||||
return await OnePasswordImporter.ImportFromCsvAsync(fileContents);
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@
|
||||
<ClickOutsideHandler OnClose="CloseModal" ContentId="importServiceModal">
|
||||
<ModalWrapper OnEnter="HandleModalConfirm">
|
||||
<div id="importServiceModal" class="relative top-20 mx-auto p-5 shadow-lg rounded-md bg-white dark:bg-gray-800 border-2 border-gray-300 dark:border-gray-400 md:min-w-[32rem]">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg p-4 max-w-lg w-full mx-auto">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg p-4 w-full mx-auto">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div class="flex"><img src="@LogoUrl" alt="@ServiceName logo" class="w-8 h-8 float-left mr-4" /><h3 class="text-xl font-semibold dark:text-white">Import from @ServiceName</h3></div>
|
||||
<button @onclick="CloseModal" class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300">
|
||||
@@ -55,26 +55,28 @@
|
||||
@switch (CurrentStep)
|
||||
{
|
||||
case ImportStep.FileUpload:
|
||||
@if (!string.IsNullOrEmpty(ImportError))
|
||||
{
|
||||
<div class="mb-4 p-4 text-red-700 bg-red-100 rounded-lg dark:bg-red-200 dark:text-red-800" role="alert">
|
||||
@ImportError
|
||||
</div>
|
||||
}
|
||||
<div class="max-w-lg mx-auto">
|
||||
@if (!string.IsNullOrEmpty(ImportError))
|
||||
{
|
||||
<div class="mb-4 p-4 text-red-700 bg-red-100 rounded-lg dark:bg-red-200 dark:text-red-800" role="alert">
|
||||
@ImportError
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (IsImporting)
|
||||
{
|
||||
<LoadingIndicator />
|
||||
}
|
||||
@if (IsImporting)
|
||||
{
|
||||
<LoadingIndicator />
|
||||
}
|
||||
|
||||
<div class="@(IsImporting ? "hidden" : "")">
|
||||
@ChildContent
|
||||
<div class="mb-4 bg-amber-50 border border-amber-400 dark:bg-amber-800/30 dark:border-amber-500/50 rounded-lg p-4">
|
||||
<p class="mb-4 text-gray-700 dark:text-gray-200">Upload your @ServiceName export file:</p>
|
||||
<InputFile OnChange="HandleFileUpload" class="text-gray-700 dark:text-gray-200 file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-primary-50 file:text-primary-700 hover:file:bg-primary-100 dark:file:bg-primary-900/40 dark:file:text-primary-300 dark:hover:file:bg-primary-800/60" />
|
||||
</div>
|
||||
<div class="flex justify-end mt-6 space-x-2">
|
||||
<Button OnClick="@CloseModal" Color="secondary">Cancel</Button>
|
||||
<div class="@(IsImporting ? "hidden" : "")">
|
||||
@ChildContent
|
||||
<div class="mb-4 bg-amber-50 border border-amber-400 dark:bg-amber-800/30 dark:border-amber-500/50 rounded-lg p-4">
|
||||
<p class="mb-4 text-gray-700 dark:text-gray-200">Upload your @ServiceName export file:</p>
|
||||
<InputFile OnChange="HandleFileUpload" class="text-gray-700 dark:text-gray-200 file:mr-4 file:py-2 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-primary-50 file:text-primary-700 hover:file:bg-primary-100 dark:file:bg-primary-900/40 dark:file:text-primary-300 dark:hover:file:bg-primary-800/60" />
|
||||
</div>
|
||||
<div class="flex justify-end mt-6 space-x-2">
|
||||
<Button OnClick="@CloseModal" Color="secondary">Cancel</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
break;
|
||||
@@ -113,19 +115,21 @@
|
||||
break;
|
||||
|
||||
case ImportStep.Confirm:
|
||||
@if (IsImporting)
|
||||
{
|
||||
<LoadingIndicator />
|
||||
}
|
||||
else {
|
||||
<div class="mb-4">
|
||||
<p class="mb-4 text-gray-700 dark:text-gray-300">Are you sure you want to import (@ImportedCredentials.Count) credentials? Note: the import process can take a short while.</p>
|
||||
</div>
|
||||
<div class="flex justify-end mt-6 space-x-2">
|
||||
<Button OnClick="@HandlePreviousStep" Color="secondary">Back</Button>
|
||||
<Button OnClick="@HandleModalConfirm" Color="primary">Import</Button>
|
||||
</div>
|
||||
}
|
||||
<div class="max-w-lg mx-auto">
|
||||
@if (IsImporting)
|
||||
{
|
||||
<LoadingIndicator />
|
||||
}
|
||||
else {
|
||||
<div class="mb-4">
|
||||
<p class="mb-4 text-gray-700 dark:text-gray-300">Are you sure you want to import (@ImportedCredentials.Count) credentials? Note: the import process can take a short while.</p>
|
||||
</div>
|
||||
<div class="flex justify-end mt-6 space-x-2">
|
||||
<Button OnClick="@HandlePreviousStep" Color="secondary">Back</Button>
|
||||
<Button OnClick="@HandleModalConfirm" Color="primary">Import</Button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
break;
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<ImportServiceBitwarden />
|
||||
<ImportServiceKeePass />
|
||||
<ImportService1Password />
|
||||
<ImportServiceAliasVault />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -769,10 +769,6 @@ video {
|
||||
margin-left: 0.75rem;
|
||||
}
|
||||
|
||||
.ml-6 {
|
||||
margin-left: 1.5rem;
|
||||
}
|
||||
|
||||
.ml-auto {
|
||||
margin-left: auto;
|
||||
}
|
||||
@@ -2644,11 +2640,6 @@ video {
|
||||
background-color: rgb(133 77 14 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark\:bg-yellow-900:is(.dark *) {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(113 63 18 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark\:bg-opacity-80:is(.dark *) {
|
||||
--tw-bg-opacity: 0.8;
|
||||
}
|
||||
@@ -2743,11 +2734,6 @@ video {
|
||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.dark\:text-yellow-100:is(.dark *) {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(254 249 195 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.dark\:text-yellow-400:is(.dark *) {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(250 204 21 / var(--tw-text-opacity));
|
||||
|
||||
143
src/AliasVault.Client/wwwroot/img/importers/1password.svg
Normal file
143
src/AliasVault.Client/wwwroot/img/importers/1password.svg
Normal file
@@ -0,0 +1,143 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 27.8.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:url(#SVGID_1_);}
|
||||
.st1{fill:url(#SVGID_00000003806953128981211710000010604494801610004877_);}
|
||||
.st2{fill:url(#SVGID_00000138534652836814865550000015513145662374071175_);}
|
||||
.st3{fill:#FFFFFF;}
|
||||
.st4{fill:url(#SVGID_00000178891249291936294170000017599752083928133535_);}
|
||||
.st5{fill:url(#SVGID_00000099624116082093634780000002130301463185760680_);}
|
||||
.st6{fill:url(#SVGID_00000159460032060823389730000000515703250918618502_);}
|
||||
.st7{fill:url(#SVGID_00000021835272673548630180000003746360465168387750_);}
|
||||
.st8{fill:url(#SVGID_00000008841417857545760810000015183692108847128232_);}
|
||||
|
||||
.st9{fill:url(#SVGID_00000036216766700710711660000006010378259165164950_);stroke:url(#SVGID_00000057135321266455722670000016196693940672554887_);stroke-miterlimit:10;}
|
||||
.st10{fill:url(#SVGID_00000006671298426127412530000003354577712219538314_);}
|
||||
.st11{fill:url(#SVGID_00000019663290019837682370000004612082602279049916_);}
|
||||
.st12{fill:url(#SVGID_00000027569226305715931490000007753892242520527007_);}
|
||||
.st13{fill:url(#SVGID_00000158006836777100829930000014279642698594133424_);}
|
||||
.st14{fill:url(#SVGID_00000034788272653732579960000016974155572991256201_);}
|
||||
.st15{fill:url(#SVGID_00000160886399886325602350000012757927620993370543_);}
|
||||
</style>
|
||||
<g>
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="256" y1="1.109584e-10" x2="256" y2="512">
|
||||
<stop offset="0" style="stop-color:#CFD4E2"/>
|
||||
<stop offset="1" style="stop-color:#A9B4CC"/>
|
||||
</linearGradient>
|
||||
<path class="st0" d="M420.1,512H91.9C41.3,512,0,470.7,0,420.1V91.9C0,41.3,41.3,0,91.9,0h328.2C470.7,0,512,41.3,512,91.9v328.2
|
||||
C512,470.7,470.7,512,420.1,512z"/>
|
||||
|
||||
<radialGradient id="SVGID_00000080892890122459656140000004003202712423285181_" cx="255.006" cy="259.5827" r="227.9617" gradientTransform="matrix(1.006 0 0 1.0202 -1.5341 -5.0938)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.96" style="stop-color:#000000;stop-opacity:0.7"/>
|
||||
<stop offset="1" style="stop-color:#000000;stop-opacity:0"/>
|
||||
</radialGradient>
|
||||
<path style="fill:url(#SVGID_00000080892890122459656140000004003202712423285181_);" d="M487.3,259.1c0,129-104,234.9-232.3,234.9
|
||||
S22.7,388.1,22.7,259.1S126.7,25.5,255,25.5S487.3,130.1,487.3,259.1z"/>
|
||||
|
||||
<linearGradient id="SVGID_00000088836898082065010640000018020030158233795000_" gradientUnits="userSpaceOnUse" x1="255" y1="31.7054" x2="255" y2="479.2018">
|
||||
<stop offset="0" style="stop-color:#F7F8FA"/>
|
||||
<stop offset="1" style="stop-color:#A5B1D4"/>
|
||||
</linearGradient>
|
||||
<circle style="fill:url(#SVGID_00000088836898082065010640000018020030158233795000_);" cx="255" cy="255.5" r="223.7"/>
|
||||
<circle class="st3" cx="255" cy="255.5" r="207.5"/>
|
||||
|
||||
<linearGradient id="SVGID_00000069371419516710993290000014674188358113625238_" gradientUnits="userSpaceOnUse" x1="255" y1="64.2809" x2="255" y2="446.6263">
|
||||
<stop offset="0" style="stop-color:#4A7DEE"/>
|
||||
<stop offset="1" style="stop-color:#D6E7FD"/>
|
||||
</linearGradient>
|
||||
<circle style="fill:url(#SVGID_00000069371419516710993290000014674188358113625238_);" cx="255" cy="255.5" r="191.2"/>
|
||||
|
||||
<linearGradient id="SVGID_00000065064387537909975690000008168498501573575072_" gradientUnits="userSpaceOnUse" x1="255" y1="78.3427" x2="255" y2="432.5645">
|
||||
<stop offset="0" style="stop-color:#1052FA"/>
|
||||
<stop offset="1" style="stop-color:#03A2FF"/>
|
||||
</linearGradient>
|
||||
<circle style="fill:url(#SVGID_00000065064387537909975690000008168498501573575072_);" cx="255" cy="255.5" r="177.1"/>
|
||||
|
||||
<radialGradient id="SVGID_00000033342088705839663170000005065284211989201043_" cx="255" cy="259.6645" r="177.5771" gradientTransform="matrix(1 0 0 1.0099 0 -2.5659)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.2827" style="stop-color:#000000;stop-opacity:0"/>
|
||||
<stop offset="0.94" style="stop-color:#000000;stop-opacity:0"/>
|
||||
<stop offset="1" style="stop-color:#00001E;stop-opacity:0.6"/>
|
||||
</radialGradient>
|
||||
<circle style="fill:url(#SVGID_00000033342088705839663170000005065284211989201043_);" cx="255" cy="255.5" r="177.1"/>
|
||||
|
||||
<radialGradient id="SVGID_00000035526984743727634550000013253980538971136412_" cx="254.9778" cy="255.7856" r="139.7306" gradientTransform="matrix(0.9782 0 0 0.9837 5.5688 6.0985)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.96" style="stop-color:#000000;stop-opacity:0.4"/>
|
||||
<stop offset="1" style="stop-color:#000000;stop-opacity:0"/>
|
||||
</radialGradient>
|
||||
|
||||
<ellipse style="fill:url(#SVGID_00000035526984743727634550000013253980538971136412_);" cx="255" cy="257.7" rx="136.7" ry="137.5"/>
|
||||
<g>
|
||||
|
||||
<linearGradient id="SVGID_00000150796304906679271230000000770736055720491697_" gradientUnits="userSpaceOnUse" x1="255" y1="120.2667" x2="255" y2="390.6405">
|
||||
<stop offset="0" style="stop-color:#FBF6F6"/>
|
||||
<stop offset="1" style="stop-color:#D4DFF6"/>
|
||||
</linearGradient>
|
||||
<circle style="fill:url(#SVGID_00000150796304906679271230000000770736055720491697_);" cx="255" cy="255.5" r="135.2"/>
|
||||
</g>
|
||||
|
||||
<radialGradient id="SVGID_00000019660750539865065670000003955002333269141426_" cx="255.3853" cy="299.8408" r="111.8273" fx="255.3853" fy="318.9533" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.3659" style="stop-color:#E6E9FA"/>
|
||||
<stop offset="0.5491" style="stop-color:#E3E7F9"/>
|
||||
<stop offset="0.6816" style="stop-color:#DADFF4"/>
|
||||
<stop offset="0.7983" style="stop-color:#CAD2EC"/>
|
||||
<stop offset="0.905" style="stop-color:#B4C0E1"/>
|
||||
<stop offset="1" style="stop-color:#99AAD4"/>
|
||||
</radialGradient>
|
||||
|
||||
<linearGradient id="SVGID_00000030477569789705423270000000820441070347519890_" gradientUnits="userSpaceOnUse" x1="255" y1="323.7591" x2="255" y2="187.1481">
|
||||
<stop offset="0" style="stop-color:#F2F2F2"/>
|
||||
<stop offset="1" style="stop-color:#9BA6CE"/>
|
||||
</linearGradient>
|
||||
|
||||
<circle style="fill:url(#SVGID_00000019660750539865065670000003955002333269141426_);stroke:url(#SVGID_00000030477569789705423270000000820441070347519890_);stroke-miterlimit:10;" cx="255" cy="255.5" r="67.8"/>
|
||||
<g>
|
||||
|
||||
<linearGradient id="SVGID_00000060022293084610081460000017109631303786632858_" gradientUnits="userSpaceOnUse" x1="255.0003" y1="323.8614" x2="255.0003" y2="187.0463">
|
||||
<stop offset="0" style="stop-color:#E6E9FA"/>
|
||||
<stop offset="1" style="stop-color:#99AAD4"/>
|
||||
</linearGradient>
|
||||
<path style="fill:url(#SVGID_00000060022293084610081460000017109631303786632858_);" d="M255,323.9c-37.7,0-68.4-30.7-68.4-68.4
|
||||
c0-37.7,30.7-68.4,68.4-68.4c37.7,0,68.4,30.7,68.4,68.4C323.4,293.2,292.7,323.9,255,323.9z M255,188.2
|
||||
c-37.1,0-67.2,30.1-67.2,67.2s30.1,67.2,67.2,67.2s67.2-30.1,67.2-67.2S292.1,188.2,255,188.2z"/>
|
||||
</g>
|
||||
|
||||
<linearGradient id="SVGID_00000043443407870630449420000003935611237313607865_" gradientUnits="userSpaceOnUse" x1="255.2881" y1="350.8774" x2="255.2881" y2="159.9432">
|
||||
<stop offset="0" style="stop-color:#191D37"/>
|
||||
<stop offset="1" style="stop-color:#0A0D1B"/>
|
||||
</linearGradient>
|
||||
<path style="fill:url(#SVGID_00000043443407870630449420000003935611237313607865_);" d="M272.5,350.9h-34.4c-7.2,0-13-5.9-13-13
|
||||
V246c0-1.2,0.6-2.4,1.6-3.2l10.1-7.4c2.1-1.5,2.1-4.6,0.1-6.2l-10.4-8.2c-0.9-0.7-1.5-1.9-1.5-3.1v-45c0-7.2,5.9-13,13-13h34.4
|
||||
c7.2,0,13,5.9,13,13v92c0,1.2-0.5,2.3-1.5,3.1l-10.6,8.3c-2,1.6-2,4.5-0.1,6.1l10.7,8.7c0.9,0.7,1.4,1.9,1.4,3v43.5
|
||||
C285.5,345,279.6,350.9,272.5,350.9z"/>
|
||||
|
||||
<linearGradient id="SVGID_00000124847186351877336530000004144667077095366310_" gradientUnits="userSpaceOnUse" x1="15.6569" y1="255.4536" x2="15.6569" y2="249.7905">
|
||||
<stop offset="0" style="stop-color:#000000;stop-opacity:0.4"/>
|
||||
<stop offset="0.933" style="stop-color:#000000;stop-opacity:0"/>
|
||||
</linearGradient>
|
||||
<path style="fill:url(#SVGID_00000124847186351877336530000004144667077095366310_);" d="M0,250.6v4.9h31.3c0-1.6,0-3.3,0.1-4.9H0z
|
||||
"/>
|
||||
|
||||
<linearGradient id="SVGID_00000096743138351438000510000017129655145029114014_" gradientUnits="userSpaceOnUse" x1="15.6569" y1="260.3536" x2="15.6569" y2="254.6487">
|
||||
<stop offset="5.326722e-02" style="stop-color:#FFFFFF;stop-opacity:0"/>
|
||||
<stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.5"/>
|
||||
</linearGradient>
|
||||
<path style="fill:url(#SVGID_00000096743138351438000510000017129655145029114014_);" d="M31.3,255.5H0v4.9h31.3
|
||||
C31.3,258.7,31.3,257.1,31.3,255.5z"/>
|
||||
|
||||
<linearGradient id="SVGID_00000071543710920805015200000000421061880900545470_" gradientUnits="userSpaceOnUse" x1="-159.9804" y1="255.4536" x2="-159.9804" y2="249.7905" gradientTransform="matrix(-1 0 0 1 335.3627 0)">
|
||||
<stop offset="0" style="stop-color:#000000;stop-opacity:0.4"/>
|
||||
<stop offset="0.933" style="stop-color:#000000;stop-opacity:0"/>
|
||||
</linearGradient>
|
||||
<path style="fill:url(#SVGID_00000071543710920805015200000000421061880900545470_);" d="M512,250.6v4.9h-33.3c0-1.6,0-3.3-0.1-4.9
|
||||
H512z"/>
|
||||
|
||||
<linearGradient id="SVGID_00000109007114612485663690000009752698984711783319_" gradientUnits="userSpaceOnUse" x1="-159.9804" y1="260.3536" x2="-159.9804" y2="254.6487" gradientTransform="matrix(-1 0 0 1 335.3627 0)">
|
||||
<stop offset="5.326722e-02" style="stop-color:#FFFFFF;stop-opacity:0"/>
|
||||
<stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.5"/>
|
||||
</linearGradient>
|
||||
<path style="fill:url(#SVGID_00000109007114612485663690000009752698984711783319_);" d="M478.7,255.5H512v4.9h-33.3
|
||||
C478.7,258.7,478.7,257.1,478.7,255.5z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 9.4 KiB |
@@ -65,6 +65,8 @@
|
||||
<EmbeddedResource Include="TestData\Exports\bitwarden.csv" />
|
||||
<None Remove="TestData\Exports\keepass.kdbx.csv" />
|
||||
<EmbeddedResource Include="TestData\Exports\keepass.kdbx.csv" />
|
||||
<None Remove="TestData\Exports\1password_8.csv" />
|
||||
<EmbeddedResource Include="TestData\Exports\1password_8.csv" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
Title,Url,Username,Password,OTPAuth,Favorite,Archived,Tags,Notes
|
||||
Test record 2 with 2FA,,username2fa,password2fa,otpauth://totp/Strongbox?secret=PLW4SB3PQ7MKVXY2MXF4NEXS6Y&period=30&algorithm=SHA1&digits=6,false,false,,Notes about 2FA record
|
||||
1Password Account (dpatel),https://my.1password.com,derekpatel@aliasvault.net,passwordexample,,false,false,Starter Kit,You can use this login to sign in to your account on 1password.com.
|
||||
Test record 1,https://nu.nl,username1,password1,,false,false,,
|
||||
Password entry,,passwordusername,passwordpassword,,false,false,,
|
||||
|
@@ -20,6 +20,7 @@ public class ImportExportTests
|
||||
/// <summary>
|
||||
/// Test case for importing credentials from CSV and ensuring all values are present.
|
||||
/// </summary>
|
||||
/// <returns>Async task.</returns>
|
||||
[Test]
|
||||
public async Task ImportCredentialsFromCsv()
|
||||
{
|
||||
@@ -79,16 +80,15 @@ public class ImportExportTests
|
||||
Assert.That(importedCredential.ServiceUrl, Is.EqualTo(credential.Service.Url));
|
||||
Assert.That(importedCredential.Username, Is.EqualTo(credential.Username));
|
||||
Assert.That(importedCredential.Notes, Is.EqualTo(credential.Notes));
|
||||
Assert.That(importedCredential.CreatedAt, Is.EqualTo(credential.CreatedAt));
|
||||
Assert.That(importedCredential.UpdatedAt, Is.EqualTo(credential.UpdatedAt));
|
||||
Assert.That(importedCredential.CreatedAt?.Date, Is.EqualTo(credential.CreatedAt.Date));
|
||||
Assert.That(importedCredential.UpdatedAt?.Date, Is.EqualTo(credential.UpdatedAt.Date));
|
||||
Assert.That(importedCredential.Alias!.Gender, Is.EqualTo(credential.Alias!.Gender));
|
||||
Assert.That(importedCredential.Alias!.FirstName, Is.EqualTo(credential.Alias!.FirstName));
|
||||
Assert.That(importedCredential.Alias!.LastName, Is.EqualTo(credential.Alias!.LastName));
|
||||
Assert.That(importedCredential.Alias!.NickName, Is.EqualTo(credential.Alias!.NickName));
|
||||
Assert.That(importedCredential.Alias!.BirthDate, Is.EqualTo(credential.Alias!.BirthDate));
|
||||
Assert.That(importedCredential.Alias!.Email, Is.EqualTo(credential.Alias!.Email));
|
||||
Assert.That(importedCredential.Alias!.CreatedAt, Is.EqualTo(credential.Alias!.CreatedAt));
|
||||
Assert.That(importedCredential.Alias!.UpdatedAt, Is.EqualTo(credential.Alias!.UpdatedAt));
|
||||
Assert.That(importedCredential.Alias!.CreatedAt?.Date, Is.EqualTo(credential.Alias!.CreatedAt.Date));
|
||||
Assert.That(importedCredential.Alias!.UpdatedAt?.Date, Is.EqualTo(credential.Alias!.UpdatedAt.Date));
|
||||
Assert.That(importedCredential.Password, Is.EqualTo(credential.Passwords.First().Value));
|
||||
});
|
||||
}
|
||||
@@ -165,4 +165,42 @@ public class ImportExportTests
|
||||
Assert.That(sampleCredential.Password, Is.EqualTo("&3V_$z?Aiw-_x+nbYj"));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test case for importing credentials from 1Password CSV and ensuring all values are present.
|
||||
/// </summary>
|
||||
/// <returns>Async task.</returns>
|
||||
[Test]
|
||||
public async Task ImportCredentialsFrom1PasswordCsv()
|
||||
{
|
||||
// Arrange
|
||||
var fileContent = await ResourceReaderUtility.ReadEmbeddedResourceStringAsync("AliasVault.UnitTests.TestData.Exports.1password_8.csv");
|
||||
|
||||
// Act
|
||||
var importedCredentials = await OnePasswordImporter.ImportFromCsvAsync(fileContent);
|
||||
|
||||
// Assert
|
||||
Assert.That(importedCredentials, Has.Count.EqualTo(4));
|
||||
|
||||
// Test specific entries
|
||||
var twoFactorCredential = importedCredentials.First(c => c.Username == "username2fa");
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(twoFactorCredential.ServiceName, Is.EqualTo("Test record 2 with 2FA"));
|
||||
Assert.That(twoFactorCredential.Username, Is.EqualTo("username2fa"));
|
||||
Assert.That(twoFactorCredential.Password, Is.EqualTo("password2fa"));
|
||||
Assert.That(twoFactorCredential.TwoFactorSecret, Is.EqualTo("otpauth://totp/Strongbox?secret=PLW4SB3PQ7MKVXY2MXF4NEXS6Y&period=30&algorithm=SHA1&digits=6"));
|
||||
Assert.That(twoFactorCredential.Notes, Is.EqualTo("Notes about 2FA record"));
|
||||
});
|
||||
|
||||
var onePasswordAccount = importedCredentials.First(c => c.ServiceName == "1Password Account (dpatel)");
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(onePasswordAccount.ServiceName, Is.EqualTo("1Password Account (dpatel)"));
|
||||
Assert.That(onePasswordAccount.ServiceUrl, Is.EqualTo("https://my.1password.com"));
|
||||
Assert.That(onePasswordAccount.Username, Is.EqualTo("derekpatel@aliasvault.net"));
|
||||
Assert.That(onePasswordAccount.Password, Is.EqualTo("passwordexample"));
|
||||
Assert.That(onePasswordAccount.Notes, Is.EqualTo("You can use this login to sign in to your account on 1password.com."));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="BitwardenImporter.cs" company="lanedirt">
|
||||
// Copyright (c) lanedirt. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.ImportExport.Importers;
|
||||
|
||||
using AliasVault.ImportExport.Models;
|
||||
using AliasVault.ImportExport.Models.Imports;
|
||||
using CsvHelper;
|
||||
using CsvHelper.Configuration;
|
||||
using System.Globalization;
|
||||
|
||||
/// <summary>
|
||||
/// Imports credentials from 1Password.
|
||||
/// </summary>
|
||||
public class OnePasswordImporter
|
||||
{
|
||||
/// <summary>
|
||||
/// Imports 1Password CSV file and converts contents to list of ImportedCredential model objects.
|
||||
/// </summary>
|
||||
/// <param name="fileContent">The content of the CSV file.</param>
|
||||
/// <returns>The imported list of ImportedCredential objects.</returns>
|
||||
public static async Task<List<ImportedCredential>> ImportFromCsvAsync(string fileContent)
|
||||
{
|
||||
using var reader = new StringReader(fileContent);
|
||||
using var csv = new CsvReader(reader, new CsvConfiguration(CultureInfo.InvariantCulture));
|
||||
|
||||
var credentials = new List<ImportedCredential>();
|
||||
await foreach (var record in csv.GetRecordsAsync<OnePasswordCsvRecord>())
|
||||
{
|
||||
var credential = new ImportedCredential
|
||||
{
|
||||
ServiceName = record.Title,
|
||||
ServiceUrl = record.Url,
|
||||
Username = record.Username,
|
||||
Password = record.Password,
|
||||
TwoFactorSecret = record.OTPAuth,
|
||||
Notes = record.Notes
|
||||
};
|
||||
|
||||
credentials.Add(credential);
|
||||
}
|
||||
|
||||
if (credentials.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("No records found in the CSV file.");
|
||||
}
|
||||
|
||||
return credentials;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="BitwardenCsvRecord.cs" company="lanedirt">
|
||||
// Copyright (c) lanedirt. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
using AliasVault.ImportExport.Converters;
|
||||
using CsvHelper.Configuration.Attributes;
|
||||
|
||||
namespace AliasVault.ImportExport.Models.Imports;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a 1Password CSV record that is being imported from a 1Password CSV export file.
|
||||
/// </summary>
|
||||
public class OnePasswordCsvRecord
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the title of the item.
|
||||
/// </summary>
|
||||
[Name("Title")]
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the URL of the item.
|
||||
/// </summary>
|
||||
[Name("Url")]
|
||||
public string Url { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the username of the item.
|
||||
/// </summary>
|
||||
[Name("Username")]
|
||||
public string Username { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the password of the item.
|
||||
/// </summary>
|
||||
[Name("Password")]
|
||||
public string Password { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the OTP (One-Time Password) authentication secret.
|
||||
/// </summary>
|
||||
[Name("OTPAuth")]
|
||||
public string? OTPAuth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the item is favorited.
|
||||
/// </summary>
|
||||
[Name("Favorite")]
|
||||
[TypeConverter(typeof(BooleanConverter))]
|
||||
public bool Favorite { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the item is archived.
|
||||
/// </summary>
|
||||
[Name("Archived")]
|
||||
[TypeConverter(typeof(BooleanConverter))]
|
||||
public bool Archived { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the service URL.
|
||||
/// </summary>
|
||||
[Name("Tags")]
|
||||
public string? Tags { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets any additional notes.
|
||||
/// </summary>
|
||||
[Name("Notes")]
|
||||
public string? Notes { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user