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

merge development into main: v4.3.0 #190

Merged
merged 21 commits into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
daf9455
bump version to 4.3.0.dev0
zcutlip Dec 24, 2023
3b7d5ea
[pre-commit.ci] pre-commit autoupdate
pre-commit-ci[bot] Jan 8, 2024
e52e8fd
Merge pull request #186 from zcutlip/pre-commit-ci-update-config
zcutlip Mar 13, 2024
7672670
[pre-commit.ci] pre-commit autoupdate
pre-commit-ci[bot] Mar 18, 2024
7d63a01
Merge pull request #188 from zcutlip/pre-commit-ci-update-config
zcutlip Mar 20, 2024
1dfb239
in uses_biometric(), handle account list being empty
zcutlip Mar 13, 2024
614cfcd
get _uses_bio before other facts that may depend on it
zcutlip Mar 13, 2024
5c378f1
handle missing op config
zcutlip Mar 13, 2024
52d1119
add debug logging to pypi_password.py
zcutlip Mar 16, 2024
e0fc3ed
process '*/error_output` files when santitizing
zcutlip Mar 20, 2024
c6838be
additional sanitization on mock-op response data
zcutlip Mar 20, 2024
8bdda3b
add query definition file for missing config/biometric disabled
zcutlip Mar 20, 2024
ef74b35
add response definitions for no op config/biometric disabled
zcutlip Mar 20, 2024
3c2840c
make `_OPCommandInterface._get_cli_version()` a class method
zcutlip Mar 27, 2024
3a60e1b
add path constant for no-conf-no-bio response directory
zcutlip Mar 27, 2024
df56860
add fixture to setup environment with no-conf-no-bio responses
zcutlip Mar 27, 2024
07e263d
add test module to test missing op config
zcutlip Mar 27, 2024
266074e
Merge pull request #189 from zcutlip/dev/187-handle-missing-op-config…
zcutlip Mar 28, 2024
29c52d3
add setuptools to dev-reqs.txt
zcutlip Mar 28, 2024
8ceed1c
bump version to 4.3.0
zcutlip Mar 28, 2024
9f6e985
update CHANGELOG.md for 4.3.0
zcutlip Mar 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ repos:
- id: check-yaml
- id: check-merge-conflict
- repo: https://github.com/PyCQA/flake8
rev: 6.1.0
rev: 7.0.0
hooks:
- id: flake8
- repo: https://github.com/hhatto/autopep8
rev: 'v2.0.4'
rev: 'v2.1.0'
hooks:
- id: autopep8
- repo: https://github.com/pycqa/isort
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

All notable changes to this project will be documented in this file.

## [4.3.0] 2024-03-28

## Added

- `op` config (e.g. `.config/op/confg`) missing is no longer a failure if 1Password app integration is enabled


## [4.2.0.post0] 2023-12-23

### Misc
Expand All @@ -13,6 +20,7 @@ All notable changes to this project will be documented in this file.

No release will be generated for this update


## [4.2.0] 2023-12-17

### Added
Expand Down
1 change: 1 addition & 0 deletions dev-reqs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ colored-traceback
flake8
ipython
pycodestyle
setuptools
twine
-e .
-r test-reqs.txt
Expand Down
2 changes: 1 addition & 1 deletion pyonepassword/__about__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
__title__ = "pyonepassword"
__version__ = "4.2.0.post0"
__version__ = "4.3.0"
__summary__ = "A python API to query a 1Password account using the 'op' command-line tool"

