diff --git a/swh/scanner/backend.py b/swh/scanner/backend.py --- a/swh/scanner/backend.py +++ b/swh/scanner/backend.py @@ -21,7 +21,7 @@ if len(swhids) > QUERY_LIMIT: raise LargePayloadExc( - f"The maximum number of SWHIDs this endpoint can receive is" + f"The maximum number of SWHIDs this endpoint can receive is " f"{QUERY_LIMIT}" ) diff --git a/swh/scanner/cli.py b/swh/scanner/cli.py --- a/swh/scanner/cli.py +++ b/swh/scanner/cli.py @@ -143,6 +143,15 @@ You will need to first create an account before running this operation. To create an account, visit: https://archive.softwareheritage.org/ """ + + from getpass import getpass + + from swh.auth.keycloak import ( + KeycloakError, + KeycloakOpenIDConnect, + keycloak_error_message, + ) + context = ctx.obj # Check we are actually talking to the Software Heritage itself. @@ -157,57 +166,89 @@ click.echo(click.style(msg, fg="red"), file=sys.stderr) ctx.exit(1) + # TODO: Ensure the token is valid # Check for an existing value in the configuration if web_api_config.get("auth-token") is not None: click.echo(click.style("You appear to already be logged in.", fg="green")) if not force: - click.echo("Hint: use `--force` to overwrite the current token") + click.echo( + click.style( + "Hint: use `--force` to overwrite the current token", fg="yellow" + ) + ) ctx.exit() click.echo(click.style("Continuing because of `--force`.", fg="yellow")) - # Obtain a valid token through the API - # - # Coming from the swh auth generate-token code - # (this command might eventually move there) - from getpass import getpass - - from swh.auth.keycloak import ( - KeycloakError, - KeycloakOpenIDConnect, - keycloak_error_message, - ) - - msg = "Please enter your SWH Archive credentials" - click.echo(click.style(msg, fg="yellow")) msg = "If you do not already have an account, create one one at:" click.echo(click.style(msg, fg="yellow")) msg = " https://archive.softwareheritage.org/" + click.echo(click.style(msg, fg="blue")) + + msg = "Choose an authentication method to connect the Software Heritage API:\n" + msg += ( + " `username`: Authenticate with your username and password to get a token\n" + ) + msg += " `token`: Use an existing token" click.echo(click.style(msg, fg="yellow")) - username = click.prompt("username") - password = getpass() + auth_method = click.prompt( + text=click.style("Authentication method", fg="yellow"), + type=click.Choice(["username", "token"]), + default="username", + ) + + # oidc client configuration + url = "https://auth.softwareheritage.org/auth/" + realm = "SoftwareHeritage" + client = "swh-web" + + if auth_method == "token": + raw_token = click.prompt( + text=click.style("Paste your token and press enter", fg="yellow") + ) + token = raw_token.strip() + else: + # Obtain a valid token through the API + msg = "Please enter your SWH Archive credentials (username, password):" + click.echo(click.style(msg, fg="yellow")) + username = click.prompt("username") + password = getpass() + try: + oidc_client = KeycloakOpenIDConnect(url, realm, client) + scope = "openid offline_access" + oidc_info = oidc_client.login(username, password, scope) + token = oidc_info["refresh_token"] + msg = "Token retrieved successfully" + click.echo(click.style(msg, fg="green")) + except KeycloakError as ke: + msg = keycloak_error_message(ke) + click.echo(click.style(msg, fg="red")) + ctx.exit(1) + + # Ensure the token is valid getting user info try: - url = "https://auth.softwareheritage.org/auth/" - realm = "SoftwareHeritage" - client = "swh-web" oidc_client = KeycloakOpenIDConnect(url, realm, client) - scope = "openid offline_access" - oidc_info = oidc_client.login(username, password, scope) - token = oidc_info["refresh_token"] - msg = "token retrieved successfully" + # userinfo endpoint needs the access_token + access_token = oidc_client.refresh_token(refresh_token=token)["access_token"] + oidc_info = oidc_client.userinfo(access_token=access_token) + msg = ( + f"Token verification success for username {oidc_info['preferred_username']}" + ) click.echo(click.style(msg, fg="green")) except KeycloakError as ke: - print(keycloak_error_message(ke)) - click.exit(1) + msg = keycloak_error_message(ke) + click.echo(click.style(msg, fg="red")) + ctx.exit(1) # Write the new token into the file. web_api_config["auth-token"] = token # TODO use ruamel.yaml to preserve comments in config file config_path.parent.mkdir(parents=True, exist_ok=True) config_path.write_text(yaml.safe_dump(context["config"])) - msg = "\nConfiguration file '%s' written successfully." + msg = "\nConfiguration file '%s' written successfully" msg %= click.format_filename(bytes(config_path)) click.echo(click.style(msg, fg="green")) - click.echo("`swh scanner` will now be authenticated with the new token.") + msg = "`swh scanner` will now be authenticated with the new token" + click.echo(click.style(msg, fg="green")) @scanner.command(name="scan") diff --git a/swh/scanner/dashboard/dashboard.py b/swh/scanner/dashboard/dashboard.py --- a/swh/scanner/dashboard/dashboard.py +++ b/swh/scanner/dashboard/dashboard.py @@ -6,10 +6,9 @@ from pathlib import Path import dash +from dash import dcc, html from dash.dependencies import Input, Output import dash_bootstrap_components as dbc -import dash_core_components as dcc -import dash_html_components as html import plotly.graph_objects as go from swh.model.from_disk import Directory diff --git a/swh/scanner/tests/test_dashboard.py b/swh/scanner/tests/test_dashboard.py --- a/swh/scanner/tests/test_dashboard.py +++ b/swh/scanner/tests/test_dashboard.py @@ -3,7 +3,7 @@ # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information -import dash_html_components as html +from dash import html from swh.model.swhids import CoreSWHID, ObjectType from swh.scanner.dashboard.dashboard import generate_table_body