##// END OF EJS Templates
Makes AuthSource.authenticate return a hash instead of an array....
Jean-Philippe Lang -
r3378:d6f9e576e88d
parent child
Show More
@@ -1,130 +1,130
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 'net/ldap'
18 require 'net/ldap'
19 require 'iconv'
19 require 'iconv'
20
20
21 class AuthSourceLdap < AuthSource
21 class AuthSourceLdap < AuthSource
22 validates_presence_of :host, :port, :attr_login
22 validates_presence_of :host, :port, :attr_login
23 validates_length_of :name, :host, :account_password, :maximum => 60, :allow_nil => true
23 validates_length_of :name, :host, :account_password, :maximum => 60, :allow_nil => true
24 validates_length_of :account, :base_dn, :maximum => 255, :allow_nil => true
24 validates_length_of :account, :base_dn, :maximum => 255, :allow_nil => true
25 validates_length_of :attr_login, :attr_firstname, :attr_lastname, :attr_mail, :maximum => 30, :allow_nil => true
25 validates_length_of :attr_login, :attr_firstname, :attr_lastname, :attr_mail, :maximum => 30, :allow_nil => true
26 validates_numericality_of :port, :only_integer => true
26 validates_numericality_of :port, :only_integer => true
27
27
28 before_validation :strip_ldap_attributes
28 before_validation :strip_ldap_attributes
29
29
30 def after_initialize
30 def after_initialize
31 self.port = 389 if self.port == 0
31 self.port = 389 if self.port == 0
32 end
32 end
33
33
34 def authenticate(login, password)
34 def authenticate(login, password)
35 return nil if login.blank? || password.blank?
35 return nil if login.blank? || password.blank?
36 attrs = get_user_dn(login)
36 attrs = get_user_dn(login)
37
37
38 if attrs.first && attrs.first[:dn] && authenticate_dn(attrs.first[:dn], password)
38 if attrs && attrs[:dn] && authenticate_dn(attrs[:dn], password)
39 logger.debug "Authentication successful for '#{login}'" if logger && logger.debug?
39 logger.debug "Authentication successful for '#{login}'" if logger && logger.debug?
40 return [] << attrs.first.except(:dn)
40 return attrs.except(:dn)
41 end
41 end
42 rescue Net::LDAP::LdapError => text
42 rescue Net::LDAP::LdapError => text
43 raise "LdapError: " + text
43 raise "LdapError: " + text
44 end
44 end
45
45
46 # test the connection to the LDAP
46 # test the connection to the LDAP
47 def test_connection
47 def test_connection
48 ldap_con = initialize_ldap_con(self.account, self.account_password)
48 ldap_con = initialize_ldap_con(self.account, self.account_password)
49 ldap_con.open { }
49 ldap_con.open { }
50 rescue Net::LDAP::LdapError => text
50 rescue Net::LDAP::LdapError => text
51 raise "LdapError: " + text
51 raise "LdapError: " + text
52 end
52 end
53
53
54 def auth_method_name
54 def auth_method_name
55 "LDAP"
55 "LDAP"
56 end
56 end
57
57
58 private
58 private
59
59
60 def strip_ldap_attributes
60 def strip_ldap_attributes
61 [:attr_login, :attr_firstname, :attr_lastname, :attr_mail].each do |attr|
61 [:attr_login, :attr_firstname, :attr_lastname, :attr_mail].each do |attr|
62 write_attribute(attr, read_attribute(attr).strip) unless read_attribute(attr).nil?
62 write_attribute(attr, read_attribute(attr).strip) unless read_attribute(attr).nil?
63 end
63 end
64 end
64 end
65
65
66 def initialize_ldap_con(ldap_user, ldap_password)
66 def initialize_ldap_con(ldap_user, ldap_password)
67 options = { :host => self.host,
67 options = { :host => self.host,
68 :port => self.port,
68 :port => self.port,
69 :encryption => (self.tls ? :simple_tls : nil)
69 :encryption => (self.tls ? :simple_tls : nil)
70 }
70 }
71 options.merge!(:auth => { :method => :simple, :username => ldap_user, :password => ldap_password }) unless ldap_user.blank? && ldap_password.blank?
71 options.merge!(:auth => { :method => :simple, :username => ldap_user, :password => ldap_password }) unless ldap_user.blank? && ldap_password.blank?
72 Net::LDAP.new options
72 Net::LDAP.new options
73 end
73 end
74
74
75 def get_user_attributes_from_ldap_entry(entry)
75 def get_user_attributes_from_ldap_entry(entry)
76 [
76 {
77 :dn => entry.dn,
77 :dn => entry.dn,
78 :firstname => AuthSourceLdap.get_attr(entry, self.attr_firstname),
78 :firstname => AuthSourceLdap.get_attr(entry, self.attr_firstname),
79 :lastname => AuthSourceLdap.get_attr(entry, self.attr_lastname),
79 :lastname => AuthSourceLdap.get_attr(entry, self.attr_lastname),
80 :mail => AuthSourceLdap.get_attr(entry, self.attr_mail),
80 :mail => AuthSourceLdap.get_attr(entry, self.attr_mail),
81 :auth_source_id => self.id
81 :auth_source_id => self.id
82 ]
82 }
83 end
83 end
84
84
85 # Return the attributes needed for the LDAP search. It will only
85 # Return the attributes needed for the LDAP search. It will only
86 # include the user attributes if on-the-fly registration is enabled
86 # include the user attributes if on-the-fly registration is enabled
87 def search_attributes
87 def search_attributes
88 if onthefly_register?
88 if onthefly_register?
89 ['dn', self.attr_firstname, self.attr_lastname, self.attr_mail]
89 ['dn', self.attr_firstname, self.attr_lastname, self.attr_mail]
90 else
90 else
91 ['dn']
91 ['dn']
92 end
92 end
93 end
93 end
94
94
95 # Check if a DN (user record) authenticates with the password
95 # Check if a DN (user record) authenticates with the password
96 def authenticate_dn(dn, password)
96 def authenticate_dn(dn, password)
97 if dn.present? && password.present?
97 if dn.present? && password.present?
98 initialize_ldap_con(dn, password).bind
98 initialize_ldap_con(dn, password).bind
99 end
99 end
100 end
100 end
101
101
102 # Get the user's dn and any attributes for them, given their login
102 # Get the user's dn and any attributes for them, given their login
103 def get_user_dn(login)
103 def get_user_dn(login)
104 ldap_con = initialize_ldap_con(self.account, self.account_password)
104 ldap_con = initialize_ldap_con(self.account, self.account_password)
105 login_filter = Net::LDAP::Filter.eq( self.attr_login, login )
105 login_filter = Net::LDAP::Filter.eq( self.attr_login, login )
106 object_filter = Net::LDAP::Filter.eq( "objectClass", "*" )
106 object_filter = Net::LDAP::Filter.eq( "objectClass", "*" )
107 attrs = []
107 attrs = {}
108
108
109 ldap_con.search( :base => self.base_dn,
109 ldap_con.search( :base => self.base_dn,
110 :filter => object_filter & login_filter,
110 :filter => object_filter & login_filter,
111 :attributes=> search_attributes) do |entry|
111 :attributes=> search_attributes) do |entry|
112
112
113 if onthefly_register?
113 if onthefly_register?
114 attrs = get_user_attributes_from_ldap_entry(entry)
114 attrs = get_user_attributes_from_ldap_entry(entry)
115 else
115 else
116 attrs = [:dn => entry.dn]
116 attrs = {:dn => entry.dn}
117 end
117 end
118
118
119 logger.debug "DN found for #{login}: #{attrs.first[:dn]}" if logger && logger.debug?
119 logger.debug "DN found for #{login}: #{attrs[:dn]}" if logger && logger.debug?
120 end
120 end
121
121
122 attrs
122 attrs
123 end
123 end
124
124
125 def self.get_attr(entry, attr_name)
125 def self.get_attr(entry, attr_name)
126 if !attr_name.blank?
126 if !attr_name.blank?
127 entry[attr_name].is_a?(Array) ? entry[attr_name].first : entry[attr_name]
127 entry[attr_name].is_a?(Array) ? entry[attr_name].first : entry[attr_name]
128 end
128 end
129 end
129 end
130 end
130 end
@@ -1,360 +1,360
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
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 the LDAP") if logger
119 logger.info("User '#{user.login}' created from the LDAP") if logger
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 User.hash_password(clear_password) == self.hashed_password
165 end
165 end
166
166
167 # Generate and set a random password. Useful for automated user creation
167 # Generate and set a random password. Useful for automated user creation
168 # Based on Token#generate_token_value
168 # Based on Token#generate_token_value
169 #
169 #
170 def random_password
170 def random_password
171 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
171 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
172 password = ''
172 password = ''
173 40.times { |i| password << chars[rand(chars.size-1)] }
173 40.times { |i| password << chars[rand(chars.size-1)] }
174 self.password = password
174 self.password = password
175 self.password_confirmation = password
175 self.password_confirmation = password
176 self
176 self
177 end
177 end
178
178
179 def pref
179 def pref
180 self.preference ||= UserPreference.new(:user => self)
180 self.preference ||= UserPreference.new(:user => self)
181 end
181 end
182
182
183 def time_zone
183 def time_zone
184 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
184 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
185 end
185 end
186
186
187 def wants_comments_in_reverse_order?
187 def wants_comments_in_reverse_order?
188 self.pref[:comments_sorting] == 'desc'
188 self.pref[:comments_sorting] == 'desc'
189 end
189 end
190
190
191 # Return user's RSS key (a 40 chars long string), used to access feeds
191 # Return user's RSS key (a 40 chars long string), used to access feeds
192 def rss_key
192 def rss_key
193 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
193 token = self.rss_token || Token.create(:user => self, :action => 'feeds')
194 token.value
194 token.value
195 end
195 end
196
196
197 # Return user's API key (a 40 chars long string), used to access the API
197 # Return user's API key (a 40 chars long string), used to access the API
198 def api_key
198 def api_key
199 token = self.api_token || self.create_api_token(:action => 'api')
199 token = self.api_token || self.create_api_token(:action => 'api')
200 token.value
200 token.value
201 end
201 end
202
202
203 # Return an array of project ids for which the user has explicitly turned mail notifications on
203 # Return an array of project ids for which the user has explicitly turned mail notifications on
204 def notified_projects_ids
204 def notified_projects_ids
205 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
205 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
206 end
206 end
207
207
208 def notified_project_ids=(ids)
208 def notified_project_ids=(ids)
209 Member.update_all("mail_notification = #{connection.quoted_false}", ['user_id = ?', id])
209 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?
210 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
211 @notified_projects_ids = nil
212 notified_projects_ids
212 notified_projects_ids
213 end
213 end
214
214
215 def self.find_by_rss_key(key)
215 def self.find_by_rss_key(key)
216 token = Token.find_by_value(key)
216 token = Token.find_by_value(key)
217 token && token.user.active? ? token.user : nil
217 token && token.user.active? ? token.user : nil
218 end
218 end
219
219
220 def self.find_by_api_key(key)
220 def self.find_by_api_key(key)
221 token = Token.find_by_action_and_value('api', key)
221 token = Token.find_by_action_and_value('api', key)
222 token && token.user.active? ? token.user : nil
222 token && token.user.active? ? token.user : nil
223 end
223 end
224
224
225 # Makes find_by_mail case-insensitive
225 # Makes find_by_mail case-insensitive
226 def self.find_by_mail(mail)
226 def self.find_by_mail(mail)
227 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
227 find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
228 end
228 end
229
229
230 def to_s
230 def to_s
231 name
231 name
232 end
232 end
233
233
234 # Returns the current day according to user's time zone
234 # Returns the current day according to user's time zone
235 def today
235 def today
236 if time_zone.nil?
236 if time_zone.nil?
237 Date.today
237 Date.today
238 else
238 else
239 Time.now.in_time_zone(time_zone).to_date
239 Time.now.in_time_zone(time_zone).to_date
240 end
240 end
241 end
241 end
242
242
243 def logged?
243 def logged?
244 true
244 true
245 end
245 end
246
246
247 def anonymous?
247 def anonymous?
248 !logged?
248 !logged?
249 end
249 end
250
250
251 # Return user's roles for project
251 # Return user's roles for project
252 def roles_for_project(project)
252 def roles_for_project(project)
253 roles = []
253 roles = []
254 # No role on archived projects
254 # No role on archived projects
255 return roles unless project && project.active?
255 return roles unless project && project.active?
256 if logged?
256 if logged?
257 # Find project membership
257 # Find project membership
258 membership = memberships.detect {|m| m.project_id == project.id}
258 membership = memberships.detect {|m| m.project_id == project.id}
259 if membership
259 if membership
260 roles = membership.roles
260 roles = membership.roles
261 else
261 else
262 @role_non_member ||= Role.non_member
262 @role_non_member ||= Role.non_member
263 roles << @role_non_member
263 roles << @role_non_member
264 end
264 end
265 else
265 else
266 @role_anonymous ||= Role.anonymous
266 @role_anonymous ||= Role.anonymous
267 roles << @role_anonymous
267 roles << @role_anonymous
268 end
268 end
269 roles
269 roles
270 end
270 end
271
271
272 # Return true if the user is a member of project
272 # Return true if the user is a member of project
273 def member_of?(project)
273 def member_of?(project)
274 !roles_for_project(project).detect {|role| role.member?}.nil?
274 !roles_for_project(project).detect {|role| role.member?}.nil?
275 end
275 end
276
276
277 # Return true if the user is allowed to do the specified action on project
277 # Return true if the user is allowed to do the specified action on project
278 # action can be:
278 # action can be:
279 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
279 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
280 # * a permission Symbol (eg. :edit_project)
280 # * a permission Symbol (eg. :edit_project)
281 def allowed_to?(action, project, options={})
281 def allowed_to?(action, project, options={})
282 if project
282 if project
283 # No action allowed on archived projects
283 # No action allowed on archived projects
284 return false unless project.active?
284 return false unless project.active?
285 # No action allowed on disabled modules
285 # No action allowed on disabled modules
286 return false unless project.allows_to?(action)
286 return false unless project.allows_to?(action)
287 # Admin users are authorized for anything else
287 # Admin users are authorized for anything else
288 return true if admin?
288 return true if admin?
289
289
290 roles = roles_for_project(project)
290 roles = roles_for_project(project)
291 return false unless roles
291 return false unless roles
292 roles.detect {|role| (project.is_public? || role.member?) && role.allowed_to?(action)}
292 roles.detect {|role| (project.is_public? || role.member?) && role.allowed_to?(action)}
293
293
294 elsif options[:global]
294 elsif options[:global]
295 # Admin users are always authorized
295 # Admin users are always authorized
296 return true if admin?
296 return true if admin?
297
297
298 # authorize if user has at least one role that has this permission
298 # authorize if user has at least one role that has this permission
299 roles = memberships.collect {|m| m.roles}.flatten.uniq
299 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))
300 roles.detect {|r| r.allowed_to?(action)} || (self.logged? ? Role.non_member.allowed_to?(action) : Role.anonymous.allowed_to?(action))
301 else
301 else
302 false
302 false
303 end
303 end
304 end
304 end
305
305
306 def self.current=(user)
306 def self.current=(user)
307 @current_user = user
307 @current_user = user
308 end
308 end
309
309
310 def self.current
310 def self.current
311 @current_user ||= User.anonymous
311 @current_user ||= User.anonymous
312 end
312 end
313
313
314 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
314 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
315 # one anonymous user per database.
315 # one anonymous user per database.
316 def self.anonymous
316 def self.anonymous
317 anonymous_user = AnonymousUser.find(:first)
317 anonymous_user = AnonymousUser.find(:first)
318 if anonymous_user.nil?
318 if anonymous_user.nil?
319 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
319 anonymous_user = AnonymousUser.create(:lastname => 'Anonymous', :firstname => '', :mail => '', :login => '', :status => 0)
320 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
320 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
321 end
321 end
322 anonymous_user
322 anonymous_user
323 end
323 end
324
324
325 protected
325 protected
326
326
327 def validate
327 def validate
328 # Password length validation based on setting
328 # Password length validation based on setting
329 if !password.nil? && password.size < Setting.password_min_length.to_i
329 if !password.nil? && password.size < Setting.password_min_length.to_i
330 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
330 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
331 end
331 end
332 end
332 end
333
333
334 private
334 private
335
335
336 # Return password digest
336 # Return password digest
337 def self.hash_password(clear_password)
337 def self.hash_password(clear_password)
338 Digest::SHA1.hexdigest(clear_password || "")
338 Digest::SHA1.hexdigest(clear_password || "")
339 end
339 end
340 end
340 end
341
341
342 class AnonymousUser < User
342 class AnonymousUser < User
343
343
344 def validate_on_create
344 def validate_on_create
345 # There should be only one AnonymousUser in the database
345 # There should be only one AnonymousUser in the database
346 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
346 errors.add_to_base 'An anonymous user already exists.' if AnonymousUser.find(:first)
347 end
347 end
348
348
349 def available_custom_fields
349 def available_custom_fields
350 []
350 []
351 end
351 end
352
352
353 # Overrides a few properties
353 # Overrides a few properties
354 def logged?; false end
354 def logged?; false end
355 def admin; false end
355 def admin; false end
356 def name(*args); I18n.t(:label_user_anonymous) end
356 def name(*args); I18n.t(:label_user_anonymous) end
357 def mail; nil end
357 def mail; nil end
358 def time_zone; nil end
358 def time_zone; nil end
359 def rss_key; nil end
359 def rss_key; nil end
360 end
360 end
@@ -1,206 +1,206
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 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 begin
20 begin
21 require 'mocha'
21 require 'mocha'
22 rescue
22 rescue
23 # Won't run some tests
23 # Won't run some tests
24 end
24 end
25
25
26 class AccountTest < ActionController::IntegrationTest
26 class AccountTest < ActionController::IntegrationTest
27 fixtures :users, :roles
27 fixtures :users, :roles
28
28
29 # Replace this with your real tests.
29 # Replace this with your real tests.
30 def test_login
30 def test_login
31 get "my/page"
31 get "my/page"
32 assert_redirected_to "/login?back_url=http%3A%2F%2Fwww.example.com%2Fmy%2Fpage"
32 assert_redirected_to "/login?back_url=http%3A%2F%2Fwww.example.com%2Fmy%2Fpage"
33 log_user('jsmith', 'jsmith')
33 log_user('jsmith', 'jsmith')
34
34
35 get "my/account"
35 get "my/account"
36 assert_response :success
36 assert_response :success
37 assert_template "my/account"
37 assert_template "my/account"
38 end
38 end
39
39
40 def test_autologin
40 def test_autologin
41 user = User.find(1)
41 user = User.find(1)
42 Setting.autologin = "7"
42 Setting.autologin = "7"
43 Token.delete_all
43 Token.delete_all
44
44
45 # User logs in with 'autologin' checked
45 # User logs in with 'autologin' checked
46 post '/login', :username => user.login, :password => 'admin', :autologin => 1
46 post '/login', :username => user.login, :password => 'admin', :autologin => 1
47 assert_redirected_to 'my/page'
47 assert_redirected_to 'my/page'
48 token = Token.find :first
48 token = Token.find :first
49 assert_not_nil token
49 assert_not_nil token
50 assert_equal user, token.user
50 assert_equal user, token.user
51 assert_equal 'autologin', token.action
51 assert_equal 'autologin', token.action
52 assert_equal user.id, session[:user_id]
52 assert_equal user.id, session[:user_id]
53 assert_equal token.value, cookies['autologin']
53 assert_equal token.value, cookies['autologin']
54
54
55 # Session is cleared
55 # Session is cleared
56 reset!
56 reset!
57 User.current = nil
57 User.current = nil
58 # Clears user's last login timestamp
58 # Clears user's last login timestamp
59 user.update_attribute :last_login_on, nil
59 user.update_attribute :last_login_on, nil
60 assert_nil user.reload.last_login_on
60 assert_nil user.reload.last_login_on
61
61
62 # User comes back with his autologin cookie
62 # User comes back with his autologin cookie
63 cookies[:autologin] = token.value
63 cookies[:autologin] = token.value
64 get '/my/page'
64 get '/my/page'
65 assert_response :success
65 assert_response :success
66 assert_template 'my/page'
66 assert_template 'my/page'
67 assert_equal user.id, session[:user_id]
67 assert_equal user.id, session[:user_id]
68 assert_not_nil user.reload.last_login_on
68 assert_not_nil user.reload.last_login_on
69 assert user.last_login_on.utc > 10.second.ago.utc
69 assert user.last_login_on.utc > 10.second.ago.utc
70 end
70 end
71
71
72 def test_lost_password
72 def test_lost_password
73 Token.delete_all
73 Token.delete_all
74
74
75 get "account/lost_password"
75 get "account/lost_password"
76 assert_response :success
76 assert_response :success
77 assert_template "account/lost_password"
77 assert_template "account/lost_password"
78
78
79 post "account/lost_password", :mail => 'jSmith@somenet.foo'
79 post "account/lost_password", :mail => 'jSmith@somenet.foo'
80 assert_redirected_to "/login"
80 assert_redirected_to "/login"
81
81
82 token = Token.find(:first)
82 token = Token.find(:first)
83 assert_equal 'recovery', token.action
83 assert_equal 'recovery', token.action
84 assert_equal 'jsmith@somenet.foo', token.user.mail
84 assert_equal 'jsmith@somenet.foo', token.user.mail
85 assert !token.expired?
85 assert !token.expired?
86
86
87 get "account/lost_password", :token => token.value
87 get "account/lost_password", :token => token.value
88 assert_response :success
88 assert_response :success
89 assert_template "account/password_recovery"
89 assert_template "account/password_recovery"
90
90
91 post "account/lost_password", :token => token.value, :new_password => 'newpass', :new_password_confirmation => 'newpass'
91 post "account/lost_password", :token => token.value, :new_password => 'newpass', :new_password_confirmation => 'newpass'
92 assert_redirected_to "/login"
92 assert_redirected_to "/login"
93 assert_equal 'Password was successfully updated.', flash[:notice]
93 assert_equal 'Password was successfully updated.', flash[:notice]
94
94
95 log_user('jsmith', 'newpass')
95 log_user('jsmith', 'newpass')
96 assert_equal 0, Token.count
96 assert_equal 0, Token.count
97 end
97 end
98
98
99 def test_register_with_automatic_activation
99 def test_register_with_automatic_activation
100 Setting.self_registration = '3'
100 Setting.self_registration = '3'
101
101
102 get 'account/register'
102 get 'account/register'
103 assert_response :success
103 assert_response :success
104 assert_template 'account/register'
104 assert_template 'account/register'
105
105
106 post 'account/register', :user => {:login => "newuser", :language => "en", :firstname => "New", :lastname => "User", :mail => "newuser@foo.bar"},
106 post 'account/register', :user => {:login => "newuser", :language => "en", :firstname => "New", :lastname => "User", :mail => "newuser@foo.bar"},
107 :password => "newpass", :password_confirmation => "newpass"
107 :password => "newpass", :password_confirmation => "newpass"
108 assert_redirected_to 'my/account'
108 assert_redirected_to 'my/account'
109 follow_redirect!
109 follow_redirect!
110 assert_response :success
110 assert_response :success
111 assert_template 'my/account'
111 assert_template 'my/account'
112
112
113 user = User.find_by_login('newuser')
113 user = User.find_by_login('newuser')
114 assert_not_nil user
114 assert_not_nil user
115 assert user.active?
115 assert user.active?
116 assert_not_nil user.last_login_on
116 assert_not_nil user.last_login_on
117 end
117 end
118
118
119 def test_register_with_manual_activation
119 def test_register_with_manual_activation
120 Setting.self_registration = '2'
120 Setting.self_registration = '2'
121
121
122 post 'account/register', :user => {:login => "newuser", :language => "en", :firstname => "New", :lastname => "User", :mail => "newuser@foo.bar"},
122 post 'account/register', :user => {:login => "newuser", :language => "en", :firstname => "New", :lastname => "User", :mail => "newuser@foo.bar"},
123 :password => "newpass", :password_confirmation => "newpass"
123 :password => "newpass", :password_confirmation => "newpass"
124 assert_redirected_to '/login'
124 assert_redirected_to '/login'
125 assert !User.find_by_login('newuser').active?
125 assert !User.find_by_login('newuser').active?
126 end
126 end
127
127
128 def test_register_with_email_activation
128 def test_register_with_email_activation
129 Setting.self_registration = '1'
129 Setting.self_registration = '1'
130 Token.delete_all
130 Token.delete_all
131
131
132 post 'account/register', :user => {:login => "newuser", :language => "en", :firstname => "New", :lastname => "User", :mail => "newuser@foo.bar"},
132 post 'account/register', :user => {:login => "newuser", :language => "en", :firstname => "New", :lastname => "User", :mail => "newuser@foo.bar"},
133 :password => "newpass", :password_confirmation => "newpass"
133 :password => "newpass", :password_confirmation => "newpass"
134 assert_redirected_to '/login'
134 assert_redirected_to '/login'
135 assert !User.find_by_login('newuser').active?
135 assert !User.find_by_login('newuser').active?
136
136
137 token = Token.find(:first)
137 token = Token.find(:first)
138 assert_equal 'register', token.action
138 assert_equal 'register', token.action
139 assert_equal 'newuser@foo.bar', token.user.mail
139 assert_equal 'newuser@foo.bar', token.user.mail
140 assert !token.expired?
140 assert !token.expired?
141
141
142 get 'account/activate', :token => token.value
142 get 'account/activate', :token => token.value
143 assert_redirected_to '/login'
143 assert_redirected_to '/login'
144 log_user('newuser', 'newpass')
144 log_user('newuser', 'newpass')
145 end
145 end
146
146
147 if Object.const_defined?(:Mocha)
147 if Object.const_defined?(:Mocha)
148
148
149 def test_onthefly_registration
149 def test_onthefly_registration
150 # disable registration
150 # disable registration
151 Setting.self_registration = '0'
151 Setting.self_registration = '0'
152 AuthSource.expects(:authenticate).returns([:login => 'foo', :firstname => 'Foo', :lastname => 'Smith', :mail => 'foo@bar.com', :auth_source_id => 66])
152 AuthSource.expects(:authenticate).returns({:login => 'foo', :firstname => 'Foo', :lastname => 'Smith', :mail => 'foo@bar.com', :auth_source_id => 66})
153
153
154 post 'account/login', :username => 'foo', :password => 'bar'
154 post 'account/login', :username => 'foo', :password => 'bar'
155 assert_redirected_to 'my/page'
155 assert_redirected_to 'my/page'
156
156
157 user = User.find_by_login('foo')
157 user = User.find_by_login('foo')
158 assert user.is_a?(User)
158 assert user.is_a?(User)
159 assert_equal 66, user.auth_source_id
159 assert_equal 66, user.auth_source_id
160 assert user.hashed_password.blank?
160 assert user.hashed_password.blank?
161 end
161 end
162
162
163 def test_onthefly_registration_with_invalid_attributes
163 def test_onthefly_registration_with_invalid_attributes
164 # disable registration
164 # disable registration
165 Setting.self_registration = '0'
165 Setting.self_registration = '0'
166 AuthSource.expects(:authenticate).returns([:login => 'foo', :lastname => 'Smith', :auth_source_id => 66])
166 AuthSource.expects(:authenticate).returns({:login => 'foo', :lastname => 'Smith', :auth_source_id => 66})
167
167
168 post 'account/login', :username => 'foo', :password => 'bar'
168 post 'account/login', :username => 'foo', :password => 'bar'
169 assert_response :success
169 assert_response :success
170 assert_template 'account/register'
170 assert_template 'account/register'
171 assert_tag :input, :attributes => { :name => 'user[firstname]', :value => '' }
171 assert_tag :input, :attributes => { :name => 'user[firstname]', :value => '' }
172 assert_tag :input, :attributes => { :name => 'user[lastname]', :value => 'Smith' }
172 assert_tag :input, :attributes => { :name => 'user[lastname]', :value => 'Smith' }
173 assert_no_tag :input, :attributes => { :name => 'user[login]' }
173 assert_no_tag :input, :attributes => { :name => 'user[login]' }
174 assert_no_tag :input, :attributes => { :name => 'user[password]' }
174 assert_no_tag :input, :attributes => { :name => 'user[password]' }
175
175
176 post 'account/register', :user => {:firstname => 'Foo', :lastname => 'Smith', :mail => 'foo@bar.com'}
176 post 'account/register', :user => {:firstname => 'Foo', :lastname => 'Smith', :mail => 'foo@bar.com'}
177 assert_redirected_to '/my/account'
177 assert_redirected_to '/my/account'
178
178
179 user = User.find_by_login('foo')
179 user = User.find_by_login('foo')
180 assert user.is_a?(User)
180 assert user.is_a?(User)
181 assert_equal 66, user.auth_source_id
181 assert_equal 66, user.auth_source_id
182 assert user.hashed_password.blank?
182 assert user.hashed_password.blank?
183 end
183 end
184
184
185 def test_login_and_logout_should_clear_session
185 def test_login_and_logout_should_clear_session
186 get '/login'
186 get '/login'
187 sid = session[:session_id]
187 sid = session[:session_id]
188
188
189 post '/login', :username => 'admin', :password => 'admin'
189 post '/login', :username => 'admin', :password => 'admin'
190 assert_redirected_to 'my/page'
190 assert_redirected_to 'my/page'
191 assert_not_equal sid, session[:session_id], "login should reset session"
191 assert_not_equal sid, session[:session_id], "login should reset session"
192 assert_equal 1, session[:user_id]
192 assert_equal 1, session[:user_id]
193 sid = session[:session_id]
193 sid = session[:session_id]
194
194
195 get '/'
195 get '/'
196 assert_equal sid, session[:session_id]
196 assert_equal sid, session[:session_id]
197
197
198 get '/logout'
198 get '/logout'
199 assert_not_equal sid, session[:session_id], "logout should reset session"
199 assert_not_equal sid, session[:session_id], "logout should reset session"
200 assert_nil session[:user_id]
200 assert_nil session[:user_id]
201 end
201 end
202
202
203 else
203 else
204 puts 'Mocha is missing. Skipping tests.'
204 puts 'Mocha is missing. Skipping tests.'
205 end
205 end
206 end
206 end
@@ -1,82 +1,80
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 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 AuthSourceLdapTest < ActiveSupport::TestCase
20 class AuthSourceLdapTest < ActiveSupport::TestCase
21 fixtures :auth_sources
21 fixtures :auth_sources
22
22
23 def setup
23 def setup
24 end
24 end
25
25
26 def test_create
26 def test_create
27 a = AuthSourceLdap.new(:name => 'My LDAP', :host => 'ldap.example.net', :port => 389, :base_dn => 'dc=example,dc=net', :attr_login => 'sAMAccountName')
27 a = AuthSourceLdap.new(:name => 'My LDAP', :host => 'ldap.example.net', :port => 389, :base_dn => 'dc=example,dc=net', :attr_login => 'sAMAccountName')
28 assert a.save
28 assert a.save
29 end
29 end
30
30
31 def test_should_strip_ldap_attributes
31 def test_should_strip_ldap_attributes
32 a = AuthSourceLdap.new(:name => 'My LDAP', :host => 'ldap.example.net', :port => 389, :base_dn => 'dc=example,dc=net', :attr_login => 'sAMAccountName',
32 a = AuthSourceLdap.new(:name => 'My LDAP', :host => 'ldap.example.net', :port => 389, :base_dn => 'dc=example,dc=net', :attr_login => 'sAMAccountName',
33 :attr_firstname => 'givenName ')
33 :attr_firstname => 'givenName ')
34 assert a.save
34 assert a.save
35 assert_equal 'givenName', a.reload.attr_firstname
35 assert_equal 'givenName', a.reload.attr_firstname
36 end
36 end
37
37
38 if ldap_configured?
38 if ldap_configured?
39 context '#authenticate' do
39 context '#authenticate' do
40 setup do
40 setup do
41 @auth = AuthSourceLdap.find(1)
41 @auth = AuthSourceLdap.find(1)
42 end
42 end
43
43
44 context 'with a valid LDAP user' do
44 context 'with a valid LDAP user' do
45 should 'return the user attributes' do
45 should 'return the user attributes' do
46 response = @auth.authenticate('example1','123456')
46 attributes = @auth.authenticate('example1','123456')
47 assert response.is_a?(Array), "An array was not returned"
47 assert attributes.is_a?(Hash), "An hash was not returned"
48 assert response.first.present?, "No user data returned"
49 attributes = response.first
50 assert_equal 'Example', attributes[:firstname]
48 assert_equal 'Example', attributes[:firstname]
51 assert_equal 'One', attributes[:lastname]
49 assert_equal 'One', attributes[:lastname]
52 assert_equal 'example1@redmine.org', attributes[:mail]
50 assert_equal 'example1@redmine.org', attributes[:mail]
53 assert_equal @auth.id, attributes[:auth_source_id]
51 assert_equal @auth.id, attributes[:auth_source_id]
54 attributes.keys.each do |attribute|
52 attributes.keys.each do |attribute|
55 assert User.new.respond_to?("#{attribute}="), "Unexpected :#{attribute} attribute returned"
53 assert User.new.respond_to?("#{attribute}="), "Unexpected :#{attribute} attribute returned"
56 end
54 end
57 end
55 end
58 end
56 end
59
57
60 context 'with an invalid LDAP user' do
58 context 'with an invalid LDAP user' do
61 should 'return nil' do
59 should 'return nil' do
62 assert_equal nil, @auth.authenticate('nouser','123456')
60 assert_equal nil, @auth.authenticate('nouser','123456')
63 end
61 end
64 end
62 end
65
63
66 context 'without a login' do
64 context 'without a login' do
67 should 'return nil' do
65 should 'return nil' do
68 assert_equal nil, @auth.authenticate('','123456')
66 assert_equal nil, @auth.authenticate('','123456')
69 end
67 end
70 end
68 end
71
69
72 context 'without a password' do
70 context 'without a password' do
73 should 'return nil' do
71 should 'return nil' do
74 assert_equal nil, @auth.authenticate('edavis','')
72 assert_equal nil, @auth.authenticate('edavis','')
75 end
73 end
76 end
74 end
77
75
78 end
76 end
79 else
77 else
80 puts '(Test LDAP server not configured)'
78 puts '(Test LDAP server not configured)'
81 end
79 end
82 end
80 end
General Comments 0
You need to be logged in to leave comments. Login now