##// END OF EJS Templates
Don't preload projects and roles on Principal#memberships association (#23519)....
Jean-Philippe Lang -
r15771:3deb70d4aab9
parent child
Show More
@@ -1,187 +1,187
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 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 UsersController < ApplicationController
19 19 layout 'admin'
20 20 self.main_menu = false
21 21
22 22 before_action :require_admin, :except => :show
23 23 before_action :find_user, :only => [:show, :edit, :update, :destroy]
24 24 accept_api_auth :index, :show, :create, :update, :destroy
25 25
26 26 helper :sort
27 27 include SortHelper
28 28 helper :custom_fields
29 29 include CustomFieldsHelper
30 30 helper :principal_memberships
31 31
32 32 require_sudo_mode :create, :update, :destroy
33 33
34 34 def index
35 35 sort_init 'login', 'asc'
36 36 sort_update %w(login firstname lastname admin created_on last_login_on)
37 37
38 38 case params[:format]
39 39 when 'xml', 'json'
40 40 @offset, @limit = api_offset_and_limit
41 41 else
42 42 @limit = per_page_option
43 43 end
44 44
45 45 @status = params[:status] || 1
46 46
47 47 scope = User.logged.status(@status).preload(:email_address)
48 48 scope = scope.like(params[:name]) if params[:name].present?
49 49 scope = scope.in_group(params[:group_id]) if params[:group_id].present?
50 50
51 51 @user_count = scope.count
52 52 @user_pages = Paginator.new @user_count, @limit, params['page']
53 53 @offset ||= @user_pages.offset
54 54 @users = scope.order(sort_clause).limit(@limit).offset(@offset).to_a
55 55
56 56 respond_to do |format|
57 57 format.html {
58 58 @groups = Group.givable.sort
59 59 render :layout => !request.xhr?
60 60 }
61 61 format.api
62 62 end
63 63 end
64 64
65 65 def show
66 66 unless @user.visible?
67 67 render_404
68 68 return
69 69 end
70 70
71 71 # show projects based on current user visibility
72 @memberships = @user.memberships.where(Project.visible_condition(User.current)).to_a
72 @memberships = @user.memberships.preload(:roles, :project).where(Project.visible_condition(User.current)).to_a
73 73
74 74 respond_to do |format|
75 75 format.html {
76 76 events = Redmine::Activity::Fetcher.new(User.current, :author => @user).events(nil, nil, :limit => 10)
77 77 @events_by_day = events.group_by {|event| User.current.time_to_date(event.event_datetime)}
78 78 render :layout => 'base'
79 79 }
80 80 format.api
81 81 end
82 82 end
83 83
84 84 def new
85 85 @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option)
86 86 @user.safe_attributes = params[:user]
87 87 @auth_sources = AuthSource.all
88 88 end
89 89
90 90 def create
91 91 @user = User.new(:language => Setting.default_language, :mail_notification => Setting.default_notification_option, :admin => false)
92 92 @user.safe_attributes = params[:user]
93 93 @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation] unless @user.auth_source_id
94 94 @user.pref.safe_attributes = params[:pref]
95 95
96 96 if @user.save
97 97 Mailer.account_information(@user, @user.password).deliver if params[:send_information]
98 98
99 99 respond_to do |format|
100 100 format.html {
101 101 flash[:notice] = l(:notice_user_successful_create, :id => view_context.link_to(@user.login, user_path(@user)))
102 102 if params[:continue]
103 103 attrs = params[:user].slice(:generate_password)
104 104 redirect_to new_user_path(:user => attrs)
105 105 else
106 106 redirect_to edit_user_path(@user)
107 107 end
108 108 }
109 109 format.api { render :action => 'show', :status => :created, :location => user_url(@user) }
110 110 end
111 111 else
112 112 @auth_sources = AuthSource.all
113 113 # Clear password input
114 114 @user.password = @user.password_confirmation = nil
115 115
116 116 respond_to do |format|
117 117 format.html { render :action => 'new' }
118 118 format.api { render_validation_errors(@user) }
119 119 end
120 120 end
121 121 end
122 122
123 123 def edit
124 124 @auth_sources = AuthSource.all
125 125 @membership ||= Member.new
126 126 end
127 127
128 128 def update
129 129 if params[:user][:password].present? && (@user.auth_source_id.nil? || params[:user][:auth_source_id].blank?)
130 130 @user.password, @user.password_confirmation = params[:user][:password], params[:user][:password_confirmation]
131 131 end
132 132 @user.safe_attributes = params[:user]
133 133 # Was the account actived ? (do it before User#save clears the change)
134 134 was_activated = (@user.status_change == [User::STATUS_REGISTERED, User::STATUS_ACTIVE])
135 135 # TODO: Similar to My#account
136 136 @user.pref.safe_attributes = params[:pref]
137 137
138 138 if @user.save
139 139 @user.pref.save
140 140
141 141 if was_activated
142 142 Mailer.account_activated(@user).deliver
143 143 elsif @user.active? && params[:send_information] && @user.password.present? && @user.auth_source_id.nil? && @user != User.current
144 144 Mailer.account_information(@user, @user.password).deliver
145 145 end
146 146
147 147 respond_to do |format|
148 148 format.html {
149 149 flash[:notice] = l(:notice_successful_update)
150 150 redirect_to_referer_or edit_user_path(@user)
151 151 }
152 152 format.api { render_api_ok }
153 153 end
154 154 else
155 155 @auth_sources = AuthSource.all
156 156 @membership ||= Member.new
157 157 # Clear password input
158 158 @user.password = @user.password_confirmation = nil
159 159
160 160 respond_to do |format|
161 161 format.html { render :action => :edit }
162 162 format.api { render_validation_errors(@user) }
163 163 end
164 164 end
165 165 end
166 166
167 167 def destroy
168 168 @user.destroy
169 169 respond_to do |format|
170 170 format.html { redirect_back_or_default(users_path) }
171 171 format.api { render_api_ok }
172 172 end
173 173 end
174 174
175 175 private
176 176
177 177 def find_user
178 178 if params[:id] == 'current'
179 179 require_login || return
180 180 @user = User.current
181 181 else
182 182 @user = User.find(params[:id])
183 183 end
184 184 rescue ActiveRecord::RecordNotFound
185 185 render_404
186 186 end
187 187 end
@@ -1,206 +1,205
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 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 Principal < ActiveRecord::Base
19 19 self.table_name = "#{table_name_prefix}users#{table_name_suffix}"
20 20
21 21 # Account statuses
22 22 STATUS_ANONYMOUS = 0
23 23 STATUS_ACTIVE = 1
24 24 STATUS_REGISTERED = 2
25 25 STATUS_LOCKED = 3
26 26
27 27 class_attribute :valid_statuses
28 28
29 29 has_many :members, :foreign_key => 'user_id', :dependent => :destroy
30 30 has_many :memberships,
31 lambda {preload(:project, :roles).
32 joins(:project).
31 lambda {joins(:project).
33 32 where("#{Project.table_name}.status<>#{Project::STATUS_ARCHIVED}")},
34 33 :class_name => 'Member',
35 34 :foreign_key => 'user_id'
36 35 has_many :projects, :through => :memberships
37 36 has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify
38 37
39 38 validate :validate_status
40 39
41 40 # Groups and active users
42 41 scope :active, lambda { where(:status => STATUS_ACTIVE) }
43 42
44 43 scope :visible, lambda {|*args|
45 44 user = args.first || User.current
46 45
47 46 if user.admin?
48 47 all
49 48 else
50 49 view_all_active = false
51 50 if user.memberships.to_a.any?
52 51 view_all_active = user.memberships.any? {|m| m.roles.any? {|r| r.users_visibility == 'all'}}
53 52 else
54 53 view_all_active = user.builtin_role.users_visibility == 'all'
55 54 end
56 55
57 56 if view_all_active
58 57 active
59 58 else
60 59 # self and members of visible projects
61 60 active.where("#{table_name}.id = ? OR #{table_name}.id IN (SELECT user_id FROM #{Member.table_name} WHERE project_id IN (?))",
62 61 user.id, user.visible_project_ids
63 62 )
64 63 end
65 64 end
66 65 }
67 66
68 67 scope :like, lambda {|q|
69 68 q = q.to_s
70 69 if q.blank?
71 70 where({})
72 71 else
73 72 pattern = "%#{q}%"
74 73 sql = %w(login firstname lastname).map {|column| "LOWER(#{table_name}.#{column}) LIKE LOWER(:p)"}.join(" OR ")
75 74 sql << " OR #{table_name}.id IN (SELECT user_id FROM #{EmailAddress.table_name} WHERE LOWER(address) LIKE LOWER(:p))"
76 75 params = {:p => pattern}
77 76 if q =~ /^(.+)\s+(.+)$/
78 77 a, b = "#{$1}%", "#{$2}%"
79 78 sql << " OR (LOWER(#{table_name}.firstname) LIKE LOWER(:a) AND LOWER(#{table_name}.lastname) LIKE LOWER(:b))"
80 79 sql << " OR (LOWER(#{table_name}.firstname) LIKE LOWER(:b) AND LOWER(#{table_name}.lastname) LIKE LOWER(:a))"
81 80 params.merge!(:a => a, :b => b)
82 81 end
83 82 where(sql, params)
84 83 end
85 84 }
86 85
87 86 # Principals that are members of a collection of projects
88 87 scope :member_of, lambda {|projects|
89 88 projects = [projects] if projects.is_a?(Project)
90 89 if projects.blank?
91 90 where("1=0")
92 91 else
93 92 ids = projects.map(&:id)
94 93 active.where("#{Principal.table_name}.id IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids)
95 94 end
96 95 }
97 96 # Principals that are not members of projects
98 97 scope :not_member_of, lambda {|projects|
99 98 projects = [projects] unless projects.is_a?(Array)
100 99 if projects.empty?
101 100 where("1=0")
102 101 else
103 102 ids = projects.map(&:id)
104 103 where("#{Principal.table_name}.id NOT IN (SELECT DISTINCT user_id FROM #{Member.table_name} WHERE project_id IN (?))", ids)
105 104 end
106 105 }
107 106 scope :sorted, lambda { order(*Principal.fields_for_order_statement)}
108 107
109 108 before_create :set_default_empty_values
110 109
111 110 def reload(*args)
112 111 @project_ids = nil
113 112 super
114 113 end
115 114
116 115 def name(formatter = nil)
117 116 to_s
118 117 end
119 118
120 119 def mail=(*args)
121 120 nil
122 121 end
123 122
124 123 def mail
125 124 nil
126 125 end
127 126
128 127 def visible?(user=User.current)
129 128 Principal.visible(user).where(:id => id).first == self
130 129 end
131 130
132 131 # Returns true if the principal is a member of project
133 132 def member_of?(project)
134 133 project.is_a?(Project) && project_ids.include?(project.id)
135 134 end
136 135
137 136 # Returns an array of the project ids that the principal is a member of
138 137 def project_ids
139 138 @project_ids ||= super.freeze
140 139 end
141 140
142 141 def <=>(principal)
143 142 if principal.nil?
144 143 -1
145 144 elsif self.class.name == principal.class.name
146 145 self.to_s.casecmp(principal.to_s)
147 146 else
148 147 # groups after users
149 148 principal.class.name <=> self.class.name
150 149 end
151 150 end
152 151
153 152 # Returns an array of fields names than can be used to make an order statement for principals.
154 153 # Users are sorted before Groups.
155 154 # Examples:
156 155 def self.fields_for_order_statement(table=nil)
157 156 table ||= table_name
158 157 columns = ['type DESC'] + (User.name_formatter[:order] - ['id']) + ['lastname', 'id']
159 158 columns.uniq.map {|field| "#{table}.#{field}"}
160 159 end
161 160
162 161 # Returns the principal that matches the keyword among principals
163 162 def self.detect_by_keyword(principals, keyword)
164 163 keyword = keyword.to_s
165 164 return nil if keyword.blank?
166 165
167 166 principal = nil
168 167 principal ||= principals.detect {|a| keyword.casecmp(a.login.to_s) == 0}
169 168 principal ||= principals.detect {|a| keyword.casecmp(a.mail.to_s) == 0}
170 169
171 170 if principal.nil? && keyword.match(/ /)
172 171 firstname, lastname = *(keyword.split) # "First Last Throwaway"
173 172 principal ||= principals.detect {|a|
174 173 a.is_a?(User) &&
175 174 firstname.casecmp(a.firstname.to_s) == 0 &&
176 175 lastname.casecmp(a.lastname.to_s) == 0
177 176 }
178 177 end
179 178 if principal.nil?
180 179 principal ||= principals.detect {|a| keyword.casecmp(a.name) == 0}
181 180 end
182 181 principal
183 182 end
184 183
185 184 protected
186 185
187 186 # Make sure we don't try to insert NULL values (see #4632)
188 187 def set_default_empty_values
189 188 self.login ||= ''
190 189 self.hashed_password ||= ''
191 190 self.firstname ||= ''
192 191 self.lastname ||= ''
193 192 true
194 193 end
195 194
196 195 def validate_status
197 196 if status_changed? && self.class.valid_statuses.present?
198 197 unless self.class.valid_statuses.include?(status)
199 198 errors.add :status, :invalid
200 199 end
201 200 end
202 201 end
203 202 end
204 203
205 204 require_dependency "user"
206 205 require_dependency "group"
@@ -1,948 +1,952
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2016 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 include Redmine::SafeAttributes
22 22
23 23 # Different ways of displaying/sorting users
24 24 USER_FORMATS = {
25 25 :firstname_lastname => {
26 26 :string => '#{firstname} #{lastname}',
27 27 :order => %w(firstname lastname id),
28 28 :setting_order => 1
29 29 },
30 30 :firstname_lastinitial => {
31 31 :string => '#{firstname} #{lastname.to_s.chars.first}.',
32 32 :order => %w(firstname lastname id),
33 33 :setting_order => 2
34 34 },
35 35 :firstinitial_lastname => {
36 36 :string => '#{firstname.to_s.gsub(/(([[:alpha:]])[[:alpha:]]*\.?)/, \'\2.\')} #{lastname}',
37 37 :order => %w(firstname lastname id),
38 38 :setting_order => 2
39 39 },
40 40 :firstname => {
41 41 :string => '#{firstname}',
42 42 :order => %w(firstname id),
43 43 :setting_order => 3
44 44 },
45 45 :lastname_firstname => {
46 46 :string => '#{lastname} #{firstname}',
47 47 :order => %w(lastname firstname id),
48 48 :setting_order => 4
49 49 },
50 50 :lastnamefirstname => {
51 51 :string => '#{lastname}#{firstname}',
52 52 :order => %w(lastname firstname id),
53 53 :setting_order => 5
54 54 },
55 55 :lastname_comma_firstname => {
56 56 :string => '#{lastname}, #{firstname}',
57 57 :order => %w(lastname firstname id),
58 58 :setting_order => 6
59 59 },
60 60 :lastname => {
61 61 :string => '#{lastname}',
62 62 :order => %w(lastname id),
63 63 :setting_order => 7
64 64 },
65 65 :username => {
66 66 :string => '#{login}',
67 67 :order => %w(login id),
68 68 :setting_order => 8
69 69 },
70 70 }
71 71
72 72 MAIL_NOTIFICATION_OPTIONS = [
73 73 ['all', :label_user_mail_option_all],
74 74 ['selected', :label_user_mail_option_selected],
75 75 ['only_my_events', :label_user_mail_option_only_my_events],
76 76 ['only_assigned', :label_user_mail_option_only_assigned],
77 77 ['only_owner', :label_user_mail_option_only_owner],
78 78 ['none', :label_user_mail_option_none]
79 79 ]
80 80
81 81 has_and_belongs_to_many :groups,
82 82 :join_table => "#{table_name_prefix}groups_users#{table_name_suffix}",
83 83 :after_add => Proc.new {|user, group| group.user_added(user)},
84 84 :after_remove => Proc.new {|user, group| group.user_removed(user)}
85 85 has_many :changesets, :dependent => :nullify
86 86 has_one :preference, :dependent => :destroy, :class_name => 'UserPreference'
87 87 has_one :rss_token, lambda {where "action='feeds'"}, :class_name => 'Token'
88 88 has_one :api_token, lambda {where "action='api'"}, :class_name => 'Token'
89 89 has_one :email_address, lambda {where :is_default => true}, :autosave => true
90 90 has_many :email_addresses, :dependent => :delete_all
91 91 belongs_to :auth_source
92 92
93 93 scope :logged, lambda { where("#{User.table_name}.status <> #{STATUS_ANONYMOUS}") }
94 94 scope :status, lambda {|arg| where(arg.blank? ? nil : {:status => arg.to_i}) }
95 95
96 96 acts_as_customizable
97 97
98 98 attr_accessor :password, :password_confirmation, :generate_password
99 99 attr_accessor :last_before_login_on
100 100 attr_accessor :remote_ip
101 101
102 102 # Prevents unauthorized assignments
103 103 attr_protected :password, :password_confirmation, :hashed_password
104 104
105 105 LOGIN_LENGTH_LIMIT = 60
106 106 MAIL_LENGTH_LIMIT = 60
107 107
108 108 validates_presence_of :login, :firstname, :lastname, :if => Proc.new { |user| !user.is_a?(AnonymousUser) }
109 109 validates_uniqueness_of :login, :if => Proc.new { |user| user.login_changed? && user.login.present? }, :case_sensitive => false
110 110 # Login must contain letters, numbers, underscores only
111 111 validates_format_of :login, :with => /\A[a-z0-9_\-@\.]*\z/i
112 112 validates_length_of :login, :maximum => LOGIN_LENGTH_LIMIT
113 113 validates_length_of :firstname, :lastname, :maximum => 30
114 114 validates_length_of :identity_url, maximum: 255
115 115 validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true
116 116 validate :validate_password_length
117 117 validate do
118 118 if password_confirmation && password != password_confirmation
119 119 errors.add(:password, :confirmation)
120 120 end
121 121 end
122 122
123 123 self.valid_statuses = [STATUS_ACTIVE, STATUS_REGISTERED, STATUS_LOCKED]
124 124
125 125 before_validation :instantiate_email_address
126 126 before_create :set_mail_notification
127 127 before_save :generate_password_if_needed, :update_hashed_password
128 128 before_destroy :remove_references_before_destroy
129 129 after_save :update_notified_project_ids, :destroy_tokens, :deliver_security_notification
130 130 after_destroy :deliver_security_notification
131 131
132 132 scope :in_group, lambda {|group|
133 133 group_id = group.is_a?(Group) ? group.id : group.to_i
134 134 where("#{User.table_name}.id IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id)
135 135 }
136 136 scope :not_in_group, lambda {|group|
137 137 group_id = group.is_a?(Group) ? group.id : group.to_i
138 138 where("#{User.table_name}.id NOT IN (SELECT gu.user_id FROM #{table_name_prefix}groups_users#{table_name_suffix} gu WHERE gu.group_id = ?)", group_id)
139 139 }
140 140 scope :sorted, lambda { order(*User.fields_for_order_statement)}
141 141 scope :having_mail, lambda {|arg|
142 142 addresses = Array.wrap(arg).map {|a| a.to_s.downcase}
143 143 if addresses.any?
144 144 joins(:email_addresses).where("LOWER(#{EmailAddress.table_name}.address) IN (?)", addresses).distinct
145 145 else
146 146 none
147 147 end
148 148 }
149 149
150 150 def set_mail_notification
151 151 self.mail_notification = Setting.default_notification_option if self.mail_notification.blank?
152 152 true
153 153 end
154 154
155 155 def update_hashed_password
156 156 # update hashed_password if password was set
157 157 if self.password && self.auth_source_id.blank?
158 158 salt_password(password)
159 159 end
160 160 end
161 161
162 162 alias :base_reload :reload
163 163 def reload(*args)
164 164 @name = nil
165 165 @projects_by_role = nil
166 166 @project_ids_by_role = nil
167 167 @membership_by_project_id = nil
168 168 @notified_projects_ids = nil
169 169 @notified_projects_ids_changed = false
170 170 @builtin_role = nil
171 171 @visible_project_ids = nil
172 172 @managed_roles = nil
173 173 base_reload(*args)
174 174 end
175 175
176 176 def mail
177 177 email_address.try(:address)
178 178 end
179 179
180 180 def mail=(arg)
181 181 email = email_address || build_email_address
182 182 email.address = arg
183 183 end
184 184
185 185 def mail_changed?
186 186 email_address.try(:address_changed?)
187 187 end
188 188
189 189 def mails
190 190 email_addresses.pluck(:address)
191 191 end
192 192
193 193 def self.find_or_initialize_by_identity_url(url)
194 194 user = where(:identity_url => url).first
195 195 unless user
196 196 user = User.new
197 197 user.identity_url = url
198 198 end
199 199 user
200 200 end
201 201
202 202 def identity_url=(url)
203 203 if url.blank?
204 204 write_attribute(:identity_url, '')
205 205 else
206 206 begin
207 207 write_attribute(:identity_url, OpenIdAuthentication.normalize_identifier(url))
208 208 rescue OpenIdAuthentication::InvalidOpenId
209 209 # Invalid url, don't save
210 210 end
211 211 end
212 212 self.read_attribute(:identity_url)
213 213 end
214 214
215 215 # Returns the user that matches provided login and password, or nil
216 216 def self.try_to_login(login, password, active_only=true)
217 217 login = login.to_s
218 218 password = password.to_s
219 219
220 220 # Make sure no one can sign in with an empty login or password
221 221 return nil if login.empty? || password.empty?
222 222 user = find_by_login(login)
223 223 if user
224 224 # user is already in local database
225 225 return nil unless user.check_password?(password)
226 226 return nil if !user.active? && active_only
227 227 else
228 228 # user is not yet registered, try to authenticate with available sources
229 229 attrs = AuthSource.authenticate(login, password)
230 230 if attrs
231 231 user = new(attrs)
232 232 user.login = login
233 233 user.language = Setting.default_language
234 234 if user.save
235 235 user.reload
236 236 logger.info("User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}") if logger && user.auth_source
237 237 end
238 238 end
239 239 end
240 240 user.update_column(:last_login_on, Time.now) if user && !user.new_record? && user.active?
241 241 user
242 242 rescue => text
243 243 raise text
244 244 end
245 245
246 246 # Returns the user who matches the given autologin +key+ or nil
247 247 def self.try_to_autologin(key)
248 248 user = Token.find_active_user('autologin', key, Setting.autologin.to_i)
249 249 if user
250 250 user.update_column(:last_login_on, Time.now)
251 251 user
252 252 end
253 253 end
254 254
255 255 def self.name_formatter(formatter = nil)
256 256 USER_FORMATS[formatter || Setting.user_format] || USER_FORMATS[:firstname_lastname]
257 257 end
258 258
259 259 # Returns an array of fields names than can be used to make an order statement for users
260 260 # according to how user names are displayed
261 261 # Examples:
262 262 #
263 263 # User.fields_for_order_statement => ['users.login', 'users.id']
264 264 # User.fields_for_order_statement('authors') => ['authors.login', 'authors.id']
265 265 def self.fields_for_order_statement(table=nil)
266 266 table ||= table_name
267 267 name_formatter[:order].map {|field| "#{table}.#{field}"}
268 268 end
269 269
270 270 # Return user's full name for display
271 271 def name(formatter = nil)
272 272 f = self.class.name_formatter(formatter)
273 273 if formatter
274 274 eval('"' + f[:string] + '"')
275 275 else
276 276 @name ||= eval('"' + f[:string] + '"')
277 277 end
278 278 end
279 279
280 280 def active?
281 281 self.status == STATUS_ACTIVE
282 282 end
283 283
284 284 def registered?
285 285 self.status == STATUS_REGISTERED
286 286 end
287 287
288 288 def locked?
289 289 self.status == STATUS_LOCKED
290 290 end
291 291
292 292 def activate
293 293 self.status = STATUS_ACTIVE
294 294 end
295 295
296 296 def register
297 297 self.status = STATUS_REGISTERED
298 298 end
299 299
300 300 def lock
301 301 self.status = STATUS_LOCKED
302 302 end
303 303
304 304 def activate!
305 305 update_attribute(:status, STATUS_ACTIVE)
306 306 end
307 307
308 308 def register!
309 309 update_attribute(:status, STATUS_REGISTERED)
310 310 end
311 311
312 312 def lock!
313 313 update_attribute(:status, STATUS_LOCKED)
314 314 end
315 315
316 316 # Returns true if +clear_password+ is the correct user's password, otherwise false
317 317 def check_password?(clear_password)
318 318 if auth_source_id.present?
319 319 auth_source.authenticate(self.login, clear_password)
320 320 else
321 321 User.hash_password("#{salt}#{User.hash_password clear_password}") == hashed_password
322 322 end
323 323 end
324 324
325 325 # Generates a random salt and computes hashed_password for +clear_password+
326 326 # The hashed password is stored in the following form: SHA1(salt + SHA1(password))
327 327 def salt_password(clear_password)
328 328 self.salt = User.generate_salt
329 329 self.hashed_password = User.hash_password("#{salt}#{User.hash_password clear_password}")
330 330 self.passwd_changed_on = Time.now.change(:usec => 0)
331 331 end
332 332
333 333 # Does the backend storage allow this user to change their password?
334 334 def change_password_allowed?
335 335 return true if auth_source.nil?
336 336 return auth_source.allow_password_changes?
337 337 end
338 338
339 339 # Returns true if the user password has expired
340 340 def password_expired?
341 341 period = Setting.password_max_age.to_i
342 342 if period.zero?
343 343 false
344 344 else
345 345 changed_on = self.passwd_changed_on || Time.at(0)
346 346 changed_on < period.days.ago
347 347 end
348 348 end
349 349
350 350 def must_change_password?
351 351 (must_change_passwd? || password_expired?) && change_password_allowed?
352 352 end
353 353
354 354 def generate_password?
355 355 generate_password == '1' || generate_password == true
356 356 end
357 357
358 358 # Generate and set a random password on given length
359 359 def random_password(length=40)
360 360 chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
361 361 chars -= %w(0 O 1 l)
362 362 password = ''
363 363 length.times {|i| password << chars[SecureRandom.random_number(chars.size)] }
364 364 self.password = password
365 365 self.password_confirmation = password
366 366 self
367 367 end
368 368
369 369 def pref
370 370 self.preference ||= UserPreference.new(:user => self)
371 371 end
372 372
373 373 def time_zone
374 374 @time_zone ||= (self.pref.time_zone.blank? ? nil : ActiveSupport::TimeZone[self.pref.time_zone])
375 375 end
376 376
377 377 def force_default_language?
378 378 Setting.force_default_language_for_loggedin?
379 379 end
380 380
381 381 def language
382 382 if force_default_language?
383 383 Setting.default_language
384 384 else
385 385 super
386 386 end
387 387 end
388 388
389 389 def wants_comments_in_reverse_order?
390 390 self.pref[:comments_sorting] == 'desc'
391 391 end
392 392
393 393 # Return user's RSS key (a 40 chars long string), used to access feeds
394 394 def rss_key
395 395 if rss_token.nil?
396 396 create_rss_token(:action => 'feeds')
397 397 end
398 398 rss_token.value
399 399 end
400 400
401 401 # Return user's API key (a 40 chars long string), used to access the API
402 402 def api_key
403 403 if api_token.nil?
404 404 create_api_token(:action => 'api')
405 405 end
406 406 api_token.value
407 407 end
408 408
409 409 # Generates a new session token and returns its value
410 410 def generate_session_token
411 411 token = Token.create!(:user_id => id, :action => 'session')
412 412 token.value
413 413 end
414 414
415 415 # Returns true if token is a valid session token for the user whose id is user_id
416 416 def self.verify_session_token(user_id, token)
417 417 return false if user_id.blank? || token.blank?
418 418
419 419 scope = Token.where(:user_id => user_id, :value => token.to_s, :action => 'session')
420 420 if Setting.session_lifetime?
421 421 scope = scope.where("created_on > ?", Setting.session_lifetime.to_i.minutes.ago)
422 422 end
423 423 if Setting.session_timeout?
424 424 scope = scope.where("updated_on > ?", Setting.session_timeout.to_i.minutes.ago)
425 425 end
426 426 scope.update_all(:updated_on => Time.now) == 1
427 427 end
428 428
429 429 # Return an array of project ids for which the user has explicitly turned mail notifications on
430 430 def notified_projects_ids
431 431 @notified_projects_ids ||= memberships.select {|m| m.mail_notification?}.collect(&:project_id)
432 432 end
433 433
434 434 def notified_project_ids=(ids)
435 435 @notified_projects_ids_changed = true
436 436 @notified_projects_ids = ids.map(&:to_i).uniq.select {|n| n > 0}
437 437 end
438 438
439 439 # Updates per project notifications (after_save callback)
440 440 def update_notified_project_ids
441 441 if @notified_projects_ids_changed
442 442 ids = (mail_notification == 'selected' ? Array.wrap(notified_projects_ids).reject(&:blank?) : [])
443 443 members.update_all(:mail_notification => false)
444 444 members.where(:project_id => ids).update_all(:mail_notification => true) if ids.any?
445 445 end
446 446 end
447 447 private :update_notified_project_ids
448 448
449 449 def valid_notification_options
450 450 self.class.valid_notification_options(self)
451 451 end
452 452
453 453 # Only users that belong to more than 1 project can select projects for which they are notified
454 454 def self.valid_notification_options(user=nil)
455 455 # Note that @user.membership.size would fail since AR ignores
456 456 # :include association option when doing a count
457 457 if user.nil? || user.memberships.length < 1
458 458 MAIL_NOTIFICATION_OPTIONS.reject {|option| option.first == 'selected'}
459 459 else
460 460 MAIL_NOTIFICATION_OPTIONS
461 461 end
462 462 end
463 463
464 464 # Find a user account by matching the exact login and then a case-insensitive
465 465 # version. Exact matches will be given priority.
466 466 def self.find_by_login(login)
467 467 login = Redmine::CodesetUtil.replace_invalid_utf8(login.to_s)
468 468 if login.present?
469 469 # First look for an exact match
470 470 user = where(:login => login).detect {|u| u.login == login}
471 471 unless user
472 472 # Fail over to case-insensitive if none was found
473 473 user = where("LOWER(login) = ?", login.downcase).first
474 474 end
475 475 user
476 476 end
477 477 end
478 478
479 479 def self.find_by_rss_key(key)
480 480 Token.find_active_user('feeds', key)
481 481 end
482 482
483 483 def self.find_by_api_key(key)
484 484 Token.find_active_user('api', key)
485 485 end
486 486
487 487 # Makes find_by_mail case-insensitive
488 488 def self.find_by_mail(mail)
489 489 having_mail(mail).first
490 490 end
491 491
492 492 # Returns true if the default admin account can no longer be used
493 493 def self.default_admin_account_changed?
494 494 !User.active.find_by_login("admin").try(:check_password?, "admin")
495 495 end
496 496
497 497 def to_s
498 498 name
499 499 end
500 500
501 501 CSS_CLASS_BY_STATUS = {
502 502 STATUS_ANONYMOUS => 'anon',
503 503 STATUS_ACTIVE => 'active',
504 504 STATUS_REGISTERED => 'registered',
505 505 STATUS_LOCKED => 'locked'
506 506 }
507 507
508 508 def css_classes
509 509 "user #{CSS_CLASS_BY_STATUS[status]}"
510 510 end
511 511
512 512 # Returns the current day according to user's time zone
513 513 def today
514 514 if time_zone.nil?
515 515 Date.today
516 516 else
517 517 time_zone.today
518 518 end
519 519 end
520 520
521 521 # Returns the day of +time+ according to user's time zone
522 522 def time_to_date(time)
523 523 if time_zone.nil?
524 524 time.to_date
525 525 else
526 526 time.in_time_zone(time_zone).to_date
527 527 end
528 528 end
529 529
530 530 def logged?
531 531 true
532 532 end
533 533
534 534 def anonymous?
535 535 !logged?
536 536 end
537 537
538 538 # Returns user's membership for the given project
539 539 # or nil if the user is not a member of project
540 540 def membership(project)
541 541 project_id = project.is_a?(Project) ? project.id : project
542 542
543 543 @membership_by_project_id ||= Hash.new {|h, project_id|
544 544 h[project_id] = memberships.where(:project_id => project_id).first
545 545 }
546 546 @membership_by_project_id[project_id]
547 547 end
548 548
549 def roles
550 @roles ||= Role.joins(members: :project).where(["#{Project.table_name}.status <> ?", Project::STATUS_ARCHIVED]).where(Member.arel_table[:user_id].eq(id)).uniq
551 end
552
549 553 # Returns the user's bult-in role
550 554 def builtin_role
551 555 @builtin_role ||= Role.non_member
552 556 end
553 557
554 558 # Return user's roles for project
555 559 def roles_for_project(project)
556 560 # No role on archived projects
557 561 return [] if project.nil? || project.archived?
558 562 if membership = membership(project)
559 563 membership.roles.to_a
560 564 elsif project.is_public?
561 565 project.override_roles(builtin_role)
562 566 else
563 567 []
564 568 end
565 569 end
566 570
567 571 # Returns a hash of user's projects grouped by roles
568 572 # TODO: No longer used, should be deprecated
569 573 def projects_by_role
570 574 return @projects_by_role if @projects_by_role
571 575
572 576 result = Hash.new([])
573 577 project_ids_by_role.each do |role, ids|
574 578 result[role] = Project.where(:id => ids).to_a
575 579 end
576 580 @projects_by_role = result
577 581 end
578 582
579 583 # Returns a hash of project ids grouped by roles.
580 584 # Includes the projects that the user is a member of and the projects
581 585 # that grant custom permissions to the builtin groups.
582 586 def project_ids_by_role
583 587 return @project_ids_by_role if @project_ids_by_role
584 588
585 589 group_class = anonymous? ? GroupAnonymous : GroupNonMember
586 590 group_id = group_class.pluck(:id).first
587 591
588 592 members = Member.joins(:project, :member_roles).
589 593 where("#{Project.table_name}.status <> 9").
590 594 where("#{Member.table_name}.user_id = ? OR (#{Project.table_name}.is_public = ? AND #{Member.table_name}.user_id = ?)", self.id, true, group_id).
591 595 pluck(:user_id, :role_id, :project_id)
592 596
593 597 hash = {}
594 598 members.each do |user_id, role_id, project_id|
595 599 # Ignore the roles of the builtin group if the user is a member of the project
596 600 next if user_id != id && project_ids.include?(project_id)
597 601
598 602 hash[role_id] ||= []
599 603 hash[role_id] << project_id
600 604 end
601 605
602 606 result = Hash.new([])
603 607 if hash.present?
604 608 roles = Role.where(:id => hash.keys).to_a
605 609 hash.each do |role_id, proj_ids|
606 610 role = roles.detect {|r| r.id == role_id}
607 611 if role
608 612 result[role] = proj_ids.uniq
609 613 end
610 614 end
611 615 end
612 616 @project_ids_by_role = result
613 617 end
614 618
615 619 # Returns the ids of visible projects
616 620 def visible_project_ids
617 621 @visible_project_ids ||= Project.visible(self).pluck(:id)
618 622 end
619 623
620 624 # Returns the roles that the user is allowed to manage for the given project
621 625 def managed_roles(project)
622 626 if admin?
623 627 @managed_roles ||= Role.givable.to_a
624 628 else
625 629 membership(project).try(:managed_roles) || []
626 630 end
627 631 end
628 632
629 633 # Returns true if user is arg or belongs to arg
630 634 def is_or_belongs_to?(arg)
631 635 if arg.is_a?(User)
632 636 self == arg
633 637 elsif arg.is_a?(Group)
634 638 arg.users.include?(self)
635 639 else
636 640 false
637 641 end
638 642 end
639 643
640 644 # Return true if the user is allowed to do the specified action on a specific context
641 645 # Action can be:
642 646 # * a parameter-like Hash (eg. :controller => 'projects', :action => 'edit')
643 647 # * a permission Symbol (eg. :edit_project)
644 648 # Context can be:
645 649 # * a project : returns true if user is allowed to do the specified action on this project
646 650 # * an array of projects : returns true if user is allowed on every project
647 651 # * nil with options[:global] set : check if user has at least one role allowed for this action,
648 652 # or falls back to Non Member / Anonymous permissions depending if the user is logged
649 653 def allowed_to?(action, context, options={}, &block)
650 654 if context && context.is_a?(Project)
651 655 return false unless context.allows_to?(action)
652 656 # Admin users are authorized for anything else
653 657 return true if admin?
654 658
655 659 roles = roles_for_project(context)
656 660 return false unless roles
657 661 roles.any? {|role|
658 662 (context.is_public? || role.member?) &&
659 663 role.allowed_to?(action) &&
660 664 (block_given? ? yield(role, self) : true)
661 665 }
662 666 elsif context && context.is_a?(Array)
663 667 if context.empty?
664 668 false
665 669 else
666 670 # Authorize if user is authorized on every element of the array
667 671 context.map {|project| allowed_to?(action, project, options, &block)}.reduce(:&)
668 672 end
669 673 elsif context
670 674 raise ArgumentError.new("#allowed_to? context argument must be a Project, an Array of projects or nil")
671 675 elsif options[:global]
672 676 # Admin users are always authorized
673 677 return true if admin?
674 678
675 679 # authorize if user has at least one role that has this permission
676 roles = memberships.collect {|m| m.roles}.flatten.uniq
677 roles << (self.logged? ? Role.non_member : Role.anonymous)
678 roles.any? {|role|
680 rls = self.roles.to_a
681 rls << builtin_role
682 rls.any? {|role|
679 683 role.allowed_to?(action) &&
680 684 (block_given? ? yield(role, self) : true)
681 685 }
682 686 else
683 687 false
684 688 end
685 689 end
686 690
687 691 # Is the user allowed to do the specified action on any project?
688 692 # See allowed_to? for the actions and valid options.
689 693 #
690 694 # NB: this method is not used anywhere in the core codebase as of
691 695 # 2.5.2, but it's used by many plugins so if we ever want to remove
692 696 # it it has to be carefully deprecated for a version or two.
693 697 def allowed_to_globally?(action, options={}, &block)
694 698 allowed_to?(action, nil, options.reverse_merge(:global => true), &block)
695 699 end
696 700
697 701 def allowed_to_view_all_time_entries?(context)
698 702 allowed_to?(:view_time_entries, context) do |role, user|
699 703 role.time_entries_visibility == 'all'
700 704 end
701 705 end
702 706
703 707 # Returns true if the user is allowed to delete the user's own account
704 708 def own_account_deletable?
705 709 Setting.unsubscribe? &&
706 710 (!admin? || User.active.where("admin = ? AND id <> ?", true, id).exists?)
707 711 end
708 712
709 713 safe_attributes 'firstname',
710 714 'lastname',
711 715 'mail',
712 716 'mail_notification',
713 717 'notified_project_ids',
714 718 'language',
715 719 'custom_field_values',
716 720 'custom_fields',
717 721 'identity_url'
718 722
719 723 safe_attributes 'login',
720 724 :if => lambda {|user, current_user| user.new_record?}
721 725
722 726 safe_attributes 'status',
723 727 'auth_source_id',
724 728 'generate_password',
725 729 'must_change_passwd',
726 730 'login',
727 731 'admin',
728 732 :if => lambda {|user, current_user| current_user.admin?}
729 733
730 734 safe_attributes 'group_ids',
731 735 :if => lambda {|user, current_user| current_user.admin? && !user.new_record?}
732 736
733 737 # Utility method to help check if a user should be notified about an
734 738 # event.
735 739 #
736 740 # TODO: only supports Issue events currently
737 741 def notify_about?(object)
738 742 if mail_notification == 'all'
739 743 true
740 744 elsif mail_notification.blank? || mail_notification == 'none'
741 745 false
742 746 else
743 747 case object
744 748 when Issue
745 749 case mail_notification
746 750 when 'selected', 'only_my_events'
747 751 # user receives notifications for created/assigned issues on unselected projects
748 752 object.author == self || is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
749 753 when 'only_assigned'
750 754 is_or_belongs_to?(object.assigned_to) || is_or_belongs_to?(object.assigned_to_was)
751 755 when 'only_owner'
752 756 object.author == self
753 757 end
754 758 when News
755 759 # always send to project members except when mail_notification is set to 'none'
756 760 true
757 761 end
758 762 end
759 763 end
760 764
761 765 def self.current=(user)
762 766 RequestStore.store[:current_user] = user
763 767 end
764 768
765 769 def self.current
766 770 RequestStore.store[:current_user] ||= User.anonymous
767 771 end
768 772
769 773 # Returns the anonymous user. If the anonymous user does not exist, it is created. There can be only
770 774 # one anonymous user per database.
771 775 def self.anonymous
772 776 anonymous_user = AnonymousUser.unscoped.first
773 777 if anonymous_user.nil?
774 778 anonymous_user = AnonymousUser.unscoped.create(:lastname => 'Anonymous', :firstname => '', :login => '', :status => 0)
775 779 raise 'Unable to create the anonymous user.' if anonymous_user.new_record?
776 780 end
777 781 anonymous_user
778 782 end
779 783
780 784 # Salts all existing unsalted passwords
781 785 # It changes password storage scheme from SHA1(password) to SHA1(salt + SHA1(password))
782 786 # This method is used in the SaltPasswords migration and is to be kept as is
783 787 def self.salt_unsalted_passwords!
784 788 transaction do
785 789 User.where("salt IS NULL OR salt = ''").find_each do |user|
786 790 next if user.hashed_password.blank?
787 791 salt = User.generate_salt
788 792 hashed_password = User.hash_password("#{salt}#{user.hashed_password}")
789 793 User.where(:id => user.id).update_all(:salt => salt, :hashed_password => hashed_password)
790 794 end
791 795 end
792 796 end
793 797
794 798 protected
795 799
796 800 def validate_password_length
797 801 return if password.blank? && generate_password?
798 802 # Password length validation based on setting
799 803 if !password.nil? && password.size < Setting.password_min_length.to_i
800 804 errors.add(:password, :too_short, :count => Setting.password_min_length.to_i)
801 805 end
802 806 end
803 807
804 808 def instantiate_email_address
805 809 email_address || build_email_address
806 810 end
807 811
808 812 private
809 813
810 814 def generate_password_if_needed
811 815 if generate_password? && auth_source.nil?
812 816 length = [Setting.password_min_length.to_i + 2, 10].max
813 817 random_password(length)
814 818 end
815 819 end
816 820
817 821 # Delete all outstanding password reset tokens on password change.
818 822 # Delete the autologin tokens on password change to prohibit session leakage.
819 823 # This helps to keep the account secure in case the associated email account
820 824 # was compromised.
821 825 def destroy_tokens
822 826 if hashed_password_changed? || (status_changed? && !active?)
823 827 tokens = ['recovery', 'autologin', 'session']
824 828 Token.where(:user_id => id, :action => tokens).delete_all
825 829 end
826 830 end
827 831
828 832 # Removes references that are not handled by associations
829 833 # Things that are not deleted are reassociated with the anonymous user
830 834 def remove_references_before_destroy
831 835 return if self.id.nil?
832 836
833 837 substitute = User.anonymous
834 838 Attachment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
835 839 Comment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
836 840 Issue.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
837 841 Issue.where(['assigned_to_id = ?', id]).update_all('assigned_to_id = NULL')
838 842 Journal.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
839 843 JournalDetail.
840 844 where(["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]).
841 845 update_all(['old_value = ?', substitute.id.to_s])
842 846 JournalDetail.
843 847 where(["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]).
844 848 update_all(['value = ?', substitute.id.to_s])
845 849 Message.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
846 850 News.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
847 851 # Remove private queries and keep public ones
848 852 ::Query.where('user_id = ? AND visibility = ?', id, ::Query::VISIBILITY_PRIVATE).delete_all
849 853 ::Query.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
850 854 TimeEntry.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id])
851 855 Token.where('user_id = ?', id).delete_all
852 856 Watcher.where('user_id = ?', id).delete_all
853 857 WikiContent.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
854 858 WikiContent::Version.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id])
855 859 end
856 860
857 861 # Return password digest
858 862 def self.hash_password(clear_password)
859 863 Digest::SHA1.hexdigest(clear_password || "")
860 864 end
861 865
862 866 # Returns a 128bits random salt as a hex string (32 chars long)
863 867 def self.generate_salt
864 868 Redmine::Utils.random_hex(16)
865 869 end
866 870
867 871 # Send a security notification to all admins if the user has gained/lost admin privileges
868 872 def deliver_security_notification
869 873 options = {
870 874 field: :field_admin,
871 875 value: login,
872 876 title: :label_user_plural,
873 877 url: {controller: 'users', action: 'index'}
874 878 }
875 879
876 880 deliver = false
877 881 if (admin? && id_changed? && active?) || # newly created admin
878 882 (admin? && admin_changed? && active?) || # regular user became admin
879 883 (admin? && status_changed? && active?) # locked admin became active again
880 884
881 885 deliver = true
882 886 options[:message] = :mail_body_security_notification_add
883 887
884 888 elsif (admin? && destroyed? && active?) || # active admin user was deleted
885 889 (!admin? && admin_changed? && active?) || # admin is no longer admin
886 890 (admin? && status_changed? && !active?) # admin was locked
887 891
888 892 deliver = true
889 893 options[:message] = :mail_body_security_notification_remove
890 894 end
891 895
892 896 if deliver
893 897 users = User.active.where(admin: true).to_a
894 898 Mailer.security_notification(users, options).deliver
895 899 end
896 900 end
897 901 end
898 902
899 903 class AnonymousUser < User
900 904 validate :validate_anonymous_uniqueness, :on => :create
901 905
902 906 self.valid_statuses = [STATUS_ANONYMOUS]
903 907
904 908 def validate_anonymous_uniqueness
905 909 # There should be only one AnonymousUser in the database
906 910 errors.add :base, 'An anonymous user already exists.' if AnonymousUser.exists?
907 911 end
908 912
909 913 def available_custom_fields
910 914 []
911 915 end
912 916
913 917 # Overrides a few properties
914 918 def logged?; false end
915 919 def admin; false end
916 920 def name(*args); I18n.t(:label_user_anonymous) end
917 921 def mail=(*args); nil end
918 922 def mail; nil end
919 923 def time_zone; nil end
920 924 def rss_key; nil end
921 925
922 926 def pref
923 927 UserPreference.new(:user => self)
924 928 end
925 929
926 930 # Returns the user's bult-in role
927 931 def builtin_role
928 932 @builtin_role ||= Role.anonymous
929 933 end
930 934
931 935 def membership(*args)
932 936 nil
933 937 end
934 938
935 939 def member_of?(*args)
936 940 false
937 941 end
938 942
939 943 # Anonymous user can not be destroyed
940 944 def destroy
941 945 false
942 946 end
943 947
944 948 protected
945 949
946 950 def instantiate_email_address
947 951 end
948 952 end
@@ -1,31 +1,31
1 1 api.group do
2 2 api.id @group.id
3 3 api.name @group.lastname
4 4 api.builtin @group.builtin_type if @group.builtin_type
5 5
6 6 render_api_custom_values @group.visible_custom_field_values, api
7 7
8 8 api.array :users do
9 9 @group.users.each do |user|
10 10 api.user :id => user.id, :name => user.name
11 11 end
12 12 end if include_in_api_response?('users') && !@group.builtin?
13 13
14 14 api.array :memberships do
15 @group.memberships.each do |membership|
15 @group.memberships.preload(:roles, :project).each do |membership|
16 16 api.membership do
17 17 api.id membership.id
18 18 api.project :id => membership.project.id, :name => membership.project.name
19 19 api.array :roles do
20 20 membership.member_roles.each do |member_role|
21 21 if member_role.role
22 22 attrs = {:id => member_role.role.id, :name => member_role.role.name}
23 23 attrs.merge!(:inherited => true) if member_role.inherited_from.present?
24 24 api.role attrs
25 end
25 end
26 26 end
27 27 end
28 28 end if membership.project
29 29 end
30 30 end if include_in_api_response?('memberships')
31 31 end
General Comments 0
You need to be logged in to leave comments. Login now