You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by ma...@apache.org on 2019/05/10 15:18:48 UTC

[airavata-django-portal] branch master updated (cddab50 -> c5ce801)

This is an automated email from the ASF dual-hosted git repository.

machristie pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git.


    from cddab50  Merge branch 'airavata-3030'
     new 6603754  AIRAVATA-2925 Forgot/reset password forms
     new cd9c637  AIRAVATA-2925 Handle unverified account in forgot password
     new c7ebe40  AIRAVATA-2925 Adding forgot password link to login form
     new c5ce801  AIRAVATA-2925 Factored out common form code

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 django_airavata/apps/auth/forms.py                 |  43 +++++++
 django_airavata/apps/auth/iam_admin_client.py      |   6 +
 .../auth/migrations/0004_password_reset_request.py |  64 ++++++++++
 django_airavata/apps/auth/models.py                |   9 ++
 .../django_airavata_auth/create_account.html       |  52 +-------
 .../django_airavata_auth/forgot_password.html      |  24 ++++
 .../django_airavata_auth/partials/form.html        |  13 ++
 .../django_airavata_auth/partials/form_field.html  |  25 ++++
 .../partials/non_field_errors.html                 |   6 +
 .../partials/username_password_login_form.html     |   1 +
 .../django_airavata_auth/reset_password.html       |  23 ++++
 .../django_airavata_auth/verify_email.html         |  44 +------
 django_airavata/apps/auth/urls.py                  |   3 +
 django_airavata/apps/auth/views.py                 | 140 +++++++++++++++++++--
 14 files changed, 357 insertions(+), 96 deletions(-)
 create mode 100644 django_airavata/apps/auth/migrations/0004_password_reset_request.py
 create mode 100644 django_airavata/apps/auth/templates/django_airavata_auth/forgot_password.html
 create mode 100644 django_airavata/apps/auth/templates/django_airavata_auth/partials/form.html
 create mode 100644 django_airavata/apps/auth/templates/django_airavata_auth/partials/form_field.html
 create mode 100644 django_airavata/apps/auth/templates/django_airavata_auth/partials/non_field_errors.html
 create mode 100644 django_airavata/apps/auth/templates/django_airavata_auth/reset_password.html


[airavata-django-portal] 01/04: AIRAVATA-2925 Forgot/reset password forms

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 660375444ff69c6c1e79cb7c5e911c47c3a357bf
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Fri May 10 10:19:48 2019 -0400

    AIRAVATA-2925 Forgot/reset password forms
---
 django_airavata/apps/auth/forms.py                 |  43 +++++++
 django_airavata/apps/auth/iam_admin_client.py      |   6 +
 .../auth/migrations/0004_password_reset_request.py |  64 ++++++++++
 django_airavata/apps/auth/models.py                |   9 ++
 .../django_airavata_auth/forgot_password.html      |  68 +++++++++++
 .../django_airavata_auth/reset_password.html       |  66 +++++++++++
 django_airavata/apps/auth/urls.py                  |   3 +
 django_airavata/apps/auth/views.py                 | 130 +++++++++++++++++++--
 8 files changed, 381 insertions(+), 8 deletions(-)

diff --git a/django_airavata/apps/auth/forms.py b/django_airavata/apps/auth/forms.py
index 2d54d5d..f78dd84 100644
--- a/django_airavata/apps/auth/forms.py
+++ b/django_airavata/apps/auth/forms.py
@@ -99,3 +99,46 @@ class ResendEmailVerificationLinkForm(forms.Form):
                                       'placeholder': 'Username'}),
         min_length=6,
         validators=[USERNAME_VALIDATOR])
+
+
+class ForgotPasswordForm(forms.Form):
+    error_css_class = "is-invalid"
+    username = forms.CharField(
+        label='Username',
+        widget=forms.TextInput(attrs={'class': 'form-control',
+                                      'placeholder': 'Username'}),
+        min_length=6,
+        validators=[USERNAME_VALIDATOR],
+        help_text=USERNAME_VALIDATOR.message)
+
+
+class ResetPasswordForm(forms.Form):
+    error_css_class = "is-invalid"
+
+    password = forms.CharField(
+        label='Password',
+        widget=forms.PasswordInput(attrs={'class': 'form-control',
+                                          'placeholder': 'Password'}),
+        min_length=8,
+        max_length=48,
+        validators=[PASSWORD_VALIDATOR],
+        help_text=PASSWORD_VALIDATOR.message)
+    password_again = forms.CharField(
+        label='Password (again)',
+        widget=forms.PasswordInput(attrs={'class': 'form-control',
+                                          'placeholder': 'Password (again)'}))
+
+    def clean(self):
+        cleaned_data = super().clean()
+        password = cleaned_data.get('password')
+        password_again = cleaned_data.get('password_again')
+
+        if password and password_again and password != password_again:
+            self.add_error(
+                'password',
+                forms.ValidationError("Passwords do not match"))
+            self.add_error(
+                'password_again',
+                forms.ValidationError("Passwords do not match"))
+
+        return cleaned_data
diff --git a/django_airavata/apps/auth/iam_admin_client.py b/django_airavata/apps/auth/iam_admin_client.py
index 7783ce7..6dc2ceb 100644
--- a/django_airavata/apps/auth/iam_admin_client.py
+++ b/django_airavata/apps/auth/iam_admin_client.py
@@ -45,3 +45,9 @@ def is_user_exist(username):
 def get_user(username):
     authz_token = utils.get_service_account_authz_token()
     return iamadmin_client_pool.getUser(authz_token, username)
