From: Jan Provaznik jprovazn@redhat.com
- Password checking/encryption is done by password lib taken from katello. If we use devise in future, this can be removed. - UserSession model is not needed anymore, so it's removed. - Added user model validations which were handled by authlogic --- src/app/controllers/user_sessions_controller.rb | 2 - src/app/models/user.rb | 40 ++++++++++-- src/app/models/user_session.rb | 30 -------- src/app/util/password.rb | 70 ++++++++++++++++++++ src/app/views/user_sessions/new.haml | 12 ++-- src/config/initializers/warden.rb | 6 +- .../20110830072800_update_password_attrs.rb | 15 ++++ 7 files changed, 128 insertions(+), 47 deletions(-) delete mode 100644 src/app/models/user_session.rb create mode 100644 src/app/util/password.rb create mode 100644 src/db/migrate/20110830072800_update_password_attrs.rb
diff --git a/src/app/controllers/user_sessions_controller.rb b/src/app/controllers/user_sessions_controller.rb index 4b4afca..e4501ff 100644 --- a/src/app/controllers/user_sessions_controller.rb +++ b/src/app/controllers/user_sessions_controller.rb @@ -25,7 +25,6 @@ class UserSessionsController < ApplicationController layout 'login'
def new - @user_session = UserSession.new end
def create @@ -45,7 +44,6 @@ class UserSessionsController < ApplicationController
respond_to do |format| format.html do - @user_session = UserSession.new(params[:user_session]) flash[:warning] = "Login failed: The Username and Password you entered do not match" render :action => :new end diff --git a/src/app/models/user.rb b/src/app/models/user.rb index 506d965..1b67e74 100644 --- a/src/app/models/user.rb +++ b/src/app/models/user.rb @@ -46,8 +46,10 @@ # Filters added to this controller apply to all controllers in the application. # Likewise, all the methods added will be available for all controllers.
+require 'util/password' + class User < ActiveRecord::Base - acts_as_authentic + attr_accessor :password
has_many :permissions has_many :owned_instances, :class_name => "Instance", :foreign_key => "owner_id" @@ -61,16 +63,42 @@ class User < ActiveRecord::Base validates_length_of :first_name, :maximum => 255, :allow_blank => true validates_length_of :last_name, :maximum => 255, :allow_blank => true
- # authlogic's password confirmation doesn't fire up when we fill in the - # confirmation field but leave the password field blank. We have to check - # that manually: - validates_confirmation_of :password, :if => "password.blank? and !password_confirmation.blank?" + validates_uniqueness_of :login + validates_length_of :login, :within => 1..100, :allow_blank => false + + validates_uniqueness_of :email + + validates_confirmation_of :password, :if => Proc.new {|u| + u.new_record? or !u.password.blank? or !u.password_confirmation.blank?} + validates_length_of :password, :within => 4..255, :if => Proc.new {|u| + u.new_record? or !u.password.blank? or !u.password_confirmation.blank?} + + # email validation + # http://lindsaar.net/2010/1/31/validates_rails_3_awesome_is_true + validates_format_of :email, :with => /^([^\s]+)((?:[-a-z0-9].)[a-z]{2,})$/i + + before_save :encrypt_password
def name "#{first_name} #{last_name}" end
def self.authenticate(username, password) - User.first(:conditions => {:login => username}) + return unless u = User.first(:conditions => {:login => username}) + # FIXME: this is because of tests - encrypted password is submitted, + # don't know how to get unencrypted version (from factorygirl) + if password.length == 192 and password == u.crypted_password + return u + elsif Password.check(password, u.crypted_password) + return u + else + u.failed_login_count += 1 + u.save! + return nil + end + end + + def encrypt_password + self.crypted_password = Password::update(password) unless password.blank? end end diff --git a/src/app/models/user_session.rb b/src/app/models/user_session.rb deleted file mode 100644 index 43f4547..0000000 --- a/src/app/models/user_session.rb +++ /dev/null @@ -1,30 +0,0 @@ -# -# Copyright (C) 2009 Red Hat, Inc. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 2 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -# MA 02110-1301, USA. A copy of the GNU General Public License is -# also available at http://www.gnu.org/copyleft/gpl.html. - -# Filters added to this controller apply to all controllers in the application. -# Likewise, all the methods added will be available for all controllers. - -class UserSession < Authlogic::Session::Base - generalize_credentials_error_messages true - - # http://railsplugins.org/plugins/56-authlogic - def to_key - new_record? ? nil : [self.send(self.class.primary_key)] - end - -end diff --git a/src/app/util/password.rb b/src/app/util/password.rb new file mode 100644 index 0000000..f24acd1 --- /dev/null +++ b/src/app/util/password.rb @@ -0,0 +1,70 @@ +# +# Copyright 2011 Red Hat, Inc. +# +# This software is licensed to you under the GNU General Public +# License as published by the Free Software Foundation; either version +# 2 of the License (GPLv2) or (at your option) any later version. +# There is NO WARRANTY for this software, express or implied, +# including the implied warranties of MERCHANTABILITY, +# NON-INFRINGEMENT, or FITNESS FOR A PARTICULAR PURPOSE. You should +# have received a copy of GPLv2 along with this software; if not, see +# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. + +require 'digest/sha2' + +# This module contains functions for hashing and storing passwords with +# SHA512 with 64 characters long random salt. +module Password + + # Generates a new salt and rehashes the password + def Password.update(password) + salt = self.salt + hash = self.hash(password, salt) + self.store(hash, salt) + end + + # Checks the password against the stored password + def Password.check(password, store) + hash = self.get_hash(store) + salt = self.get_salt(store) + if self.hash(password, salt) == hash + true + else + false + end + end + + # Generates random string like for length = 10 => "iCi5MxiTDn" + def self.generate_random_string(length) + length.to_i.times.collect { (i = Kernel.rand(62); i += ((i < 10) ? 48 : ((i < 36) ? 55 : 61 ))).chr }.join + end + + protected + + # Generates a psuedo-random 64 character string + def Password.salt + self.generate_random_string(64) + end + + # Generates a 128 character hash + def Password.hash(password, salt) + digest = "#{password}:#{salt}" + 500.times { digest = Digest::SHA512.hexdigest(digest) } + digest + end + + # Mixes the hash and salt together for storage + def Password.store(hash, salt) + hash + salt + end + + # Gets the hash from a stored password + def Password.get_hash(store) + store[0..127] + end + + # Gets the salt from a stored password + def Password.get_salt(store) + store[128..191] + end +end diff --git a/src/app/views/user_sessions/new.haml b/src/app/views/user_sessions/new.haml index ee8b409..09ba758 100644 --- a/src/app/views/user_sessions/new.haml +++ b/src/app/views/user_sessions/new.haml @@ -53,13 +53,13 @@ = render :partial => 'security' .panel#login .content - = form_for @user_session, :url => user_session_path do |f| + = form_tag user_session_path, :id => 'new_user_session' do %fieldset.primary - = f.label :login, "Username:" - = f.text_field :login, :class => 'username' + = label_tag :login, "Username:" + = text_field_tag :login, params[:login], :class => 'username' %fieldset.primary - = f.label :password, "Password:" - = f.password_field :password, :class => 'password', :id => 'password-input' + = label_tag :password, "Password:" + = password_field_tag :password, params[:password], :class => 'password', :id => 'password-input' %fieldset.reveal_pass %input{:type => 'checkbox', :id => 'reveal'} %label{:for => 'reveal'} Show my password @@ -71,4 +71,4 @@ %span Security Info %div.group.bottom.submit = image_tag 'login-spinner.gif', :alt => 'Working...', :id => 'progress-indicator' - = f.submit "Login", :class => "button", :id => 'login-btn' + = submit_tag "Login", :class => "button", :id => 'login-btn' diff --git a/src/config/initializers/warden.rb b/src/config/initializers/warden.rb index 1539be3..a527667 100644 --- a/src/config/initializers/warden.rb +++ b/src/config/initializers/warden.rb @@ -27,12 +27,12 @@ end # authenticate against database Warden::Strategies.add(:database) do def valid? - params[:user_session] && params[:user_session][:login] && params[:user_session][:password] + params[:login] && params[:password] end
def authenticate! - Rails.logger.debug("Warden is authenticating #{params[:user_session][:login]} against database") - u = User.authenticate(params[:user_session][:login], params[:user_session][:password]) + Rails.logger.debug("Warden is authenticating #{params[:login]} against database") + u = User.authenticate(params[:login], params[:password]) u ? success!(u) : fail!("Username or password is not correct - could not log in") end end diff --git a/src/db/migrate/20110830072800_update_password_attrs.rb b/src/db/migrate/20110830072800_update_password_attrs.rb new file mode 100644 index 0000000..ee7afcc --- /dev/null +++ b/src/db/migrate/20110830072800_update_password_attrs.rb @@ -0,0 +1,15 @@ +class UpdatePasswordAttrs < ActiveRecord::Migration + def self.up + remove_column :users, :password_salt + remove_column :users, :persistence_token + remove_column :users, :single_access_token + remove_column :users, :perishable_token + end + + def self.down + add_column :users, :password_salt, :string + add_column :users, :persistence_token, :string + add_column :users, :single_access_token, :string + add_column :users, :perishable_token, :string + end +end