Project

General

Profile

« Previous | Next » 

Revision 7a4ec5cf

Added by Paul Kelly almost 14 years ago

  • ID 7a4ec5cf7cd482e4adac225312aa02dbe82ff561

Fixes #145 - Added AuthSourceInternal

Added a password setting facility to the user class for AuthSourceInternal
Switched to SHA1
Changed :ldap to :login
Applied numerous fixes from Ohad Levy
Fixed malformed user page
Switched attr_accessible to attr_protected
Swiched prepare_password to a before_validation, otherwise validations will fail
Lots of test fixes!

View differences:

app/controllers/application_controller.rb
#Force a user to login if ldap authentication is enabled
def require_login
return true unless SETTINGS[:ldap]
unless (session[:user] and (@user = User.find(session[:user])))
return true unless SETTINGS[:login]
unless session[:user] and @username = User.find(session[:user])
session[:original_uri] = request.request_uri
redirect_to login_users_path
end
......
# returns current user
def current_user
@user
@username
end
def invalid_request
app/controllers/users_controller.rb
class UsersController < ApplicationController
filter_parameter_logging :password
filter_parameter_logging :password, :password_confirmation
before_filter :require_login, :except => [:login, :logout]
def index
......
def create
@user = User.new(params[:user])
@user.admin = params[:admin]
if @user.save
flash[:foreman_notice] = "Successfully created user."
redirect_to users_url
......
def update
@user = User.find(params[:id])
admin = params[:user].delete :admin
if @user.update_attributes(params[:user])
@user.update_attribute :admin, admin
flash[:foreman_notice] = "Successfully updated user."
redirect_to users_url
else
......
end
def destroy
@user = User.find(params[:id])
if @user.destroy
user = User.find(params[:id])
if user == current_user
flash[:foreman_notice] = "You are currently logged in, suicidal?"
redirect_to :back and return
end
if user.destroy
flash[:foreman_notice] = "Successfully destroyed user."
else
flash[:foreman_error] = @user.errors.full_messages.join("<br>")
flash[:foreman_error] = user.errors.full_messages.join("<br>")
end
redirect_to users_url
end
# Called from the login form.
# Stores the username in the session and redirects required URL or default homepage
def login
session[:user] = nil
if request.post?
......
redirect_to login_users_path
end
def auth_source_selected
render :update do |page|
if params[:auth_source_id] and AuthSource.find(params[:auth_source_id]).can_set_password?
page.show 'password', 'verification'
else
page.hide 'password', 'verification'
end
end
end
end
app/models/auth_source_internal.rb
class AuthSourceInternal < AuthSource
def authenticate(login, password)
return nil if login.blank? || password.blank?
User.find_by_login(login).try :matching_password?, password
end
def auth_method_name
"INTERNAL"
end
alias_method :to_label, :auth_method_name
def can_set_password?
true
end
end
app/models/auth_source_ldap.rb
attrs = []
# get user's DN
if not account.nil? and self.account.include? "$login" then
logger.debug "LDAP-Auth with User login"
ldap_con = initialize_ldap_con(self.account.sub("$login", login), password)
logger.debug "LDAP-Auth with User login"
ldap_con = initialize_ldap_con(self.account.sub("$login", login), password)
else
ldap_con = initialize_ldap_con(self.account, self.account_password)
ldap_con = initialize_ldap_con(self.account, self.account_password)
end
login_filter = Net::LDAP::Filter.eq( self.attr_login, login )
object_filter = Net::LDAP::Filter.eq( "objectClass", "*" )
app/models/host_mailer.rb
def error_state(report)
host = report.host
email = host.owner.recipients if SETTINGS[:ldap] and not host.owner.nil?
email = host.owner.recipients if SETTINGS[:login] and not host.owner.nil?
email = SETTINGS[:administrator] if email.empty?
raise "unable to find recipients" if email.empty?
recipients email
app/models/user.rb
class User < ActiveRecord::Base
attr_protected :password_hash, :password_salt, :admin
attr_accessor :password, :password_confirmation
belongs_to :auth_source
has_many :changes, :class_name => 'Audit', :as => :user
has_many :usergroups, :through => :usergroup_member
has_many :direct_hosts, :as => :owner, :class_name => "Host"
validates_uniqueness_of :login, :message => "already exists"
validates_presence_of :login, :mail
validates_presence_of :login, :mail, :auth_source_id
validates_presence_of :password_hash, :if => Proc.new {|user| user.manage_password?}
validates_confirmation_of :password, :if => Proc.new {|user| user.manage_password?}, :unless => Proc.new {|user| user.password.empty?}
validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
validates_length_of :login, :maximum => 30
validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-\.]*$/i, :allow_nil => true
......
validates_length_of :mail, :maximum => 60, :allow_nil => true
before_destroy Ensure_not_used_by.new(:hosts)
validate :name_used_in_a_usergroup
before_validation :prepare_password
def to_label
"#{firstname} #{lastname}"
end
alias_method :to_s, :to_label
alias_method :name, :to_label
def <=>(other)
......
if user = find(:first, :conditions => ["login=?", login])
# user is already in local database
if user.auth_source
# user has an external authentication method
# user has an authentication method
return nil unless user.auth_source.authenticate(login, password)
else
# TODO: Add support for local password authentication
return nil
end
else
# user is not yet registered, try to authenticate with available sources
......
user.login = login
if user.save
user.reload
logger.info("User '#{user.login}' created from the LDAP") if logger
logger.info "User '#{user.login}' auto-created from #{user.auth_source}"
else
logger.info("Failed to save User '#{user.login}' #{user.errors.full_messages}") if logger
logger.info "Failed to save User '#{user.login}' #{user.errors.full_messages}"
end
end
end
user.update_attribute(:last_login_on, Time.now.utc) if user && !user.new_record?
user
user.update_attribute(:last_login_on, Time.now.utc) if user and not user.new_record?
return user
rescue => text
raise text
end
def matching_password?(pass)
self.password_hash == encrypt_password(pass)
end
def indirect_hosts
all_groups = []
for usergroup in usergroups
......
[mail]
end
protected
def manage_password?
auth_source and auth_source.can_set_password?
end
private
def prepare_password
unless password.blank?
self.password_salt = Digest::SHA1.hexdigest([Time.now, rand].join)
self.password_hash = encrypt_password(password)
end
end
def encrypt_password(pass)
Digest::SHA1.hexdigest([pass, password_salt].join)
end
def validate
def name_used_in_a_usergroup
if Usergroup.all.map(&:name).include?(self.login)
errors.add_to_base "A usergroup already exists with this name"
end
app/views/home/_topbar.rhtml
<% if @user -%>
<%= link_to "Sign Out #{@user.login}", logout_users_path %>
<% end -%>
<%= link_to 'Wiki', "http://theforeman.org/wiki/foreman" %>
<%= link_to 'Support', "http://theforeman.org/projects/foreman/boards" %>
<%= link_to("Sign Out #{@username.login}", logout_users_path) if @username %>
<%= link_to 'Wiki', "http://theforeman.org/wiki/foreman" %>
<%= link_to 'Support', "http://theforeman.org/projects/foreman/boards" %>
app/views/home/settings.erb
<li><%= link_to 'Global Parameters', common_parameters_path %> </li>
<li><%= link_to "External Variables", lookup_keys_path %> </li>
<li><%= link_to 'LDAP Authentication', auth_source_ldaps_path %> </li>
<li><%= link_to_if SETTINGS[:ldap], 'Users', users_path %> </li>
<li><%= link_to_if SETTINGS[:ldap], 'Usergroups', usergroups_path %> </li>
<li><%= link_to_if SETTINGS[:login], 'Users', users_path %> </li>
<li><%= link_to_if SETTINGS[:login], 'Usergroups', usergroups_path %> </li>
</ul>
</div>
app/views/hosts/_form.html.erb
<table>
<tr>
<td> <%= f.text_area :comment, :size => "80x5", :title => "additional information about this host" %></td>
<% if SETTINGS[:ldap] -%>
<% if SETTINGS[:login] -%>
<td>Owned by</td>
<td>
<%= f.select :is_owned_by,
app/views/hosts/_unattended.html.erb
</tr>
</table>
<%= link_to_function "Switch to custom disk layout", toggle_div("custom_disk") %> <br>
<span id="custom_disk", style=display:<%= @host.disk.empty? ? "none" : "inline" %>;>
<span id="custom_disk", style="display:<%= @host.disk.empty? ? "none" : "inline" %>;">
<%= f.text_area :disk, :size => "80x10", :title => 'Use custom Disk layout' %><br>
<small>What ever text you use in here, would be used as your OS disk layout options<br>
If you want to use the partition table option, delete all of the text from this field
app/views/users/_form.html.erb
<% form_for @user do |f| %>
<%= f.error_messages %>
<table>
<table border="0">
<tr>
<td> Login<br /> <%= f.text_field :login %> </td>
<td width="1%"> Login</td> <td> <%= f.text_field :login %> </td>
</tr>
<tr>
<td> First name<br /> <%= f.text_field :firstname %> </td>
<td> First name</td> <td> <%= f.text_field :firstname %> </td>
</tr>
<tr>
<td> Last name<br /> <%= f.text_field :lastname %> </td>
<td> Last name</td> <td> <%= f.text_field :lastname %> </td>
</tr>
<tr>
<td> Mail<br /> <%= f.text_field :mail %> </td>
</tr>
<% if @user.auth_source and @user.auth_source.can_set_password? -%>
<tr>
<td> Password<br /> <%= f.password_field :password %> </td>
</tr>
<tr>
<td> Verification<br /> <%= password_field_tag :verification, nil, :size => 30 %> </td>
</tr>
<% end -%>
<td> Mail</td> <td> <%= f.text_field :mail %> </td>
</tr>
<tr>
<td> Authorized by<br />
<%= f.collection_select :auth_source_id, AuthSource.all.delete_if{|a| a.to_label.nil?}, :id, :to_label %>
<td nowrap> Authorized by</td>
<td>
<%= f.collection_select :auth_source_id, AuthSource.all.delete_if{|a| a.to_label.nil?}, :id, :to_label, {:include_blank => true} %>
</td>
</tr>
<tr id="password" <%= 'style="display:none"' unless @user.manage_password? -%>>
<td> Password </td>
<td> <%= password_field :user, :password %> </td>
</tr>
<tr id="verification" <%= 'style="display:none"' unless @user.manage_password? -%>>
<td> Verification </td>
<td> <%= password_field :user, :password_confirmation %> </td>
</tr>
<tr>
<td> Administrator <%= f.check_box :admin %> </td>
<td colspan="2"> Administrator <%= f.check_box :admin %> </td>
</tr>
<tr>
<td> <%= f.submit "Submit" %> </td>
<td colspan="2"> <%= f.submit "Submit" %> </td>
</tr>
</table>
<% end %>
<%= observe_field(:user_auth_source_id,
:url => { :action => :auth_source_selected},
:with => 'auth_source_id') %>
config/initializers/foreman.rb
Puppet.parse_config
$puppet = Puppet.settings.instance_variable_get(:@values) if Rails.env == "test"
SETTINGS[:login] ||= SETTINGS[:ldap]
# Add an empty method to nil. Now no need for if x and x.empty?. Just x.empty?
class NilClass
def empty?
config/routes.rb
map.resources :fact_values
map.resources :ptables
map.resources :auth_source_ldaps
map.resources :users, :collection => {:login => [:get, :post], :logout => :get}
map.resources :users, :collection => {:login => [:get, :post], :logout => :get, :auth_source_selected => :get}
#default
map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id.:format'
config/settings.yaml
#:modulepath: /etc/puppet/modules/
:tftppath: tftp/
#:rrd_report_url: report/
#:ldap: true
:login: true
#your default puppet server - can be overridden in the host level
#if none specified, plain "puppet" will be used.
#:puppet_server: puppet
db/migrate/20100628123400_add_internal_auth.rb
class AddInternalAuth < ActiveRecord::Migration
def self.up
add_column :users, :password_hash, :string, :limit => 128
add_column :users, :password_salt, :string, :limit => 128
User.reset_column_information
user = User.find_or_create_by_login(:login => "admin", :admin => true, :firstname => "Admin", :lastname => "User", :mail => "root@" + Facter.domain)
src = AuthSourceInternal.find_or_create_by_type "AuthSourceInternal"
src.update_attribute :name, "Internal"
user.auth_source = src
user.password="changeme"
if user.save
say "****************************************************************************************"
say "The newly created internal account named admin has been allocated a password of 'changeme'"
say "Set this to something else in the settings/users page"
say "****************************************************************************************"
else
say user.errors.full_messages.join(", ")
end
end
def self.down
if auth = AuthSourceInternal.first
auth.users.each {|u| u.destroy}
auth.destroy
end
remove_column :users, :password_salt
remove_column :users, :password_hash
end
end
test/fixtures/auth_sources.yml
onthefly_register: true
type: AuthSourceLdap
tls: true
internal:
name: Internal
type: AuthSourceInternal
test/fixtures/users.yml
login: one
firstname: One
lastname: User
mail: MyString
mail: userone@someware.com
admin: false
last_login_on: 2009-10-12 21:50:04
auth_source_id: 1
auth_source: auth_sources(:one)
two:
login: two
firstname: Two
lastname: User
mail: MyString
mail: usertwo@someware.com
admin: false
last_login_on: 2009-10-12 21:50:04
auth_source_id: 1
auth_source: auth_sources(:one)
test/functional/users_controller_test.rb
end
test "should get edit" do
u = User.new :login => "foo", :mail => "foo@bar.com"
u = User.new :login => "foo", :mail => "foo@bar.com", :auth_source => auth_sources(:one)
assert u.save!
logger.info "************ ID = #{u.id}"
get :edit, {:id => u.id}, set_session_user
......
end
test "should update user" do
user = User.create :login => "foo", :mail => "foo@bar.com"
user = User.create :login => "foo", :mail => "foo@bar.com", :auth_source => auth_sources(:one)
put :update, { :commit => "Submit", :id => user.id, :user => {:login => "johnsmith"} }, set_session_user
mod_user = User.find_by_id(user.id)
......
assert_redirected_to users_path
end
test "should delete" do
user = User.last
delete :destroy, {:id => user}, set_session_user
test "should set password" do
user = User.new :login => "foo", :mail => "foo@bar.com", :firstname => "john", :lastname => "smith", :auth_source => auth_sources(:internal)
user.password = "changeme"
assert user.save
put :update, {:commit => "Submit", :id => user.id,
:user => {
:login => "johnsmith", :password => "dummy", :password_confirmation => "dummy"
},
}, set_session_user
mod_user = User.find_by_id(user.id)
assert mod_user.matching_password? "dummy"
assert_redirected_to users_path
end
test "should detect password validation mismatches" do
user = User.new :login => "foo", :mail => "foo@bar.com", :firstname => "john", :lastname => "smith", :auth_source => auth_sources(:internal)
user.password = "changeme"
assert user.save
put :update, {:commit => "Submit", :id => user.id,
:user => {
:login => "johnsmith", :password => "dummy", :password_confirmation => "DUMMY"
},
}, set_session_user
mod_user = User.find_by_id(user.id)
assert mod_user.matching_password? "changeme"
assert_template :edit
end
test "should not ask for confirmation if no password is set" do
user = User.new :login => "foo", :mail => "foo@bar.com", :firstname => "john", :lastname => "smith", :auth_source => auth_sources(:internal)
user.password = "changeme"
assert user.save
put :update, {:commit => "Submit", :id => user.id,
:user => { :login => "foobar" },
}, set_session_user
assert_redirected_to users_url
end
test "should delete different user" do
user = users(:one)
delete :destroy, {:id => user}, {:user => users(:two)}
assert_redirected_to users_url
assert !User.exists?(user.id)
end
test "should not delete same user" do
@request.env['HTTP_REFERER'] = users_path
user = users(:one)
delete :destroy, {:id => user}, {:user => user}
assert_redirected_to users_url
assert User.exists?(user.id)
assert @response.flash[:foreman_notice] == "You are currently logged in, suicidal?"
end
end
test/test_helper.rb
end
def set_session_user
if SETTINGS[:ldap] or SETTINGS[:ldaps]
if SETTINGS[:login]
{:user => User.first}
else
{}
test/unit/host_mailer_test.rb
end
test "mail should have any recipient if email or admin are not defined" do
user = User.new :mail => "chuck.norris@vurbia.com", :login => "Chuck_Norris"
assert user.save!
user = User.create :mail => "chuck.norris@vurbia.com", :login => "Chuck_Norris", :auth_source => auth_sources(:one)
assert user.valid?
@options[:email] = nil
SETTINGS[:administrator] = nil
assert HostMailer.deliver_summary(@options).to.include?("chuck.norris@vurbia.com")
test/unit/report_observer_test.rb
:architecture => Architecture.find_or_create_by_name("i386"),
:environment => Environment.find_or_create_by_name("envy"),
:disk => "empty partition"
h.update_attribute :owner, User.first if SETTINGS[:ldap]
h.update_attribute :owner, User.first if SETTINGS[:login]
p = Puppet::Transaction::Report.new
p.save
test/unit/user_test.rb
class UserTest < ActiveSupport::TestCase
def setup
@user = User.create :login => "foo", :mail => "foo@bar.com"
@user = User.create :auth_source => auth_sources(:one), :login => "foo", :mail => "foo@bar.com"
end
test "should have login" do
u = User.new :mail => "foo@bar.com"
u = User.new :auth_source => auth_sources(:one), :mail => "foo@bar.com"
assert !u.save
end
test "should have mail" do
u = User.new :login => "foo"
u = User.new :auth_source => auth_sources(:one), :login => "foo"
assert !u.save
end
test "login should be unique" do
u = User.create :login => "foo", :mail => "foo@bar.com"
u = User.new :auth_source => auth_sources(:one), :login => "foo", :mail => "foo@bar.com"
assert !u.valid?
end
test "login should also be unique across usergroups" do
ug = Usergroup.create :name => "foo"
u = User.create :login => "foo", :mail => "foo@bar.com"
u = User.new :auth_source => auth_sources(:one), :login => "foo", :mail => "foo@bar.com"
assert !u.valid?
end
test "mail should have format" do
u = User.create :login => "foo", :mail => "bar"
u = User.new :auth_source => auth_sources(:one), :login => "foo", :mail => "bar"
assert !u.valid?
end
test "login size should not exceed the 30 characters" do
u = User.new :login => "a" * 31, :mail => "foo@bar.com"
u = User.new :auth_source => auth_sources(:one), :login => "a" * 31, :mail => "foo@bar.com"
assert !u.save
end
......
assert !@user.save
@user.firstname = " _''. - nah"
assert @user.save!
assert @user.save
end
test "lastname should have the correct format" do
......
assert !@user.save
@user.lastname = " _''. - nah"
assert @user.save!
assert @user.save
end
test "firstname should not exceed the 30 characters" do
......
end
test "mail should not exceed the 60 characters" do
u = User.create :login => "foo", :mail => "foo" * 20 + "@bar.com"
u = User.create :auth_source => auth_sources(:one), :login => "foo"
u.mail = "foo" * 20 + "@bar.com"
assert !u.save
end
test "to_label method should return a firstname and the lastname" do
@user.firstname = "Ali Al"
@user.lastname = "Salame"
assert @user.save!
assert @user.save
assert_equal "Ali Al Salame", @user.to_label
end
test/unit/usergroup_test.rb
end
test "name is unique across user as well as usergroup" do
user = User.create :login => "user", :mail => "user@someware.com"
usergroup = Usergroup.create :name => "user"
user = User.create :auth_source => auth_sources(:one), :login => "user", :mail => "user@someware.com"
usergroup = Usergroup.create :name => "user"
assert !usergroup.valid?
end
def populate_usergroups
@u1 = User.find_or_create_by_login :login => "u1", :mail => "u1@someware.com", :firstname => "u1"
@u2 = User.find_or_create_by_login :login => "u2", :mail => "u2@someware.com", :firstname => "u2"
@u3 = User.find_or_create_by_login :login => "u3", :mail => "u3@someware.com", :firstname => "u3"
@u4 = User.find_or_create_by_login :login => "u4", :mail => "u4@someware.com", :firstname => "u4"
@u5 = User.find_or_create_by_login :login => "u5", :mail => "u5@someware.com", :firstname => "u5"
@u6 = User.find_or_create_by_login :login => "u6", :mail => "u6@someware.com", :firstname => "u6"
@u1 = User.find_or_create_by_login :login => "u1", :mail => "u1@someware.com", :firstname => "u1", :auth_source => auth_sources(:one)
@u2 = User.find_or_create_by_login :login => "u2", :mail => "u2@someware.com", :firstname => "u2", :auth_source => auth_sources(:one)
@u3 = User.find_or_create_by_login :login => "u3", :mail => "u3@someware.com", :firstname => "u3", :auth_source => auth_sources(:one)
@u4 = User.find_or_create_by_login :login => "u4", :mail => "u4@someware.com", :firstname => "u4", :auth_source => auth_sources(:one)
@u5 = User.find_or_create_by_login :login => "u5", :mail => "u5@someware.com", :firstname => "u5", :auth_source => auth_sources(:one)
@u6 = User.find_or_create_by_login :login => "u6", :mail => "u6@someware.com", :firstname => "u6", :auth_source => auth_sources(:one)
@ug1 = Usergroup.find_or_create_by_name :name => "ug1"
@ug2 = Usergroup.find_or_create_by_name :name => "ug2"
......
test "removes user join model records" do
ug1 = Usergroup.find_or_create_by_name :name => "ug1"
u1 = User.find_or_create_by_login :login => "u1", :mail => "u1@someware.com"
u1 = User.find_or_create_by_login :login => "u1", :mail => "u1@someware.com", :auth_source => auth_sources(:one)
ug1.users = [u1]
assert_difference('UsergroupMember.count', -1) do
ug1.destroy

Also available in: Unified diff