+
+
+def reset_user_password(username, new_password):
+    authz_token = utils.get_service_account_authz_token()
+    return iamadmin_client_pool.resetUserPassword(
+        authz_token, username, new_password)
diff --git a/django_airavata/apps/auth/migrations/0004_password_reset_request.py b/django_airavata/apps/auth/migrations/0004_password_reset_request.py
new file mode 100644
index 0000000..e8fed01
--- /dev/null
+++ b/django_airavata/apps/auth/migrations/0004_password_reset_request.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.20 on 2019-05-07 15:49
+from __future__ import unicode_literals
+
+import uuid
+
+from django.db import migrations, models
+
+from django_airavata.apps.auth.models import PASSWORD_RESET_EMAIL_TEMPLATE
+
+
+def default_templates(apps, schema_editor):
+
+    EmailTemplate = apps.get_model("django_airavata_auth", "EmailTemplate")
+    verify_email_template = EmailTemplate(
+        template_type=PASSWORD_RESET_EMAIL_TEMPLATE,
+        subject="{{first_name}} {{last_name}} ({{username}}), "
+                "Reset your password in {{portal_title}}",
+        body="""
+        <p>
+        Dear {{first_name}} {{last_name}},
+        </p>
+
+        <p>
+        Please click the link below to reset your password. This link is
+        valid for 24 hours.
+        </p>
+
+        <p><a href="{{url}}">{{url}}</a></p>
+
+        <p>If you didn't request to reset your password, just ignore this message.</p>
+        """.strip())
+    verify_email_template.save()
+
+
+def delete_default_templates(apps, schema_editor):
+    EmailTemplate = apps.get_model("django_airavata_auth", "EmailTemplate")
+    EmailTemplate.objects.filter(
+        template_type=PASSWORD_RESET_EMAIL_TEMPLATE).delete()
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('django_airavata_auth', '0003_default_email_templates'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='PasswordResetRequest',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('username', models.CharField(max_length=64)),
+                ('reset_code', models.CharField(default=uuid.uuid4, max_length=36, unique=True)),
+                ('created_date', models.DateTimeField(auto_now_add=True)),
+            ],
+        ),
+        migrations.AlterField(
+            model_name='emailtemplate',
+            name='template_type',
+            field=models.IntegerField(choices=[(1, 'Verify Email Template'), (2, 'New User Email Template'), (3, 'Password Reset Email Template')], primary_key=True, serialize=False),
+        ),
+        migrations.RunPython(default_templates, reverse_code=delete_default_templates)
+    ]
diff --git a/django_airavata/apps/auth/models.py b/django_airavata/apps/auth/models.py
index e169759..5e4d109 100644
--- a/django_airavata/apps/auth/models.py
+++ b/django_airavata/apps/auth/models.py
@@ -4,6 +4,7 @@ from django.db import models
 
 VERIFY_EMAIL_TEMPLATE = 1
 NEW_USER_EMAIL_TEMPLATE = 2
+PASSWORD_RESET_EMAIL_TEMPLATE = 3
 
 
 class EmailVerification(models.Model):
@@ -18,6 +19,7 @@ class EmailTemplate(models.Model):
     TEMPLATE_TYPE_CHOICES = (
         (VERIFY_EMAIL_TEMPLATE, 'Verify Email Template'),
         (NEW_USER_EMAIL_TEMPLATE, 'New User Email Template'),
+        (PASSWORD_RESET_EMAIL_TEMPLATE, 'Password Reset Email Template'),
     )
     template_type = models.IntegerField(
         primary_key=True, choices=TEMPLATE_TYPE_CHOICES)
@@ -31,3 +33,10 @@ class EmailTemplate(models.Model):
             if self.template_type == choice[0]:
                 return choice[1]
         return "Unknown"
