Explorar o código

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 %!s(int64=13) %!d(string=hai) anos
pai
achega
eb3576f

+ 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,