Browse Source

HUE-8530 [organization] Adding create user and login forms

Romain 6 years ago
parent
commit
69b95fca95

+ 8 - 5
desktop/core/src/desktop/auth/backend.py

@@ -36,26 +36,29 @@ import requests
 
 
 import django.contrib.auth.backends
 import django.contrib.auth.backends
 from django.contrib import auth
 from django.contrib import auth
-from django.contrib.auth.models import User
 from django.core.urlresolvers import reverse
 from django.core.urlresolvers import reverse
 from django.core.exceptions import ImproperlyConfigured, PermissionDenied
 from django.core.exceptions import ImproperlyConfigured, PermissionDenied
 from django.http import HttpResponseRedirect
 from django.http import HttpResponseRedirect
 from django.forms import ValidationError
 from django.forms import ValidationError
 from importlib import import_module
 from importlib import import_module
-
 from django_auth_ldap.backend import LDAPBackend
 from django_auth_ldap.backend import LDAPBackend
 from django_auth_ldap.config import LDAPSearch
 from django_auth_ldap.config import LDAPSearch
+from liboauth.metrics import oauth_authentication_time
+from mozilla_django_oidc.auth import OIDCAuthenticationBackend, default_username_algo
+from mozilla_django_oidc.utils import absolutify, import_from_settings
 
 
 import desktop.conf
 import desktop.conf
 from desktop import metrics
 from desktop import metrics
 from desktop.settings import LOAD_BALANCER_COOKIE
 from desktop.settings import LOAD_BALANCER_COOKIE
-from liboauth.metrics import oauth_authentication_time
-from mozilla_django_oidc.auth import OIDCAuthenticationBackend, default_username_algo
-from mozilla_django_oidc.utils import absolutify, import_from_settings
 
 
 from useradmin import ldap_access
 from useradmin import ldap_access
 from useradmin.models import get_profile, get_default_user_group, UserProfile
 from useradmin.models import get_profile, get_default_user_group, UserProfile
 
 
+if desktop.conf.ENABLE_ORGANIZATIONS.get():
+  from useradmin.models import OrganizationUser as User
+else:
+  from django.contrib.auth.models import User
+
 
 
 LOG = logging.getLogger(__name__)
 LOG = logging.getLogger(__name__)
 
 

+ 97 - 8
desktop/core/src/desktop/auth/forms.py

@@ -20,9 +20,8 @@ import logging
 
 
 from django.conf import settings
 from django.conf import settings
 from django.contrib.auth import authenticate, get_backends
 from django.contrib.auth import authenticate, get_backends
-from django.contrib.auth.models import User
-from django.contrib.auth.forms import AuthenticationForm as AuthAuthenticationForm, UserCreationForm as AuthUserCreationForm
-from django.forms import CharField, TextInput, PasswordInput, ChoiceField, ValidationError
+from django.contrib.auth.forms import AuthenticationForm as DjangoAuthenticationForm, UserCreationForm as DjangoUserCreationForm
+from django.forms import CharField, TextInput, PasswordInput, ChoiceField, ValidationError, Form
 from django.utils.safestring import mark_safe
 from django.utils.safestring import mark_safe
 from django.utils.encoding import smart_str
 from django.utils.encoding import smart_str
 from django.utils.translation import ugettext_lazy as _t, ugettext as _
 from django.utils.translation import ugettext_lazy as _t, ugettext as _
@@ -32,6 +31,12 @@ from useradmin.hue_password_policy import hue_get_password_validators
 
 
 from desktop.auth.backend import is_admin
 from desktop.auth.backend import is_admin
 
 
+if conf.ENABLE_ORGANIZATIONS.get():
+  from useradmin.models import OrganizationUser as User
+else:
+  from django.contrib.auth.models import User
+
+
 LOG = logging.getLogger(__name__)
 LOG = logging.getLogger(__name__)
 
 
 
 
@@ -57,7 +62,7 @@ def get_server_choices():
   return auth_choices
   return auth_choices
 
 
 
 
-class AuthenticationForm(AuthAuthenticationForm):
+class AuthenticationForm(DjangoAuthenticationForm):
   """
   """
   Adds appropriate classes to authentication form
   Adds appropriate classes to authentication form
   """
   """
