Skip to content

Commit

Permalink
Merge pull request #70 from GrinGrin/feat/bitwarden-powershell
Browse files Browse the repository at this point in the history
Add Dynamic Folder Bitwarden PowerShell script
  • Loading branch information
lemonmojo committed Aug 8, 2023
2 parents d5d26d4 + 3f53cd8 commit 899e6e5
Showing 1 changed file with 46 additions and 0 deletions.
46 changes: 46 additions & 0 deletions Dynamic Folder/Bitwarden/Bitwarden (PowerShell).rdfe
@@ -0,0 +1,46 @@
{
"Name": "Dynamic Folder Export",
"Objects": [
{
"Notes": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\r\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\r\n\t<head>\r\n\t\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /><title>\r\n\t\t</title>\r\n\t\t<style type=\"text/css\">\r\n\t\t\t.csB8AC2BC8{text-align:left;text-indent:0pt;margin:0pt 0pt 0pt 0pt}\r\n\t\t\t.csCE67CBC9{color:#000000;background-color:transparent;font-family:Calibri;font-size:11pt;font-weight:normal;font-style:normal;}\r\n\t\t\t.cs8A3D3EFA{color:#000000;background-color:transparent;font-family:'Times New Roman';font-size:18pt;font-weight:bold;font-style:normal;}\r\n\t\t\t.cs32D317EB{text-align:left;text-indent:0pt;margin:12pt 0pt 12pt 0pt}\r\n\t\t\t.csD05B7528{color:#000000;background-color:transparent;font-family:'Times New Roman';font-size:12pt;font-weight:bold;font-style:normal;}\r\n\t\t\t.csD599CF66{color:#000000;background-color:transparent;font-family:'Times New Roman';font-size:12pt;font-weight:normal;font-style:normal;}\r\n\t\t\t.cs3785182E{color:#000000;background-color:transparent;font-family:'Times New Roman';font-size:12pt;font-weight:normal;font-style:normal;text-decoration: none;}\r\n\t\t\t.cs5E4473C3{color:#0000FF;background-color:transparent;font-family:'Times New Roman';font-size:12pt;font-weight:normal;font-style:normal;text-decoration: underline;}\r\n\t\t\t.cs73206D29{color:#000000;background-color:transparent;font-family:'Times New Roman';font-size:13.5pt;font-weight:bold;font-style:normal;}\r\n\t\t\t.cs6B0FFF63{text-align:left;margin:0pt 0pt 0pt 0pt;list-style-type:disc;color:#000000;background-color:transparent;font-family:Arial;font-size:12pt;font-weight:normal;font-style:normal}\r\n\t\t\t.csC22FCEB2{text-align:left;margin:0pt 0pt 0pt 0pt;list-style-type:circle;color:#000000;background-color:transparent;font-family:'Courier New';font-size:12pt;font-weight:normal;font-style:normal}\r\n\t\t\t.csE1537053{color:#000000;background-color:transparent;font-family:Arial;font-size:12pt;font-weight:normal;font-style:normal;}\r\n\t\t\t.csB67336A5{text-align:left;text-indent:-18pt;margin:0pt 0pt 0pt 36pt}\r\n\t\t\t.csD08E4B8C{color:#C00000;background-color:transparent;font-family:'Times New Roman';font-size:13.5pt;font-weight:bold;font-style:normal;text-decoration: underline;}\r\n\t\t</style>\r\n\t</head>\r\n\t<body>\r\n\t\t<h2 class=\"csB8AC2BC8\">\r\n\t\t\t<span class=\"csCE67CBC9\">&nbsp;</span><span class=\"cs8A3D3EFA\">Bitwarden Dynamic Folder sample with Powershell</span></h2>\r\n\t\t<p class=\"cs32D317EB\"><span class=\"csD05B7528\">Version</span><span class=\"csD599CF66\">: 1.0.0<br/></span><span class=\"csD05B7528\">Author</span><span class=\"csD599CF66\">: Nicolas Grimler</span></p><p class=\"cs32D317EB\"><span class=\"csD599CF66\">This Dynamic Folder sample allows you to import credentials from Bitwarden. The Bitwarden CLI client is required and the full executable path where it is installed must be configured in the &quot;Custom Properties&quot; section. Also, your Bitwarden login details must be provided in the &quot;Credentials&quot; section.</span></p><p class=\"cs32D317EB\"><span class=\"csD599CF66\">It use the Bitwarden User API to login and the master password to unlock the vault. Please read <a class=\"cs3785182E\" href=\"https://bitwarden.com/help/personal-api-key/\"><span class=\"cs5E4473C3\">https://bitwarden.com/help/personal-api-key/</span></a></span><span class=\"csD599CF66\"> to know how to get your personal API Key.<br/>If you don&#39;t want to use an API Key, please ensure that you are already logged in using the bw.exe CLI tool as the script will not handle the TOTP 2FA handshake.</span></p><p class=\"cs32D317EB\"><span class=\"csD599CF66\">At the moment, only credentials and secure notes are collected. The folder structure is as presented in Bitwarden (Folder, Folder/Subfolder, ...). Support for full directory structure may be implemented in future version.</span></p><h3 class=\"csB8AC2BC8\">\r\n\t\t\t<span class=\"cs73206D29\">Requirements</span></h3>\r\n\t\t<ul style=\"margin-top:0;margin-bottom:0;\">\r\n\t\t\t<li class=\"cs6B0FFF63\"><span class=\"csD599CF66\"><a class=\"cs3785182E\" href=\"https://help.bitwarden.com/article/cli\"><span class=\"cs5E4473C3\">Bitwarden command-line tool (CLI)</span></a></span></li><li class=\"cs6B0FFF63\"><span class=\"csD599CF66\">PowerShell, either:</span><ul style=\"margin-top:0;margin-bottom:0;\">\r\n\t\t\t\t<li class=\"csC22FCEB2\"><span class=\"csD599CF66\">Legacy PowerShell (version 5.1 as standard Windows installation)</span></li><li class=\"csC22FCEB2\"><span class=\"csD599CF66\">PowerShell Core (6.x and later) available in <a class=\"cs3785182E\" href=\"https://apps.microsoft.com/store/detail/powershell/9MZ1SNWT0N5D?hl=en-us&amp;gl=us\"><span class=\"cs5E4473C3\">Microsoft Store</span></a></span><span class=\"csD599CF66\"> or <a class=\"cs3785182E\" href=\"https://github.com/PowerShell/PowerShell\"><span class=\"cs5E4473C3\">GitHub</span></a></span></li></ul>\r\n\t\t\t</li></ul>\r\n\t\t<p class=\"csB8AC2BC8\"><span class=\"csE1537053\">&nbsp;</span></p><h3 class=\"csB8AC2BC8\">\r\n\t\t\t<span class=\"cs73206D29\">Setup</span></h3>\r\n\t\t<ul style=\"margin-top:0;margin-bottom:0;\">\r\n\t\t\t<li class=\"cs6B0FFF63\"><span class=\"csD599CF66\">Specify the full, absolute path to the Bitwarden CLI tool in the &quot;Custom Properties&quot; section.</span></li><li class=\"cs6B0FFF63\"><span class=\"csD599CF66\">Specify your server URL if on-premise instance, or offical Bitwarden URL</span></li><li class=\"cs6B0FFF63\"><span class=\"csD599CF66\">Specify your ClientID &amp; ClientSecret for the API</span></li><li class=\"cs6B0FFF63\"><span class=\"csD599CF66\">Specify you master password to unlock the vault</span></li></ul>\r\n\t\t<p class=\"csB67336A5\"><span class=\"csD599CF66\">&nbsp;</span></p><p class=\"csB8AC2BC8\"><span class=\"csD08E4B8C\">Important note:</span></p><p class=\"csB8AC2BC8\"><span class=\"csD599CF66\">In the configuration of the interpreter used to run the script, </span><span class=\"csD05B7528\">check</span><span class=\"csD599CF66\"> the box &quot;Do not load the PowerShell profile&quot; as it may otherwise add unwanted messages invalidating the JSON output and causing errors.</span></p></body>\r\n</html>\r\n",
"Script": "# Env config\r\n$global:OutputEncoding = New-Object Text.Utf8Encoding -ArgumentList (,$false) # BOM-less\r\n[Console]::OutputEncoding = $global:OutputEncoding\r\n$PSStyle.OutputRendering = 'PlainText'\r\n\r\n# Bitwarden access config\r\n$Bitwarden = ( New-Object PSObject |\r\n Add-Member -PassThru NoteProperty exec_path '$CustomProperty.BitWardenCLIExecutable$' |\r\n Add-Member -PassThru NoteProperty serverUrl '$CustomProperty.BitWardenServerURL$' |\r\n Add-Member -PassThru NoteProperty clientId '$CustomProperty.APIClientID$' |\r\n Add-Member -PassThru NoteProperty clientSecret '$CustomProperty.APIClientSecret$' |\r\n Add-Member -PassThru NoteProperty password '$CustomProperty.AccountPassword$' |\r\n Add-Member -PassThru NoteProperty session '' )\r\n\r\n# Check bw.exe path validity\r\nif (!(Test-Path -Path \"$($Bitwarden.exec_path)\" -PathType Leaf)) {\r\n Write-Error -Message \"Bitwarden CLI utility not found at specified path. Please check CLI utility path in Custom Properties.\" -ErrorAction Stop\r\n}\r\n\r\n# Structures\r\n$final = @{ Objects = @(@{ Type = \"Folder\"; ID = \"personal\"; Name = \"Personal Vault\"; IconName = \"Flat/Objects/User Record\"; Objects = @(); }); }\r\n\r\n# Functions\r\nfunction Get-VaultItems {\r\n [CmdletBinding()]\r\n param (\r\n [Parameter(Mandatory=$false)]\r\n [string]$folderId = \"\",\r\n [Parameter(Mandatory=$false)]\r\n [string]$collectionId = \"\"\r\n )\r\n\r\n if ($folderId -eq \"\" -and $collectionId -eq \"\") { Write-Error -Message \"Folder ID or Collection ID needed, none provided.\" -ErrorAction Stop }\r\n\r\n if ($folderId -ne \"\" -and $collectionId -eq \"\") {\r\n $tmpItems = (Invoke-Command -ScriptBloc { & \"$($Bitwarden.exec_path)\" list items --folderid $folderId --session \"$($Bitwarden.session)\"}) | ConvertFrom-Json\r\n } elseif ($folderId -eq \"\" -and $collectionId -ne \"\") {\r\n $tmpItems = (Invoke-Command -ScriptBloc { & \"$($Bitwarden.exec_path)\" list items --collectionid $collectionId --session \"$($Bitwarden.session)\"}) | ConvertFrom-Json\r\n } else {\r\n Write-Error -Message \"Either FolderId or CollectionId are needed, not both.\" -ErrorAction Stop\r\n }\r\n $items = [array]@()\r\n foreach ($item in $tmpItems) {\r\n # Skip shared items with an organization to prevent duplicates\r\n if ($folderid -ne \"\" -and $null -ne $item.organizationid) { continue }\r\n\r\n # Parse item of type Login/Secure Note only\r\n switch ($item.type) {\r\n \"1\" { # Login\r\n $row = \"\" | Select-Object Type,ID,Name,Notes,Favorite,Username,Password,URL,CustomProperties\r\n $row.Type = \"Credential\"\r\n $row.ID = $item.id\r\n $row.Name = $item.name\r\n if ($null -ne $item.notes) {\r\n $row.Notes = $item.notes.Replace(\"`r`n\", \"<br />\").Replace(\"`r\", \"<br />\").Replace(\"`n\", \"<br />\")\r\n }\r\n if ($item.favorite -eq \"true\") { $row.Favorite = $true } else { $row.Favorite = $false }\r\n $row.Username = $item.login.username\r\n $row.Password = $item.login.password\r\n if ($item.login.uris.Count -gt 0) {\r\n $row.URL = $item.login.uris[0].uri\r\n }\r\n $row.CustomProperties = [array]@()\r\n if ($item.fields.count -gt 0) {\r\n $itemFields = [array]@()\r\n $fieldIndex = 0\r\n foreach ($field in $item.fields) {\r\n $frow = \"\" | Select-Object Type,Name,Value\r\n switch ($field.type) {\r\n \"0\" { $frow.Type = \"Text\" }\r\n \"1\" { $frow.Type = \"Protected\" }\r\n \"2\" { $frow.Type = \"YesNo\" }\r\n }\r\n if ($null -eq $field.name) {\r\n $frow.Name = \"UnnamedField$($fieldIndex)\"\r\n $fieldIndex++\r\n } else {\r\n $frow.Name = $field.name\r\n }\r\n $frow.Value = $field.value\r\n\r\n $itemFields += $frow\r\n }\r\n \r\n $row.CustomProperties = $itemFields\r\n }\r\n $items += $row\r\n }\r\n \"2\" { # Secure Note\r\n $row = \"\" | Select-Object Type,ID,Name,Notes,TemplateID,CustomProperties\r\n $row.Type = \"Information\"\r\n $row.ID = $item.id\r\n $row.Name = $item.name\r\n if ($null -ne $item.notes) {\r\n $row.Notes = $item.notes.Replace(\"`r`n\", \"<br />\").Replace(\"`r\", \"<br />\").Replace(\"`n\", \"<br />\")\r\n }\r\n $row.TemplateID = \"Custom\"\r\n $row.CustomProperties = @()\r\n $itemFields = [array]@()\r\n if ($item.fields.count -gt 0) {\r\n $fieldIndex = 0\r\n foreach ($field in $item.fields) {\r\n $frow = \"\" | Select-Object Type,Name,Value\r\n switch ($field.type) {\r\n \"0\" { $frow.Type = \"Text\" }\r\n \"1\" { $frow.Type = \"Protected\" }\r\n \"2\" { $frow.Type = \"YesNo\" }\r\n }\r\n if ($null -eq $field.name) {\r\n $frow.Name = \"UnnamedField$($fieldIndex)\"\r\n $fieldIndex++\r\n } else {\r\n $frow.Name = $field.name\r\n }\r\n $frow.Value = $field.value\r\n\r\n $itemFields += $frow\r\n }\r\n } else {\r\n $itemFields += @{ Type = \"Header\"; Name = \"See notes for details\"; Value = \"\"; }\r\n }\r\n $row.CustomProperties = $itemFields\r\n $items += $row\r\n }\r\n }\r\n }\r\n\r\n return $items\r\n}\r\n\r\n# Get Vault status\r\n$status = (Invoke-Command -ScriptBloc { & \"$($Bitwarden.exec_path)\" status }) | ConvertFrom-Json\r\n\r\nif ($null -ne $status) {\r\n switch ($status.status) {\r\n \"unauthenticated\" {\r\n if ($null -eq $status.serverUrl -or $status.serverUrl -ne $Bitwarden.serverUrl) {\r\n # Vault not configured, configure server\r\n [void](Invoke-Command -ScriptBlock { & \"$($Bitwarden.exec_path)\" config server \"$($Bitwarden.serverUrl)\" })\r\n }\r\n\r\n # Prepare Vault login using API key\r\n $env:BW_CLIENTID = $Bitwarden.clientId\r\n $env:BW_CLIENTSECRET = $Bitwarden.clientSecret\r\n $env:BW_PASSWORD = $Bitwarden.password\r\n [void](Invoke-Command -ScriptBlock { & \"$($Bitwarden.exec_path)\" login --apikey})\r\n\r\n # Unlock Vault using password\r\n $Bitwarden.session = Invoke-Command -ScriptBlock { & \"$($Bitwarden.exec_path)\" unlock --passwordenv BW_PASSWORD --raw }\r\n\r\n if ($null -eq $Bitwarden.session -or $Bitwarden.session -eq \"\") {\r\n Write-Error -Message \"Unable to authenticate and unlock your vault. Please check your API credentials and master password in Custom Properties.\" -ErrorAction Stop\r\n }\r\n\r\n # Clear env variables\r\n Remove-Item -Path Env:\\BW_*\r\n }\r\n \"locked\" {\r\n # Vault is locked, unlock it with password\r\n $env:BW_PASSWORD = $Bitwarden.password\r\n $Bitwarden.session = Invoke-Command -ScriptBlock { & \"$($Bitwarden.exec_path)\" unlock --passwordenv BW_PASSWORD --raw }\r\n\r\n if ($null -eq $Bitwarden.session -or $Bitwarden.session -eq \"\") {\r\n Write-Error -Message \"Unable to unlock your vault. Please check your master password in Custom Properties.\" -ErrorAction Stop\r\n }\r\n\r\n # Clear env variables\r\n Remove-Item -Path Env:\\BW_*\r\n }\r\n }\r\n} else {\r\n Write-Error -Message \"Unable to get Vault status, check Server URL in Custom Properties or your connectivity.\" -ErrorAction Stop\r\n}\r\n\r\nif ($null -ne $Bitwarden.session) {\r\n # Sync Vault to latest version from server\r\n [void](Invoke-Command -ScriptBlock { & \"$($Bitwarden.exec_path)\" sync --session \"$($Bitwarden.session)\"})\r\n\r\n # Get and parse Personal Vault folders\r\n $tmpFolders = (Invoke-Command -ScriptBloc { & \"$($Bitwarden.exec_path)\" list folders --session \"$($Bitwarden.session)\"}) | ConvertFrom-Json\r\n\r\n foreach ($folder in $tmpFolders) {\r\n if ($null -ne $folder.id) {\r\n $tF = @{ Type = \"Folder\"; ID = $folder.id; Name = $folder.name; Objects = [array]@(Get-VaultItems -folderId $folder.id); }\r\n if ($tF.Objects.Count -ne 0) { $final.Objects[0].Objects += $tF; $tF = $null }\r\n } else {\r\n # Add default folder\r\n $tF = @{ Type = \"Folder\"; ID = \"nofolder\"; Name = \"No folder\"; Objects = [array]@(Get-VaultItems -folderId null); }\r\n if ($tF.Objects.Count -ne 0) { $final.Objects[0].Objects += $tF; $tF = $null }\r\n }\r\n }\r\n\r\n # Get and parse Organisations and Collections\r\n $organizations = (Invoke-Command -ScriptBloc { & \"$($Bitwarden.exec_path)\" list organizations --session \"$($Bitwarden.session)\"}) | ConvertFrom-Json\r\n\r\n foreach ($org in $organizations) {\r\n # Get collections for the organization\r\n $collections = (Invoke-Command -ScriptBloc { & \"$($Bitwarden.exec_path)\" list collections --organizationid $org.id --session \"$($Bitwarden.session)\"}) | ConvertFrom-Json\r\n $tOrgCollections = [array]@()\r\n foreach ($coll in $collections) {\r\n $tF = @{ Type = \"Folder\"; ID = $coll.id; Name = $coll.name; IconName = \"Flat/Software/Tree\"; Objects = [array]@(Get-VaultItems -collectionId $coll.id); }\r\n if ($tF.Objects.Count -ne 0) { $tOrgCollections += $tF; $tF = $null }\r\n }\r\n if ($tOrgCollections.Count -gt 0) {\r\n # Create organization folder\r\n $final.Objects += @{ Type = \"Folder\"; ID = $org.id; Name = $org.name; IconName = \"Flat/Money/Bank\"; Objects = $tOrgCollections; }\r\n }\r\n }\r\n}\r\n\r\n# Adapt JSON output for PowerShell version\r\nif ($PSVersionTable.PSVersion -ge '6.2') {\r\n #ConvertTo-Json -InputObject $final -Depth 100 -EscapeHandling EscapeHtml |Out-file \".\\bitwarden_output.json\" -Force\r\n ConvertTo-Json -InputObject $final -Depth 100 -EscapeHandling EscapeHtml\r\n} else {\r\n #ConvertTo-Json -InputObject $final -Depth 100 |Out-file \".\\bitwarden_output.json\" -Force\r\n ConvertTo-Json -InputObject $final -Depth 100\r\n}\r\n",
"Type": "DynamicFolder",
"Name": "Bitwarden (PowerShell)",
"Description": "This Dynamic Folder sample allows you to import credentials from Bitwarden using Powershell.",
"CustomProperties": [
{
"Name": "Bitwarden CLI Configuration",
"Type": "Header",
"Value": ""
},
{
"Name": "BitWarden CLI Executable",
"Type": "Text",
"Value": "<full_absolute_path_to>\\bw.exe"
},
{
"Name": "BitWarden Server URL",
"Type": "URL",
"Value": "https://vault.bitwarden.com"
},
{
"Name": "API Client ID",
"Type": "Protected",
"Value": "user.<YOUR_CLIENT_ID>"
},
{
"Name": "API Client Secret",
"Type": "Protected",
"Value": "<YOUR_CLIENT_SECRET>"
},
{
"Name": "Account Password",
"Type": "Protected",
"Value": "<YOUR_SECRET_MASTER_PASSWORD>"
}
],
"ScriptInterpreter": "powershell",
"DynamicCredentialScriptInterpreter": "json"
}
]
}

0 comments on commit 899e6e5

Please sign in to comment.