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

feature: Add get options to cli for interface with get_credential and support JSON output #678

Merged
merged 15 commits into from
Apr 26, 2024
Merged
65 changes: 59 additions & 6 deletions keyring/cli.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
"""Simple command line interface to get/set password from a keyring"""

from __future__ import annotations

import argparse
import getpass
import json
import sys

from . import (
backend,
completion,
core,
credentials,
delete_password,
get_password,
set_keyring,
set_password,
get_credential,
)
from .util import platform_

Expand Down Expand Up @@ -41,6 +46,32 @@ def __init__(self):
self.parser.add_argument(
"--disable", action="store_true", help="Disable keyring and exit"
)
self.parser._get_modes = ["password", "creds"]
self.parser.add_argument(
"--mode",
choices=self.parser._get_modes,
dest="get_mode",
default="password",
help="""
Mode for 'get' operation.
'password' requires a username and will return only the password.
'creds' does not require a username and will return both the username and password separated by a newline.

Default is 'password'
""",
)
self.parser._output_formats = ["plain", "json"]
self.parser.add_argument(
"--output",
choices=self.parser._output_formats,
dest="output_format",
default="plain",
help="""
Output format for 'get' operation.

Default is 'plain'
""",
)
self.parser._operations = ["get", "set", "del", "diagnose"]
self.parser.add_argument(
'operation',
Expand Down Expand Up @@ -80,15 +111,37 @@ def run(self, argv):
return method()

def _check_args(self):
if self.operation:
if self.service is None or self.username is None:
self.parser.error(f"{self.operation} requires service and username")
needs_username = self.operation != 'get' or self.get_mode != 'creds'
required = (['service'] + ['username'] * needs_username) * bool(self.operation)
if any(getattr(self, param) is None for param in required):
self.parser.error(f"{self.operation} requires {' and '.join(required)}")

def do_get(self):
password = get_password(self.service, self.username)
if password is None:
credential = getattr(self, f'_get_{self.get_mode}')()
if credential is None:
raise SystemExit(1)
print(password)
getattr(self, f'_emit_{self.output_format}')(credential)

def _emit_json(self, credential: credentials.Credential):
print(
json.dumps(dict(username=credential.username, password=credential.password))
)

def _emit_plain(self, credential: credentials.Credential):
if credential.username:
print(credential.username)
print(credential.password)

def _get_cred(self) -> credentials.Credential | None:
return get_credential(self.service, self.username) # type: ignore

def _get_password(self) -> credentials.Credential | None:
password = get_password(self.service, self.username) # type: ignore
return (
credentials.SimpleCredential(None, password)
if password is not None
else None
)

def do_set(self):
password = self.input_password(
Expand Down
1 change: 1 addition & 0 deletions newsfragments/678.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added options for 'keyring get' command to support credential retrieval and emit as JSON.
Loading