Skip to content

Commit

Permalink
Allow to disable autologin in core (for login, logout CLI actions) an…
Browse files Browse the repository at this point in the history
…d keyring for CLI login action
  • Loading branch information
psrok1 committed Oct 6, 2022
1 parent 8536ca1 commit 45c19e9
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 46 deletions.
2 changes: 1 addition & 1 deletion mwdblib/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "4.3.1"
__version__ = "4.4.0"
36 changes: 25 additions & 11 deletions mwdblib/api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,21 @@ class APIClient:
mwdb.api.delete(f'object/{sha256}')
"""

def __init__(self, _auth_token: Optional[str] = None, **api_options: Any) -> None:
def __init__(
self,
_auth_token: Optional[str] = None,
autologin: bool = True,
**api_options: Any,
) -> None:
self.options: APIClientOptions = APIClientOptions(**api_options)
self.auth_token: Optional[JWTAuthToken] = None

# These state variables will be filled after
# successful authentication
self.username: Optional[str] = None
self.password: Optional[str] = None
self.api_key: Optional[str] = None

self._server_metadata: Optional[dict] = None

self.session: requests.Session = requests.Session()
Expand All @@ -92,10 +104,12 @@ def __init__(self, _auth_token: Optional[str] = None, **api_options: Any) -> Non

if _auth_token:
self.set_auth_token(_auth_token)
if self.options.api_key:
self.set_api_key(self.options.api_key)
elif self.options.username and self.options.password:
self.login(self.options.username, self.options.password)

if autologin:
if self.options.api_key:
self.set_api_key(self.options.api_key)
elif self.options.username and self.options.password:
self.login(self.options.username, self.options.password)

@property
def server_metadata(self) -> dict:
Expand Down Expand Up @@ -151,8 +165,8 @@ def login(self, username: str, password: str) -> None:
)["token"]
self.set_auth_token(token)
# Store credentials in API options
self.options.username = username
self.options.password = password
self.username = username
self.password = password

def set_api_key(self, api_key: str) -> None:
"""
Expand All @@ -165,8 +179,8 @@ def set_api_key(self, api_key: str) -> None:
:param api_key: API key to set
"""
self.set_auth_token(api_key)
# Store credentials in API options
self.options.api_key = api_key
# Store credentials in API state
self.api_key = api_key

def logout(self) -> None:
"""
Expand Down Expand Up @@ -251,10 +265,10 @@ def request(
# Forget current auth_key
self.logout()
# If no password set: re-raise
if self.options.password is None:
if self.username is None or self.password is None:
raise
# Try to log in
self.login(self.options.username, self.options.password)
self.login(self.username, self.password)
# Retry failed request...
except LimitExceededError as e:
if not self.options.obey_ratelimiter:
Expand Down
2 changes: 2 additions & 0 deletions mwdblib/api/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,11 +219,13 @@ def store_credentials(self) -> None:
keyring.set_password(
f"mwdb:{self.api_url}", self.username, self.password
)
self.config_parser.set(instance_section, "use_keyring", "1")
else:
if self.api_key:
self.config_parser.set(instance_section, "api_key", self.api_key)
else:
self.config_parser.set(instance_section, "password", self.password)
self.config_parser.set(instance_section, "use_keyring", "0")
# Perform configuration writeback
if self.config_path:
with self.config_path.open("w") as f:
Expand Down
21 changes: 18 additions & 3 deletions mwdblib/cli/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
@click.option(
"--password", "-p", type=str, default=None, help="MWDB password (default: ask)"
)
@click.option(
"--use-keyring/--no-keyring",
default=None,
help="Don't use keyring, store credentials in plaintext",
)
@click.option("--via-api-key", "-A", is_flag=True, help="Use API key provided by stdin")
@click.option(
"--api-key",
Expand All @@ -19,18 +24,22 @@
default=None,
help="API key token (default: password-based authentication)",
)
@pass_mwdb
@pass_mwdb(autologin=False)
@click.pass_context
def login_command(ctx, mwdb, username, password, via_api_key, api_key):
def login_command(ctx, mwdb, username, password, use_keyring, via_api_key, api_key):
"""Store credentials for MWDB authentication"""
if via_api_key:
api_key = click.prompt("Provide your API key token", hide_input=True)

if api_key is None:
if username is None:
username = click.prompt("Username")
if password is None:
password = click.prompt("Password", hide_input=True)

if use_keyring is not None:
mwdb.api.options.use_keyring = use_keyring

try:
# Try to use credentials
if api_key is None:
Expand All @@ -43,10 +52,16 @@ def login_command(ctx, mwdb, username, password, via_api_key, api_key):
click.echo("Error: Login failed - {}".format(str(e)), err=True)
ctx.abort()
mwdb.api.options.store_credentials()
click.echo(
f"Logged in successfully to {mwdb.api.options.api_url} "
f"as {mwdb.api.logged_user}",
err=True,
)


@main.command("logout")
@pass_mwdb
@pass_mwdb(autologin=False)
def logout_command(mwdb):
"""Reset stored credentials"""
mwdb.api.options.clear_stored_credentials()
click.echo(f"Logged out successfully from {mwdb.api.options.api_url}", err=True)
74 changes: 43 additions & 31 deletions mwdblib/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,37 +8,49 @@
from ..exc import MWDBError, NotAuthenticatedError


def pass_mwdb(fn):
@click.option("--api-url", type=str, default=None, help="URL to MWDB instance API")
@click.option(
"--config-path", type=str, default=None, help="Alternative configuration path"
)
@functools.wraps(fn)
def wrapper(*args, **kwargs):
ctx = get_current_context()
mwdb_options = {}
api_url = kwargs.pop("api_url")
if api_url:
mwdb_options["api_url"] = api_url
config_path = kwargs.pop("config_path")
if config_path:
mwdb_options["config_path"] = config_path
mwdb = MWDB(**mwdb_options)
try:
return fn(mwdb=mwdb, *args, **kwargs)
except NotAuthenticatedError:
click.echo(
"Error: Not authenticated. Use `mwdb login` first to set credentials.",
err=True,
)
ctx.abort()
except MWDBError as error:
click.echo(
"{}: {}".format(error.__class__.__name__, error.args[0]), err=True
)
ctx.abort()

return wrapper
def pass_mwdb(*fn, autologin=True):
def uses_mwdb(fn):
@click.option(
"--api-url", type=str, default=None, help="URL to MWDB instance API"
)
@click.option(
"--config-path",
type=str,
default=None,
help="Alternative configuration path",
)
@functools.wraps(fn)
def wrapper(*args, **kwargs):
ctx = get_current_context()
mwdb_options = {}
api_url = kwargs.pop("api_url")
if api_url:
mwdb_options["api_url"] = api_url
config_path = kwargs.pop("config_path")
if config_path:
mwdb_options["config_path"] = config_path
mwdb = MWDB(autologin=autologin, **mwdb_options)
try:
return fn(mwdb=mwdb, *args, **kwargs)
except NotAuthenticatedError:
click.echo(
"Error: Not authenticated. Use `mwdb login` first "
"to set credentials.",
err=True,
)
ctx.abort()
except MWDBError as error:
click.echo(
"{}: {}".format(error.__class__.__name__, error.args[0]), err=True
)
ctx.abort()

return wrapper

if fn:
return uses_mwdb(fn[0])
else:
return uses_mwdb


@click.group()
Expand Down
5 changes: 5 additions & 0 deletions mwdblib/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ class MWDB:
:param api_key: MWDB API key
:param username: MWDB account username
:param password: MWDB account password
:param autologin: Login automatically using credentials stored in configuration
or provided in arguments (default: True)
:param verify_ssl: Verify SSL certificate correctness (default: True)
:param obey_ratelimiter: If ``False``, HTTP 429 errors will cause an exception
like all other error codes.
Expand Down Expand Up @@ -78,6 +80,9 @@ class MWDB:
Added ``use_keyring``, ``emit_warnings`` and ``config_path`` options.
``username`` and ``password`` can be passed directly to the constructor.
.. versionadded:: 4.4.0
Added ``autologin`` option.
Usage example:
.. code-block:: python
Expand Down

0 comments on commit 45c19e9

Please sign in to comment.