From 13b215a42e425894f00eb66c0878cf34cabedf7e Mon Sep 17 00:00:00 2001 From: "C. Weaver" Date: Mon, 22 Mar 2021 15:44:42 -0400 Subject: [PATCH 1/3] Implement credential nicknames. These are purely labels for users' convenience. Nicknames are editable internally, but this capacity is not yet exposed to users. The nickname is now included in the downloadable credential file, but will currently be ignored by the client. --- .../0006_scramcredentials_nickname.py | 19 ++++++++++++ scimma_admin/hopskotch_auth/models.py | 16 +++++++++- .../templates/hopskotch_auth/create.html | 2 ++ .../hopskotch_auth/edit_credential.html | 2 +- .../templates/hopskotch_auth/index.html | 12 +++++++- scimma_admin/hopskotch_auth/views.py | 30 ++++++++++++++++--- 6 files changed, 74 insertions(+), 7 deletions(-) create mode 100644 scimma_admin/hopskotch_auth/migrations/0006_scramcredentials_nickname.py diff --git a/scimma_admin/hopskotch_auth/migrations/0006_scramcredentials_nickname.py b/scimma_admin/hopskotch_auth/migrations/0006_scramcredentials_nickname.py new file mode 100644 index 0000000..27db853 --- /dev/null +++ b/scimma_admin/hopskotch_auth/migrations/0006_scramcredentials_nickname.py @@ -0,0 +1,19 @@ +# Generated by Django 3.0.7 on 2021-03-22 19:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hopskotch_auth', '0005_add_authz'), + ] + + operations = [ + migrations.AddField( + model_name='scramcredentials', + name='nickname', + field=models.CharField(default='', max_length=64), + preserve_default=False, + ), + ] diff --git a/scimma_admin/hopskotch_auth/models.py b/scimma_admin/hopskotch_auth/models.py index 68414e2..369af18 100644 --- a/scimma_admin/hopskotch_auth/models.py +++ b/scimma_admin/hopskotch_auth/models.py @@ -87,6 +87,12 @@ class SCRAMCredentials(models.Model): default = False, ) + # a user-chosen nickname, which need not be globally unique and may be changed + nickname = models.CharField( + max_length = 64, + editable = True, + ) + @classmethod def generate(cls, owner: User, username: str, password: str, alg: SCRAMAlgorithm, salt: Optional[bytes] = None, iterations: int = 4096): @@ -168,7 +174,7 @@ def delete_credentials(user, cred_username): creds.delete() -def new_credentials(owner): +def new_credentials(owner, nickname: str = ""): username = rand_username(owner) alphabet = string.ascii_letters + string.digits @@ -181,6 +187,7 @@ def new_credentials(owner): alg=SCRAMAlgorithm.SHA512, salt=rand_salt, ) + creds.nickname=nickname if (len(nickname) > 0) else username creds.save() bundle = CredentialGenerationBundle( creds=creds, @@ -190,6 +197,13 @@ def new_credentials(owner): return bundle +def check_credential_nickname(owner: User, nickname: str) -> bool: + """ Return whether the specified credential nickname is free for use (not already in use) by + the given user. + """ + return not SCRAMCredentials.objects.filter(owner=owner, nickname=nickname).exists() + + @dataclass class CredentialGenerationBundle: """ The collection of data generated ephemerally for new user credentials. """ diff --git a/scimma_admin/hopskotch_auth/templates/hopskotch_auth/create.html b/scimma_admin/hopskotch_auth/templates/hopskotch_auth/create.html index f286e98..faa6f46 100644 --- a/scimma_admin/hopskotch_auth/templates/hopskotch_auth/create.html +++ b/scimma_admin/hopskotch_auth/templates/hopskotch_auth/create.html @@ -22,11 +22,13 @@

New Credentials Generated

This is the only time the password will be revealed.

Username:

{{ username }}

Password:

{{ password }}
+

Nickname:

{{ nickname }}
{% csrf_token %} +

These credentials will be usable within 10 seconds.

diff --git a/scimma_admin/hopskotch_auth/templates/hopskotch_auth/edit_credential.html b/scimma_admin/hopskotch_auth/templates/hopskotch_auth/edit_credential.html index d589994..bae6942 100644 --- a/scimma_admin/hopskotch_auth/templates/hopskotch_auth/edit_credential.html +++ b/scimma_admin/hopskotch_auth/templates/hopskotch_auth/edit_credential.html @@ -4,7 +4,7 @@ {% block page-header %}Credential Management{% endblock %} {% block page-body %} -

Credential {{ cred.username }}

+

Credential {{ cred.username }} ({{ cred.nickname }})

