Skip to content

Commit

Permalink
Merge pull request #183 from zcutlip/development
Browse files Browse the repository at this point in the history
merge development into main: v4.2.0
  • Loading branch information
zcutlip authored Dec 18, 2023
2 parents a068906 + 739c9e3 commit d39b810
Show file tree
Hide file tree
Showing 152 changed files with 1,288 additions and 76 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
Expand All @@ -64,7 +64,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v3

# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
Expand All @@ -77,6 +77,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions

name: Python testing and linting
name: testing and linting

on:
push:
Expand All @@ -12,16 +12,17 @@ on:
jobs:
build:

runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
python: ["3.9", "3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
- name: Install dependencies
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ repos:
hooks:
- id: autopep8
- repo: https://github.com/pycqa/isort
rev: 5.12.0
rev: 5.13.0
hooks:
- id: isort
name: isort (python)
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@

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

## [4.2.0] 2023-12-17

### Added

- User editing (gh-159)
- `OP.user_edit()`

### Testing

- Ensured all tests pass under Windows
- Added Vagrant file to aid in automated local development testing on windows
- Added `windows-latest` to OS matrix in github testing workflow


## [4.1.0] 2023-11-28

### Added
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# PYONEPASSWORD

![PyPI - Version](https://img.shields.io/pypi/v/pyonepassword)
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pyonepassword)
![Testing & linting](https://github.com/zcutlip/pyonepassword/actions/workflows/python-package.yml/badge.svg)
![CodeQL](https://github.com/zcutlip/pyonepassword/actions/workflows/codeql.yml/badge.svg)



## Description

A Python API to sign into and query a 1Password account using the `op` command.
Expand Down Expand Up @@ -358,6 +365,12 @@ For details on editing existing document item file contents, see [document-editi

See examples in [examples/document_editing](examples/document_editing.py)

### User Editing

User editing is supported via the `OP.user_edit()` method. It supports toggling travel mode on and off, as well as setting a new user name. Only one user at a time may be edited via this method.

See examples in [examples/user_editing](examples/user_editing.py)

### More Examples

Lots more examples are available in the `examples` directory
7 changes: 2 additions & 5 deletions examples/sign_in_fail_auth_required.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
"""
An example of signing in with existing authentication required but not existing
An example of signing in with existing authentication required but not available
Demonstrates OPAuthenticationException
"""

from do_signin import do_signin

from pyonepassword import OP, logging
from pyonepassword import logging
from pyonepassword.api.authentication import EXISTING_AUTH_REQD
from pyonepassword.api.exceptions import OPAuthenticationException

if __name__ == "__main__":
import inspect
print(__name__)
print(inspect.getfile(OP))
logger = logging.console_logger("example-sign-in", level=logging.DEBUG)
try:
# ensure OP() fails if there's no existing authentication
Expand Down
37 changes: 37 additions & 0 deletions examples/user_editing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from do_signin import do_signin

from pyonepassword.api.exceptions import OPUserEditException


def user_edit_set_travel_mode():
op = do_signin()
user_name = "Example User"

# Toggle travel mode on
# user_edit() returns the unique user ID
user_id = op.user_edit(user_name, travel_mode=True)

# Toggle travel mode back off
# When editing users, either user name or unique user ID is okay
op.user_edit(user_id, travel_mode=False)


def user_edit_set_user_name():
op = do_signin()
user_name = "Example User"

new_user_name = "Example User - updated"

# set new username
op.user_edit(user_name, new_user_name=new_user_name)


def user_edit_invalid_user():

op = do_signin()
user_name = "Nonexistent User"

try:
op.user_edit(user_name, travel_mode=False)
except OPUserEditException as e:
print(e.err_output)
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.1.0"
__version__ = "4.2.0"
__summary__ = "A python API to query a 1Password account using the 'op' command-line tool"

"""
Expand Down
22 changes: 20 additions & 2 deletions pyonepassword/_op_cli_argv.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ def vault_list_argv(cls, op_exe, group_name_or_id=None, user_name_or_id=None):
if group_name_or_id:
sub_cmd_args.extend(["--group", group_name_or_id])
if user_name_or_id:
user_name_or_id = RedactedString(user_name_or_id)
sub_cmd_args.extend(["--user", user_name_or_id])
argv = cls.vault_generic_argv(op_exe, "list", sub_cmd_args)
return argv
Expand All @@ -209,10 +210,26 @@ def user_generic_argv(cls,

@classmethod
def user_get_argv(cls, op_exe, user_name_or_id):
user_name_or_id = RedactedString(user_name_or_id)
sub_cmd_args = [user_name_or_id]
argv = cls.user_generic_argv(op_exe, "get", sub_cmd_args)
return argv

@classmethod
def user_edit_argv(cls, op_exe, user_name_or_id, new_name, travel_mode):
user_name_or_id = RedactedString(user_name_or_id)
sub_cmd_args = [user_name_or_id]
if new_name:
sub_cmd_args.extend(["--name", new_name])
if travel_mode in (False, True):
arg = "on" if travel_mode else "off"
sub_cmd_args.extend(["--travel-mode", arg])
elif travel_mode is not None:
raise TypeError("travel_mode must be bool or None")

argv = cls.user_generic_argv(op_exe, "edit", sub_cmd_args)
return argv

@classmethod
def user_list_argv(cls, op_exe, group_name_or_id=None, vault=None):
sub_cmd_args = []
Expand All @@ -237,15 +254,16 @@ def group_generic_argv(cls,
return argv

@classmethod
def group_get_argv(cls, op_exe, user_name_or_id):
sub_cmd_args = [user_name_or_id]
def group_get_argv(cls, op_exe, group_name_or_id):
sub_cmd_args = [group_name_or_id]
argv = cls.group_generic_argv(op_exe, "get", sub_cmd_args)
return argv

@classmethod
def group_list_argv(cls, op_exe, user_name_or_id=None, vault=None):
sub_cmd_args = []
if user_name_or_id:
user_name_or_id = RedactedString(user_name_or_id)
sub_cmd_args.extend(["--user", user_name_or_id])
if vault:
sub_cmd_args.extend(["--vault", vault])
Expand Down
30 changes: 28 additions & 2 deletions pyonepassword/_op_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
OPItemListException,
OPSigninException,
OPUnknownAccountException,
OPUserEditException,
OPUserGetException,
OPUserListException,
OPVaultGetException,
Expand Down Expand Up @@ -627,14 +628,32 @@ def _signed_in_accounts(cls, op_path, decode="utf-8"):
return output

def _user_get(self, user_name_or_id: str, decode: str = "utf-8") -> str:
get_user_argv = self._user_get_argv(user_name_or_id)
user_get_argv = self._user_get_argv(user_name_or_id)
try:
output = self._run_with_auth_check(self.op_path, self._account_identifier,
get_user_argv, capture_stdout=True, decode=decode)
user_get_argv, capture_stdout=True, decode=decode)
except OPCmdFailedException as ocfe:
raise OPUserGetException.from_opexception(ocfe) from ocfe
return output

def _user_edit(self,
user_name_or_id: str,
new_name: Optional[str],
travel_mode: Optional[bool],
decode: str = "utf-8"):

user_edit_argv = self._user_edit_argv(
user_name_or_id, new_name, travel_mode)

try:
# 'op user edit' doesn't have any output if successful
# if it fails, stderr will be in the exception object
self._run_with_auth_check(self.op_path, self._account_identifier,
user_edit_argv, capture_stdout=True, decode=decode)
except OPCmdFailedException as ocfe:
raise OPUserEditException.from_opexception(ocfe) from ocfe
return

def _user_list(self, group_name_or_id=None, vault=None, decode: str = "utf-8") -> str:
user_list_argv = self._user_list_argv(
group_name_or_id=group_name_or_id, vault=vault)
Expand Down Expand Up @@ -871,6 +890,13 @@ def _user_get_argv(self, user_name_or_id: str):
get_user_argv = _OPArgv.user_get_argv(self.op_path, user_name_or_id)
return get_user_argv

def _user_edit_argv(self, user_name_or_id: str, new_name: Optional[str], travel_mode: Optional[bool]):
get_user_argv = _OPArgv.user_edit_argv(self.op_path,
user_name_or_id,
new_name,
travel_mode)
return get_user_argv

def _user_list_argv(self, group_name_or_id=None, vault=None):
user_list_argv = _OPArgv.user_list_argv(
self.op_path, group_name_or_id=group_name_or_id, vault=vault)
Expand Down
2 changes: 2 additions & 0 deletions pyonepassword/api/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
OPSigninException,
OPSignoutException,
OPUnknownAccountException,
OPUserEditException,
OPUserGetException,
OPUserListException,
OPVaultGetException,
Expand Down Expand Up @@ -93,6 +94,7 @@
"OPSvcAcctCommandNotSupportedException",
"OPUnknownAccountException",
"OPUnknownItemTypeException",
"OPUserEditException",
"OPUserGetException",
"OPUserListException",
"OPVaultGetException",
Expand Down
15 changes: 11 additions & 4 deletions pyonepassword/py_op_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def __init__(self, stderr_out, returncode):


class OPItemGetException(OPCmdFailedException):
MSG = "1Password 'get item' failed."
MSG = "1Password 'item get' failed."

def __init__(self, stderr_out, returncode):
super().__init__(stderr_out, returncode)
Expand Down Expand Up @@ -169,7 +169,14 @@ def __init__(self, stderr_out, returncode):


class OPUserGetException(OPCmdFailedException):
MSG = "1Password 'get user' failed."
MSG = "1Password 'user get' failed."

def __init__(self, stderr_out, returncode):
super().__init__(stderr_out, returncode)


class OPUserEditException(OPCmdFailedException):
MSG = "1Password 'user edit' failed."

def __init__(self, stderr_out, returncode):
super().__init__(stderr_out, returncode)
Expand All @@ -183,7 +190,7 @@ def __init__(self, stderr_out, returncode):


class OPVaultGetException(OPCmdFailedException):
MSG = "1Password 'get vault' failed."
MSG = "1Password 'vault get' failed."

def __init__(self, stderr_out, returncode):
super().__init__(stderr_out, returncode)
Expand All @@ -197,7 +204,7 @@ def __init__(self, stderr_out, returncode):


class OPGroupGetException(OPCmdFailedException):
MSG = "1Password 'get group' failed."
MSG = "1Password 'group get' failed."

def __init__(self, stderr_out, returncode):
super().__init__(stderr_out, returncode)
Expand Down
Loading

0 comments on commit d39b810

Please sign in to comment.