Explorar el Código

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

Romain hace 6 años
padre
commit
69b95fca95

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

@@ -36,26 +36,29 @@ import requests
 
 import django.contrib.auth.backends
 from django.contrib import auth
-from django.contrib.auth.models import User
 from django.core.urlresolvers import reverse
 from django.core.exceptions import ImproperlyConfigured, PermissionDenied
 from django.http import HttpResponseRedirect
 from django.forms import ValidationError
 from importlib import import_module
-
 from django_auth_ldap.backend import LDAPBackend
 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
 from desktop import metrics
 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.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__)
 

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

@@ -20,9 +20,8 @@ import logging
 
 from django.conf import settings
 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.encoding import smart_str
 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
 
+if conf.ENABLE_ORGANIZATIONS.get():
+  from useradmin.models import OrganizationUser as User
+else:
+  from django.contrib.auth.models import User
+
+
 LOG = logging.getLogger(__name__)
 
 
@@ -57,7 +62,7 @@ def get_server_choices():
   return auth_choices
 
 
-class AuthenticationForm(AuthAuthenticationForm):
+class AuthenticationForm(DjangoAuthenticationForm):
   """
   Adds appropriate classes to authentication form
   """
@@ -103,6 +108,77 @@ class AuthenticationForm(AuthAuthenticationForm):
     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):
   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
 
 
-class UserCreationForm(AuthUserCreationForm):
+class UserCreationForm(DjangoUserCreationForm):
   """
   Accepts one password field and populates the others.
   password fields with the value of that password field
   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):
     if data and 'password' in data:
@@ -186,6 +260,21 @@ class UserCreationForm(AuthUserCreationForm):
     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):
   def __init__(self, *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.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.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 login_notrequired
 from desktop.lib.django_util import JsonResponse
 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 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:
   import urllib.request, urllib.error
@@ -116,11 +117,14 @@ def dt_login(request, from_modal=False):
       AuthenticationForm = ImpersonationAuthenticationForm
     else:
       AuthenticationForm = auth_forms.AuthenticationForm
+    if ENABLE_ORGANIZATIONS.get():
+      UserCreationForm = OrganizationUserCreationForm
+      AuthenticationForm = OrganizationAuthenticationForm
 
   if request.method == 'POST':
     request.audit = {
       '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!
@@ -309,4 +313,3 @@ def oidc_failed(request):
     return HttpResponseRedirect('/')
   access_warn(request, "401 Unauthorized by oidc")
   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.
 
 <%!
-  from desktop import conf
   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 desktop.conf import CUSTOM, ENABLE_ORGANIZATIONS
+  from desktop.views import commonheader, commonfooter
 %>
 
 <%namespace name="hueIcons" file="/hue_icons.mako" />
@@ -55,95 +57,107 @@ ${ commonheader(_("Welcome to Hue"), "login", user, request, "50px", True, True)
     <div class="logo">
       <svg style="height: 80px; width: 200px;"><use xlink:href="#hi-logo"></use></svg>
     </div>
+
     <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>
-
       <hr class="separator-line"/>
-    %endif
+    % endif
 
-    %if first_login_ever:
+    % if first_login_ever:
       <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>
-    %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
-      %if 'AllowAllBackend' in backend_names or backend_names == ['OAuthBackend']:
+      % if 'AllowAllBackend' in backend_names or backend_names == ['OAuthBackend']:
         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
-      %endif
+      % endif
     ">
       ${ form['password'] | n,unicode }
     </div>
 
     ${ form['password'].errors | n,unicode }
 
-    %if active_directory:
+    % if active_directory:
     <div
       %if 'server' in form.fields and len(form.fields['server'].choices) == 1:
         class="hide"
       %endif
       >
-      %if 'server' in form.fields:
+      % if 'server' in form.fields:
         ${ form['server'] | n,unicode }
-      %endif
+      % endif
     </div>
-    %endif
+    % endif
 
-    %if 'ImpersonationBackend' in backend_names:
+    % if 'ImpersonationBackend' in backend_names:
     <div class="text-input">
       ${ form['login_as'] | n,unicode }
     </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:
-         ${ form.errors[error]|unicode,n }
+         ${ form.errors[error] | unicode,n }
         % endfor
-      %endif
-    %endif
+      % endif
+    % endif
 
-    %if first_login_ever:
+    % if first_login_ever:
       <input type="submit" class="btn btn-primary" value="${_('Create Account')}"/>
-    %else:
+    % else:
       <input type="submit" class="btn btn-primary" value="${_('Sign In')}"/>
-    %endif
+    % endif
     <input type="hidden" name="next" value="${next}"/>
 
   </form>
 
-  %if conf.CUSTOM.LOGIN_SPLASH_HTML.get():
+  % if CUSTOM.LOGIN_SPLASH_HTML.get():
   <div class="alert alert-info" id="login-splash">
     ${ conf.CUSTOM.LOGIN_SPLASH_HTML.get() | n,unicode }
   </div>
-  %endif
+  % endif
 </div>
 
 
 <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') }"> -
   % endif
   ${ _('Hue and the Hue logo are trademarks of Cloudera, Inc.') }