{% if cred.suspended %}
diff --git a/scimma_admin/hopskotch_auth/templates/hopskotch_auth/index.html b/scimma_admin/hopskotch_auth/templates/hopskotch_auth/index.html index 3510b54..9bb3953 100644 --- a/scimma_admin/hopskotch_auth/templates/hopskotch_auth/index.html +++ b/scimma_admin/hopskotch_auth/templates/hopskotch_auth/index.html @@ -20,6 +20,7 @@

Active credentials

Kafka Username + Nickname Created On (Pacific Time) Actions @@ -28,6 +29,7 @@

Active credentials

{% for cred in credentials %} {{ cred.username }} + {{ cred.nickname }} {{ cred.created_at|localtime }}
@@ -43,7 +45,7 @@

Active credentials

{% empty %} - No existing credentials found. + No existing credentials found. {% endfor %} @@ -56,6 +58,14 @@

Create credentials

A username and password will be generated for you.

{% csrf_token %} + + +
diff --git a/scimma_admin/hopskotch_auth/views.py b/scimma_admin/hopskotch_auth/views.py index c25532f..24087b0 100644 --- a/scimma_admin/hopskotch_auth/views.py +++ b/scimma_admin/hopskotch_auth/views.py @@ -67,12 +67,30 @@ def login_failure(request): def create(request): logger.info(f"User {request.user.username} ({request.user.email}) requested " f"to create a new credential from {request.META['REMOTE_ADDR']}") - bundle = new_credentials(request.user) + + nickname = "" + if "nickname" in request.POST: + nickname = request.POST.get("nickname") + logger.info(f"User requested credential nickname is {nickname}") + + if len(nickname) > SCRAMCredentials.nickname.field.max_length: + return redirect_with_error(request, "Create a credential", + "requested credential nickname too long", + "index") + + # check only non-empty nicknames, as empty ones will be replaced with the auto-generated + # name by new_credentials + if len(nickname) > 0 and not check_credential_nickname(request.user, nickname): + return redirect_with_error(request, "Create a credential", + "requested credential nickname is already in use", + "index") + + bundle = new_credentials(request.user, nickname) logger.info(f"Created new credential {bundle.username} on behalf of user " f"{request.user.username} ({request.user.email})") return render( request, 'hopskotch_auth/create.html', - dict(username=bundle.username, password=bundle.password), + dict(username=bundle.username, password=bundle.password, nickname=bundle.creds.nickname), ) @@ -106,8 +124,12 @@ def delete(request): @login_required def download(request): myfile = StringIO() - myfile.write("username,password\n") - myfile.write(f"{request.POST['username']},{request.POST['password']}") + myfile.write("username,password,kafkahost,nickname\n") + myfile.write(f"{request.POST['username']},{request.POST['password']}") + if('nickname' in request.POST): + myfile.write(f",{request.POST['nickname']}") + else: + myfile.write(",") myfile.flush() myfile.seek(0) # move the pointer to the beginning of the buffer response = HttpResponse(FileWrapper(myfile), content_type='text/plain') From 40f4d3fefce2c3599af5791c4825843129b3e4b5 Mon Sep 17 00:00:00 2001 From: "C. Weaver" Date: Mon, 22 Mar 2021 16:00:27 -0400 Subject: [PATCH 2/3] Remove accidental fragment of another feature --- scimma_admin/hopskotch_auth/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scimma_admin/hopskotch_auth/views.py b/scimma_admin/hopskotch_auth/views.py index 24087b0..b27e47f 100644 --- a/scimma_admin/hopskotch_auth/views.py +++ b/scimma_admin/hopskotch_auth/views.py @@ -124,7 +124,7 @@ def delete(request): @login_required def download(request): myfile = StringIO() - myfile.write("username,password,kafkahost,nickname\n") + myfile.write("username,password,nickname\n") myfile.write(f"{request.POST['username']},{request.POST['password']}") if('nickname' in request.POST): myfile.write(f",{request.POST['nickname']}") From adb73f4fb5e59b0aff0e6d8394a8af62b480d8fd Mon Sep 17 00:00:00 2001 From: "C. Weaver" Date: Mon, 22 Mar 2021 16:04:12 -0400 Subject: [PATCH 3/3] Improve credential page header --- .../templates/hopskotch_auth/edit_credential.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scimma_admin/hopskotch_auth/templates/hopskotch_auth/edit_credential.html b/scimma_admin/hopskotch_auth/templates/hopskotch_auth/edit_credential.html index bae6942..ebda194 100644 --- a/scimma_admin/hopskotch_auth/templates/hopskotch_auth/edit_credential.html +++ b/scimma_admin/hopskotch_auth/templates/hopskotch_auth/edit_credential.html @@ -4,7 +4,8 @@ {% block page-header %}Credential Management{% endblock %} {% block page-body %} -

Credential {{ cred.username }} ({{ cred.nickname }})

+

Credential {{ cred.username }} + {% if cred.nickname != cred.username %} ({{ cred.nickname }}){% endif %}

{% if cred.suspended %}