Ver código fonte

HUE-408 [desktop] User creation form usage when first creating a user

Added custom auth and user creation forms to validate authentication
and user creation. Added defensive logic around creating first user.
Added test case to ensure this.
abec 13 anos atrás
pai
commit
eb3576fdf8

+ 42 - 0
desktop/core/src/desktop/auth/forms.py

@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+# Licensed to Cloudera, Inc. under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  Cloudera, Inc. licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from django.contrib.auth.forms import AuthenticationForm as AuthAuthenticationForm, UserCreationForm as AuthUserCreationForm
+from django.forms import CharField, TextInput, PasswordInput
+from django.utils.translation import ugettext_lazy as _t
+
+class AuthenticationForm(AuthAuthenticationForm):
+  """
+  Adds appropriate classes to authentication form
+  """
+  username = CharField(label=_t("Username"), max_length=30, widget=TextInput(attrs={'class': 'input-large', 'maxlength': 30}))
+  password = CharField(label=_t("Password"), widget=PasswordInput(attrs={'class': 'input-large', 'maxlength': 30}))
+
+class UserCreationForm(AuthUserCreationForm):
+  """
+  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', 'maxlength': 30}))
+
+  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(UserCreationForm, self).__init__(data=data, *args, **kwargs)

+ 37 - 22
desktop/core/src/desktop/auth/views.py

@@ -29,6 +29,7 @@ from hadoop.fs.exceptions import WebHdfsException
 from useradmin.views import ensure_home_directory
 
 from desktop.auth.backend import AllowFirstUserDjangoBackend
+from desktop.auth.forms import UserCreationForm, AuthenticationForm
 from desktop.lib.django_util import render
 from desktop.lib.django_util import login_notrequired
 from desktop.log.access import access_warn, last_access_map
@@ -69,36 +70,50 @@ def first_login_ever():
 def dt_login(request):
   """Used by the non-jframe login"""
   redirect_to = request.REQUEST.get('next', '/')
-  login_errors = False
   is_first_login_ever = first_login_ever()
+
   if request.method == 'POST':
-    form = django.contrib.auth.forms.AuthenticationForm(data=request.POST)
-    if form.is_valid():
-      login(request, form.get_user())
-      if request.session.test_cookie_worked():
-        request.session.delete_test_cookie()
-
-      if is_first_login_ever:
-        try:
-          ensure_home_directory(request.fs, request.POST.get('username'))
-        except (IOError, WebHdfsException), e:
-          LOG.error(_('Could not create home directory.'), exc_info=e)
-          request.error(_('Could not create home directory.'))
-
-      access_warn(request, '"%s" login ok' % (request.user.username,))
-      return HttpResponseRedirect(redirect_to)
-    else:
-      access_warn(request, 'Failed login for user "%s"' % (request.POST.get('username'),))
-      login_errors = True
+    # For first login, need to validate user info!
+    first_user_form = is_first_login_ever and UserCreationForm(data=request.POST) or None
+    first_user = first_user_form and first_user_form.is_valid()
+
+    if first_user or not is_first_login_ever:
+      auth_form = AuthenticationForm(data=request.POST)
+
+      if auth_form.is_valid():
+        # Must login by using the AuthenticationForm.
+        # It provides 'backends' on the User object.
+        user = auth_form.get_user()
+        login(request, user)
+
+        if request.session.test_cookie_worked():
+          request.session.delete_test_cookie()
+
+        if is_first_login_ever:
+          # Create home directory for first user.
+          try:
+            ensure_home_directory(request.fs, user.username)
+          except (IOError, WebHdfsException), e:
+            LOG.error(_('Could not create home directory.'), exc_info=e)
+            request.error(_('Could not create home directory.'))
+
+        access_warn(request, '"%s" login ok' % (user.username,))
+        return HttpResponseRedirect(redirect_to)
+
+      else:
+        access_warn(request, 'Failed login for user "%s"' % (request.POST.get('username'),))
+
   else:
-    form = django.contrib.auth.forms.AuthenticationForm()
+    first_user_form = None
+    auth_form = AuthenticationForm()
+
   request.session.set_test_cookie()
   return render('login.mako', request, {
     'action': urlresolvers.reverse('desktop.auth.views.dt_login'),
-    'form': form,
+    'form': first_user_form or auth_form,
     'next': redirect_to,
     'first_login_ever': is_first_login_ever,
-    'login_errors': login_errors,
+    'login_errors': request.method == 'POST',
   })
 
 

+ 14 - 0
desktop/core/src/desktop/auth/views_test.py

@@ -19,6 +19,7 @@ from nose.tools import assert_true, assert_false, assert_equal
 
 from django.contrib.auth.models import User
 from django.test.client import Client
+from desktop import conf
 from desktop.lib.django_test_util import make_logged_in_client
 from hadoop.test_base import PseudoHdfsTestBase
 from hadoop import pseudo_hdfs4
@@ -46,6 +47,19 @@ class TestLoginWithHadoop(PseudoHdfsTestBase):
     assert_equal(200, response.status_code, "Expected ok status.")
     assert_false(response.context['first_login_ever'])
 
+  def test_bad_first_user(self):
+    finish = conf.AUTH.BACKEND.set_for_testing("desktop.auth.backend.AllowFirstUserDjangoBackend")
+
+    response = self.c.get('/accounts/login/')
+    assert_equal(200, response.status_code, "Expected ok status.")
+    assert_true(response.context['first_login_ever'])
+
+    response = self.c.post('/accounts/login/', dict(username="foo 1", password="foo"))
+    assert_equal(200, response.status_code, "Expected ok status.")
+    assert_true('This value may contain only letters, numbers and @/./+/-/_ characters.' in response.content, response)
+
+    finish()
+
   def test_login_home_creation_failure(self):
     response = self.c.get('/accounts/login/')
     assert_equal(200, response.status_code, "Expected ok status.")

+ 4 - 2
desktop/core/src/desktop/templates/login.mako

@@ -60,10 +60,12 @@ from django.utils.translation import ugettext as _
 			<div class="span4 offset4">
 				<form method="POST" action="${action}" class="well">
 					<label>${_('Username')}
-						<input name="username" class="input-large" type="text" maxlength="30">
+						${ form['username'] }
+						${ form['username'].errors }
 					</label>
 					<label>${_('Password')}
-						<input name="password" class="input-large" type="password" maxlength="30">
+						${ form['password'] }
+						${ form['password'].errors }
 					</label>
 
 					%if first_login_ever==True:

+ 4 - 3
desktop/core/src/desktop/views.py

@@ -219,17 +219,18 @@ def serve_404_error(request, *args, **kwargs):
 
 def serve_500_error(request, *args, **kwargs):
   """Registered handler for 500. We use the debug view to make debugging easier."""
+  exc_info = sys.exc_info()
   if desktop.conf.HTTP_500_DEBUG_MODE.get():
-    return django.views.debug.technical_500_response(request, *sys.exc_info())
+    return django.views.debug.technical_500_response(request, *exc_info)
   try:
-    return render("500.mako", request, {'traceback': traceback.extract_tb(sys.exc_info()[2])})
+    return render("500.mako", request, {'traceback': traceback.extract_tb(exc_info[2])})
   except:
     # Fallback to technical 500 response if ours fails
     # Will end up here:
     #   - Middleware or authentication backends problems
     #   - Certain missing imports
     #   - Packaging and install issues
-    return django.views.debug.technical_500_response(request, *sys.exc_info())
+    return django.views.debug.technical_500_response(request, *exc_info)
 
 _LOG_LEVELS = {
   "critical": logging.CRITICAL,