"""
Expand Down
37 changes: 27 additions & 10 deletions pyonepassword/_op_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
OPAuthenticationException,
OPCmdFailedException,
OPCmdMalformedSvcAcctTokenException,
OPConfigNotFoundException,
OPDocumentDeleteException,
OPDocumentEditException,
OPDocumentGetException,
Expand Down Expand Up @@ -183,26 +184,42 @@ def uses_biometric(cls, op_path: str = "op", encoding: str = "utf-8", account_li
if account_list is None:
account_list = cls._get_account_list(op_path, decode=encoding)
acct: OPAccount
for acct in account_list:
if not acct.shorthand:
continue
# There is at least one account_shorthand found in `op account list`
if not account_list:
uses_bio = False
break
else:
for acct in account_list:
if not acct.shorthand:
continue
# There is at least one account_shorthand found in `op account list`
uses_bio = False
break
return uses_bio

def _gather_facts(self):
self._op_config = OPCLIConfig()
self._cli_version = self._get_cli_version(self.op_path)
self._account_list = self._get_account_list(self.op_path)
self._uses_bio = self.uses_biometric(
op_path=self.op_path, account_list=self._account_list)
self.logger.debug(f"uses bio: {self._uses_bio}")
try:
# if we haven't done a sign-in via the CLI/without desktop app integration
# there won't be a config
self._op_config = OPCLIConfig()
except OPConfigNotFoundException:
if self._uses_bio:
# set this to None. We should only use it if biometric is diabled,
# in which case this should be a hard error
self._op_config = None
else:
raise
self._cli_version = self._get_cli_version(self.op_path)
self._account_list = self._get_account_list(self.op_path)

self._account_identifier = self._normalize_account_id()
self._sess_var = self._compute_session_var_name()

def _get_cli_version(self, op_path: str) -> OPCLIVersion:
@classmethod
def _get_cli_version(cls, op_path: str) -> OPCLIVersion:
argv = _OPArgv.cli_version_argv(op_path)
output = self._run(argv, capture_stdout=True, decode="utf-8")
output = cls._run(argv, capture_stdout=True, decode="utf-8")
output = output.rstrip()
cli_version = OPCLIVersion(output)
return cli_version
Expand Down
14 changes: 12 additions & 2 deletions scripts/pypi_password.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import sys
from argparse import ArgumentParser

from pyonepassword.logging import console_debug_logger

# isort: split
parent_path = os.path.dirname(
os.path.dirname(
Expand All @@ -27,7 +29,7 @@
)


def do_signin(vault=None, op_path="op", use_existing_session=False, account=None):
def do_signin(vault=None, op_path="op", use_existing_session=False, account=None, logger=None):
auth = EXISTING_AUTH_IGNORE
if use_existing_session:
auth = EXISTING_AUTH_AVAIL
Expand All @@ -36,6 +38,7 @@ def do_signin(vault=None, op_path="op", use_existing_session=False, account=None
uses_biometric = OP.uses_biometric(op_path=op_path)
try:
op = OP(vault=vault, op_path=op_path,
logger=logger,
existing_auth=auth, password_prompt=False, account=account)
except OPAuthenticationException as e:
if uses_biometric:
Expand All @@ -60,6 +63,9 @@ def pypi_parse_args():
help="Attempt to use an existing 'op' session. If unsuccessful master password will be requested.", action='store_true')
parser.add_argument("--account", "-A",
help="1Password account to use. (See op signin --help, for valid identifiers")
parser.add_argument("--debug",
help="Enable debug logging to the console",
action='store_true')
parsed = parser.parse_args()
return parsed

Expand All @@ -68,10 +74,14 @@ def main():
op: OP
print(f"pyonepassword version {OP.version()}", file=sys.stderr)
parsed = pypi_parse_args()
logger = None
if parsed.debug:
logger = console_debug_logger("pypi_password")
pypi_item_name = parsed.pypi_item_name
try:
op = do_signin(use_existing_session=parsed.use_session,
account=parsed.account)
account=parsed.account,
logger=logger)
except OPSigninException as e:
print("sign-in failed", file=sys.stderr)
print(e.err_output, file=sys.stderr)
Expand Down
3 changes: 2 additions & 1 deletion scripts/sanitize.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
from pathlib import Path
from typing import Dict

WHITELIST = ["*/output", "*.txt", "*.json", "*.py", "*/input/**"]
WHITELIST = ["*/output", "*/error_output",
"*.txt", "*.json", "*.py", "*/input/**"]


def digest_file(file_path):
Expand Down
37 changes: 37 additions & 0 deletions tests/config/mock-op/no-conf-no-bio-response-directory.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"meta": {
"response_dir": "tests/config/mock-op/responses-no-conf-no-bio",
"input_dir": "input"
},
"commands": {
"--account|5GHHPJK5HZC5BAT7WDUXW57G44|--format|json|whoami": {
"exit_status": 1,
"stdout": "output",
"stderr": "error_output",
"name": "whoami-account-uuid",
"changes_state": false
},
"--version": {
"exit_status": 0,
"stdout": "output",
"stderr": "error_output",
"name": "cli-version",
"changes_state": false
},
"--format|json|account|list": {
"exit_status": 0,
"stdout": "output",
"stderr": "error_output",
"name": "list-signed-in-accounts",
"changes_state": false
},
"--format|json|whoami": {
"exit_status": 1,
"stdout": "output",
"stderr": "error_output",
"name": "whoami",
"changes_state": false
}
},
"commands_with_input": {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[MAIN]
config-path = ./tests/config/mock-op
response-path = responses-no-conf-no-bio
response-dir-file = no-conf-no-bio-response-directory.json
ignore-signin-fail = true
skip-global-signin = True


# be sure to do the following before generating responses
# - remove ~/.config/op
# - disable TouchID (or other biometric) in 1Password.app developer settings
[cli-version]
type = cli-version
create-instance = False

[list-signed-in-accounts]
type = account-list
create-instance = False
# account_list returns an empty list, but succeeds
expected-return = 0

[whoami]
type = whoami
create-instance = False
expected-return = 1
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
2.26.0
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[ERROR] 2024/03/19 19:33:50 no account found for filter 5GHHPJK5HZC5BAT7WDUXW57G44
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[ERROR] 2024/03/19 19:34:06 no account found for filter
Empty file.
Original file line number Diff line number Diff line change
@@ -1 +1 @@
[ERROR] 2023/12/15 21:26:48 "Zachary Cutlip" isn't a user in this account. Specify the user with their UUID, email address, or name.
[ERROR] 2023/12/15 21:26:48 "Example User" isn't a user in this account. Specify the user with their UUID, email address, or name.
14 changes: 14 additions & 0 deletions tests/fixtures/op_fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
ITEM_DELETE_MULTIPLE_STATE_CONFIG_PATH,
ITEM_DELETE_MULTIPLE_TITLE_GLOB_STATE_CONFIG_PATH,
ITEM_EDIT_STATE_CONFIG_PATH,
NO_CONF_NO_BIO_RESP_DIRECTORY_PATH,
RESP_DIRECTORY_PATH,
SVC_ACCT_CORRUPT_RESP_DIRECTORY_PATH,
SVC_ACCT_NOT_YET_AUTH_STATE_CONFIG_PATH,
Expand Down Expand Up @@ -126,6 +127,14 @@ def _setup_alt_env():
os.environ["LOG_OP_ERR"] = "1"


def _setup_no_conf_no_bio_env():
os.environ["MOCK_OP_RESPONSE_DIRECTORY"] = str(
NO_CONF_NO_BIO_RESP_DIRECTORY_PATH)
# os.environ["MOCK_OP_SIGNIN_SUCCEED"] = "1"
# os.environ["MOCK_OP_SIGNIN_USES_BIO"] = "1"
os.environ["LOG_OP_ERR"] = "1"


def _setup_no_bio_normal_env():
_setup_normal_env()
os.environ["MOCK_OP_SIGNIN_USES_BIO"] = "0"
Expand Down Expand Up @@ -392,6 +401,11 @@ def setup_svc_account_revoked_env():
_setup_svc_acct_env(resp_dir_path=SVC_ACCT_REVOKED_RESP_DIRECTORY_PATH)


@fixture
def setup_no_conf_no_bio_env():
_setup_no_conf_no_bio_env()


@fixture
def setup_normal_op_env_signin_failure():
_setup_normal_env(signin_success="0")
Expand Down
5 changes: 5 additions & 0 deletions tests/fixtures/paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@
SVC_ACCT_REVOKED_RESP_DIRECTORY_PATH = Path(
MOCK_OP_CONFIG_PATH, "svc-acct-revoked-token-directory.json"
)

NO_CONF_NO_BIO_RESP_DIRECTORY_PATH = Path(
MOCK_OP_CONFIG_PATH, "no-conf-no-bio-response-directory.json"
)

SVC_ACCT_NOT_YET_AUTH_RESP_PATH = Path(
MOCK_OP_CONFIG_PATH, "responses-svc-acct-not-yet-auth"
)
Expand Down
81 changes: 81 additions & 0 deletions tests/test_auth_scenarios/test_missing_op_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import pytest

from pyonepassword import OP
from pyonepassword.api.exceptions import OPConfigNotFoundException
from pyonepassword.op_cli_version import OPCLIVersion
from pyonepassword.py_op_exceptions import OPWhoAmiException


@pytest.mark.usefixtures("invalid_op_cli_config_missing")
@pytest.mark.usefixtures("setup_no_conf_no_bio_env")
def test_missing_op_config_010(console_logger):

with pytest.raises(OPConfigNotFoundException):
OP(op_path="mock-op", logger=console_logger)


@pytest.mark.usefixtures("invalid_op_cli_config_missing")
@pytest.mark.usefixtures("setup_no_conf_no_bio_env")
def test_missing_op_config_020(console_logger):
"""
Simulate:
- running op --format json account list with no ~/.config/op/
- Integration with the desktop app, and hence biometric, is disabled