@@ -103,6 +108,77 @@ class AuthenticationForm(AuthAuthenticationForm):
     return self.authenticate()
     return self.authenticate()
 
 
 
 
+class OrganizationAuthenticationForm(Form):
+  """
+  Adds appropriate classes to authentication form
+  """
+  error_messages = {
+    'invalid_login': _t("Invalid email or password"),
+    'inactive': _t("Account deactivated. Please contact an administrator."),
+  }
+
+  # username = None
+  email = CharField(label=_t("Email"), widget=TextInput(attrs={'maxlength': 150, 'placeholder': _t("Email"), 'autocomplete': 'off', 'autofocus': 'autofocus'}))
+  password = CharField(label=_t("Password"), widget=PasswordInput(attrs={'placeholder': _t("Password"), 'autocomplete': 'off'}))
+
+  def __init__(self, request=None, *args, **kwargs):
+    """
+    The 'request' parameter is set for custom auth use by subclasses.
+    The form data comes in via the standard 'data' kwarg.
+    """
+    self.request = request
+    self.user_cache = None
+    super(OrganizationAuthenticationForm, self).__init__(*args, **kwargs)
+
+  def clean(self):
+    email = self.cleaned_data.get('email')
+    password = self.cleaned_data.get('password')
+
+    if email is not None and password:
+      self.user_cache = authenticate(self.request, email=email, password=password)
+      if self.user_cache is None:
+        raise self.get_invalid_login_error()
+      else:
+        self.confirm_login_allowed(self.user_cache)
+
+    return self.cleaned_data
+
+  # def authenticate(self):
+  #   return super(OrganizationAuthenticationForm, self).clean()
+
+  def confirm_login_allowed(self, user):
+        """
+        Controls whether the given User may log in. This is a policy setting,
+        independent of end-user authentication. This default behavior is to
+        allow login by active users, and reject login by inactive users.
+
+        If the given user cannot log in, this method should raise a
+        ``forms.ValidationError``.
+
+        If the given user may log in, this method should return None.
+        """
+        if not user.is_active:
+            raise ValidationError(
+                self.error_messages['inactive'],
+                code='inactive',
+            )
+
+  def get_user(self):
+      return self.user_cache
+
+  def get_invalid_login_error(self):
+      return ValidationError(
+          self.error_messages['invalid_login'],
+          code='invalid_login',
+          params={'email': 'Email'},
+      )
+
+
+  # def clean(self):
+  #   # TODO: checks for inactivity
+  #   return self.authenticate()
+
+
 class ImpersonationAuthenticationForm(AuthenticationForm):
 class ImpersonationAuthenticationForm(AuthenticationForm):
   login_as = CharField(label=_t("Login as"), max_length=30, widget=TextInput(attrs={'placeholder': _t("Login as username"), 'autocomplete': 'off'}))
   login_as = CharField(label=_t("Login as"), max_length=30, widget=TextInput(attrs={'placeholder': _t("Login as username"), 'autocomplete': 'off'}))
 
 
@@ -168,15 +244,13 @@ class LdapAuthenticationForm(AuthenticationForm):
     return self.cleaned_data
     return self.cleaned_data
 
 
 
 
-class UserCreationForm(AuthUserCreationForm):
+class UserCreationForm(DjangoUserCreationForm):
   """
   """
   Accepts one password field and populates the others.
   Accepts one password field and populates the others.
   password fields with the value of that password field
   password fields with the value of that password field
   Adds appropriate classes to authentication form.
   Adds appropriate classes to authentication form.
   """
   """
-  password = CharField(label=_t("Password"),
-                       widget=PasswordInput(attrs={'class': 'input-large'}),
-                       validators=hue_get_password_validators())
+  password = CharField(label=_t("Password"), widget=PasswordInput(attrs={'class': 'input-large'}), validators=hue_get_password_validators())
 
 
   def __init__(self, data=None, *args, **kwargs):
   def __init__(self, data=None, *args, **kwargs):
     if data and 'password' in data:
     if data and 'password' in data:
