From 942c02cd9a056de2bc79a5badad91f7b97ad2800 Mon Sep 17 00:00:00 2001 From: kaiyou <pierre@jaury.eu> Date: Tue, 31 Mar 2020 15:02:40 +0200 Subject: [PATCH] Improve the profile and service cli --- hiboo/profile/cli.py | 93 ++++++++++++++++++++++++++++++++------- hiboo/service/__init__.py | 2 +- hiboo/service/cli.py | 16 +++++++ requirements-prod.txt | 1 + requirements.txt | 1 + 5 files changed, 97 insertions(+), 16 deletions(-) create mode 100644 hiboo/service/cli.py diff --git a/hiboo/profile/cli.py b/hiboo/profile/cli.py index a23e051a..318595cb 100644 --- a/hiboo/profile/cli.py +++ b/hiboo/profile/cli.py @@ -2,28 +2,91 @@ from hiboo.profile import blueprint from hiboo import models import click +import terminaltables +import csv + + +@blueprint.cli.command() +@click.argument("service_uuid") +@click.option("-s", "--status") +@click.option("-u", "--username") +def list(service_uuid, status=None, username=None, ): + """ Select and list profiles. + """ + criteria = [models.Profile.service_uuid == service_uuid] + if status is not None: + criteria.append(models.Profile.status == status) + if username is not None: + criteria.append(models.Profile.username.ilike('%{}%'.format(username))) + click.echo(terminaltables.SingleTable([('uuid', 'username', 'status', 'claim_names')]+ + [ + [profile.uuid, profile.username, profile.status, ','.join([item.username for item in profile.claimnames])] + for profile in models.Profile.query.filter(*criteria).all() + ], title='Profiles').table) @blueprint.cli.command() @click.argument("service_uuid") @click.argument("username") -@click.argument("password_hash") @click.argument("claim_names", nargs=-1) -def unclaimed(service_uuid, username, password_hash, claim_names): +@click.option("-p", "--password-hash", "password_hash") +def unclaimed(service_uuid, username, claim_names, password_hash=None): + """ This creates or updates an unclaimed profile. + """ assert models.Service.query.get(service_uuid) - assert not models.Profile.query.filter_by(service_uuid=service_uuid, username=username).first() - profile = models.Profile( - service_uuid=service_uuid, - username=username, - status=models.Profile.UNCLAIMED, - extra={"password": password_hash} - ) - models.db.session.add(profile) - for username in claim_names: - claim_name = models.ClaimName( + profile = models.Profile.query.filter_by(service_uuid=service_uuid, username=username).first() + # Do nothing if the profile is already claimed + if profile and not profile.status == models.Profile.UNCLAIMED: + click.echo("Profile {} was claimed already".format(username)) + return + # Create the profile if necessary + if profile: + click.echo("Profile {} exists already".format(username)) + else: + profile = models.Profile( service_uuid=service_uuid, - profile=profile, - username=username + username=username, + status=models.Profile.UNCLAIMED, ) - models.db.session.add(claim_name) + click.echo("Profile {} was created".format(username)) + models.db.session.add(profile) + # Update the profile password if one was provided + if password_hash is not None: + profile.extra={"password": password_hash} + click.echo("Password updated for profile {}".format(username)) + # Update profile claim names if any is provided (never delete an existing claim name) + for username in claim_names: + claim_name = models.ClaimName.query.filter_by(service_uuid=service_uuid, username=username).first() + if claim_name and not claim_name.profile == profile: + click.echo("Claim name conflict for {}, already belongs to {}".format(username, claim_name.profile.username)) + elif claim_name: + click.echo("Claim {} already exists for {}".format(username, profile.username)) + else: + claim_name = models.ClaimName( + service_uuid=service_uuid, + profile=profile, + username=username + ) + click.echo("Claim created for {}, belongs to {}".format(username, profile.username)) + models.db.session.add(claim_name) models.db.session.commit() + + +@blueprint.cli.command() +@click.argument("service_uuid") +@click.argument("filename") +def csv_unclaimed(service_uuid, filename="/dev/stdin"): + """ Import unclaimed profiles from a CSV. + + The first two columns must be the username and password, additional + columns will be treated as additional claim names. + + Duplicates will be taken care of and updated, unless the profile has alredy + been claimed. + """ + assert models.Service.query.get(service_uuid) + with open(filename, "r") as csvfile: + reader = csv.reader(csvfile, delimiter=',', quotechar='"') + for row in reader: + unclaimed.callback(service_uuid, row[0], row[2:], row[1]) + diff --git a/hiboo/service/__init__.py b/hiboo/service/__init__.py index 6332441f..92e48805 100644 --- a/hiboo/service/__init__.py +++ b/hiboo/service/__init__.py @@ -3,4 +3,4 @@ import flask blueprint = flask.Blueprint("service", __name__, template_folder="templates") -from hiboo.service import admin +from hiboo.service import admin, cli diff --git a/hiboo/service/cli.py b/hiboo/service/cli.py new file mode 100644 index 00000000..45d9b2b0 --- /dev/null +++ b/hiboo/service/cli.py @@ -0,0 +1,16 @@ +from hiboo.service import blueprint +from hiboo import models + +import click +import terminaltables + + +@blueprint.cli.command() +def list(): + """ List all services. + """ + click.echo(terminaltables.SingleTable([('uuid', 'name', 'application')]+ + [ + [service.uuid, service.name, service.application_id] + for service in models.Service.query.all() + ], title='Services').table) \ No newline at end of file diff --git a/requirements-prod.txt b/requirements-prod.txt index 3a499fe5..d546aad5 100644 --- a/requirements-prod.txt +++ b/requirements-prod.txt @@ -48,6 +48,7 @@ redis==3.3.11 requests==2.22.0 six==1.13.0 SQLAlchemy==1.3.11 +terminaltables==3.1.0 urllib3==1.25.7 validators==0.14.0 Werkzeug==0.16.0 diff --git a/requirements.txt b/requirements.txt index a2e40bd2..a7a855b2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,3 +21,4 @@ mysqlclient psycopg2 jwcrypto argon2_cffi +terminaltables -- GitLab