Changeset View
Standalone View
swh/web/client/cli.py
- This file was added.
# Copyright (C) 2020 The Software Heritage developers | |||||
# See the AUTHORS file at the top-level directory of this distribution | |||||
# License: GNU General Public License version 3, or any later version | |||||
# See top-level LICENSE file for more information | |||||
from getpass import getpass | |||||
import json | |||||
import click | |||||
from click.core import Context | |||||
from swh.web.client.auth import OpenIDConnectSession | |||||
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"]) | |||||
def _output_json(obj): | |||||
vlorentz: The group should be named "authentication", as it's not a single action | |||||
Done Inline Actionsack anlambert: ack | |||||
print(json.dumps(obj, indent=4, sort_keys=True)) | |||||
@click.group(name='auth', context_settings=CONTEXT_SETTINGS) | |||||
@click.option('--oidc-server-url', 'oidc_server_url', | |||||
default='https://auth.softwareheritage.org/auth/', | |||||
help=('URL of OpenID Connect server (default to ' | |||||
'"https://auth.softwareheritage.org/auth/")')) | |||||
@click.option('--realm-name', 'realm_name', | |||||
default='SoftwareHeritage', | |||||
help=('Name of the OpenID Connect authentication realm ' | |||||
'(default to "SoftwareHeritage")')) | |||||
@click.option('--client-id', 'client_id', | |||||
default='swh-web', | |||||
help=('OpenID Connect client identifier in the realm ' | |||||
'(default to "swh-web")')) | |||||
@click.pass_context | |||||
def auth(ctx: Context, oidc_server_url: str, realm_name: str, client_id: str): | |||||
""" | |||||
Authenticate Software Heritage users with OpenID Connect. | |||||
This CLI tool eases the retrieval of bearer tokens to authenticate | |||||
Done Inline ActionsYou should first use ctx.ensure_object() vlorentz: You should first use `ctx.ensure_object()` | |||||
Done Inline Actionsack anlambert: ack | |||||
a user querying the Software Heritage Web API. | |||||
""" | |||||
ctx.ensure_object(dict) | |||||
Not Done Inline Actionsyou can remove that pass ;) you've got a free pass for that one :D ardumont: you can remove that `pass` ;)
you've got a free pass for that one :D | |||||
Done Inline ActionsWhoopsie, thanks for spotting ! anlambert: Whoopsie, thanks for spotting ! | |||||
ctx.obj['oidc_session'] = OpenIDConnectSession( | |||||
oidc_server_url, realm_name, client_id) | |||||
Done Inline ActionsWhy the session- prefix? vlorentz: Why the `session-` prefix? | |||||
Done Inline ActionsTo explicit the fact that it opens an authenticated session on OIDC server. But I agree shorter is better and the doc give the details, I will remove the prefix. anlambert: To explicit the fact that it opens an authenticated session on OIDC server.
But I agree… | |||||
@auth.command('login') | |||||
Done Inline Actionsif you want the session- prefix, then the function name should be session_login vlorentz: if you want the `session-` prefix, then the function name should be `session_login` | |||||
@click.argument('username') | |||||
@click.pass_context | |||||
def login(ctx: Context, username: str): | |||||
""" | |||||
Login and create new offline OpenID Connect session. | |||||
Login with USERNAME, create a new OpenID Connect session and get | |||||
access and refresh tokens. | |||||
User will be prompted for his password and tokens will be printed in | |||||
JSON format to standard output. | |||||
When its access token has expired, user can request a new one using the | |||||
session-refresh command of that CLI tool without having to authenticate | |||||
using a password again. | |||||
The created OpenID Connect session is an offline one so the provided | |||||
refresh token has a much longer expiration time than classical OIDC | |||||
sessions (usually several dozens of days). | |||||
""" | |||||
password = getpass() | |||||
Done Inline Actionsprint(json.dumps(resp_json, indent=4, sort_keys=True)) to be developer-readable vlorentz: `print(json.dumps(resp_json, indent=4, sort_keys=True))` to be developer-readable | |||||
Done Inline Actionsack anlambert: ack | |||||
oidc_profile = ctx.obj['oidc_session'].login(username, password) | |||||
_output_json(oidc_profile) | |||||
Done Inline ActionsHiding the traceback makes debugging harder :/ And we should exit with non-0 in case of error. vlorentz: Hiding the traceback makes debugging harder :/
And we should exit with non-0 in case of error. | |||||
Done Inline ActionsCan I just remove the try/except block then ? anlambert: Can I just remove the try/except block then ? | |||||
Not Done Inline ActionsI guess. Or catch specific expected errors, and let everything raise to the toplevel vlorentz: I guess. Or catch specific expected errors, and let everything raise to the toplevel | |||||
Done Inline ActionsOk will remove the blocks then. anlambert: Ok will remove the blocks then. | |||||
@auth.command('refresh') | |||||
@click.argument('refresh_token') | |||||
@click.pass_context | |||||
def refresh(ctx: Context, refresh_token: str): | |||||
""" | |||||
Refresh an offline OpenID Connect session. | |||||
Get a new access token from REFRESH_TOKEN when previous one expired. | |||||
New access token will be printed in JSON format to standard output. | |||||
""" | |||||
oidc_profile = ctx.obj['oidc_session'].refresh(refresh_token) | |||||
if 'access_token' in oidc_profile: | |||||
_output_json(oidc_profile['access_token']) | |||||
else: | |||||
Done Inline Actionssame vlorentz: same | |||||
# print oidc error | |||||
Not Done Inline Actionsmaybe add a output_json function (or something) to avoid the repetition? I saw this at least thrice already. ardumont: maybe add a `output_json` function (or something) to avoid the repetition?
I saw this at least… | |||||
Done Inline ActionsAgreed ! anlambert: Agreed ! | |||||
_output_json(oidc_profile) | |||||
Done Inline Actionssame vlorentz: same | |||||
@auth.command('logout') | |||||
Done Inline Actionssame vlorentz: same | |||||
@click.argument('refresh_token') | |||||
@click.pass_context | |||||
def logout(ctx: Context, refresh_token: str): | |||||
""" | |||||
Logout from an offline OpenID Connect session. | |||||
Use REFRESH_TOKEN to logout from an offline OpenID Connect session. | |||||
Access and refresh tokens are no more usable after that operation. | |||||
""" | |||||
ctx.obj['oidc_session'].logout(refresh_token) | |||||
print('Successfully logged out from OpenID Connect session') | |||||
Done Inline Actionssame vlorentz: same |
The group should be named "authentication", as it's not a single action