Verify:
an empty list is returned
"""
OP.set_logger(console_logger)
accounts = OP._get_account_list(op_path="mock-op")
assert len(accounts) == 0


@pytest.mark.usefixtures("invalid_op_cli_config_missing")
@pytest.mark.usefixtures("setup_no_conf_no_bio_env")
def test_missing_op_config_030(console_logger):
"""
Simulate:
- running op --format json whoami with no ~/.config/op/
- Integration with the desktop app, and hence biometric, is disabled

Verify:
OPWhoAmiException is raised
"""
OP.set_logger(console_logger)
with pytest.raises(OPWhoAmiException):
OP._whoami(op_path="mock-op")


@pytest.mark.usefixtures("invalid_op_cli_config_missing")
@pytest.mark.usefixtures("setup_no_conf_no_bio_env")
def test_missing_op_config_040(console_logger):
"""
Simulate:
- running op --version with no ~/.config/op/
- Integration with the desktop app, and hence biometric, is disabled

Verify:
a version is returned
version is instance of OPCLIVersion
"""
OP.set_logger(console_logger)
version = OP._get_cli_version(op_path="mock-op")
assert version
assert isinstance(version, OPCLIVersion)


@pytest.mark.usefixtures("invalid_op_cli_config_missing")
@pytest.mark.usefixtures("setup_no_conf_no_bio_env")
def test_missing_op_config_050(console_logger):
"""
Simulate:
- running op --format json whoami <account_uuid> with no ~/.config/op/
- Integration with the desktop app, and hence biometric, is disabled

Verify:
OPWhoAmiException is raised
"""
account_uuid = "5GHHPJK5HZC5BAT7WDUXW57G44"
OP.set_logger(console_logger)
with pytest.raises(OPWhoAmiException):
OP._whoami(op_path="mock-op", account=account_uuid)