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 2021/06/25 18:26:06 UTC

[airavata-django-portal] 01/03: AIRAVATA-3468 Check if profile is complete and redirect to profile editor if not

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

machristie pushed a commit to branch AIRAVATA-3319-handle-missing-name-and-email-attributes-from-cilo
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 96bc6bf3bf27ce096d8dc384a65e68c66eca9ac0
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Thu Jun 24 11:49:28 2021 -0400

    AIRAVATA-3468 Check if profile is complete and redirect to profile editor if not
---
 django_airavata/apps/auth/backends.py              |  1 +
 django_airavata/apps/auth/middleware.py            | 26 ++++++++++++--
 django_airavata/apps/auth/models.py                | 40 +++++++++++++++++++---
 django_airavata/apps/auth/signals.py               | 17 +++++----
 .../js/components/UserProfileEditor.vue            | 18 ++++++++--
 django_airavata/apps/auth/urls.py                  |  2 +-
 django_airavata/settings.py                        |  1 +
 7 files changed, 88 insertions(+), 17 deletions(-)

diff --git a/django_airavata/apps/auth/backends.py b/django_airavata/apps/auth/backends.py
index 349d4d2..24f3143 100644
--- a/django_airavata/apps/auth/backends.py
+++ b/django_airavata/apps/auth/backends.py
@@ -220,6 +220,7 @@ class KeycloakBackend(object):
 
         # Update User model fields
         user = user_profile.user
+        user.username = username
         user.email = email
         user.first_name = first_name
         user.last_name = last_name
diff --git a/django_airavata/apps/auth/middleware.py b/django_airavata/apps/auth/middleware.py
index eb8a722..7921405 100644
--- a/django_airavata/apps/auth/middleware.py
+++ b/django_airavata/apps/auth/middleware.py
@@ -4,8 +4,10 @@ import logging
 
 from django.conf import settings
 from django.contrib.auth import logout
+from django.shortcuts import redirect
 
 from . import utils
+from django.urls import reverse
 
 log = logging.getLogger(__name__)
 
@@ -33,7 +35,10 @@ def gateway_groups_middleware(get_response):
     """Add 'is_gateway_admin' and 'is_read_only_gateway_admin' to request."""
     def middleware(request):
 
-        if not request.user.is_authenticated or not request.authz_token:
+        request.is_gateway_admin = False
+        request.is_read_only_gateway_admin = False
+
+        if not request.user.is_authenticated or not request.authz_token or not request.user.user_profile.is_complete:
             return get_response(request)
 
         try:
@@ -66,8 +71,23 @@ def gateway_groups_middleware(get_response):
         except Exception as e:
             log.warning("Failed to set is_gateway_admin, "
                         "is_read_only_gateway_admin for user", exc_info=e)
-            request.is_gateway_admin = False
-            request.is_read_only_gateway_admin = False
 
         return get_response(request)
     return middleware
+
+
+def user_profile_completeness_check(get_response):
+    """Check if user profile is complete and if not, redirect to user profile editor."""
+    def middleware(request):
+
+        if not request.user.is_authenticated:
+            return get_response(request)
+
+        if (not request.user.user_profile.is_complete and
+                request.path != reverse('django_airavata_auth:user_profile') and
+                request.path != reverse('django_airavata_auth:logout') and
+                request.META['HTTP_ACCEPT'] != 'application/json'):
+            return redirect('django_airavata_auth:user_profile')
+        else:
+            return get_response(request)
+    return middleware
diff --git a/django_airavata/apps/auth/models.py b/django_airavata/apps/auth/models.py
index c8c0da3..86ab451 100644
--- a/django_airavata/apps/auth/models.py
+++ b/django_airavata/apps/auth/models.py
@@ -3,6 +3,9 @@ import uuid
 from django.conf import settings
 from django.db import models
 
+from . import forms
+from django.core.exceptions import ValidationError
+
 VERIFY_EMAIL_TEMPLATE = 1
 NEW_USER_EMAIL_TEMPLATE = 2
 PASSWORD_RESET_EMAIL_TEMPLATE = 3
@@ -58,11 +61,38 @@ class UserProfile(models.Model):
 
     @property
     def is_complete(self):
