-
Notifications
You must be signed in to change notification settings - Fork 76
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #70 from GrinGrin/feat/bitwarden-powershell
Add Dynamic Folder Bitwarden PowerShell script
- Loading branch information
Showing
1 changed file
with
46 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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\"> </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 "Custom Properties" section. Also, your Bitwarden login details must be provided in the "Credentials" 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'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&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\"> </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 "Custom Properties" 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 & 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\"> </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 "Do not load the PowerShell profile" 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" | ||
} | ||
] | ||
} |