Repository: ansible-collections/community.general

Issue:
onepassword lookup does not work for 1password.eu
### Summary

When using the community.general.onepassword lookup, the module assumes a subdomain of 1password.com, so tasks using subdomains of 1password.eu fail.

### Issue Type

Bug Report

### Component Name

https://github.com/ansible-collections/community.general/blob/f74b83663bcc1f6269a2bb56ba646f24f6218578/plugins/lookup/onepassword.py#L171

### Ansible Version

```console (paste below)
$ ansible --version
ansible [core 2.11.1] 
  config file = None
  configured module search path = ['/home/bendwyer/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /opt/venv/ansible/lib64/python3.6/site-packages/ansible
  ansible collection location = /home/bendwyer/.ansible/collections:/usr/share/ansible/collections
  executable location = /opt/venv/ansible/bin/ansible
  python version = 3.6.8 (default, Aug 24 2020, 17:57:11) [GCC 8.3.1 20191121 (Red Hat 8.3.1-5)]
  jinja version = 2.11.3
  libyaml = True
```


### Configuration

```console (paste below)
$ ansible-config dump --only-changed
CACHE_PLUGIN(env: ANSIBLE_CACHE_PLUGIN) = jsonfile
CACHE_PLUGIN_CONNECTION(env: ANSIBLE_CACHE_PLUGIN_CONNECTION) = /home/bendwyer/.ansible/cache/fact_cache
CACHE_PLUGIN_TIMEOUT(env: ANSIBLE_CACHE_PLUGIN_TIMEOUT) = 7200
DEFAULT_FORKS(env: ANSIBLE_FORKS) = 50
DEFAULT_HOST_LIST(env: ANSIBLE_INVENTORY) = ['/home/bendwyer/automate/ansible/inventory/environments/production']
INVENTORY_CACHE_ENABLED(env: ANSIBLE_INVENTORY_CACHE) = True
INVENTORY_CACHE_PLUGIN(env: ANSIBLE_INVENTORY_CACHE_PLUGIN) = jsonfile
INVENTORY_CACHE_PLUGIN_CONNECTION(env: ANSIBLE_INVENTORY_CACHE_CONNECTION) = /home/bendwyer/.ansible/cache/inventory_cache
INVENTORY_CACHE_TIMEOUT(env: ANSIBLE_INVENTORY_CACHE_TIMEOUT) = 7200
```


### OS / Environment

```console
$ cat /etc/centos-release
CentOS Linux release 8.3.2011
```

### Steps to Reproduce

<!--- Paste example playbooks or commands between quotes below -->
```yaml (paste below)
---
- name: test
  hosts: localhost
  connection: local
  gather_facts: false
  vars:
    record_name:  # name of item we want the password for
    subdomain:  # 1Password subdomain (currently only for 1Password.com)
    master_password:  # password for logging into 1Password vault
    username:  # email for logging into 1Password vault
    secret_key:  # secret key for logging into 1Password vault

  tasks:
    - name: Retrieve password when never signed in to 1Password
      ansible.builtin.debug:
        var: lookup("community.general.onepassword", "{{ record_name }}", subdomain="{{ subdomain }}", master_password="{{ master_password }}", username="{{ email }}", secret_key="{{ secret_key }}")
    
```

### Expected Results

Login to 1password.eu and retrieve password associated with `{{ record_name }}`.

### Actual Results

```console (paste below)
TASK [Retrieve password when never signed in to 1Password] *********************
fatal: [localhost]: FAILED! => {"msg": "An unhandled exception occurred while running the lookup plugin 'community.general.onepassword'. Error was a <class 'ansible.errors.AnsibleLookupError'>, original message: Your Secret Key has changed. You must enter your new Secret Key to sign in.\nEnter the Secret Key for [REDACTED - username] at [REDACTED - subdomain].1password.com: [ERROR] 2021/06/07 10:42:26 invalid account key format length 0\n"}
```

### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:
Files identified in the description:
None

If these files are inaccurate, 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/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
!component =community.general/plugins/lookup/onepassword.py
Files identified in the description:
* [`community.general/plugins/lookup/onepassword.py`](https://github.com/ansible-collections/community.general/blob/master/community.general/plugins/lookup/onepassword.py)

If these files are inaccurate, 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/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
Hi @aminvakil,

I tested it this morning and am still encountering the same error I referenced when I opened this issue:

```console
"An unhandled exception occurred while running the lookup plugin 'community.general.onepassword'. 
Error was a <class 'ansible.errors.AnsibleLookupError'>, original message: Your Secret Key has changed. You must enter your new Secret Key to sign in.\nEnter the Secret Key for username at subdomain.1password.com: [ERROR] 2021/06/08 09:03:49 invalid account key format length 0\n"
```

Using the exact same credentials (copy + paste) into the `op` cli works, but the flow is a little different:

```console
$ op signin subdomain.domain username secret_key --output=raw
Enter the password for email@company.com at subdomain.1password.eu: 
generated-token
```

Alternatively, it is possible to login all at once by also passing the master password:

```console
echo master_password | op signin subdomain.domain username secret_key --output=raw
```

Here is an updated playbook that I am using to try and figure this out:

```yaml
- name: test
  hosts: localhost
  connection: local
  gather_facts: false
  vars:
    op_item_name:  name
    op_subdomain: subdomain
    op_domain: 1password.eu
    op_master_password: master-password
    op_username: user@company.com
    op_secret_key: secret-key-with-dashes

  tasks:
    - name: Retrieve password when never signed in to 1Password
      ansible.builtin.debug:
        var: lookup("community.general.onepassword", op_item_name, subdomain=op_subdomain, domain=op_domain, master_password=op_master_password, username=op_username, secret_key=op_secret_key)
      ignore_errors: true

    - name: try logging in using shell and retrieve token
      ansible.builtin.shell: |
        echo '{{ op_master_password }}' | op signin {{ op_subdomain }}.{{ op_domain }} {{ op_username }} {{ op_secret_key }} --output=raw
      register: token
      no_log: true

    - debug:
        var: token.stdout
```

Interestingly, if I try to use `{{ token.stdout }}` with the lookup, it fails:

```yaml
    - name: Retrieve password for {{ op_item_name }} when already signed in to 1Password
      ansible.builtin.debug:
        var: lookup("community.general.onepassword", op_item_name)
      environment:
      # https://support.1password.com/command-line/#appendix-session-management
        OP_SESSION_subdomain: '{{ token.stdout }}'
```

```console
TASK [Retrieve password with token] ******************************************************************************************************************
fatal: [localhost]: FAILED! => {
    "msg": "An unhandled exception occurred while running the lookup plugin 'community.general.onepassword'. Error was a <class 'ansible.errors.AnsibleLookupError'>, original message: Unable to perform initial sign in to 1Password. subdomain, username, secret_key, and master_password are required to perform initial sign in."
}
```
Just making sure you have used the latest code from https://github.com/ansible-collections/community.general/pull/2735 branch, right?
Hi @aminvakil ,

I just started a new project with a project-local collection pulling your branch, and the playbook above worked. Sorry for the confusion. The lookup is now working as expected.
@bendwyer Great! If you would also review PR and approve or mention any other stuff in PR that needs to be changed would be great.

Resolving patch:
diff --git a/plugins/lookup/onepassword.py b/plugins/lookup/onepassword.py
--- a/plugins/lookup/onepassword.py
+++ b/plugins/lookup/onepassword.py
@@ -30,6 +30,11 @@
         aliases: ['vault_password']
       section:
         description: Item section containing the field to retrieve (case-insensitive). If absent will return first match from any section.
+      domain:
+        description: Domain of 1Password. Default is U(1password.com).
+        version_added: 3.2.0
+        default: '1password.com'
+        type: str
       subdomain:
         description: The 1Password subdomain to authenticate against.
       username:
@@ -109,6 +114,7 @@ def __init__(self, path='op'):
         self.logged_in = False
         self.token = None
         self.subdomain = None
+        self.domain = None
         self.username = None
         self.secret_key = None
         self.master_password = None
@@ -168,7 +174,7 @@ def full_login(self):
 
         args = [
             'signin',
-            '{0}.1password.com'.format(self.subdomain),
+            '{0}.{1}'.format(self.subdomain, self.domain),
             to_bytes(self.username),
             to_bytes(self.secret_key),
             '--output=raw',
@@ -265,6 +271,7 @@ def run(self, terms, variables=None, **kwargs):
         section = kwargs.get('section')
         vault = kwargs.get('vault')
         op.subdomain = kwargs.get('subdomain')
+        op.domain = kwargs.get('domain', '1password.com')
         op.username = kwargs.get('username')
         op.secret_key = kwargs.get('secret_key')
         op.master_password = kwargs.get('master_password', kwargs.get('vault_password'))


Test patch: