mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-06-04 23:37:12 +00:00
onepassword* lookups: drop support for op v1 (#12061)
Drop support for op v1.
This commit is contained in:
parent
3378d0a202
commit
ea02e6a5a9
8 changed files with 7 additions and 267 deletions
2
changelogs/fragments/onepassword-v2.yml
Normal file
2
changelogs/fragments/onepassword-v2.yml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
breaking_changes:
|
||||
- "onepassword* lookup plugins - drop support for ``op`` version 1 (https://github.com/ansible-collections/community.general/pull/12061)."
|
||||
|
|
@ -11,6 +11,7 @@ from __future__ import annotations
|
|||
class ModuleDocFragment:
|
||||
DOCUMENTATION = r"""
|
||||
requirements:
|
||||
- C(op) 1Password command line utility version 2 or later.
|
||||
- See U(https://support.1password.com/command-line/)
|
||||
options:
|
||||
master_password:
|
||||
|
|
@ -39,7 +40,6 @@ options:
|
|||
service_account_token:
|
||||
description:
|
||||
- The access key for a service account.
|
||||
- Only works with 1Password CLI version 2 or later.
|
||||
type: str
|
||||
vault:
|
||||
description: Vault containing the item to retrieve (case-insensitive). If absent, searches all vaults.
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@ author:
|
|||
short_description: Fetch field values from 1Password
|
||||
description:
|
||||
- P(community.general.onepassword#lookup) wraps the C(op) command line utility to fetch specific field values from 1Password.
|
||||
requirements:
|
||||
- C(op) 1Password command line utility
|
||||
options:
|
||||
_terms:
|
||||
description:
|
||||
|
|
@ -214,151 +212,6 @@ class OnePassCLIBase(metaclass=abc.ABCMeta):
|
|||
return to_text(b_out).strip()
|
||||
|
||||
|
||||
class OnePassCLIv1(OnePassCLIBase):
|
||||
supports_version = "1"
|
||||
|
||||
def _parse_field(self, data_json, field_name, section_title):
|
||||
"""
|
||||
Retrieves the desired field from the `op` response payload
|
||||
|
||||
When the item is a `password` type, the password is a key within the `details` key:
|
||||
|
||||
$ op get item 'test item' | jq
|
||||
{
|
||||
[...]
|
||||
"templateUuid": "005",
|
||||
"details": {
|
||||
"notesPlain": "",
|
||||
"password": "foobar",
|
||||
"passwordHistory": [],
|
||||
"sections": [
|
||||
{
|
||||
"name": "linked items",
|
||||
"title": "Related Items"
|
||||
}
|
||||
]
|
||||
},
|
||||
[...]
|
||||
}
|
||||
|
||||
However, when the item is a `login` type, the password is within a fields array:
|
||||
|
||||
$ op get item 'test item' | jq
|
||||
{
|
||||
[...]
|
||||
"details": {
|
||||
"fields": [
|
||||
{
|
||||
"designation": "username",
|
||||
"name": "username",
|
||||
"type": "T",
|
||||
"value": "foo"
|
||||
},
|
||||
{
|
||||
"designation": "password",
|
||||
"name": "password",
|
||||
"type": "P",
|
||||
"value": "bar"
|
||||
}
|
||||
],
|
||||
[...]
|
||||
},
|
||||
[...]
|
||||
"""
|
||||
data = json.loads(data_json)
|
||||
if section_title is None:
|
||||
# https://github.com/ansible-collections/community.general/pull/1610:
|
||||
# check the details dictionary for `field_name` and return it immediately if it exists
|
||||
# when the entry is a "password" instead of a "login" item, the password field is a key
|
||||
# in the `details` dictionary:
|
||||
if field_name in data["details"]:
|
||||
return data["details"][field_name]
|
||||
|
||||
# when the field is not found above, iterate through the fields list in the object details
|
||||
for field_data in data["details"].get("fields", []):
|
||||
if field_data.get("name", "").lower() == field_name.lower():
|
||||
return field_data.get("value", "")
|
||||
|
||||
for section_data in data["details"].get("sections", []):
|
||||
if section_title is not None and section_title.lower() != section_data["title"].lower():
|
||||
continue
|
||||
|
||||
for field_data in section_data.get("fields", []):
|
||||
if field_data.get("t", "").lower() == field_name.lower():
|
||||
return field_data.get("v", "")
|
||||
|
||||
return ""
|
||||
|
||||
def assert_logged_in(self):
|
||||
args = ["get", "account"]
|
||||
if self.account_id:
|
||||
args.extend(["--account", self.account_id])
|
||||
elif self.subdomain:
|
||||
account = f"{self.subdomain}.{self.domain}"
|
||||
args.extend(["--account", account])
|
||||
|
||||
rc, out, err = self._run(args, ignore_errors=True)
|
||||
|
||||
return not bool(rc)
|
||||
|
||||
def full_signin(self):
|
||||
if self.connect_host or self.connect_token:
|
||||
raise AnsibleLookupError(
|
||||
"1Password Connect is not available with 1Password CLI version 1. Please use version 2 or later."
|
||||
)
|
||||
|
||||
if self.service_account_token:
|
||||
raise AnsibleLookupError(
|
||||
"1Password CLI version 1 does not support Service Accounts. Please use version 2 or later."
|
||||
)
|
||||
|
||||
required_params = [
|
||||
"subdomain",
|
||||
"username",
|
||||
"secret_key",
|
||||
"master_password",
|
||||
]
|
||||
self._check_required_params(required_params)
|
||||
|
||||
args = [
|
||||
"signin",
|
||||
f"{self.subdomain}.{self.domain}",
|
||||
to_bytes(self.username),
|
||||
to_bytes(self.secret_key),
|
||||
"--raw",
|
||||
]
|
||||
|
||||
return self._run(args, command_input=to_bytes(self.master_password))
|
||||
|
||||
def get_raw(self, item_id, vault=None, token=None):
|
||||
args = ["get", "item", item_id]
|
||||
|
||||
if self.account_id:
|
||||
args.extend(["--account", self.account_id])
|
||||
|
||||
if vault is not None:
|
||||
args += [f"--vault={vault}"]
|
||||
|
||||
if token is not None:
|
||||
args += [to_bytes("--session=") + token]
|
||||
|
||||
return self._run(args)
|
||||
|
||||
def signin(self):
|
||||
self._check_required_params(["master_password"])
|
||||
|
||||
args = ["signin", "--raw"]
|
||||
if self.subdomain:
|
||||
args.append(self.subdomain)
|
||||
|
||||
return self._run(args, command_input=to_bytes(self.master_password))
|
||||
|
||||
def get_secret_reference(self, reference):
|
||||
raise AnsibleLookupError(
|
||||
"Secret references are not supported in op v1. Upgrade to op v2 or use item names/UUIDs"
|
||||
)
|
||||
|
||||
|
||||
class OnePassCLIv2(OnePassCLIBase):
|
||||
"""
|
||||
CLIv2 Syntax Reference: https://developer.1password.com/docs/cli/upgrade#step-2-update-your-scripts
|
||||
|
|
|
|||
|
|
@ -8,15 +8,12 @@ DOCUMENTATION = r"""
|
|||
name: onepassword_doc
|
||||
author:
|
||||
- Sam Doran (@samdoran)
|
||||
requirements:
|
||||
- C(op) 1Password command line utility version 2 or later.
|
||||
short_description: Fetch documents stored in 1Password
|
||||
version_added: "8.1.0"
|
||||
description:
|
||||
- P(community.general.onepassword_doc#lookup) wraps C(op) command line utility to fetch one or more documents from 1Password.
|
||||
notes:
|
||||
- The document contents are a string exactly as stored in 1Password.
|
||||
- This plugin requires C(op) version 2 or later.
|
||||
options:
|
||||
_terms:
|
||||
description: Identifier(s) (case-insensitive UUID or name) of item(s) to retrieve.
|
||||
|
|
|
|||
|
|
@ -12,8 +12,6 @@ author:
|
|||
- Scott Buchanan (@scottsb)
|
||||
- Andrew Zenk (@azenk)
|
||||
- Sam Doran (@samdoran)
|
||||
requirements:
|
||||
- C(op) 1Password command line utility
|
||||
short_description: Fetch an entire item from 1Password
|
||||
description:
|
||||
- P(community.general.onepassword_raw#lookup) wraps C(op) command line utility to fetch an entire item from 1Password.
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@ DOCUMENTATION = r"""
|
|||
name: onepassword_ssh_key
|
||||
author:
|
||||
- Mohammed Babelly (@mohammedbabelly20)
|
||||
requirements:
|
||||
- C(op) 1Password command line utility version 2 or later.
|
||||
short_description: Fetch SSH keys stored in 1Password
|
||||
version_added: "10.3.0"
|
||||
description:
|
||||
|
|
@ -17,7 +15,6 @@ description:
|
|||
notes:
|
||||
- By default, it returns the private key value in PKCS#8 format, unless O(ssh_format=true) is passed.
|
||||
- The pluging works only for C(SSHKEY) type items.
|
||||
- This plugin requires C(op) version 2 or later.
|
||||
options:
|
||||
_terms:
|
||||
description: Identifier(s) (case-insensitive UUID or name) of item(s) to retrieve.
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import json
|
|||
import os
|
||||
|
||||
from ansible_collections.community.general.plugins.lookup.onepassword import (
|
||||
OnePassCLIv1,
|
||||
OnePassCLIv2,
|
||||
)
|
||||
|
||||
|
|
@ -20,26 +19,6 @@ def load_file(file):
|
|||
|
||||
# Intentionally excludes metadata leaf nodes that would exist in real output if not relevant.
|
||||
MOCK_ENTRIES = {
|
||||
OnePassCLIv1: [
|
||||
{
|
||||
"vault_name": 'Acme "Quot\'d" Servers',
|
||||
"queries": ["0123456789", 'Mock "Quot\'d" Server'],
|
||||
"expected": ["t0pS3cret", "t0pS3cret"],
|
||||
"output": load_file("v1_out_01.json"),
|
||||
},
|
||||
{
|
||||
"vault_name": "Acme Logins",
|
||||
"queries": ["9876543210", "Mock Website", "acme.com"],
|
||||
"expected": ["t0pS3cret", "t0pS3cret", "t0pS3cret"],
|
||||
"output": load_file("v1_out_02.json"),
|
||||
},
|
||||
{
|
||||
"vault_name": "Acme Logins",
|
||||
"queries": ["864201357"],
|
||||
"expected": ["vauxhall"],
|
||||
"output": load_file("v1_out_03.json"),
|
||||
},
|
||||
],
|
||||
OnePassCLIv2: [
|
||||
{
|
||||
"vault_name": "Test Vault",
|
||||
|
|
|
|||
|
|
@ -13,73 +13,12 @@ from ansible.errors import AnsibleLookupError, AnsibleOptionsError
|
|||
from ansible.plugins.loader import lookup_loader
|
||||
|
||||
from ansible_collections.community.general.plugins.lookup.onepassword import (
|
||||
OnePassCLIv1,
|
||||
OnePassCLIv2,
|
||||
)
|
||||
|
||||
from .onepassword_common import MOCK_ENTRIES
|
||||
|
||||
OP_VERSION_FIXTURES = ["opv1", "opv2"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("args", "rc", "expected_call_args", "expected_call_kwargs", "expected"),
|
||||
(
|
||||
(
|
||||
[],
|
||||
0,
|
||||
["get", "account"],
|
||||
{"ignore_errors": True},
|
||||
True,
|
||||
),
|
||||
(
|
||||
[],
|
||||
1,
|
||||
["get", "account"],
|
||||
{"ignore_errors": True},
|
||||
False,
|
||||
),
|
||||
(
|
||||
["acme"],
|
||||
1,
|
||||
["get", "account", "--account", "acme.1password.com"],
|
||||
{"ignore_errors": True},
|
||||
False,
|
||||
),
|
||||
),
|
||||
)
|
||||
def test_assert_logged_in_v1(mocker, args, rc, expected_call_args, expected_call_kwargs, expected):
|
||||
mocker.patch.object(OnePassCLIv1, "_run", return_value=[rc, "", ""])
|
||||
|
||||
op_cli = OnePassCLIv1(*args)
|
||||
result = op_cli.assert_logged_in()
|
||||
|
||||
op_cli._run.assert_called_with(expected_call_args, **expected_call_kwargs)
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_full_signin_v1(mocker):
|
||||
mocker.patch.object(OnePassCLIv1, "_run", return_value=[0, "", ""])
|
||||
|
||||
op_cli = OnePassCLIv1(
|
||||
subdomain="acme",
|
||||
username="bob@acme.com",
|
||||
secret_key="SECRET",
|
||||
master_password="ONEKEYTORULETHEMALL",
|
||||
)
|
||||
result = op_cli.full_signin()
|
||||
|
||||
op_cli._run.assert_called_with(
|
||||
[
|
||||
"signin",
|
||||
"acme.1password.com",
|
||||
b"bob@acme.com",
|
||||
b"SECRET",
|
||||
"--raw",
|
||||
],
|
||||
command_input=b"ONEKEYTORULETHEMALL",
|
||||
)
|
||||
assert result == [0, "", ""]
|
||||
OP_VERSION_FIXTURES = ["opv2"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
|
@ -153,10 +92,7 @@ def test_full_signin_v2(mocker):
|
|||
|
||||
@pytest.mark.parametrize(
|
||||
("version", "version_class"),
|
||||
(
|
||||
("1.17.2", OnePassCLIv1),
|
||||
("2.27.4", OnePassCLIv2),
|
||||
),
|
||||
(("2.27.4", OnePassCLIv2),),
|
||||
)
|
||||
def test_op_correct_cli_class(fake_op, version, version_class):
|
||||
op = fake_op(version)
|
||||
|
|
@ -165,6 +101,8 @@ def test_op_correct_cli_class(fake_op, version, version_class):
|
|||
|
||||
|
||||
def test_op_unsupported_cli_version(fake_op):
|
||||
with pytest.raises(AnsibleLookupError, match="is unsupported"):
|
||||
fake_op("1.17.2")
|
||||
with pytest.raises(AnsibleLookupError, match="is unsupported"):
|
||||
fake_op("99.77.77")
|
||||
|
||||
|
|
@ -249,17 +187,6 @@ def test_op_assert_logged_in(mocker, login_status, op_fixture, request):
|
|||
op.set_token.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("op_fixture", OP_VERSION_FIXTURES)
|
||||
def test_op_get_raw_v1(mocker, op_fixture, request):
|
||||
op = request.getfixturevalue(op_fixture)
|
||||
mocker.patch.object(op._cli, "get_raw", return_value=[99, "RAW OUTPUT", ""])
|
||||
|
||||
result = op.get_raw("some item")
|
||||
|
||||
assert result == "RAW OUTPUT"
|
||||
op._cli.get_raw.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("op_fixture", "output", "expected"),
|
||||
(
|
||||
|
|
@ -354,16 +281,3 @@ def test_op_connect_partial_args(plugin, connect_host, connect_token, mocker):
|
|||
|
||||
with pytest.raises(AnsibleOptionsError):
|
||||
op_lookup.run("login", vault_name="test vault", connect_host=connect_host, connect_token=connect_token)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("kwargs"),
|
||||
(
|
||||
{"connect_host": "http://localhost", "connect_token": "foobar"},
|
||||
{"service_account_token": "foobar"},
|
||||
),
|
||||
)
|
||||
def test_opv1_unsupported_features(kwargs):
|
||||
op_cli = OnePassCLIv1(**kwargs)
|
||||
with pytest.raises(AnsibleLookupError):
|
||||
op_cli.full_signin()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue