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