Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement support for filters in data sources #127

Merged
merged 12 commits into from
Apr 20, 2024
14 changes: 12 additions & 2 deletions docs/data-sources/item_login.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Use this data source to get information on an existing Login.
## Example Usage

```terraform
# Find the identifier of the resource you want to import:
# Option 1: Find the identifier of the resource you want to import:
#
# $ bw list items --search "Mysql Root Credentials" | jq '.[] .id'
# ? Master password: [hidden]
Expand All @@ -25,6 +25,11 @@ data "bitwarden_item_login" "database_credentials" {
id = "ec4e447f-9aed-4203-b834-c8f3848828f7"
}

# Option 2: Use filters directly in the resource declaration
data "bitwarden_item_secure_note" "ssh_notes" {
search = "SSH Private Key"
}

# Later to be accessed as
# ... = data.bitwarden_item_login.database_credentials.username
# ... = data.bitwarden_item_login.database_credentials.password
Expand All @@ -42,9 +47,14 @@ data "bitwarden_item_login" "database_credentials" {
<!-- schema generated by tfplugindocs -->
## Schema

### Required
### Optional

- `filter_collection_id` (String) Filter search results by collection ID
- `filter_folder_id` (String) Filter search results by folder ID
- `filter_organization_id` (String) Filter search results by organization ID
- `filter_url` (String) Filter search results by URL
- `id` (String) Identifier.
- `search` (String) Search items matching the search string. Can be combined with filters to narrow down the search.

### Read-Only

Expand Down
13 changes: 11 additions & 2 deletions docs/data-sources/item_secure_note.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Use this data source to get information on an existing Secure Note.
## Example Usage

```terraform
# Find the identifier of the resource you want to import:
# Option 1: Find the identifier of the resource you want to import:
#
# $ bw list items --search "SSH Private Key" | jq '.[] .id'
# ? Master password: [hidden]
Expand All @@ -25,6 +25,11 @@ data "bitwarden_item_secure_note" "ssh_notes" {
id = "a9e19f26-1b8c-4568-bc09-191e2cf56ed6"
}

# Option 2: Use filters directly in the resource declaration
data "bitwarden_item_secure_note" "ssh_notes" {
search = "SSH Private Key"
}