@@ -186,6 +260,21 @@ class UserCreationForm(AuthUserCreationForm):
     super(UserCreationForm, self).__init__(data=data, *args, **kwargs)
     super(UserCreationForm, self).__init__(data=data, *args, **kwargs)
 
 
 
 
+class OrganizationUserCreationForm(DjangoUserCreationForm):
+  password = CharField(label=_t("Password"), widget=PasswordInput(attrs={'class': 'input-large'}), validators=hue_get_password_validators())
+
+  def __init__(self, data=None, *args, **kwargs):
+    if data and 'password' in data:
+      data = data.copy()
+      data['password1'] = data['password']
+      data['password2'] = data['password']
+    super(OrganizationUserCreationForm, self).__init__(data=data, *args, **kwargs)
+
+  class Meta(DjangoUserCreationForm.Meta):
+    model = User
+    fields = ('email',)
+
+
 class LdapUserCreationForm(UserCreationForm):
 class LdapUserCreationForm(UserCreationForm):
   def __init__(self, *args, **kwargs):
   def __init__(self, *args, **kwargs):
     super(LdapUserCreationForm, self).__init__(*args, **kwargs)
     super(LdapUserCreationForm, self).__init__(*args, **kwargs)

+ 11 - 8
desktop/core/src/desktop/auth/views.py

@@ -37,20 +37,21 @@ from django.contrib.sessions.models import Session
 from django.http import HttpResponseRedirect
 from django.http import HttpResponseRedirect
 from django.utils.translation import ugettext as _
 from django.utils.translation import ugettext as _
 
 
+from hadoop.fs.exceptions import WebHdfsException
+from notebook.connectors.base import get_api
+from useradmin.models import get_profile, UserProfile
+from useradmin.views import ensure_home_directory, require_change_password
+
 from desktop.auth import forms as auth_forms
 from desktop.auth import forms as auth_forms
 from desktop.auth.backend import OIDCBackend
 from desktop.auth.backend import OIDCBackend
-from desktop.auth.forms import ImpersonationAuthenticationForm
+from desktop.auth.forms import ImpersonationAuthenticationForm, OrganizationUserCreationForm, OrganizationAuthenticationForm
+from desktop.conf import OAUTH, ENABLE_ORGANIZATIONS
 from desktop.lib.django_util import render
 from desktop.lib.django_util import render
 from desktop.lib.django_util import login_notrequired
 from desktop.lib.django_util import login_notrequired
 from desktop.lib.django_util import JsonResponse
 from desktop.lib.django_util import JsonResponse
 from desktop.log.access import access_log, access_warn, last_access_map
 from desktop.log.access import access_log, access_warn, last_access_map
-from desktop.conf import OAUTH
 from desktop.settings import LOAD_BALANCER_COOKIE
 from desktop.settings import LOAD_BALANCER_COOKIE
 
 
-from hadoop.fs.exceptions import WebHdfsException
-from useradmin.models import get_profile, UserProfile
-from useradmin.views import ensure_home_directory, require_change_password
-from notebook.connectors.base import get_api
 
 
 if sys.version_info[0] > 2:
 if sys.version_info[0] > 2:
   import urllib.request, urllib.error
   import urllib.request, urllib.error
@@ -116,11 +117,14 @@ def dt_login(request, from_modal=False):
       AuthenticationForm = ImpersonationAuthenticationForm
       AuthenticationForm = ImpersonationAuthenticationForm
     else:
     else:
       AuthenticationForm = auth_forms.AuthenticationForm
       AuthenticationForm = auth_forms.AuthenticationForm
+    if ENABLE_ORGANIZATIONS.get():
+      UserCreationForm = OrganizationUserCreationForm
+      AuthenticationForm = OrganizationAuthenticationForm
 
 
   if request.method == 'POST':
   if request.method == 'POST':
     request.audit = {
     request.audit = {
       'operation': 'USER_LOGIN',
       'operation': 'USER_LOGIN',
-      'username': request.POST.get('username')
+      'username': request.POST.get('username', request.POST.get('email'))
     }
     }
 
 
     # For first login, need to validate user info!
     # For first login, need to validate user info!