-        # TODO: implement this to check if there are any missing fields on the
-        # User model (email, first_name, last_name) or if the username was is
-        # invalid (for example if defaulted to the IdP's 'sub' claim) or if
-        # there are any extra profile fields that are not valid
-        return False
+        return (self.is_username_valid and
+                self.is_first_name_valid and
+                self.is_last_name_valid and
+                self.is_email_valid)
+
+    @property
+    def is_username_valid(self):
+        # use forms.USERNAME_VALIDATOR with an exception when the username is
+        # equal to the email
+        try:
+            forms.USERNAME_VALIDATOR(self.user.username)
+            validates = True
+        except ValidationError:
+            validates = False
+        return (validates or (self.is_email_valid and self.user.email == self.user.username))
+
+    @property
+    def is_first_name_valid(self):
+        return self.is_non_empty(self.user.first_name)
+
+    @property
+    def is_last_name_valid(self):
+        return self.is_non_empty(self.user.last_name)
+
+    @property
+    def is_email_valid(self):
+        # Only checking for non-empty only; assumption is that email is verified
+        # before it is set or updated
+        return self.is_non_empty(self.user.email)
+
+    def is_non_empty(self, value: str):
+        return value is not None and value.strip() != ""
 
 
 class UserInfo(models.Model):
diff --git a/django_airavata/apps/auth/signals.py b/django_airavata/apps/auth/signals.py
index dfc76cd..7cf1652 100644
--- a/django_airavata/apps/auth/signals.py
+++ b/django_airavata/apps/auth/signals.py
@@ -43,11 +43,16 @@ def initialize_user_profile(sender, request, user, **kwargs):
         if not user_profile_client_pool.doesUserExist(authz_token,
                                                       user.username,
                                                       settings.GATEWAY_ID):
-            user_profile_client_pool.initializeUserProfile(authz_token)
-            log.info("initialized user profile for {}".format(user.username))
-            # Since user profile created, inform admins of new user
-            utils.send_new_user_email(
-                request, user.username, user.email, user.first_name, user.last_name)
-            log.info("sent new user email for user {}".format(user.username))
+            if user.user_profile.is_complete:
+                user_profile_client_pool.initializeUserProfile(authz_token)
+                log.info("initialized user profile for {}".format(user.username))
+                # Since user profile created, inform admins of new user
+                utils.send_new_user_email(
+                    request, user.username, user.email, user.first_name, user.last_name)
+                log.info("sent new user email for user {}".format(user.username))
+            else:
+                log.info(f"user profile not complete for {user.username}, "
+                         "skipping initializing Airavata user profile")
+
     else:
         log.warning(f"Logged in user {user.username} has no access token")
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/UserProfileEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/UserProfileEditor.vue
index 506114c..b6b31fe 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/components/UserProfileEditor.vue
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/components/UserProfileEditor.vue
@@ -1,7 +1,15 @@
 <template>
   <b-card>
     <b-form-group label="Username">
-      <b-form-input disabled :value="user.username" />
+      <b-form-input
+        v-model="$v.user.username.$model"
+        @keydown.native.enter="save"
+        :state="validateState($v.user.username)"
+      />
+      <b-form-invalid-feedback v-if="!$v.user.username.emailOrMatchesRegex">
+        Username can only contain lowercase letters, numbers, underscores and
+        hyphens OR it can be the same as the email address.
+      </b-form-invalid-feedback>
     </b-form-group>
     <b-form-group label="First Name">
       <b-form-input
@@ -46,7 +54,7 @@
 import { models } from "django-airavata-api";
 import { errors } from "django-airavata-common-ui";
 import { validationMixin } from "vuelidate";
-import { email, required } from "vuelidate/lib/validators";
+import { email, helpers, or, required, sameAs } from "vuelidate/lib/validators";
 
 export default {
   name: "user-profile-editor",
@@ -63,8 +71,14 @@ export default {
     };
   },
   validations() {
+    const usernameRegex = helpers.regex("username", /^[a-z0-9_-]+$/);
+    const emailOrMatchesRegex = or(usernameRegex, sameAs('email'));
     return {
       user: {
+        username: {
+          required,
+          emailOrMatchesRegex,
+        },
         first_name: {
           required,
         },
diff --git a/django_airavata/apps/auth/urls.py b/django_airavata/apps/auth/urls.py
index 887ed74..b9a4d3b 100644
--- a/django_airavata/apps/auth/urls.py
+++ b/django_airavata/apps/auth/urls.py
@@ -5,7 +5,7 @@ from rest_framework import routers
 from . import views
 
 router = routers.DefaultRouter()
-router.register(r'users', views.UserViewSet, base_name='user')
+router.register(r'users', views.UserViewSet, basename='user')
 app_name = 'django_airavata_auth'
 urlpatterns = [
     url(r'^', include(router.urls)),
diff --git a/django_airavata/settings.py b/django_airavata/settings.py
index 3ab8b1e..d2ce38f 100644
--- a/django_airavata/settings.py
+++ b/django_airavata/settings.py
@@ -130,6 +130,7 @@ MIDDLEWARE = [
     # Wagtail related middleware
     'wagtail.core.middleware.SiteMiddleware',
     'wagtail.contrib.redirects.middleware.RedirectMiddleware',
+    'django_airavata.apps.auth.middleware.user_profile_completeness_check',
 ]
 
 ROOT_URLCONF = 'django_airavata.urls'