##// END OF EJS Templates
Allow AuthSources to control if they allow password changes....
Eric Davis -
r3631:908d44519c41
parent child
Show More
@@ -1,183 +1,183
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class MyController < ApplicationController
18 class MyController < ApplicationController
19 before_filter :require_login
19 before_filter :require_login
20
20
21 helper :issues
21 helper :issues
22 helper :custom_fields
22 helper :custom_fields
23
23
24 BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_issues,
24 BLOCKS = { 'issuesassignedtome' => :label_assigned_to_me_issues,
25 'issuesreportedbyme' => :label_reported_issues,
25 'issuesreportedbyme' => :label_reported_issues,
26 'issueswatched' => :label_watched_issues,
26 'issueswatched' => :label_watched_issues,
27 'news' => :label_news_latest,
27 'news' => :label_news_latest,
28 'calendar' => :label_calendar,
28 'calendar' => :label_calendar,
29 'documents' => :label_document_plural,
29 'documents' => :label_document_plural,
30 'timelog' => :label_spent_time
30 'timelog' => :label_spent_time
31 }.merge(Redmine::Views::MyPage::Block.additional_blocks).freeze
31 }.merge(Redmine::Views::MyPage::Block.additional_blocks).freeze
32
32
33 DEFAULT_LAYOUT = { 'left' => ['issuesassignedtome'],
33 DEFAULT_LAYOUT = { 'left' => ['issuesassignedtome'],
34 'right' => ['issuesreportedbyme']
34 'right' => ['issuesreportedbyme']
35 }.freeze
35 }.freeze
36
36
37 verify :xhr => true,
37 verify :xhr => true,
38 :only => [:add_block, :remove_block, :order_blocks]
38 :only => [:add_block, :remove_block, :order_blocks]
39
39
40 def index
40 def index
41 page
41 page
42 render :action => 'page'
42 render :action => 'page'
43 end
43 end
44
44
45 # Show user's page
45 # Show user's page
46 def page
46 def page
47 @user = User.current
47 @user = User.current
48 @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT
48 @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT
49 end
49 end
50
50
51 # Edit user's account
51 # Edit user's account
52 def account
52 def account
53 @user = User.current
53 @user = User.current
54 @pref = @user.pref
54 @pref = @user.pref
55 if request.post?
55 if request.post?
56 @user.attributes = params[:user]
56 @user.attributes = params[:user]
57 @user.mail_notification = (params[:notification_option] == 'all')
57 @user.mail_notification = (params[:notification_option] == 'all')
58 @user.pref.attributes = params[:pref]
58 @user.pref.attributes = params[:pref]
59 @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
59 @user.pref[:no_self_notified] = (params[:no_self_notified] == '1')
60 if @user.save
60 if @user.save
61 @user.pref.save
61 @user.pref.save
62 @user.notified_project_ids = (params[:notification_option] == 'selected' ? params[:notified_project_ids] : [])
62 @user.notified_project_ids = (params[:notification_option] == 'selected' ? params[:notified_project_ids] : [])
63 set_language_if_valid @user.language
63 set_language_if_valid @user.language
64 flash[:notice] = l(:notice_account_updated)
64 flash[:notice] = l(:notice_account_updated)
65 redirect_to :action => 'account'
65 redirect_to :action => 'account'
66 return
66 return
67 end
67 end
68 end
68 end
69 @notification_options = [[l(:label_user_mail_option_all), 'all'],
69 @notification_options = [[l(:label_user_mail_option_all), 'all'],
70 [l(:label_user_mail_option_none), 'none']]
70 [l(:label_user_mail_option_none), 'none']]
71 # Only users that belong to more than 1 project can select projects for which they are notified
71 # Only users that belong to more than 1 project can select projects for which they are notified
72 # Note that @user.membership.size would fail since AR ignores :include association option when doing a count
72 # Note that @user.membership.size would fail since AR ignores :include association option when doing a count
73 @notification_options.insert 1, [l(:label_user_mail_option_selected), 'selected'] if @user.memberships.length > 1
73 @notification_options.insert 1, [l(:label_user_mail_option_selected), 'selected'] if @user.memberships.length > 1
74 @notification_option = @user.mail_notification? ? 'all' : (@user.notified_projects_ids.empty? ? 'none' : 'selected')
74 @notification_option = @user.mail_notification? ? 'all' : (@user.notified_projects_ids.empty? ? 'none' : 'selected')
75 end
75 end
76
76
77 # Manage user's password
77 # Manage user's password
78 def password
78 def password
79 @user = User.current
79 @user = User.current
80 if @user.auth_source_id
80 unless @user.change_password_allowed?
81 flash[:error] = l(:notice_can_t_change_password)
81 flash[:error] = l(:notice_can_t_change_password)
82 redirect_to :action => 'account'
82 redirect_to :action => 'account'
83 return
83 return
84 end
84 end
85 if request.post?
85 if request.post?
86 if @user.check_password?(params[:password])
86 if @user.check_password?(params[:password])
87 @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
87 @user.password, @user.password_confirmation = params[:new_password], params[:new_password_confirmation]
88 if @user.save
88 if @user.save
89 flash[:notice] = l(:notice_account_password_updated)
89 flash[:notice] = l(:notice_account_password_updated)
90 redirect_to :action => 'account'
90 redirect_to :action => 'account'
91 end
91 end
92 else
92 else
93 flash[:error] = l(:notice_account_wrong_password)
93 flash[:error] = l(:notice_account_wrong_password)
94 end
94 end
95 end
95 end
96 end
96 end
97
97
98 # Create a new feeds key
98 # Create a new feeds key
99 def reset_rss_key
99 def reset_rss_key
100 if request.post?
100 if request.post?
101 if User.current.rss_token
101 if User.current.rss_token
102 User.current.rss_token.destroy
102 User.current.rss_token.destroy
103 User.current.reload
103 User.current.reload
104 end
104 end
105 User.current.rss_key
105 User.current.rss_key
106 flash[:notice] = l(:notice_feeds_access_key_reseted)
106 flash[:notice] = l(:notice_feeds_access_key_reseted)
107 end
107 end
108 redirect_to :action => 'account'
108 redirect_to :action => 'account'
109 end
109 end
110
110
111 # Create a new API key
111 # Create a new API key
112 def reset_api_key
112 def reset_api_key
113 if request.post?
113 if request.post?
114 if User.current.api_token
114 if User.current.api_token
115 User.current.api_token.destroy
115 User.current.api_token.destroy
116 User.current.reload
116 User.current.reload
117 end
117 end
118 User.current.api_key
118 User.current.api_key
119 flash[:notice] = l(:notice_api_access_key_reseted)
119 flash[:notice] = l(:notice_api_access_key_reseted)
120 end
120 end
121 redirect_to :action => 'account'
121 redirect_to :action => 'account'
122 end
122 end
123
123
124 # User's page layout configuration
124 # User's page layout configuration
125 def page_layout
125 def page_layout
126 @user = User.current
126 @user = User.current
127 @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT.dup
127 @blocks = @user.pref[:my_page_layout] || DEFAULT_LAYOUT.dup
128 @block_options = []
128 @block_options = []
129 BLOCKS.each {|k, v| @block_options << [l("my.blocks.#{v}", :default => [v, v.to_s.humanize]), k.dasherize]}
129 BLOCKS.each {|k, v| @block_options << [l("my.blocks.#{v}", :default => [v, v.to_s.humanize]), k.dasherize]}
130 end
130 end
131
131
132 # Add a block to user's page
132 # Add a block to user's page
133 # The block is added on top of the page
133 # The block is added on top of the page
134 # params[:block] : id of the block to add
134 # params[:block] : id of the block to add
135 def add_block
135 def add_block
136 block = params[:block].to_s.underscore
136 block = params[:block].to_s.underscore
137 (render :nothing => true; return) unless block && (BLOCKS.keys.include? block)
137 (render :nothing => true; return) unless block && (BLOCKS.keys.include? block)
138 @user = User.current
138 @user = User.current
139 layout = @user.pref[:my_page_layout] || {}
139 layout = @user.pref[:my_page_layout] || {}
140 # remove if already present in a group
140 # remove if already present in a group
141 %w(top left right).each {|f| (layout[f] ||= []).delete block }
141 %w(top left right).each {|f| (layout[f] ||= []).delete block }
142 # add it on top
142 # add it on top
143 layout['top'].unshift block
143 layout['top'].unshift block
144 @user.pref[:my_page_layout] = layout
144 @user.pref[:my_page_layout] = layout
145 @user.pref.save
145 @user.pref.save
146 render :partial => "block", :locals => {:user => @user, :block_name => block}
146 render :partial => "block", :locals => {:user => @user, :block_name => block}
147 end
147 end
148
148
149 # Remove a block to user's page
149 # Remove a block to user's page
150 # params[:block] : id of the block to remove
150 # params[:block] : id of the block to remove
151 def remove_block
151 def remove_block
152 block = params[:block].to_s.underscore
152 block = params[:block].to_s.underscore
153 @user = User.current
153 @user = User.current
154 # remove block in all groups
154 # remove block in all groups
155 layout = @user.pref[:my_page_layout] || {}
155 layout = @user.pref[:my_page_layout] || {}
156 %w(top left right).each {|f| (layout[f] ||= []).delete block }
156 %w(top left right).each {|f| (layout[f] ||= []).delete block }
157 @user.pref[:my_page_layout] = layout
157 @user.pref[:my_page_layout] = layout
158 @user.pref.save
158 @user.pref.save
159 render :nothing => true
159 render :nothing => true
160 end
160 end
161
161
162 # Change blocks order on user's page
162 # Change blocks order on user's page
163 # params[:group] : group to order (top, left or right)
163 # params[:group] : group to order (top, left or right)
164 # params[:list-(top|left|right)] : array of block ids of the group
164 # params[:list-(top|left|right)] : array of block ids of the group
165 def order_blocks
165 def order_blocks
166 group = params[:group]
166 group = params[:group]
167 @user = User.current
167 @user = User.current
168 if group.is_a?(String)
168 if group.is_a?(String)
169 group_items = (params["list-#{group}"] || []).collect(&:underscore)
169 group_items = (params["list-#{group}"] || []).collect(&:underscore)
170 if group_items and group_items.is_a? Array
170 if group_items and group_items.is_a? Array
171 layout = @user.pref[:my_page_layout] || {}
171 layout = @user.pref[:my_page_layout] || {}
172 # remove group blocks if they are presents in other groups
172 # remove group blocks if they are presents in other groups
173 %w(top left right).each {|f|
173 %w(top left right).each {|f|
174 layout[f] = (layout[f] || []) - group_items
174 layout[f] = (layout[f] || []) - group_items
175 }
175 }
176 layout[group] = group_items
176 layout[group] = group_items
177 @user.pref[:my_page_layout] = layout
177 @user.pref[:my_page_layout] = layout
178 @user.pref.save
178 @user.pref.save
179 end
179 end
180 end
180 end
181 render :nothing => true
181 render :nothing => true
182 end
182 end
183 end
183 end
@@ -1,49 +1,58
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class AuthSource < ActiveRecord::Base
18 class AuthSource < ActiveRecord::Base
19 has_many :users
19 has_many :users
20
20
21 validates_presence_of :name
21 validates_presence_of :name
22 validates_uniqueness_of :name
22 validates_uniqueness_of :name
23 validates_length_of :name, :maximum => 60
23 validates_length_of :name, :maximum => 60
24
24
25 def authenticate(login, password)
25 def authenticate(login, password)
26 end
26 end
27
27
28 def test_connection
28 def test_connection
29 end
29 end
30
30
31 def auth_method_name
31 def auth_method_name
32 "Abstract"
32 "Abstract"
33 end
33 end
34
34
35 def allow_password_changes?
36 self.class.allow_password_changes?
37 end
38
39 # Does this auth source backend allow password changes?
40 def self.allow_password_changes?
41 false
42 end
43
35 # Try to authenticate a user not yet registered against available sources
44 # Try to authenticate a user not yet registered against available sources
36 def self.authenticate(login, password)
45 def self.authenticate(login, password)
37 AuthSource.find(:all, :conditions => ["onthefly_register=?", true]).each do |source|
46 AuthSource.find(:all, :conditions => ["onthefly_register=?", true]).each do |source|
38 begin
47 begin
39 logger.debug "Authenticating '#{login}' against '#{source.name}'" if logger && logger.debug?
48 logger.debug "Authenticating '#{login}' against '#{source.name}'" if logger && logger.debug?
40 attrs = source.authenticate(login, password)
49 attrs = source.authenticate(login, password)
41 rescue => e
50 rescue => e
42 logger.error "Error during authentication: #{e.message}"
51 logger.error "Error during authentication: #{e.message}"
43 attrs = nil
52 attrs = nil
44 end
53 end
45 return attrs if attrs
54 return attrs if attrs
46 end
55 end
47 return nil
56 return nil
48 end
57 end
49 end
58 end
@@ -1,360 +1,370
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require "digest/sha1"
18 require "digest/sha1"
19
19
20 class User < Principal
20 class User < Principal
21
21
22 # Account statuses
22 # Account statuses
23 STATUS_ANONYMOUS = 0
23 STATUS_ANONYMOUS = 0
24 STATUS_ACTIVE = 1
24 STATUS_ACTIVE = 1
25 STATUS_REGISTERED = 2
25 STATUS_REGISTERED = 2
26 STATUS_LOCKED = 3
26 STATUS_LOCKED = 3
27
27
28 USER_FORMATS = {
28 USER_FORMATS = {
29 :firstname_lastname => '#{firstname} #{lastname}',
29 :firstname_lastname => '#{firstname} #{lastname}',
30 :firstname => '#{firstname}',
30 :firstname => '#{firstname}',
31 :lastname_firstname => '#{lastname} #{firstname}',
31 :lastname_firstname => '#{lastname} #{firstname}',
32 :lastname_coma_firstname => '#{lastname}, #{firstname}',
32 :lastname_coma_firstname => '#{lastname}, #{firstname}',
33 :username => '#{login}'
33 :username => '#{login}'
34 }
34 }
35
35
36 has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
36 has_and_belongs_to_many :groups, :after_add => Proc.new {|user, group| group.user_added(user)},
37 :after_remove => Proc.new {|user, group| group.user_removed(user)}
37 :after_remove => Proc.new {|user, group| group.user_removed(user)}
38 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
38 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
39 has_many :changesets, :dependent => :nullify
39 has_many :changesets, :dependent => :nullify
40 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
40 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
41 has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
41 has_one :rss_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='feeds'"
42 has_one :api_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='api'"
42 has_one :api_token, :dependent => :destroy, :class_name => 'Token', :conditions => "action='api'"
43 belongs_to :auth_source
43 belongs_to :auth_source
44
44
45 # Active non-anonymous users scope
45 # Active non-anonymous users scope
46 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
46 named_scope :active, :conditions => "#{User.table_name}.status = #{STATUS_ACTIVE}"
47
47
48 acts_as_customizable
48 acts_as_customizable
49
49
50 attr_accessor :password, :password_confirmation
50 attr_accessor :password, :password_confirmation
51 attr_accessor :last_before_login_on
51 attr_accessor :last_before_login_on
52 # Prevents unauthorized assignments
52 # Prevents unauthorized assignments
53 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password, :group_ids
53 attr_protected :login, :admin, :password, :password_confirmation, :hashed_password, :group_ids
54
54
55 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
55 validates_presence_of :login, :firstname, :lastname, :mail, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
56 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }
56 validates_uniqueness_of :login, :if => Proc.new { |user| !user.login.blank? }
57 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
57 validates_uniqueness_of :mail, :if => Proc.new { |user| !user.mail.blank? }, :case_sensitive => false
58 # Login must contain lettres, numbers, underscores only
58 # Login must contain lettres, numbers, underscores only
59 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
59 validates_format_of :login, :with => /^[a-z0-9_\-@\.]*$/i
60 validates_length_of :login, :maximum => 30
60 validates_length_of :login, :maximum => 30
61 validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-\.]*$/i
61 validates_format_of :firstname, :lastname, :with => /^[\w\s\'\-\.]*$/i
62 validates_length_of :firstname, :lastname, :maximum => 30
62 validates_length_of :firstname, :lastname, :maximum => 30
63 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
63 validates_format_of :mail, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true
64 validates_length_of :mail, :maximum => 60, :allow_nil => true
64 validates_length_of :mail, :maximum => 60, :allow_nil => true
65 validates_confirmation_of :password, :allow_nil => true
65 validates_confirmation_of :password, :allow_nil => true
66
66
67 def before_create
67 def before_create
68 self.mail_notification = false
68 self.mail_notification = false
69 true
69 true
70 end
70 end
71
71
72 def before_save
72 def before_save
73 # update hashed_password if password was set
73 # update hashed_password if password was set
74 self.hashed_password = User.hash_password(self.password) if self.password
74 self.hashed_password = User.hash_password(self.password) if self.password && self.auth_source_id.blank?
75 end
75 end
76
76
77 def reload(*args)
77 def reload(*args)
78 @name = nil
78 @name = nil
79 super
79 super
80 end
80 end
81
81
82 def identity_url=(url)
82 def identity_url=(url)
83 if url.blank?
83 if url.blank?
84 write_attribute(:identity_url, '')
84 write_attribute(:identity_url, '')
85 else
85 else
86 begin
86 begin
87 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
87 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
88 rescue OpenIdAuthentication::InvalidOpenId
88 rescue OpenIdAuthentication::InvalidOpenId
89 # Invlaid url, don't save
89 # Invlaid url, don't save
90 end
90 end
91 end
91 end
92 self.read_attribute(:identity_url)
92 self.read_attribute(:identity_url)
93 end
93 end
94
94
95 # Returns the user that matches provided login and password, or nil
95 # Returns the user that matches provided login and password, or nil
96 def self.try_to_login(login, password)
96 def self.try_to_login(login, password)
97 # Make sure no one can sign in with an empty password
97 # Make sure no one can sign in with an empty password
98 return nil if password.to_s.empty?
98 return nil if password.to_s.empty?
99 user = find(:first, :conditions => ["login=?", login])
99 user = find(:first, :conditions => ["login=?", login])
100 if user
100 if user
101 # user is already in local database
101 # user is already in local database
102 return nil if !user.active?
102 return nil if !user.active?
103 if user.auth_source
103 if user.auth_source
104 # user has an external authentication method
104 # user has an external authentication method
105 return nil unless user.auth_source.authenticate(login, password)
105 return nil unless user.auth_source.authenticate(login, password)
106 else
106 else
107 # authentication with local password
107 # authentication with local password
108 return nil unless User.hash_password(password) == user.hashed_password
108 return nil unless User.hash_password(password) == user.hashed_password
109 end
109 end
110 else
110 else
111 # user is not yet registered, try to authenticate with available sources
111 # user is not yet registered, try to authenticate with available sources
112 attrs = AuthSource.authenticate(login, password)
112 attrs = AuthSource.authenticate(login, password)
113 if attrs
113 if attrs
114 user = new(attrs)
114 user = new(attrs)
115 user.login = login
115 user.login = login
116 user.language = Setting.default_language
116 user.language = Setting.default_language
117 if user.save
117 if user.save
118 user.reload
118 user.reload
119 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger
119 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
120 end
120 end
121 end
121 end
122 end
122 end
123 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
123 user.update_attribute(:last_login_on, Time.now) if user && !user.new_record?
124 user
124 user
125 rescue => text
125 rescue => text
126 raise text
126 raise text
127 end
127 end
128
128
129 # Returns the user who matches the given autologin +key+ or nil
129 # Returns the user who matches the given autologin +key+ or nil
130 def self.try_to_autologin(key)
130 def self.try_to_autologin(key)
131 tokens = Token.find_all_by_action_and_value('autologin', key)
131 tokens = Token.find_all_by_action_and_value('autologin', key)
132 # Make sure there's only 1 token that matches the key
132 # Make sure there's only 1 token that matches the key
133 if tokens.size == 1
133 if tokens.size == 1
134 token = tokens.first
134 token = tokens.first
135 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
135 if (token.created_on > Setting.autologin.to_i.day.ago) && token.user && token.user.active?
136 token.user.update_attribute(:last_login_on, Time.now)
136 token.user.update_attribute(:last_login_on, Time.now)
137 token.user
137 token.user
138 end
138 end
139 end
139 end
140 end
140 end
141
141
142 # Return user's full name for display
142 # Return user's full name for display
143 def name(formatter = nil)
143 def name(formatter = nil)
144 if formatter
144 if formatter
145 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
145 eval('"' + (USER_FORMATS[formatter] || USER_FORMATS[:firstname_lastname]) + '"')
146 else
146 else
147 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
147 @name ||= eval('"' + (USER_FORMATS[Setting.user_format] || USER_FORMATS[:firstname_lastname]) + '"')
148 end
148 end
149 end
149 end
150
150
151 def active?
151 def active?
152 self.status == STATUS_ACTIVE
152 self.status == STATUS_ACTIVE
153 end
153 end
154
154
155 def registered?
155 def registered?
156 self.status == STATUS_REGISTERED
156 self.status == STATUS_REGISTERED
157 end
157 end
158
158
159 def locked?
159 def locked?
160 self.status == STATUS_LOCKED
160 self.status == STATUS_LOCKED
161 end
161 end
162
162
163 def check_password?(clear_password)
163 def check_password?(clear_password)
164 User.hash_password(clear_password) == self.hashed_password
164 if auth_source_id.present?
165 auth_source.authenticate(self.login, clear_password)
166 else
167 User.hash_password(clear_password) == self.hashed_password
168 end
169 end
170
171 # Does the backend storage allow this user to change their password?
172 def change_password_allowed?
173 return true if auth_source_id.blank?
174 return auth_source.allow_password_changes?
165 end
175 end
166
176
167 # Generate and set a random password. Useful for automated user creation
177 # Generate and set a random password. Useful for automated user creation
168 # Based on Token#generate_token_value
178 # Based on Token#generate_token_value
169 #
179 #
170 def random_password
180 def random_password
171 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
181 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
172 password = ''
182 password = ''
173 40.times { |i| password << chars[rand(chars.size-1)] }
183 40.times { |i| password << chars[rand(chars.size-1)] }
174 self.password = password
184 self.password = password
175 self.password_confirmation = password
185 self.password_confirmation = password
176 self
186 self
177 end
187 end
178
188
179 def pref
189 def pref
180 self.preference ||= UserPreference.new(:user => self)
190 self.preference ||= UserPreference.new(:user => self)
181 end
191 end
182
192
183 def time_zone
193 def time_zone
184 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
194 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
185 end
195 end
186
196
187 def wants_comments_in_reverse_order?
197 def wants_comments_in_reverse_order?
188 self.pref[:comments_sorting] == 'desc'
198 self.pref[:comments_sorting] == 'desc'
189 end
199 end
190
200
191 # Return user's RSS key (a 40 chars long string), used to access feeds
201 # Return user's RSS key (a 40 chars long string), used to access feeds
192 def rss_key
202 def rss_key
193 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
203 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
194 token.value
204 token.value
195 end
205 end
196
206
197 # Return user's API key (a 40 chars long string), used to access the API
207 # Return user's API key (a 40 chars long string), used to access the API
198 def api_key
208 def api_key
199 token = self.api_token || self.create_api_token(:action => 'api')
209 token = self.api_token || self.create_api_token(:action => 'api')
200 token.value
210 token.value
201 end
211 end
202
212
203 # Return an array of project ids for which the user has explicitly turned mail notifications on
213 # Return an array of project ids for which the user has explicitly turned mail notifications on
204 def notified_projects_ids
214 def notified_projects_ids
205 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
215 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
206 end
216 end
207
217
208 def notified_project_ids=(ids)
218 def notified_project_ids=(ids)
209 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
219 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
210 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
220 Member.update_all("mail_notification = #{connection.quoted_true}", ['user_id = ? AND project_id IN (?)', id, ids]) if ids && !ids.empty?
211 @notified_projects_ids = nil
221 @notified_projects_ids = nil
212 notified_projects_ids
222 notified_projects_ids
213 end
223 end
214
224
215 def self.find_by_rss_key(key)
225 def self.find_by_rss_key(key)
216 token = Token.find_by_value(key)
226 token = Token.find_by_value(key)
217 token && token.user.active? ? token.user : nil
227 token && token.user.active? ? token.user : nil
218 end
228 end
219
229
220 def self.find_by_api_key(key)
230 def self.find_by_api_key(key)
221 token = Token.find_by_action_and_value('api', key)
231 token = Token.find_by_action_and_value('api', key)
222 token && token.user.active? ? token.user : nil
232 token && token.user.active? ? token.user : nil
223 end
233 end
224
234
225 # Makes find_by_mail case-insensitive
235 # Makes find_by_mail case-insensitive
226 def self.find_by_mail(mail)
236 def self.find_by_mail(mail)
227 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
237 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
228 end
238 end
229
239
230 def to_s
240 def to_s
231 name
241 name
232 end
242 end
233
243
234 # Returns the current day according to user's time zone
244 # Returns the current day according to user's time zone
235 def today
245 def today
236 if time_zone.nil?
246 if time_zone.nil?
237 Date.today
247 Date.today
238 else
248 else
239 Time.now.in_time_zone(time_zone).to_date
249 Time.now.in_time_zone(time_zone).to_date
240 end
250 end
241 end
251 end
242
252
243 def logged?
253 def logged?
244 true
254 true
245 end
255 end
246
256
247 def anonymous?
257 def anonymous?
248 !logged?
258 !logged?
249 end
259 end
250
260
251 # Return user's roles for project
261 # Return user's roles for project
252 def roles_for_project(project)
262 def roles_for_project(project)
253 roles = []
263 roles = []
254 # No role on archived projects
264 # No role on archived projects
255 return roles unless project && project.active?
265 return roles unless project && project.active?
256 if logged?
266 if logged?
257 # Find project membership
267 # Find project membership
258 membership = memberships.detect {|m| m.project_id == project.id}
268 membership = memberships.detect {|m| m.project_id == project.id}
259 if membership
269 if membership
260 roles = membership.roles
270 roles = membership.roles
261 else
271 else
262 @role_non_member ||= Role.non_member
272 @role_non_member ||= Role.non_member
263 roles << @role_non_member
273 roles << @role_non_member
264 end
274 end
265 else
275 else
266 @role_anonymous ||= Role.anonymous
276 @role_anonymous ||= Role.anonymous
267 roles << @role_anonymous
277 roles << @role_anonymous
268 end
278 end
269 roles
279 roles
270 end
280 end
271
281
272 # Return true if the user is a member of project
282 # Return true if the user is a member of project
273 def member_of?(project)
283 def member_of?(project)
274 !roles_for_project(project).detect {|role| role.member?}.nil?
284 !roles_for_project(project).detect {|role| role.member?}.nil?
275 end
285 end
276
286
277 # Return true if the user is allowed to do the specified action on project
287 # Return true if the user is allowed to do the specified action on project
278 # action can be:
288 # action can be:
279 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
289 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
280 # * a permission Symbol (eg. :edit_project)
290 # * a permission Symbol (eg. :edit_project)
281 def allowed_to?(action, project, options={})
291 def allowed_to?(action, project, options={})
282 if project
292 if project
283 # No action allowed on archived projects
293 # No action allowed on archived projects
284 return false unless project.active?
294 return false unless project.active?
285 # No action allowed on disabled modules
295 # No action allowed on disabled modules
286 return false unless project.allows_to?(action)
296 return false unless project.allows_to?(action)
287 # Admin users are authorized for anything else
297 # Admin users are authorized for anything else
288 return true if admin?
298 return true if admin?
289
299
290 roles = roles_for_project(project)
300 roles = roles_for_project(project)
291 return false unless roles
301 return false unless roles
292 roles.detect {|role| (project.is_public? || role.member?) && role.allowed_to?(action)}
302 roles.detect {|role| (project.is_public? || role.member?) && role.allowed_to?(action)}
293
303
294 elsif options[:global]
304 elsif options[:global]
295 # Admin users are always authorized
305 # Admin users are always authorized
296 return true if admin?
306 return true if admin?
297
307
298 # authorize if user has at least one role that has this permission
308 # authorize if user has at least one role that has this permission
299 roles = memberships.collect {|m| m.roles}.flatten.uniq
309 roles = memberships.collect {|m| m.roles}.flatten.uniq
300 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
310 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
301 else
311 else
302 false
312 false
303 end
313 end
304 end
314 end
305
315
306 def self.current=(user)
316 def self.current=(user)
307 @current_user = user
317 @current_user = user
308 end
318 end
309
319
310 def self.current
320 def self.current
311 @current_user ||= User.anonymous
321 @current_user ||= User.anonymous
312 end
322 end
313
323
314 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
324 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
315 # one anonymous user per database.
325 # one anonymous user per database.
316 def self.anonymous
326 def self.anonymous
317 anonymous_user = AnonymousUser.find(:first)
327 anonymous_user = AnonymousUser.find(:first)
318 if anonymous_user.nil?
328 if anonymous_user.nil?
319 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
329 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
320 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
330 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
321 end
331 end
322 anonymous_user
332 anonymous_user
323 end
333 end
324
334
325 protected
335 protected
326
336
327 def validate
337 def validate
328 # Password length validation based on setting
338 # Password length validation based on setting
329 if !password.nil? && password.size < Setting.password_min_length.to_i
339 if !password.nil? && password.size < Setting.password_min_length.to_i
330 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
340 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
331 end
341 end
332 end
342 end
333
343
334 private
344 private
335
345
336 # Return password digest
346 # Return password digest
337 def self.hash_password(clear_password)
347 def self.hash_password(clear_password)
338 Digest::SHA1.hexdigest(clear_password || "")
348 Digest::SHA1.hexdigest(clear_password || "")
339 end
349 end
340 end
350 end
341
351
342 class AnonymousUser < User
352 class AnonymousUser < User
343
353
344 def validate_on_create
354 def validate_on_create
345 # There should be only one AnonymousUser in the database
355 # There should be only one AnonymousUser in the database
346 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
356 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
347 end
357 end
348
358
349 def available_custom_fields
359 def available_custom_fields
350 []
360 []
351 end
361 end
352
362
353 # Overrides a few properties
363 # Overrides a few properties
354 def logged?; false end
364 def logged?; false end
355 def admin; false end
365 def admin; false end
356 def name(*args); I18n.t(:label_user_anonymous) end
366 def name(*args); I18n.t(:label_user_anonymous) end
357 def mail; nil end
367 def mail; nil end
358 def time_zone; nil end
368 def time_zone; nil end
359 def rss_key; nil end
369 def rss_key; nil end
360 end
370 end
@@ -1,62 +1,62
1 <div class="contextual">
1 <div class="contextual">
2 <%= link_to(l(:button_change_password), :action => 'password') unless @user.auth_source_id %>
2 <%= link_to(l(:button_change_password), :action => 'password') if @user.change_password_allowed? %>
3 <%= call_hook(:view_my_account_contextual, :user => @user)%>
3 <%= call_hook(:view_my_account_contextual, :user => @user)%>
4 </div>
4 </div>
5 <h2><%=l(:label_my_account)%></h2>
5 <h2><%=l(:label_my_account)%></h2>
6 <%= error_messages_for 'user' %>
6 <%= error_messages_for 'user' %>
7
7
8 <% form_for :user, @user, :url => { :action => "account" },
8 <% form_for :user, @user, :url => { :action => "account" },
9 :builder => TabularFormBuilder,
9 :builder => TabularFormBuilder,
10 :lang => current_language,
10 :lang => current_language,
11 :html => { :id => 'my_account_form' } do |f| %>
11 :html => { :id => 'my_account_form' } do |f| %>
12 <div class="splitcontentleft">
12 <div class="splitcontentleft">
13 <h3><%=l(:label_information_plural)%></h3>
13 <h3><%=l(:label_information_plural)%></h3>
14 <div class="box tabular">
14 <div class="box tabular">
15 <p><%= f.text_field :firstname, :required => true %></p>
15 <p><%= f.text_field :firstname, :required => true %></p>
16 <p><%= f.text_field :lastname, :required => true %></p>
16 <p><%= f.text_field :lastname, :required => true %></p>
17 <p><%= f.text_field :mail, :required => true %></p>
17 <p><%= f.text_field :mail, :required => true %></p>
18 <p><%= f.select :language, lang_options_for_select %></p>
18 <p><%= f.select :language, lang_options_for_select %></p>
19 <% if Setting.openid? %>
19 <% if Setting.openid? %>
20 <p><%= f.text_field :identity_url %></p>
20 <p><%= f.text_field :identity_url %></p>
21 <% end %>
21 <% end %>
22
22
23 <% @user.custom_field_values.select(&:editable?).each do |value| %>
23 <% @user.custom_field_values.select(&:editable?).each do |value| %>
24 <p><%= custom_field_tag_with_label :user, value %></p>
24 <p><%= custom_field_tag_with_label :user, value %></p>
25 <% end %>
25 <% end %>
26 <%= call_hook(:view_my_account, :user => @user, :form => f) %>
26 <%= call_hook(:view_my_account, :user => @user, :form => f) %>
27 </div>
27 </div>
28
28
29 <%= submit_tag l(:button_save) %>
29 <%= submit_tag l(:button_save) %>
30 </div>
30 </div>
31
31
32 <div class="splitcontentright">
32 <div class="splitcontentright">
33 <h3><%=l(:field_mail_notification)%></h3>
33 <h3><%=l(:field_mail_notification)%></h3>
34 <div class="box">
34 <div class="box">
35 <%= select_tag 'notification_option', options_for_select(@notification_options, @notification_option),
35 <%= select_tag 'notification_option', options_for_select(@notification_options, @notification_option),
36 :onchange => 'if ($("notification_option").value == "selected") {Element.show("notified-projects")} else {Element.hide("notified-projects")}' %>
36 :onchange => 'if ($("notification_option").value == "selected") {Element.show("notified-projects")} else {Element.hide("notified-projects")}' %>
37 <% content_tag 'div', :id => 'notified-projects', :style => (@notification_option == 'selected' ? '' : 'display:none;') do %>
37 <% content_tag 'div', :id => 'notified-projects', :style => (@notification_option == 'selected' ? '' : 'display:none;') do %>
38 <p><% User.current.projects.each do |project| %>
38 <p><% User.current.projects.each do |project| %>
39 <label><%= check_box_tag 'notified_project_ids[]', project.id, @user.notified_projects_ids.include?(project.id) %> <%=h project.name %></label><br />
39 <label><%= check_box_tag 'notified_project_ids[]', project.id, @user.notified_projects_ids.include?(project.id) %> <%=h project.name %></label><br />
40 <% end %></p>
40 <% end %></p>
41 <p><em><%= l(:text_user_mail_option) %></em></p>
41 <p><em><%= l(:text_user_mail_option) %></em></p>
42 <% end %>
42 <% end %>
43 <p><label><%= check_box_tag 'no_self_notified', 1, @user.pref[:no_self_notified] %> <%= l(:label_user_mail_no_self_notified) %></label></p>
43 <p><label><%= check_box_tag 'no_self_notified', 1, @user.pref[:no_self_notified] %> <%= l(:label_user_mail_no_self_notified) %></label></p>
44 </div>
44 </div>
45
45
46 <h3><%=l(:label_preferences)%></h3>
46 <h3><%=l(:label_preferences)%></h3>
47 <div class="box tabular">
47 <div class="box tabular">
48 <% fields_for :pref, @user.pref, :builder => TabularFormBuilder, :lang => current_language do |pref_fields| %>
48 <% fields_for :pref, @user.pref, :builder => TabularFormBuilder, :lang => current_language do |pref_fields| %>
49 <p><%= pref_fields.check_box :hide_mail %></p>
49 <p><%= pref_fields.check_box :hide_mail %></p>
50 <p><%= pref_fields.select :time_zone, ActiveSupport::TimeZone.all.collect {|z| [ z.to_s, z.name ]}, :include_blank => true %></p>
50 <p><%= pref_fields.select :time_zone, ActiveSupport::TimeZone.all.collect {|z| [ z.to_s, z.name ]}, :include_blank => true %></p>
51 <p><%= pref_fields.select :comments_sorting, [[l(:label_chronological_order), 'asc'], [l(:label_reverse_chronological_order), 'desc']] %></p>
51 <p><%= pref_fields.select :comments_sorting, [[l(:label_chronological_order), 'asc'], [l(:label_reverse_chronological_order), 'desc']] %></p>
52 <% end %>
52 <% end %>
53 </div>
53 </div>
54
54
55 </div>
55 </div>
56 <% end %>
56 <% end %>
57
57
58 <% content_for :sidebar do %>
58 <% content_for :sidebar do %>
59 <%= render :partial => 'sidebar' %>
59 <%= render :partial => 'sidebar' %>
60 <% end %>
60 <% end %>
61
61
62 <% html_title(l(:label_my_account)) -%>
62 <% html_title(l(:label_my_account)) -%>
@@ -1,312 +1,338
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class UserTest < ActiveSupport::TestCase
20 class UserTest < ActiveSupport::TestCase
21 fixtures :users, :members, :projects, :roles, :member_roles, :auth_sources
21 fixtures :users, :members, :projects, :roles, :member_roles, :auth_sources
22
22
23 def setup
23 def setup
24 @admin = User.find(1)
24 @admin = User.find(1)
25 @jsmith = User.find(2)
25 @jsmith = User.find(2)
26 @dlopper = User.find(3)
26 @dlopper = User.find(3)
27 end
27 end
28
28
29 test 'object_daddy creation' do
29 test 'object_daddy creation' do
30 User.generate_with_protected!(:firstname => 'Testing connection')
30 User.generate_with_protected!(:firstname => 'Testing connection')
31 User.generate_with_protected!(:firstname => 'Testing connection')
31 User.generate_with_protected!(:firstname => 'Testing connection')
32 assert_equal 2, User.count(:all, :conditions => {:firstname => 'Testing connection'})
32 assert_equal 2, User.count(:all, :conditions => {:firstname => 'Testing connection'})
33 end
33 end
34
34
35 def test_truth
35 def test_truth
36 assert_kind_of User, @jsmith
36 assert_kind_of User, @jsmith
37 end
37 end
38
38
39 def test_create
39 def test_create
40 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
40 user = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
41
41
42 user.login = "jsmith"
42 user.login = "jsmith"
43 user.password, user.password_confirmation = "password", "password"
43 user.password, user.password_confirmation = "password", "password"
44 # login uniqueness
44 # login uniqueness
45 assert !user.save
45 assert !user.save
46 assert_equal 1, user.errors.count
46 assert_equal 1, user.errors.count
47
47
48 user.login = "newuser"
48 user.login = "newuser"
49 user.password, user.password_confirmation = "passwd", "password"
49 user.password, user.password_confirmation = "passwd", "password"
50 # password confirmation
50 # password confirmation
51 assert !user.save
51 assert !user.save
52 assert_equal 1, user.errors.count
52 assert_equal 1, user.errors.count
53
53
54 user.password, user.password_confirmation = "password", "password"
54 user.password, user.password_confirmation = "password", "password"
55 assert user.save
55 assert user.save
56 end
56 end
57
57
58 def test_mail_uniqueness_should_not_be_case_sensitive
58 def test_mail_uniqueness_should_not_be_case_sensitive
59 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
59 u = User.new(:firstname => "new", :lastname => "user", :mail => "newuser@somenet.foo")
60 u.login = 'newuser1'
60 u.login = 'newuser1'
61 u.password, u.password_confirmation = "password", "password"
61 u.password, u.password_confirmation = "password", "password"
62 assert u.save
62 assert u.save
63
63
64 u = User.new(:firstname => "new", :lastname => "user", :mail => "newUser@Somenet.foo")
64 u = User.new(:firstname => "new", :lastname => "user", :mail => "newUser@Somenet.foo")
65 u.login = 'newuser2'
65 u.login = 'newuser2'
66 u.password, u.password_confirmation = "password", "password"
66 u.password, u.password_confirmation = "password", "password"
67 assert !u.save
67 assert !u.save
68 assert_equal I18n.translate('activerecord.errors.messages.taken'), u.errors.on(:mail)
68 assert_equal I18n.translate('activerecord.errors.messages.taken'), u.errors.on(:mail)
69 end
69 end
70
70
71 def test_update
71 def test_update
72 assert_equal "admin", @admin.login
72 assert_equal "admin", @admin.login
73 @admin.login = "john"
73 @admin.login = "john"
74 assert @admin.save, @admin.errors.full_messages.join("; ")
74 assert @admin.save, @admin.errors.full_messages.join("; ")
75 @admin.reload
75 @admin.reload
76 assert_equal "john", @admin.login
76 assert_equal "john", @admin.login
77 end
77 end
78
78
79 def test_destroy
79 def test_destroy
80 User.find(2).destroy
80 User.find(2).destroy
81 assert_nil User.find_by_id(2)
81 assert_nil User.find_by_id(2)
82 assert Member.find_all_by_user_id(2).empty?
82 assert Member.find_all_by_user_id(2).empty?
83 end
83 end
84
84
85 def test_validate
85 def test_validate
86 @admin.login = ""
86 @admin.login = ""
87 assert !@admin.save
87 assert !@admin.save
88 assert_equal 1, @admin.errors.count
88 assert_equal 1, @admin.errors.count
89 end
89 end
90
90
91 def test_password
91 def test_password
92 user = User.try_to_login("admin", "admin")
92 user = User.try_to_login("admin", "admin")
93 assert_kind_of User, user
93 assert_kind_of User, user
94 assert_equal "admin", user.login
94 assert_equal "admin", user.login
95 user.password = "hello"
95 user.password = "hello"
96 assert user.save
96 assert user.save
97
97
98 user = User.try_to_login("admin", "hello")
98 user = User.try_to_login("admin", "hello")
99 assert_kind_of User, user
99 assert_kind_of User, user
100 assert_equal "admin", user.login
100 assert_equal "admin", user.login
101 assert_equal User.hash_password("hello"), user.hashed_password
101 assert_equal User.hash_password("hello"), user.hashed_password
102 end
102 end
103
103
104 def test_name_format
104 def test_name_format
105 assert_equal 'Smith, John', @jsmith.name(:lastname_coma_firstname)
105 assert_equal 'Smith, John', @jsmith.name(:lastname_coma_firstname)
106 Setting.user_format = :firstname_lastname
106 Setting.user_format = :firstname_lastname
107 assert_equal 'John Smith', @jsmith.reload.name
107 assert_equal 'John Smith', @jsmith.reload.name
108 Setting.user_format = :username
108 Setting.user_format = :username
109 assert_equal 'jsmith', @jsmith.reload.name
109 assert_equal 'jsmith', @jsmith.reload.name
110 end
110 end
111
111
112 def test_lock
112 def test_lock
113 user = User.try_to_login("jsmith", "jsmith")
113 user = User.try_to_login("jsmith", "jsmith")
114 assert_equal @jsmith, user
114 assert_equal @jsmith, user
115
115
116 @jsmith.status = User::STATUS_LOCKED
116 @jsmith.status = User::STATUS_LOCKED
117 assert @jsmith.save
117 assert @jsmith.save
118
118
119 user = User.try_to_login("jsmith", "jsmith")
119 user = User.try_to_login("jsmith", "jsmith")
120 assert_equal nil, user
120 assert_equal nil, user
121 end
121 end
122
122
123 if ldap_configured?
123 if ldap_configured?
124 context "#try_to_login using LDAP" do
124 context "#try_to_login using LDAP" do
125 context "on the fly registration" do
125 context "on the fly registration" do
126 setup do
126 setup do
127 @auth_source = AuthSourceLdap.find(1)
127 @auth_source = AuthSourceLdap.find(1)
128 end
128 end
129
129
130 context "with a successful authentication" do
130 context "with a successful authentication" do
131 should "create a new user account if it doesn't exist" do
131 should "create a new user account if it doesn't exist" do
132 assert_difference('User.count') do
132 assert_difference('User.count') do
133 user = User.try_to_login('edavis', '123456')
133 user = User.try_to_login('edavis', '123456')
134 assert !user.admin?
134 assert !user.admin?
135 end
135 end
136 end
136 end
137
137
138 should "retrieve existing user" do
138 should "retrieve existing user" do
139 user = User.try_to_login('edavis', '123456')
139 user = User.try_to_login('edavis', '123456')
140 user.admin = true
140 user.admin = true
141 user.save!
141 user.save!
142
142
143 assert_no_difference('User.count') do
143 assert_no_difference('User.count') do
144 user = User.try_to_login('edavis', '123456')
144 user = User.try_to_login('edavis', '123456')
145 assert user.admin?
145 assert user.admin?
146 end
146 end
147 end
147 end
148 end
148 end
149 end
149 end
150 end
150 end
151
151
152 else
152 else
153 puts "Skipping LDAP tests."
153 puts "Skipping LDAP tests."
154 end
154 end
155
155
156 def test_create_anonymous
156 def test_create_anonymous
157 AnonymousUser.delete_all
157 AnonymousUser.delete_all
158 anon = User.anonymous
158 anon = User.anonymous
159 assert !anon.new_record?
159 assert !anon.new_record?
160 assert_kind_of AnonymousUser, anon
160 assert_kind_of AnonymousUser, anon
161 end
161 end
162
162
163 should_have_one :rss_token
163 should_have_one :rss_token
164
164
165 def test_rss_key
165 def test_rss_key
166 assert_nil @jsmith.rss_token
166 assert_nil @jsmith.rss_token
167 key = @jsmith.rss_key
167 key = @jsmith.rss_key
168 assert_equal 40, key.length
168 assert_equal 40, key.length
169
169
170 @jsmith.reload
170 @jsmith.reload
171 assert_equal key, @jsmith.rss_key
171 assert_equal key, @jsmith.rss_key
172 end
172 end
173
173
174
174
175 should_have_one :api_token
175 should_have_one :api_token
176
176
177 context "User#api_key" do
177 context "User#api_key" do
178 should "generate a new one if the user doesn't have one" do
178 should "generate a new one if the user doesn't have one" do
179 user = User.generate_with_protected!(:api_token => nil)
179 user = User.generate_with_protected!(:api_token => nil)
180 assert_nil user.api_token
180 assert_nil user.api_token
181
181
182 key = user.api_key
182 key = user.api_key
183 assert_equal 40, key.length
183 assert_equal 40, key.length
184 user.reload
184 user.reload
185 assert_equal key, user.api_key
185 assert_equal key, user.api_key
186 end
186 end
187
187
188 should "return the existing api token value" do
188 should "return the existing api token value" do
189 user = User.generate_with_protected!
189 user = User.generate_with_protected!
190 token = Token.generate!(:action => 'api')
190 token = Token.generate!(:action => 'api')
191 user.api_token = token
191 user.api_token = token
192 assert user.save
192 assert user.save
193
193
194 assert_equal token.value, user.api_key
194 assert_equal token.value, user.api_key
195 end
195 end
196 end
196 end
197
197
198 context "User#find_by_api_key" do
198 context "User#find_by_api_key" do
199 should "return nil if no matching key is found" do
199 should "return nil if no matching key is found" do
200 assert_nil User.find_by_api_key('zzzzzzzzz')
200 assert_nil User.find_by_api_key('zzzzzzzzz')
201 end
201 end
202
202
203 should "return nil if the key is found for an inactive user" do
203 should "return nil if the key is found for an inactive user" do
204 user = User.generate_with_protected!(:status => User::STATUS_LOCKED)
204 user = User.generate_with_protected!(:status => User::STATUS_LOCKED)
205 token = Token.generate!(:action => 'api')
205 token = Token.generate!(:action => 'api')
206 user.api_token = token
206 user.api_token = token
207 user.save
207 user.save
208
208
209 assert_nil User.find_by_api_key(token.value)
209 assert_nil User.find_by_api_key(token.value)
210 end
210 end
211
211
212 should "return the user if the key is found for an active user" do
212 should "return the user if the key is found for an active user" do
213 user = User.generate_with_protected!(:status => User::STATUS_ACTIVE)
213 user = User.generate_with_protected!(:status => User::STATUS_ACTIVE)
214 token = Token.generate!(:action => 'api')
214 token = Token.generate!(:action => 'api')
215 user.api_token = token
215 user.api_token = token
216 user.save
216 user.save
217
217
218 assert_equal user, User.find_by_api_key(token.value)
218 assert_equal user, User.find_by_api_key(token.value)
219 end
219 end
220 end
220 end
221
221
222 def test_roles_for_project
222 def test_roles_for_project
223 # user with a role
223 # user with a role
224 roles = @jsmith.roles_for_project(Project.find(1))
224 roles = @jsmith.roles_for_project(Project.find(1))
225 assert_kind_of Role, roles.first
225 assert_kind_of Role, roles.first
226 assert_equal "Manager", roles.first.name
226 assert_equal "Manager", roles.first.name
227
227
228 # user with no role
228 # user with no role
229 assert_nil @dlopper.roles_for_project(Project.find(2)).detect {|role| role.member?}
229 assert_nil @dlopper.roles_for_project(Project.find(2)).detect {|role| role.member?}
230 end
230 end
231
231
232 def test_mail_notification_all
232 def test_mail_notification_all
233 @jsmith.mail_notification = true
233 @jsmith.mail_notification = true
234 @jsmith.notified_project_ids = []
234 @jsmith.notified_project_ids = []
235 @jsmith.save
235 @jsmith.save
236 @jsmith.reload
236 @jsmith.reload
237 assert @jsmith.projects.first.recipients.include?(@jsmith.mail)
237 assert @jsmith.projects.first.recipients.include?(@jsmith.mail)
238 end
238 end
239
239
240 def test_mail_notification_selected
240 def test_mail_notification_selected
241 @jsmith.mail_notification = false
241 @jsmith.mail_notification = false
242 @jsmith.notified_project_ids = [1]
242 @jsmith.notified_project_ids = [1]
243 @jsmith.save
243 @jsmith.save
244 @jsmith.reload
244 @jsmith.reload
245 assert Project.find(1).recipients.include?(@jsmith.mail)
245 assert Project.find(1).recipients.include?(@jsmith.mail)
246 end
246 end
247
247
248 def test_mail_notification_none
248 def test_mail_notification_none
249 @jsmith.mail_notification = false
249 @jsmith.mail_notification = false
250 @jsmith.notified_project_ids = []
250 @jsmith.notified_project_ids = []
251 @jsmith.save
251 @jsmith.save
252 @jsmith.reload
252 @jsmith.reload
253 assert !@jsmith.projects.first.recipients.include?(@jsmith.mail)
253 assert !@jsmith.projects.first.recipients.include?(@jsmith.mail)
254 end
254 end
255
255
256 def test_comments_sorting_preference
256 def test_comments_sorting_preference
257 assert !@jsmith.wants_comments_in_reverse_order?
257 assert !@jsmith.wants_comments_in_reverse_order?
258 @jsmith.pref.comments_sorting = 'asc'
258 @jsmith.pref.comments_sorting = 'asc'
259 assert !@jsmith.wants_comments_in_reverse_order?
259 assert !@jsmith.wants_comments_in_reverse_order?
260 @jsmith.pref.comments_sorting = 'desc'
260 @jsmith.pref.comments_sorting = 'desc'
261 assert @jsmith.wants_comments_in_reverse_order?
261 assert @jsmith.wants_comments_in_reverse_order?
262 end
262 end
263
263
264 def test_find_by_mail_should_be_case_insensitive
264 def test_find_by_mail_should_be_case_insensitive
265 u = User.find_by_mail('JSmith@somenet.foo')
265 u = User.find_by_mail('JSmith@somenet.foo')
266 assert_not_nil u
266 assert_not_nil u
267 assert_equal 'jsmith@somenet.foo', u.mail
267 assert_equal 'jsmith@somenet.foo', u.mail
268 end
268 end
269
269
270 def test_random_password
270 def test_random_password
271 u = User.new
271 u = User.new
272 u.random_password
272 u.random_password
273 assert !u.password.blank?
273 assert !u.password.blank?
274 assert !u.password_confirmation.blank?
274 assert !u.password_confirmation.blank?
275 end
275 end
276
277 context "#change_password_allowed?" do
278 should "be allowed if no auth source is set" do
279 user = User.generate_with_protected!
280 assert user.change_password_allowed?
281 end
282
283 should "delegate to the auth source" do
284 user = User.generate_with_protected!
285
286 allowed_auth_source = AuthSource.generate!
287 def allowed_auth_source.allow_password_changes?; true; end
288
289 denied_auth_source = AuthSource.generate!
290 def denied_auth_source.allow_password_changes?; false; end
291
292 assert user.change_password_allowed?
293
294 user.auth_source = allowed_auth_source
295 assert user.change_password_allowed?, "User not allowed to change password, though auth source does"
296
297 user.auth_source = denied_auth_source
298 assert !user.change_password_allowed?, "User allowed to change password, though auth source does not"
299 end
300
301 end
276
302
277 if Object.const_defined?(:OpenID)
303 if Object.const_defined?(:OpenID)
278
304
279 def test_setting_identity_url
305 def test_setting_identity_url
280 normalized_open_id_url = 'http://example.com/'
306 normalized_open_id_url = 'http://example.com/'
281 u = User.new( :identity_url => 'http://example.com/' )
307 u = User.new( :identity_url => 'http://example.com/' )
282 assert_equal normalized_open_id_url, u.identity_url
308 assert_equal normalized_open_id_url, u.identity_url
283 end
309 end
284
310
285 def test_setting_identity_url_without_trailing_slash
311 def test_setting_identity_url_without_trailing_slash
286 normalized_open_id_url = 'http://example.com/'
312 normalized_open_id_url = 'http://example.com/'
287 u = User.new( :identity_url => 'http://example.com' )
313 u = User.new( :identity_url => 'http://example.com' )
288 assert_equal normalized_open_id_url, u.identity_url
314 assert_equal normalized_open_id_url, u.identity_url
289 end
315 end
290
316
291 def test_setting_identity_url_without_protocol
317 def test_setting_identity_url_without_protocol
292 normalized_open_id_url = 'http://example.com/'
318 normalized_open_id_url = 'http://example.com/'
293 u = User.new( :identity_url => 'example.com' )
319 u = User.new( :identity_url => 'example.com' )
294 assert_equal normalized_open_id_url, u.identity_url
320 assert_equal normalized_open_id_url, u.identity_url
295 end
321 end
296
322
297 def test_setting_blank_identity_url
323 def test_setting_blank_identity_url
298 u = User.new( :identity_url => 'example.com' )
324 u = User.new( :identity_url => 'example.com' )
299 u.identity_url = ''
325 u.identity_url = ''
300 assert u.identity_url.blank?
326 assert u.identity_url.blank?
301 end
327 end
302
328
303 def test_setting_invalid_identity_url
329 def test_setting_invalid_identity_url
304 u = User.new( :identity_url => 'this is not an openid url' )
330 u = User.new( :identity_url => 'this is not an openid url' )
305 assert u.identity_url.blank?
331 assert u.identity_url.blank?
306 end
332 end
307
333
308 else
334 else
309 puts "Skipping openid tests."
335 puts "Skipping openid tests."
310 end
336 end
311
337
312 end
338 end
General Comments 0
You need to be logged in to leave comments. Login now