@@ -309,4 +313,3 @@ def oidc_failed(request):
     return HttpResponseRedirect('/')
     return HttpResponseRedirect('/')
   access_warn(request, "401 Unauthorized by oidc")
   access_warn(request, "401 Unauthorized by oidc")
   return render("oidc_failed.mako", request, dict(uri=request.build_absolute_uri()), status=401)
   return render("oidc_failed.mako", request, dict(uri=request.build_absolute_uri()), status=401)
-

+ 58 - 44
desktop/core/src/desktop/templates/login.mako

@@ -15,10 +15,12 @@
 ## limitations under the License.
 ## limitations under the License.
 
 
 <%!
 <%!
-  from desktop import conf
   from django.utils.translation import ugettext as _
   from django.utils.translation import ugettext as _
-  from desktop.views import commonheader, commonfooter
+
   from useradmin.hue_password_policy import is_password_policy_enabled, get_password_hint
   from useradmin.hue_password_policy import is_password_policy_enabled, get_password_hint
+
+  from desktop.conf import CUSTOM, ENABLE_ORGANIZATIONS
+  from desktop.views import commonheader, commonfooter
 %>
 %>
 
 
 <%namespace name="hueIcons" file="/hue_icons.mako" />
 <%namespace name="hueIcons" file="/hue_icons.mako" />