+
+
+class PasswordResetRequest(models.Model):
+    username = models.CharField(max_length=64)
+    reset_code = models.CharField(
+        max_length=36, unique=True, default=uuid.uuid4)
+    created_date = models.DateTimeField(auto_now_add=True)
diff --git a/django_airavata/apps/auth/templates/django_airavata_auth/forgot_password.html b/django_airavata/apps/auth/templates/django_airavata_auth/forgot_password.html
new file mode 100644
index 0000000..573c13d
--- /dev/null
+++ b/django_airavata/apps/auth/templates/django_airavata_auth/forgot_password.html
@@ -0,0 +1,68 @@
+{% extends 'base.html' %}
+
+{% block content %}
+
+<main class="main-content">
+<div class="container">
+
+  <div class="row">
+    <div class="col">
+      <div class="card">
+        <div class="card-body">
+          <h5 class="card-title">Forgot Password?</h5>
+          <p class="card-text">If you forgot your password you can reset it. Just enter the username for your account and click Email Reset Link.</p>
+          {% if messages %}
+            {% for message in messages %}
+            <div class="alert {% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}alert-danger{% elif message.level == DEFAULT_MESSAGE_LEVELS.SUCCESS %}alert-success{% else %}alert-secondary{% endif %}" role="alert">
+              {{ message }}
+            </div>
+            {% endfor %}
+          {% endif %}
+          <form action="{% url 'django_airavata_auth:forgot_password' %}" method="post">
+            {% for error in form.non_field_errors %}
+            <div class="alert alert-danger" role="alert">
+              {{ error }}
+            </div>
+            {% endfor %}
+            {% csrf_token %}
+
+            {% for field in form %}
+            <div class="form-group">
+              <label for="{{ field.id_for_label }}">{{ field.label }}</label>
+              <input id="{{ field.id_for_label }}" type="{{ field.field.widget.input_type }}"
+                class="form-control{% if field.errors %} is-invalid{% endif %}" name="{{ field.name }}"
+                placeholder="{{ field.field.widget.attrs.placeholder }}"
+                aria-describedby="{{ field.id_for_label }}-help"
+                {% if field.value %} value="{{ field.value }}" {% endif %}
+                {% if field.field.required %} required {% endif %} />
+              {% if field.help_text %}
+              <small id="{{ field.id_for_label }}-help" class="form-text text-muted">
+                {{ field.help_text | escape }}
+              </small>
+              {% endif %}
+              <div class="invalid-feedback">
+                {% if field.errors|length == 1 %}
+                  {{ field.errors|first| escape }}
+                {% else %}
+                  <ul>
+                    {% for error in field.errors %}
+                    <li>{{ error | escape }}</li>
+                    {% endfor %}
+                  </ul>
+                {% endif %}
+              </div>
+            </div>
+            {% endfor %}
+
+            <button type="submit" class="btn btn-primary btn-block">
+              Email Reset Link
+            </button>
+          </form>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+</main>
+
+{% endblock %}
diff --git a/django_airavata/apps/auth/templates/django_airavata_auth/reset_password.html b/django_airavata/apps/auth/templates/django_airavata_auth/reset_password.html
new file mode 100644
index 0000000..0d367f8
--- /dev/null
+++ b/django_airavata/apps/auth/templates/django_airavata_auth/reset_password.html
@@ -0,0 +1,66 @@
+{% extends 'base.html' %}
+
+{% block content %}
+
+<main class="main-content">
+<div class="container">
+
+  <div class="row">
+    <div class="col">
+      <div class="card">
+        <div class="card-body">
+          <h5 class="card-title">Reset Password</h5>
+          {% if messages %}
+            {% for message in messages %}
+            <div class="alert {% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}alert-danger{% elif message.level == DEFAULT_MESSAGE_LEVELS.SUCCESS %}alert-success{% else %}alert-secondary{% endif %}" role="alert">
+              {{ message }}
+            </div>
+            {% endfor %}
+          {% endif %}
+          <form action="{% url 'django_airavata_auth:reset_password' code %}" method="post">
+            {% for error in form.non_field_errors %}
+            <div class="alert alert-danger" role="alert">
+              {{ error }}
+            </div>
+            {% endfor %}
+            {% csrf_token %}
+            {% for field in form %}
+            <div class="form-group">
+              <label for="{{ field.id_for_label }}">{{ field.label }}</label>
+              <input id="{{ field.id_for_label }}" type="{{ field.field.widget.input_type }}"
+                class="form-control{% if field.errors %} is-invalid{% endif %}" name="{{ field.name }}"
+                placeholder="{{ field.field.widget.attrs.placeholder }}"
+                aria-describedby="{{ field.id_for_label }}-help"
+                {% if field.value %} value="{{ field.value }}" {% endif %}
+                {% if field.field.required %} required {% endif %} />
+              {% if field.help_text %}
+              <small id="{{ field.id_for_label }}-help" class="form-text text-muted">
+                {{ field.help_text | escape }}
+              </small>
+              {% endif %}
+              <div class="invalid-feedback">
+                {% if field.errors|length == 1 %}
+                  {{ field.errors|first| escape }}
+                {% else %}
+                  <ul>
+                    {% for error in field.errors %}
+                    <li>{{ error | escape }}</li>
+                    {% endfor %}
+                  </ul>
+                {% endif %}
+              </div>
+            </div>
+            {% endfor %}
+
+            <button type="submit" class="btn btn-primary btn-block">
+              Reset
+            </button>
+          </form>
+        </div>
+      </div>
+    </div>
+  </div>
+</div>
+</main>
+
+{% endblock %}
diff --git a/django_airavata/apps/auth/urls.py b/django_airavata/apps/auth/urls.py
index 4905665..658df10 100644
--- a/django_airavata/apps/auth/urls.py
+++ b/django_airavata/apps/auth/urls.py
@@ -20,4 +20,7 @@ urlpatterns = [
         name="verify_email"),
     url(r'^resend-email-link/', views.resend_email_link,
         name="resend_email_link"),
