Repository: ansible-collections/community.general

Issue:
OnePassword library assumes incorrectly that a field als an attribute 'value'
### Summary

When using the 'lookup' method to get a field value from a field in a OnePassword Vault, the method might raise a KeyError when trying to access the key 'value' on the field dict. Given a template in the OnePassword vault, a label for a field might exist without a value if the user has omitted this value on purpose.

### Issue Type

Bug Report

### Component Name

onepassword

### Ansible Version

```console (paste below)
$ ansible --version
ansible [core 2.15.3]
  python version = 3.9.17 (main, Jun 20 2023, 17:20:08) [Clang 14.0.3 (clang-1403.0.22.14.1)] 
  jinja version = 3.0.1
```


### Community.general Version

```console (paste below)
$ ansible-galaxy collection list community.general
community.general 7.3.0  
community.general 2.0.1  

```


### Configuration

```console (paste below)
$ ansible-config dump --only-changed

```


### OS / Environment

_No response_

### Steps to Reproduce

<!--- Paste example playbooks or commands between quotes below -->
```yaml (paste below)
1. Create a onepassword item
2. Add a field with a label 'some-omitted-property'
3. Leave the value empty
4. Add a rule lookup(..., 'some-omitted-property') to an ansible script
5. Run ansible
```


### Expected Results

Empty (or None-like) value at the location of lookup(..., 'some-omitted-property')

### Actual Results

```console (paste below)
KeyError("value", "value")
```


### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct
OnePassword library assumes incorrectly that a field als an attribute 'value'
### Summary

When using the 'lookup' method to get a field value from a field in a OnePassword Vault, the method might raise a KeyError when trying to access the key 'value' on the field dict. Given a template in the OnePassword vault, a label for a field might exist without a value if the user has omitted this value on purpose.

### Issue Type

Bug Report

### Component Name

onepassword

### Ansible Version

```console (paste below)
$ ansible --version
ansible [core 2.15.3]
  python version = 3.9.17 (main, Jun 20 2023, 17:20:08) [Clang 14.0.3 (clang-1403.0.22.14.1)] 
  jinja version = 3.0.1
```


### Community.general Version

```console (paste below)
$ ansible-galaxy collection list community.general
community.general 7.3.0  
community.general 2.0.1  

```


### Configuration

```console (paste below)
$ ansible-config dump --only-changed

```


### OS / Environment

_No response_

### Steps to Reproduce

<!--- Paste example playbooks or commands between quotes below -->
```yaml (paste below)
1. Create a onepassword item
2. Add a field with a label 'some-omitted-property'
3. Leave the value empty
4. Add a rule lookup(..., 'some-omitted-property') to an ansible script
5. Run ansible
```


### Expected Results

Empty (or None-like) value at the location of lookup(..., 'some-omitted-property')

### Actual Results

```console (paste below)
KeyError("value", "value")
```


### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:
Files identified in the description:
* [`plugins/lookup/onepassword.py`](https://github.com/ansible-collections/community.general/blob/main/plugins/lookup/onepassword.py)

If these files are incorrect, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @azenk @samdoran @scottsb
[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: notify --->
Very good find. In the v1 class, the `.get()` method is used to protect against this.

<details>
  <summary>Simple reproducer playbook</summary>

```yaml
- hosts: localhost

  tasks:
    - debug:
        msg: "{{ lookup('onepassword', 'Dummy Login', field='some-omitted-property') }}"

```
</details>
Files identified in the description:
* [`plugins/lookup/onepassword.py`](https://github.com/ansible-collections/community.general/blob/main/plugins/lookup/onepassword.py)

If these files are incorrect, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @azenk @samdoran @scottsb
[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: notify --->
Very good find. In the v1 class, the `.get()` method is used to protect against this.

<details>
  <summary>Simple reproducer playbook</summary>

```yaml
- hosts: localhost

  tasks:
    - debug:
        msg: "{{ lookup('onepassword', 'Dummy Login', field='some-omitted-property') }}"

```
</details>

Resolving patch:
diff --git a/plugins/lookup/onepassword.py b/plugins/lookup/onepassword.py
--- a/plugins/lookup/onepassword.py
+++ b/plugins/lookup/onepassword.py
@@ -462,10 +462,10 @@ def _parse_field(self, data_json, field_name, section_title=None):
                 # If the field name doesn't exist in the section, match on the value of "label"
                 # then "id" and return "value"
                 if field.get("label") == field_name:
-                    return field["value"]
+                    return field.get("value", "")
 
                 if field.get("id") == field_name:
-                    return field["value"]
+                    return field.get("value", "")
 
             # Look at the section data and get an indentifier. The value of 'id' is either a unique ID
             # or a human-readable string. If a 'label' field exists, prefer that since
@@ -475,10 +475,10 @@ def _parse_field(self, data_json, field_name, section_title=None):
             if section_title == current_section_title:
                 # In the correct section. Check "label" then "id" for the desired field_name
                 if field.get("label") == field_name:
-                    return field["value"]
+                    return field.get("value", "")
 
                 if field.get("id") == field_name:
-                    return field["value"]
+                    return field.get("value", "")
 
         return ""
 


Test patch:
diff --git a/tests/unit/plugins/lookup/onepassword_common.py b/tests/unit/plugins/lookup/onepassword_common.py
--- a/tests/unit/plugins/lookup/onepassword_common.py
+++ b/tests/unit/plugins/lookup/onepassword_common.py
@@ -81,5 +81,47 @@ def load_file(file):
             "expected": ["first value"],
             "output": load_file("v2_out_03.json")
         },
+        {
+            # Request data from an omitted value (label lookup, no section)
+            "vault_name": "Test Vault",
+            "queries": ["Omitted values"],
+            "kwargs": {
+                "field": "label-without-value",
+            },
+            "expected": [""],
+            "output": load_file("v2_out_04.json")
+        },
+        {
+            # Request data from an omitted value (id lookup, no section)
+            "vault_name": "Test Vault",
+            "queries": ["Omitted values"],
+            "kwargs": {
+                "field": "67890q7mspf4x6zrlw3qejn7m",
+            },
+            "expected": [""],
+            "output": load_file("v2_out_04.json")
+        },
+        {
+            # Request data from an omitted value (label lookup, with section)
+            "vault_name": "Test Vault",
+            "queries": ["Omitted values"],
+            "kwargs": {
+                "field": "section-label-without-value",
+                "section": "section-without-values"
+            },
+            "expected": [""],
+            "output": load_file("v2_out_04.json")
+        },
+        {
+            # Request data from an omitted value (id lookup, with section)
+            "vault_name": "Test Vault",
+            "queries": ["Omitted values"],
+            "kwargs": {
+                "field": "123345q7mspf4x6zrlw3qejn7m",
+                "section": "section-without-values",
+            },
+            "expected": [""],
+            "output": load_file("v2_out_04.json")
+        },
     ],
 }
diff --git a/tests/unit/plugins/lookup/onepassword_fixtures/v2_out_04.json b/tests/unit/plugins/lookup/onepassword_fixtures/v2_out_04.json
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/lookup/onepassword_fixtures/v2_out_04.json
@@ -0,0 +1,67 @@
+{
+  "id": "bgqegp3xcxnpfkb45olwigpkpi",
+  "title": "OmittedValues",
+  "version": 1,
+  "vault": {
+    "id": "stpebbaccrq72xulgouxsk4p7y",
+    "name": "Private"
+  },
+  "category": "LOGIN",
+  "last_edited_by": "WOUTERRUYBH7BFPHMZ2KKGL6AU",
+  "created_at": "2023-09-12T08:30:07Z",
+  "updated_at": "2023-09-12T08:30:07Z",
+  "additional_information": "fluxility",
+  "sections": [
+    {
+      "id": "7osqcvd43i75teocdzbb6d7mie",
+      "label": "section-without-values"
+    }
+  ],
+  "fields": [
+    {
+      "id": "username",
+      "type": "STRING",
+      "purpose": "USERNAME",
+      "label": "username",
+      "value": "fluxility",
+      "reference": "op://Testcase/OmittedValues/username"
+    },
+    {
+      "id": "password",
+      "type": "CONCEALED",
+      "purpose": "PASSWORD",
+      "label": "password",
+      "value": "j89Dyb7psat*hkbkyLUQyq@GR.a-g2pQH_V_xtMhrn37rQ_2uRYoRiozj6TjWVLy2pbfEvjnse",
+      "entropy": 427.01202392578125,
+      "reference": "op://Testcase/OmittedValues/password",
+      "password_details": {
+        "entropy": 427,
+        "generated": true,
+        "strength": "FANTASTIC"
+      }
+    },
+    {
+      "id": "notesPlain",
+      "type": "STRING",
+      "purpose": "NOTES",
+      "label": "notesPlain",
+      "reference": "op://Testcase/OmittedValues/notesPlain"
+    },
+    {
+      "id": "67890q7mspf4x6zrlw3qejn7m",
+      "type": "URL",
+      "label": "label-without-value",
+      "reference": "op://01202392578125/OmittedValues/section-without-values/section-without-value"
+    },
+    {
+      "id": "123345q7mspf4x6zrlw3qejn7m",
+      "section": {
+        "id": "6hbtca5yrlmoptgy3nw74222",
+        "label": "section-without-values"
+      },
+      "type": "URL",
+      "label": "section-label-without-value",
+      "reference": "op://01202392578125/OmittedValues/section-without-values/section-without-value"
+    }
+  ]
+}
diff --git a/tests/unit/plugins/lookup/onepassword_fixtures/v2_out_04.json.license b/tests/unit/plugins/lookup/onepassword_fixtures/v2_out_04.json.license
new file mode 100644
--- /dev/null
+++ b/tests/unit/plugins/lookup/onepassword_fixtures/v2_out_04.json.license
@@ -0,0 +1,3 @@
+GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+SPDX-License-Identifier: GPL-3.0-or-later
+SPDX-FileCopyrightText: 2022, Ansible Project