@@ -55,95 +57,107 @@ ${ commonheader(_("Welcome to Hue"), "login", user, request, "50px", True, True)
     <div class="logo">
     <div class="logo">
       <svg style="height: 80px; width: 200px;"><use xlink:href="#hi-logo"></use></svg>
       <svg style="height: 80px; width: 200px;"><use xlink:href="#hi-logo"></use></svg>
     </div>
     </div>
+
     <h3>Query. Explore. Repeat.</h3>
     <h3>Query. Explore. Repeat.</h3>
 
 
-    %if 'OIDCBackend' in backend_names:
+    % if 'OIDCBackend' in backend_names:
       <button title="${ _('Single Sign-on') }" class="btn btn-primary" onclick="location.href='/oidc/authenticate/'">${ _('Single Sign-on') }</button>
       <button title="${ _('Single Sign-on') }" class="btn btn-primary" onclick="location.href='/oidc/authenticate/'">${ _('Single Sign-on') }</button>
-
       <hr class="separator-line"/>
       <hr class="separator-line"/>
-    %endif
+    % endif
 
 
-    %if first_login_ever:
+    % if first_login_ever:
       <div class="alert alert-info center">
       <div class="alert alert-info center">
-        ${_('Since this is your first time logging in, pick any username and password. Be sure to remember these, as')}
-        <strong>${_('they will become your Hue superuser credentials.')}</strong>
-        %if is_password_policy_enabled():
-        <p>${get_password_hint()}</p>
-        %endif
+        ${ _('Since this is your first time logging in, pick any username and password. Be sure to remember these, as') }
+        <strong>${ _('they will become your Hue superuser credentials.') }</strong>
+        % if is_password_policy_enabled():
+        <p>${ get_password_hint() }</p>
+        % endif
       </div>
       </div>
-    %endif
+    % endif
 
 
-    <div class="text-input
-      %if backend_names == ['OAuthBackend']:
-        hide
-      %endif
-      %if form['username'].errors or (not form['username'].errors and not form['password'].errors and login_errors):
-        error
-      %endif
-    ">
-      ${ form['username'] | n,unicode }
-    </div>
+    % if ENABLE_ORGANIZATIONS.get():
+      <div class="text-input
+        % if form['email'].errors or (not form['email'].errors and not form['email'].errors and login_errors):
+          error
+        % endif
+      ">
+        ${ form['email'] | n,unicode }
+      </div>
 
 
-    ${ form['username'].errors | n,unicode }
+      ${ form['email'].errors | n,unicode }
+    % else:
+      <div class="text-input
+        % if backend_names == ['OAuthBackend']:
+          hide
+        % endif
+        % if form['username'].errors or (not form['username'].errors and not form['password'].errors and login_errors):
+          error
+        % endif
+      ">
+        ${ form['username'] | n,unicode }
+      </div>
+
+      ${ form['username'].errors | n,unicode }
+    % endif
 
 
     <div class="text-input
     <div class="text-input
-      %if 'AllowAllBackend' in backend_names or backend_names == ['OAuthBackend']:
+      % if 'AllowAllBackend' in backend_names or backend_names == ['OAuthBackend']:
         hide
         hide
-      %endif
-      %if form['password'].errors or (not form['username'].errors and not form['password'].errors and login_errors):
+      % endif
+      % if form['password'].errors or (('username' in form and not form['username'].errors) and not form['password'].errors and login_errors):
         error
         error
-      %endif
+      % endif
     ">
     ">
       ${ form['password'] | n,unicode }
       ${ form['password'] | n,unicode }
     </div>
     </div>
 
 
     ${ form['password'].errors | n,unicode }
     ${ form['password'].errors | n,unicode }
 
 
-    %if active_directory:
+    % if active_directory:
     <div
     <div
       %if 'server' in form.fields and len(form.fields['server'].choices) == 1:
       %if 'server' in form.fields and len(form.fields['server'].choices) == 1:
         class="hide"
         class="hide"
       %endif
       %endif
       >
       >
-      %if 'server' in form.fields:
+      % if 'server' in form.fields:
         ${ form['server'] | n,unicode }
         ${ form['server'] | n,unicode }
-      %endif
+      % endif
     </div>
     </div>
-    %endif
+    % endif
 
 
-    %if 'ImpersonationBackend' in backend_names:
+    % if 'ImpersonationBackend' in backend_names:
     <div class="text-input">
     <div class="text-input">
       ${ form['login_as'] | n,unicode }
       ${ form['login_as'] | n,unicode }
     </div>
     </div>
-    %endif
+    % endif
 
 
-    %if login_errors and not form['username'].errors and not form['password'].errors:
-      %if form.errors:
+    % if login_errors and ('username' in form and not form['username'].errors) and not form['password'].errors:
+      % if form.errors:
         % for error in form.errors:
         % for error in form.errors:
-         ${ form.errors[error]|unicode,n }
+         ${ form.errors[error] | unicode,n }
         % endfor
         % endfor
-      %endif
-    %endif
+      % endif
+    % endif
 
 
-    %if first_login_ever:
+    % if first_login_ever:
       <input type="submit" class="btn btn-primary" value="${_('Create Account')}"/>
       <input type="submit" class="btn btn-primary" value="${_('Create Account')}"/>
-    %else:
+    % else:
       <input type="submit" class="btn btn-primary" value="${_('Sign In')}"/>
       <input type="submit" class="btn btn-primary" value="${_('Sign In')}"/>
-    %endif
+    % endif
     <input type="hidden" name="next" value="${next}"/>
     <input type="hidden" name="next" value="${next}"/>
 
 
   </form>
   </form>
 
 
-  %if conf.CUSTOM.LOGIN_SPLASH_HTML.get():
+  % if CUSTOM.LOGIN_SPLASH_HTML.get():
   <div class="alert alert-info" id="login-splash">
   <div class="alert alert-info" id="login-splash">
     ${ conf.CUSTOM.LOGIN_SPLASH_HTML.get() | n,unicode }
     ${ conf.CUSTOM.LOGIN_SPLASH_HTML.get() | n,unicode }
   </div>
   </div>
-  %endif
+  % endif
 </div>
 </div>
 
 
 
 
 <div class="trademark center muted">
 <div class="trademark center muted">
-  % if conf.CUSTOM.LOGO_SVG.get():
+  % if CUSTOM.LOGO_SVG.get():
     ${ _('Powered by') } <img src="${ static('desktop/art/hue-login-logo.png') }" width="40" style="vertical-align: middle"  alt="${ _('Hue logo') }"> -
     ${ _('Powered by') } <img src="${ static('desktop/art/hue-login-logo.png') }" width="40" style="vertical-align: middle"  alt="${ _('Hue logo') }"> -
   % endif
   % endif
   ${ _('Hue and the Hue logo are trademarks of Cloudera, Inc.') }
   ${ _('Hue and the Hue logo are trademarks of Cloudera, Inc.') }