+    url(r'^forgot-password/$', views.forgot_password, name="forgot_password"),
+    url(r'^reset-password/(?P<code>[\w-]+)/$', views.reset_password,
+        name="reset_password"),
 ]
diff --git a/django_airavata/apps/auth/views.py b/django_airavata/apps/auth/views.py
index e8e90c9..72ddf9a 100644
--- a/django_airavata/apps/auth/views.py
+++ b/django_airavata/apps/auth/views.py
@@ -1,4 +1,5 @@
 import logging
+from datetime import datetime, timedelta, timezone
 from urllib.parse import quote
 
 from django.conf import settings
@@ -297,8 +298,6 @@ def _create_and_send_email_verification_link(
     logger.debug(
         "verification_uri={}".format(verification_uri))
 
-    verify_email_template = models.EmailTemplate.objects.get(
-        pk=models.VERIFY_EMAIL_TEMPLATE)
     context = Context({
         "username": username,
         "email": email,
@@ -307,11 +306,126 @@ def _create_and_send_email_verification_link(
         "portal_title": settings.PORTAL_TITLE,
         "url": verification_uri,
     })
-    subject = Template(verify_email_template.subject).render(context)
-    body = Template(verify_email_template.body).render(context)
-    msg = EmailMessage(subject=subject, body=body,
-                       from_email="{} <{}>".format(
-                           settings.PORTAL_TITLE, settings.SERVER_EMAIL),
-                       to=["{} {} <{}>".format(first_name, last_name, email)])
+    _send_email_to_user(models.VERIFY_EMAIL_TEMPLATE, context)
+
+
+def forgot_password(request):
+    if request.method == 'POST':
+        form = forms.ForgotPasswordForm(request.POST)
+        if form.is_valid():
+            try:
+                username = form.cleaned_data['username']
+                user_exists = iam_admin_client.is_user_exist(username)
+                if user_exists:
+                    _create_and_send_password_reset_request_link(
+                        request, username)
+                # Always display this message even if you doesn't exist. Don't
+                # reveal whether a user with that username exists.
+                messages.success(
+                    request,
+                    "Reset password request processed successfully. We've "
+                    "sent an email with a password reset link to the email "
+                    "address associated with the username you provided. You "
+                    "can use that link within the next 24 hours to set a new "
+                    "password.")
+                return redirect(
+                    reverse('django_airavata_auth:forgot_password'))
+            except Exception as e:
+                logger.exception(
+                    "Failed to generate password reset request for user",
+                    exc_info=e)
+                form.add_error(None, ValidationError(str(e)))
+    else:
+        form = forms.ForgotPasswordForm()
+    return render(request, 'django_airavata_auth/forgot_password.html', {
+        'form': form
+    })
+
+
+def _create_and_send_password_reset_request_link(request, username):
+    password_reset_request = models.PasswordResetRequest(username=username)
+    password_reset_request.save()
+
+    verification_uri = request.build_absolute_uri(
+        reverse(
+            'django_airavata_auth:reset_password', kwargs={
+                'code': password_reset_request.reset_code}))
+    logger.debug(
+        "password reset verification_uri={}".format(verification_uri))
+
+    user = iam_admin_client.get_user(username)
+    context = Context({
+        "username": username,
+        "email": user.emails[0],
+        "first_name": user.firstName,
+        "last_name": user.lastName,
+        "portal_title": settings.PORTAL_TITLE,
+        "url": verification_uri,
+    })
+    _send_email_to_user(models.PASSWORD_RESET_EMAIL_TEMPLATE, context)
+
+
+def reset_password(request, code):
+    try:
+        password_reset_request = models.PasswordResetRequest.objects.get(
+            reset_code=code)
+    except ObjectDoesNotExist as e:
+        messages.error(
+            request,
+            "Reset password link is invalid. Please try again.")
+        return redirect(reverse('django_airavata_auth:forgot_password'))
+
+    now = datetime.now(timezone.utc)
+    if now - password_reset_request.created_date > timedelta(days=1):
+        password_reset_request.delete()
+        messages.error(
+            request,
+            "Reset password link has expired. Please try again.")
+        return redirect(reverse('django_airavata_auth:forgot_password'))
+
+    if request.method == "POST":
+        form = forms.ResetPasswordForm(request.POST)
+        if form.is_valid():
+            try:
+                password = form.cleaned_data['password']
+                success = iam_admin_client.reset_user_password(
+                    password_reset_request.username, password)
+                if not success:
+                    messages.error(
+                        request, "Failed to reset password. Please try again.")
+                    return redirect(
+                        reverse('django_airavata_auth:forgot_password'))
+                else:
+                    password_reset_request.delete()
+                    messages.success(
+                        request,
+                        "You may now log in with your new password.")
+                    return redirect(
+                        reverse('django_airavata_auth:login_with_password'))
+            except Exception as e:
+                logger.exception(
+                    "Failed to reset password for user", exc_info=e)
+                form.add_error(None, ValidationError(str(e)))
+    else:
+        form = forms.ResetPasswordForm()
+    return render(request, 'django_airavata_auth/reset_password.html', {
+        'form': form,
+        'code': code
+    })
+
+
+def _send_email_to_user(template_id, context):
+    email_template = models.EmailTemplate.objects.get(
+        pk=template_id)
+    subject = Template(email_template.subject).render(context)
+    body = Template(email_template.body).render(context)
+    msg = EmailMessage(
+        subject=subject,
+        body=body,
+        from_email="{} <{}>".format(settings.PORTAL_TITLE,
+                                    settings.SERVER_EMAIL),
+        to=["{} {} <{}>".format(context['first_name'],
+                                context['last_name'],
+                                context['email'])])
     msg.content_subtype = 'html'
     msg.send()


[airavata-django-portal] 04/04: AIRAVATA-2925 Factored out common form code

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit c5ce80142af305dd05b35afdd68c69cc1651747b
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Fri May 10 11:03:23 2019 -0400

    AIRAVATA-2925 Factored out common form code
---
 .../django_airavata_auth/create_account.html       | 52 +++-------------------
 .../django_airavata_auth/forgot_password.html      | 50 ++-------------------
 .../django_airavata_auth/partials/form.html        | 13 ++++++
 .../django_airavata_auth/partials/form_field.html  | 25 +++++++++++
 .../partials/non_field_errors.html                 |  6 +++
 .../django_airavata_auth/reset_password.html       | 49 ++------------------
 .../django_airavata_auth/verify_email.html         | 44 ++----------------
 7 files changed, 58 insertions(+), 181 deletions(-)

diff --git a/django_airavata/apps/auth/templates/django_airavata_auth/create_account.html b/django_airavata/apps/auth/templates/django_airavata_auth/create_account.html
index ba415ee..e7bdf2c 100644
--- a/django_airavata/apps/auth/templates/django_airavata_auth/create_account.html
+++ b/django_airavata/apps/auth/templates/django_airavata_auth/create_account.html
@@ -2,6 +2,7 @@
 
 {% block content %}
 
+<div class="main-content-wrapper">
 <main class="main-content">
 <div class="container">
   {% if options.external %}
@@ -32,53 +33,9 @@
           {% else %}
           <h5 class="card-title">Create a {{ options.password.name }} account</h5>
           {% endif %}
-          {% if messages %}
-            {% for message in messages %}
-            <div class="alert {% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}alert-danger{% elif message.level == DEFAULT_MESSAGE_LEVELS.SUCCESS %}alert-success{% else %}alert-secondary{% endif %}" role="alert">
-              {{ message }}
-            </div>
-            {% endfor %}
-          {% endif %}
-          <form action="{% url 'django_airavata_auth:create_account' %}" method="post">
-            {% for error in form.non_field_errors %}
-            <div class="alert alert-danger" role="alert">
-              {{ error }}
-            </div>
-            {% endfor %}
-            {% csrf_token %}
-
-            {% for field in form %}
-            <div class="form-group">
-              <label for="{{ field.id_for_label }}">{{ field.label }}</label>
-              <input id="{{ field.id_for_label }}" type="{{ field.field.widget.input_type }}"
-                class="form-control{% if field.errors %} is-invalid{% endif %}" name="{{ field.name }}"
-                placeholder="{{ field.field.widget.attrs.placeholder }}"
-                aria-describedby="{{ field.id_for_label }}-help"
-                {% if field.value %} value="{{ field.value }}" {% endif %}
-                {% if field.field.required %} required {% endif %} />
-              {% if field.help_text %}
-              <small id="{{ field.id_for_label }}-help" class="form-text text-muted">
-                {{ field.help_text | escape }}
-              </small>
-              {% endif %}
-              <div class="invalid-feedback">
-                {% if field.errors|length == 1 %}
-                  {{ field.errors|first| escape }}
-                {% else %}
-                  <ul>
-                    {% for error in field.errors %}
-                    <li>{{ error | escape }}</li>
-                    {% endfor %}
-                  </ul>
-                {% endif %}
-              </div>
-            </div>
-            {% endfor %}
-
-            <button type="submit" class="btn btn-primary btn-block">
-              Create
-            </button>
-          </form>
+          {% include "./partials/messages.html" %}
+          {% url 'django_airavata_auth:create_account' as form_url %}
+          {% include "./partials/form.html" with form=form url=form_url submit_text="Create" %}
         </div>
       </div>
     </div>
@@ -86,5 +43,6 @@
   {% endif %}
 </div>
 </main>
+</div>
 
 {% endblock %}
diff --git a/django_airavata/apps/auth/templates/django_airavata_auth/forgot_password.html b/django_airavata/apps/auth/templates/django_airavata_auth/forgot_password.html
index 573c13d..30524ab 100644
--- a/django_airavata/apps/auth/templates/django_airavata_auth/forgot_password.html
+++ b/django_airavata/apps/auth/templates/django_airavata_auth/forgot_password.html
@@ -11,53 +11,9 @@
         <div class="card-body">
           <h5 class="card-title">Forgot Password?</h5>
           <p class="card-text">If you forgot your password you can reset it. Just enter the username for your account and click Email Reset Link.</p>
-          {% if messages %}
-            {% for message in messages %}
-            <div class="alert {% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}alert-danger{% elif message.level == DEFAULT_MESSAGE_LEVELS.SUCCESS %}alert-success{% else %}alert-secondary{% endif %}" role="alert">
-              {{ message }}
-            </div>
-            {% endfor %}
-          {% endif %}
-          <form action="{% url 'django_airavata_auth:forgot_password' %}" method="post">
-            {% for error in form.non_field_errors %}
-            <div class="alert alert-danger" role="alert">
-              {{ error }}
-            </div>
-            {% endfor %}
-            {% csrf_token %}
-
-            {% for field in form %}
-            <div class="form-group">
-              <label for="{{ field.id_for_label }}">{{ field.label }}</label>
-              <input id="{{ field.id_for_label }}" type="{{ field.field.widget.input_type }}"
-                class="form-control{% if field.errors %} is-invalid{% endif %}" name="{{ field.name }}"
-                placeholder="{{ field.field.widget.attrs.placeholder }}"
-                aria-describedby="{{ field.id_for_label }}-help"
-                {% if field.value %} value="{{ field.value }}" {% endif %}
-                {% if field.field.required %} required {% endif %} />
-              {% if field.help_text %}
-              <small id="{{ field.id_for_label }}-help" class="form-text text-muted">
-                {{ field.help_text | escape }}
-              </small>
-              {% endif %}
-              <div class="invalid-feedback">
-                {% if field.errors|length == 1 %}
-                  {{ field.errors|first| escape }}
-                {% else %}
-                  <ul>
-                    {% for error in field.errors %}
-                    <li>{{ error | escape }}</li>
-                    {% endfor %}
-                  </ul>
-                {% endif %}
-              </div>
-            </div>
-            {% endfor %}
-
-            <button type="submit" class="btn btn-primary btn-block">
-              Email Reset Link
-            </button>
-          </form>
+          {% include "./partials/messages.html" %}
+          {% url 'django_airavata_auth:forgot_password' as form_url %}
+          {% include "./partials/form.html" with form=form url=form_url submit_text="Email Reset Link" %}
         </div>
       </div>
     </div>
diff --git a/django_airavata/apps/auth/templates/django_airavata_auth/partials/form.html b/django_airavata/apps/auth/templates/django_airavata_auth/partials/form.html
new file mode 100644
index 0000000..ce558f7
--- /dev/null
+++ b/django_airavata/apps/auth/templates/django_airavata_auth/partials/form.html
@@ -0,0 +1,13 @@
+
+<form action="{{ url }}" method="post">
+  {% include "./non_field_errors.html" %}
+  {% csrf_token %}
+
+  {% for field in form %}
+  {% include './form_field.html' %}
+  {% endfor %}
+
+  <button type="submit" class="btn btn-primary btn-block">
+    {{ submit_text }}
+  </button>
+</form>
diff --git a/django_airavata/apps/auth/templates/django_airavata_auth/partials/form_field.html b/django_airavata/apps/auth/templates/django_airavata_auth/partials/form_field.html
new file mode 100644
index 0000000..e7ab8d4
--- /dev/null
+++ b/django_airavata/apps/auth/templates/django_airavata_auth/partials/form_field.html
@@ -0,0 +1,25 @@
+<div class="form-group">
+  <label for="{{ field.id_for_label }}">{{ field.label }}</label>
+  <input id="{{ field.id_for_label }}" type="{{ field.field.widget.input_type }}"
+    class="form-control{% if field.errors %} is-invalid{% endif %}" name="{{ field.name }}"
+    placeholder="{{ field.field.widget.attrs.placeholder }}"
+    aria-describedby="{{ field.id_for_label }}-help"
+    {% if field.value %} value="{{ field.value }}" {% endif %}
+    {% if field.field.required %} required {% endif %} />
+  {% if field.help_text %}
+  <small id="{{ field.id_for_label }}-help" class="form-text text-muted">
+    {{ field.help_text | escape }}
+  </small>
+  {% endif %}
+  <div class="invalid-feedback">
+    {% if field.errors|length == 1 %}
+      {{ field.errors|first| escape }}
+    {% else %}
+      <ul>
+        {% for error in field.errors %}
+        <li>{{ error | escape }}</li>
+        {% endfor %}
+      </ul>
+    {% endif %}
+  </div>
+</div>
diff --git a/django_airavata/apps/auth/templates/django_airavata_auth/partials/non_field_errors.html b/django_airavata/apps/auth/templates/django_airavata_auth/partials/non_field_errors.html
new file mode 100644
index 0000000..0788c94
--- /dev/null
+++ b/django_airavata/apps/auth/templates/django_airavata_auth/partials/non_field_errors.html
@@ -0,0 +1,6 @@
+
+{% for error in form.non_field_errors %}
+<div class="alert alert-danger" role="alert">
+  {{ error }}
+</div>
+{% endfor %}
diff --git a/django_airavata/apps/auth/templates/django_airavata_auth/reset_password.html b/django_airavata/apps/auth/templates/django_airavata_auth/reset_password.html
index 0d367f8..4227153 100644
--- a/django_airavata/apps/auth/templates/django_airavata_auth/reset_password.html
+++ b/django_airavata/apps/auth/templates/django_airavata_auth/reset_password.html
@@ -10,52 +10,9 @@
       <div class="card">
         <div class="card-body">
           <h5 class="card-title">Reset Password</h5>
-          {% if messages %}
-            {% for message in messages %}
-            <div class="alert {% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}alert-danger{% elif message.level == DEFAULT_MESSAGE_LEVELS.SUCCESS %}alert-success{% else %}alert-secondary{% endif %}" role="alert">
-              {{ message }}
-            </div>
-            {% endfor %}
-          {% endif %}
-          <form action="{% url 'django_airavata_auth:reset_password' code %}" method="post">
-            {% for error in form.non_field_errors %}
-            <div class="alert alert-danger" role="alert">
-              {{ error }}
-            </div>
-            {% endfor %}
-            {% csrf_token %}
-            {% for field in form %}
-            <div class="form-group">
-              <label for="{{ field.id_for_label }}">{{ field.label }}</label>
-              <input id="{{ field.id_for_label }}" type="{{ field.field.widget.input_type }}"
-                class="form-control{% if field.errors %} is-invalid{% endif %}" name="{{ field.name }}"
-                placeholder="{{ field.field.widget.attrs.placeholder }}"
-                aria-describedby="{{ field.id_for_label }}-help"
-                {% if field.value %} value="{{ field.value }}" {% endif %}
-                {% if field.field.required %} required {% endif %} />
-              {% if field.help_text %}
-              <small id="{{ field.id_for_label }}-help" class="form-text text-muted">
-                {{ field.help_text | escape }}
-              </small>
-              {% endif %}
-              <div class="invalid-feedback">
-                {% if field.errors|length == 1 %}
-                  {{ field.errors|first| escape }}
-                {% else %}
-                  <ul>
-                    {% for error in field.errors %}
-                    <li>{{ error | escape }}</li>
-                    {% endfor %}
-                  </ul>
-                {% endif %}
-              </div>
-            </div>
-            {% endfor %}
-
-            <button type="submit" class="btn btn-primary btn-block">
-              Reset
-            </button>
-          </form>
+          {% include "./partials/messages.html" %}
+          {% url 'django_airavata_auth:reset_password' code as form_url %}
+          {% include "./partials/form.html" with form=form url=form_url submit_text="Reset Password" %}
         </div>
       </div>
     </div>
diff --git a/django_airavata/apps/auth/templates/django_airavata_auth/verify_email.html b/django_airavata/apps/auth/templates/django_airavata_auth/verify_email.html
index c54a2c8..537d970 100644
--- a/django_airavata/apps/auth/templates/django_airavata_auth/verify_email.html
+++ b/django_airavata/apps/auth/templates/django_airavata_auth/verify_email.html
@@ -9,47 +9,9 @@
       <div class="card">
         <div class="card-body">
           <h5 class="card-title">Resend Email Verification Link</h5>
-          {% if messages %}
-            {% for message in messages %}
-            <div class="alert {% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}alert-danger{% elif message.level == DEFAULT_MESSAGE_LEVELS.SUCCESS %}alert-success{% else %}alert-secondary{% endif %}" role="alert">
-              {{ message }}
-            </div>
-            {% endfor %}
-          {% endif %}
-          <form action="{% url 'django_airavata_auth:resend_email_link' %}" method="post">
-            {% for error in form.non_field_errors %}
-            <div class="alert alert-danger" role="alert">
-              {{ error }}
-            </div>
-            {% endfor %}
-            {% csrf_token %}
-
-            {% for field in form %}
-            <div class="form-group">
-              <label for="{{ field.id_for_label }}">{{ field.label }}</label>
-              <input id="{{ field.id_for_label }}" type="{{ field.field.widget.input_type }}"
-                class="form-control{% if field.errors %} is-invalid{% endif %}" name="{{ field.name }}"
-                placeholder="{{ field.field.widget.attrs.placeholder }}"
-                {% if field.value %} value="{{ field.value }}" {% endif %}
-                {% if field.field.required %} required {% endif %} />
-              <div class="invalid-feedback">
-                {% if field.errors|length == 1 %}
-                  {{ field.errors|first| escape }}
-                {% else %}
-                  <ul>
-                    {% for error in field.errors %}
-                    <li>{{ error | escape }}</li>
-                    {% endfor %}
-                  </ul>
-                {% endif %}
-              </div>
-            </div>
-            {% endfor %}
-
-            <button type="submit" class="btn btn-primary btn-block">
-              Resend
-            </button>
-          </form>
+          {% include "./partials/messages.html" %}
+          {% url 'django_airavata_auth:resend_email_link' code as form_url %}
+          {% include "./partials/form.html" with form=form url=form_url submit_text="Resend" %}
         </div>
       </div>
     </div>


[airavata-django-portal] 02/04: AIRAVATA-2925 Handle unverified account in forgot password

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit cd9c637bca9a32e74f29ac03b6de6fc9a0b3e7c3
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Fri May 10 10:32:02 2019 -0400

    AIRAVATA-2925 Handle unverified account in forgot password
---
 django_airavata/apps/auth/views.py | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/django_airavata/apps/auth/views.py b/django_airavata/apps/auth/views.py
index 72ddf9a..489e368 100644
--- a/django_airavata/apps/auth/views.py
+++ b/django_airavata/apps/auth/views.py
@@ -317,6 +317,16 @@ def forgot_password(request):
                 username = form.cleaned_data['username']
                 user_exists = iam_admin_client.is_user_exist(username)
                 if user_exists:
+                    user_enabled = iam_admin_client.is_user_enabled(username)
+                    if not user_enabled:
+                        messages.error(
+                            request,
+                            "Please finish creating your account before "
+                            "resetting your password. Provide your username "
+                            "below and we will send you another email "
+                            "verification link.")
+                        return redirect(
+                            reverse('django_airavata_auth:resend_email_link'))
                     _create_and_send_password_reset_request_link(
                         request, username)
                 # Always display this message even if you doesn't exist. Don't


[airavata-django-portal] 03/04: AIRAVATA-2925 Adding forgot password link to login form

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit c7ebe4064e677cdf07aada9d8b36597b18cfb982
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Fri May 10 10:34:57 2019 -0400

    AIRAVATA-2925 Adding forgot password link to login form
---
 .../django_airavata_auth/partials/username_password_login_form.html      | 1 +
 1 file changed, 1 insertion(+)

diff --git a/django_airavata/apps/auth/templates/django_airavata_auth/partials/username_password_login_form.html b/django_airavata/apps/auth/templates/django_airavata_auth/partials/username_password_login_form.html
index f3d934f..27e4394 100644
--- a/django_airavata/apps/auth/templates/django_airavata_auth/partials/username_password_login_form.html
+++ b/django_airavata/apps/auth/templates/django_airavata_auth/partials/username_password_login_form.html
@@ -13,6 +13,7 @@
                     <div class="form-group">
                         <label for="password">Password</label>
                         <input type="password" class="form-control" id="password" name="password" placeholder="Password" required>
+                        <small class="form-text text-muted"><a href="{% url 'django_airavata_auth:forgot_password' %}" class="text-reset">Forgot your password?</a></small>
                     </div>
                     {% if next %}
                     <input type="hidden" name="next" value="{{ next }}"/>