# Later to be accessed as
# ... = data.bitwarden_item_secure_note.ssh_notes.notes
#
Expand All @@ -41,9 +46,13 @@ data "bitwarden_item_secure_note" "ssh_notes" {
<!-- schema generated by tfplugindocs -->
## Schema

### Required
### Optional

- `filter_collection_id` (String) Filter search results by collection ID
- `filter_folder_id` (String) Filter search results by folder ID
- `filter_organization_id` (String) Filter search results by organization ID
- `id` (String) Identifier.
- `search` (String) Search items matching the search string. Can be combined with filters to narrow down the search.

### Read-Only

Expand Down
5 changes: 5 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ resource "bitwarden_item_login" "example" {
username = "service-account"
password = "<sensitive>"
}

# Use Bitwarden Resource
data "bitwarden_item_login" "example" {
search = "Example"
}
```

## Authentication
Expand Down
2 changes: 1 addition & 1 deletion docs/resources/item_login.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ resource "bitwarden_item_login" "administrative-user" {
- `favorite` (Boolean) Mark as a Favorite to have item appear at the top of your Vault in the UI.
- `field` (Block List) Extra fields. (see [below for nested schema](#nestedblock--field))
- `folder_id` (String) Identifier of the folder.
- `id` (String) Identifier.
- `notes` (String, Sensitive) Notes.
- `organization_id` (String) Identifier of the organization.
- `password` (String, Sensitive) Login password.
Expand All @@ -67,7 +68,6 @@ resource "bitwarden_item_login" "administrative-user" {
- `attachments` (List of Object) List of item attachments. (see [below for nested schema](#nestedatt--attachments))
- `creation_date` (String) Date the item was created.
- `deleted_date` (String) Date the item was deleted.
- `id` (String) Identifier.
- `revision_date` (String) Last time the item was updated.

<a id="nestedblock--field"></a>
Expand Down
2 changes: 1 addition & 1 deletion docs/resources/item_secure_note.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ EOT
- `favorite` (Boolean) Mark as a Favorite to have item appear at the top of your Vault in the UI.
- `field` (Block List) Extra fields. (see [below for nested schema](#nestedblock--field))
- `folder_id` (String) Identifier of the folder.
- `id` (String) Identifier.
- `notes` (String, Sensitive) Notes.
- `organization_id` (String) Identifier of the organization.
- `reprompt` (Boolean) Require master password “re-prompt” when displaying secret in the UI.
Expand All @@ -64,7 +65,6 @@ EOT
- `attachments` (List of Object) List of item attachments. (see [below for nested schema](#nestedatt--attachments))
- `creation_date` (String) Date the item was created.
- `deleted_date` (String) Date the item was deleted.
- `id` (String) Identifier.
- `revision_date` (String) Last time the item was updated.

<a id="nestedblock--field"></a>
Expand Down
7 changes: 6 additions & 1 deletion examples/data-sources/bitwarden_item_login/data-source.tf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Find the identifier of the resource you want to import:
# Option 1: Find the identifier of the resource you want to import:
#
# $ bw list items --search "Mysql Root Credentials" | jq '.[] .id'
# ? Master password: [hidden]
Expand All @@ -10,6 +10,11 @@ data "bitwarden_item_login" "database_credentials" {
id = "ec4e447f-9aed-4203-b834-c8f3848828f7"
}

# Option 2: Use filters directly in the resource declaration
data "bitwarden_item_secure_note" "ssh_notes" {
search = "SSH Private Key"
}

# Later to be accessed as
# ... = data.bitwarden_item_login.database_credentials.username
# ... = data.bitwarden_item_login.database_credentials.password
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Find the identifier of the resource you want to import:
# Option 1: Find the identifier of the resource you want to import:
#
# $ bw list items --search "SSH Private Key" | jq '.[] .id'
# ? Master password: [hidden]
Expand All @@ -10,6 +10,11 @@ data "bitwarden_item_secure_note" "ssh_notes" {
id = "a9e19f26-1b8c-4568-bc09-191e2cf56ed6"
}

# Option 2: Use filters directly in the resource declaration
data "bitwarden_item_secure_note" "ssh_notes" {
search = "SSH Private Key"
}

# Later to be accessed as
# ... = data.bitwarden_item_secure_note.ssh_notes.notes
#
Expand Down
2 changes: 1 addition & 1 deletion examples/quick/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ resource "bitwarden_item_secure_note" "vpn_note" {
# Read sensitive information from Bitwarden
# Using Login information
data "bitwarden_item_login" "mysql_credentials" {
id = "ec4e447f-9aed-4203-b834-c8f3848828f7"
search = "mysql/server-1"
}

# Later to be accessed as
Expand Down
5 changes: 5 additions & 0 deletions examples/quick/provider.tf
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,9 @@ resource "bitwarden_item_login" "example" {
name = "Example"
username = "service-account"
password = "<sensitive>"
}

# Use Bitwarden Resource
data "bitwarden_item_login" "example" {
search = "Example"
}
26 changes: 26 additions & 0 deletions internal/bitwarden/bw/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type Client interface {
GetObject(objType, itemOrSearch string) (*Object, error)
GetSessionKey() string
HasSessionKey() bool
ListObjects(objType string, options ...ListObjectsOption) ([]Object, error)
LoginWithAPIKey(password, clientId, clientSecret string) error
LoginWithPassword(username, password string) error
Logout() error
Expand Down Expand Up @@ -165,6 +166,31 @@ func (c *client) GetSessionKey() string {
return c.sessionKey
}

// ListObjects returns objects of a given type matching given filters.
func (c *client) ListObjects(objType string, options ...ListObjectsOption) ([]Object, error) {
args := []string{
"list",
objType,
}

for _, applyOption := range options {
applyOption(&args)
}

out, err := c.cmdWithSession(args...).Run()
if err != nil {
return nil, remapError(err)
}

var obj []Object
err = json.Unmarshal(out, &obj)
if err != nil {
return nil, newUnmarshallError(err, "list object", out)
}

return obj, nil
}

// LoginWithPassword logs in using a password and retrieves the session key,
// allowing authenticated requests using the client.
func (c *client) LoginWithPassword(username, password string) error {
Expand Down
34 changes: 34 additions & 0 deletions internal/bitwarden/bw/client_options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package bw

type ListObjectsOption func(args *[]string)
type ListObjectsOptionGenerator func(id string) ListObjectsOption

func WithCollectionID(id string) ListObjectsOption {
return func(args *[]string) {
*args = append(*args, "--collectionid", id)
}
}

func WithFolderID(id string) ListObjectsOption {
return func(args *[]string) {
*args = append(*args, "--folderid", id)
}
}

func WithOrganizationID(id string) ListObjectsOption {
return func(args *[]string) {
*args = append(*args, "--organizationid", id)
}
}

func WithSearch(search string) ListObjectsOption {
return func(args *[]string) {
*args = append(*args, "--search", search)
}
}

func WithUrl(url string) ListObjectsOption {
return func(args *[]string) {
*args = append(*args, "--url", url)
}
}
15 changes: 15 additions & 0 deletions internal/bitwarden/bw/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,18 @@ func TestCreateObjectEncoding(t *testing.T) {
assert.Equal(t, "create e30K", commandsExecuted()[1])
}
}

func TestListObjects(t *testing.T) {
removeMocks, commandsExecuted := test_command.MockCommands(t, map[string]string{
"list item --folderid folder-id --collectionid collection-id --search search": `[]`,
})
defer removeMocks(t)

b := NewClient("dummy")
_, err := b.ListObjects("item", WithFolderID("folder-id"), WithCollectionID("collection-id"), WithSearch("search"))

assert.NoError(t, err)
if assert.Len(t, commandsExecuted(), 1) {
assert.Equal(t, "list item --folderid folder-id --collectionid collection-id --search search", commandsExecuted()[0])
}
}
15 changes: 15 additions & 0 deletions internal/bitwarden/bw/filter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package bw

func FilterObjectsByType(objs []Object, itemType ItemType) []Object {
if itemType == 0 {
return objs
}

filtered := make([]Object, 0, len(objs))
for _, obj := range objs {
if obj.Type == itemType {
filtered = append(filtered, obj)
}
}
return filtered
}
72 changes: 72 additions & 0 deletions internal/provider/data_source_item_login_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package provider

import (
"fmt"
"regexp"
"testing"

Expand Down Expand Up @@ -55,6 +56,77 @@ func TestAccDataSourceItemLoginFailsOnWrongResourceType(t *testing.T) {
})
}

func TestAccDataSourceItemLoginBySearch(t *testing.T) {
resourceName := "bitwarden_item_login.foo"

ensureVaultwardenConfigured(t)

resource.UnitTest(t, resource.TestCase{
ProviderFactories: providerFactories,
Steps: []resource.TestStep{
{
Config: tfConfigProvider() + tfConfigResourceFolder() + tfConfigResourceItemLogin(),
Check: checkItemLogin(resourceName),
},
{
Config: tfConfigProvider() + tfConfigResourceFolder() + tfConfigResourceItemLogin() + tfConfigDataItemLoginWithSearchAndOrg("test-username"),
Check: checkItemLogin("data.bitwarden_item_login.foo_data"),
},
{
Config: tfConfigProvider() + tfConfigResourceFolder() + tfConfigResourceItemLogin() + tfConfigResourceItemLoginDuplicate() + tfConfigDataItemLoginWithSearchAndOrg("test-username"),
Check: checkItemLogin("data.bitwarden_item_login.foo_data"),
},
{
Config: tfConfigProvider() + tfConfigResourceFolder() + tfConfigResourceItemLogin() + tfConfigResourceItemLoginDuplicate() + tfConfigDataItemLoginWithSearchOnly("test-username"),
ExpectError: regexp.MustCompile("Error: too many objects found"),
},
{
Config: tfConfigProvider() + tfConfigResourceFolder() + tfConfigResourceItemLogin() + tfConfigDataItemLoginWithSearchAndOrg("missing-item"),
ExpectError: regexp.MustCompile("Error: no object found matching the filter"),
},
{
Config: tfConfigProvider() + tfConfigResourceFolder() + tfConfigResourceItemSecureNote(),
},
{
Config: tfConfigProvider() + tfConfigResourceFolder() + tfConfigResourceItemSecureNote() + tfConfigDataItemLoginWithSearchAndOrg("secure-bar"),
ExpectError: regexp.MustCompile("Error: no object found matching the filter"),
},
},
})
}

func tfConfigDataItemLoginWithSearchAndOrg(search string) string {
return fmt.Sprintf(`
data "bitwarden_item_login" "foo_data" {
provider = bitwarden

search = "%s"
filter_organization_id = "%s"
}
`, search, testOrganizationID)
}

func tfConfigDataItemLoginWithSearchOnly(search string) string {
return fmt.Sprintf(`
data "bitwarden_item_login" "foo_data" {
provider = bitwarden

search = "%s"
}
`, search)
}

func tfConfigResourceItemLoginDuplicate() string {
return `
resource "bitwarden_item_login" "foo_duplicate" {
provider = bitwarden

name = "another item with username 'test-username'"
username = "test-username"
}
`
}

func tfConfigDataItemLogin() string {
return `
data "bitwarden_item_login" "foo_data" {
Expand Down
Loading
Loading