aboutsummaryrefslogtreecommitdiffstats
path: root/app/models/user.rb
diff options
context:
space:
mode:
Diffstat (limited to 'app/models/user.rb')
-rw-r--r--app/models/user.rb356
1 files changed, 176 insertions, 180 deletions
diff --git a/app/models/user.rb b/app/models/user.rb
index 4b83d8572..1c6dc0eb0 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -28,11 +28,7 @@ require 'digest/sha1'
class User < ActiveRecord::Base
strip_attributes!
- validates_presence_of :email, :message => _("Please enter your email address")
-
- validates_presence_of :name, :message => _("Please enter your name")
-
- validates_presence_of :hashed_password, :message => _("Please enter a password")
+ attr_accessor :password_confirmation, :no_xapian_reindex
has_many :info_requests, :order => 'created_at desc'
has_many :user_info_request_sent_alerts
@@ -43,9 +39,10 @@ class User < ActiveRecord::Base
has_many :censor_rules, :order => 'created_at desc'
has_many :info_request_batches, :order => 'created_at desc'
- attr_accessor :password_confirmation, :no_xapian_reindex
+ validates_presence_of :email, :message => _("Please enter your email address")
+ validates_presence_of :name, :message => _("Please enter your name")
+ validates_presence_of :hashed_password, :message => _("Please enter a password")
validates_confirmation_of :password, :message => _("Please enter the same password twice")
-
validates_inclusion_of :admin_level, :in => [
'none',
'super',
@@ -53,6 +50,10 @@ class User < ActiveRecord::Base
validate :email_and_name_are_valid
+ after_initialize :set_defaults
+ after_save :purge_in_cache
+ after_update :reindex_referencing_models
+
acts_as_xapian :texts => [ :name, :about_me ],
:values => [
[ :created_at_numeric, 1, "created_at", :number ] # for sorting
@@ -60,11 +61,111 @@ class User < ActiveRecord::Base
:terms => [ [ :variety, 'V', "variety" ] ],
:if => :indexed_by_search?
- after_initialize :set_defaults
+ # Return user given login email, password and other form parameters (e.g. name)
+ #
+ # The specific_user_login parameter says that login as a particular user is
+ # expected, so no parallel registration form is being displayed.
+ def self.authenticate_from_form(params, specific_user_login = false)
+ params[:email].strip!
+
+ if specific_user_login
+ auth_fail_message = _("Either the email or password was not recognised, please try again.")
+ else
+ auth_fail_message = _("Either the email or password was not recognised, please try again. Or create a new account using the form on the right.")
+ end
+
+ user = find_user_by_email(params[:email])
+ if user
+ # There is user with email, check password
+ unless user.has_this_password?(params[:password])
+ user.errors.add(:base, auth_fail_message)
+ end
+ else
+ # No user of same email, make one (that we don't save in the database)
+ # for the forms code to use.
+ user = User.new(params)
+ # deliberately same message as above so as not to leak whether registered
+ user.errors.add(:base, auth_fail_message)
+ end
+ user
+ end
+
+ # Case-insensitively find a user from their email
+ def self.find_user_by_email(email)
+ self.find(:first, :conditions => [ 'lower(email) = lower(?)', email ] )
+ end
+
+ # The "internal admin" is a special user for internal use.
+ def self.internal_admin_user
+ user = User.find_by_email(AlaveteliConfiguration::contact_email)
+ if user.nil?
+ password = PostRedirect.generate_random_token
+ user = User.new(
+ :name => 'Internal admin user',
+ :email => AlaveteliConfiguration.contact_email,
+ :password => password,
+ :password_confirmation => password
+ )
+ user.save!
+ end
+
+ user
+ end
+
+ def self.owns_every_request?(user)
+ !user.nil? && user.owns_every_request?
+ end
+
+ # Can the user see every request, response, and outgoing message, even hidden ones?
+ def self.view_hidden?(user)
+ !user.nil? && user.super?
+ end
+
+ # Should the user be kept logged into their own account
+ # if they follow a /c/ redirect link belonging to another user?
+ def self.stay_logged_in_on_redirect?(user)
+ !user.nil? && user.super?
+ end
+
+ # Used for default values of last_daily_track_email
+ def self.random_time_in_last_day
+ earliest_time = Time.now - 1.day
+ latest_time = Time.now
+ earliest_time + rand(latest_time - earliest_time).seconds
+ end
+
+ # Alters last_daily_track_email for every user, so alerts will be sent
+ # spread out fairly evenly throughout the day, balancing load on the
+ # server. This is intended to be called by hand from the Ruby console. It
+ # will mean quite a few users may get more than one email alert the day you
+ # do it, so have a care and run it rarely.
+ #
+ # This SQL statement is useful for seeing how spread out users are at the moment:
+ # select extract(hour from last_daily_track_email) as h, count(*) from users group by extract(hour from last_daily_track_email) order by h;
+ def self.spread_alert_times_across_day
+ self.find(:all).each do |user|
+ user.last_daily_track_email = User.random_time_in_last_day
+ user.save!
+ end
+ nil # so doesn't print all users on console
+ end
+
+ def self.encrypted_password(password, salt)
+ string_to_hash = password + salt # TODO: need to add a secret here too?
+ Digest::SHA1.hexdigest(string_to_hash)
+ end
+
+ def self.record_bounce_for_email(email, message)
+ user = User.find_user_by_email(email)
+ return false if user.nil?
+
+ user.record_bounce(message) if user.email_bounced_at.nil?
+ return true
+ end
def created_at_numeric
# format it here as no datetime support in Xapian's value ranges
- return self.created_at.strftime("%Y%m%d%H%M%S")
+ created_at.strftime("%Y%m%d%H%M%S")
end
def variety
@@ -72,18 +173,18 @@ class User < ActiveRecord::Base
end
# requested_by: and commented_by: search queries also need updating after save
- after_update :reindex_referencing_models
def reindex_referencing_models
return if no_xapian_reindex == true
- if self.changes.include?('url_name')
- for comment in self.comments
- for info_request_event in comment.info_request_events
+ if changes.include?('url_name')
+ comments.each do |comment|
+ comment.info_request_events.each do |info_request_event|
info_request_event.xapian_mark_needs_index
end
end
- for info_request in self.info_requests
- for info_request_event in info_request.info_request_events
+
+ info_requests.each do |info_request|
+ info_request.info_request_events.each do |info_request_event|
info_request_event.xapian_mark_needs_index
end
end
@@ -91,11 +192,11 @@ class User < ActiveRecord::Base
end
def get_locale
- (self.locale || I18n.locale).to_s
+ (locale || I18n.locale).to_s
end
def visible_comments
- self.comments.find(:all, :conditions => 'visible')
+ comments.find(:all, :conditions => 'visible')
end
# Don't display any leading/trailing spaces
@@ -106,62 +207,29 @@ class User < ActiveRecord::Base
if not name.nil?
name.strip!
end
- if self.public_banned?
+ if public_banned?
# Use interpolation to return a string rather than a SafeBuffer so that
# gsub can be called on it until we upgrade to Rails 3.2. The name returned
# is not marked as HTML safe so will be escaped automatically in views. We
# do this in two steps so the string still gets picked up for translation
- name = _("{{user_name}} (Account suspended)", :user_name=> name.html_safe)
+ name = _("{{user_name}} (Account suspended)", :user_name => name.html_safe)
name = "#{name}"
end
name
end
- # Return user given login email, password and other form parameters (e.g. name)
- #
- # The specific_user_login parameter says that login as a particular user is
- # expected, so no parallel registration form is being displayed.
- def User.authenticate_from_form(params, specific_user_login = false)
- params[:email].strip!
-
- if specific_user_login
- auth_fail_message = _("Either the email or password was not recognised, please try again.")
- else
- auth_fail_message = _("Either the email or password was not recognised, please try again. Or create a new account using the form on the right.")
- end
-
- user = self.find_user_by_email(params[:email])
- if user
- # There is user with email, check password
- if !user.has_this_password?(params[:password])
- user.errors.add(:base, auth_fail_message)
- end
- else
- # No user of same email, make one (that we don't save in the database)
- # for the forms code to use.
- user = User.new(params)
- # deliberately same message as above so as not to leak whether registered
- user.errors.add(:base, auth_fail_message)
- end
- user
- end
-
- # Case-insensitively find a user from their email
- def User.find_user_by_email(email)
- return self.find(:first, :conditions => [ 'lower(email) = lower(?)', email ] )
- end
-
# When name is changed, also change the url name
def name=(name)
write_attribute(:name, name)
- self.update_url_name
+ update_url_name
end
+
def update_url_name
- url_name = MySociety::Format.simplify_url_part(self.name, 'user', 32)
+ url_name = MySociety::Format.simplify_url_part(name, 'user', 32)
# For user with same name as others, add on arbitary numeric identifier
unique_url_name = url_name
suffix_num = 2 # as there's already one without numeric suffix
- while not User.find_by_url_name(unique_url_name, :conditions => self.id.nil? ? nil : ["id <> ?", self.id] ).nil?
+ while not User.find_by_url_name(unique_url_name, :conditions => id.nil? ? nil : ["id <> ?", id] ).nil?
unique_url_name = url_name + "_" + suffix_num.to_s
suffix_num = suffix_num + 1
end
@@ -172,6 +240,7 @@ class User < ActiveRecord::Base
def password
@password
end
+
def password=(pwd)
@password = pwd
if pwd.blank?
@@ -179,40 +248,23 @@ class User < ActiveRecord::Base
return
end
create_new_salt
- self.hashed_password = User.encrypted_password(self.password, self.salt)
+ self.hashed_password = User.encrypted_password(password, salt)
end
def has_this_password?(password)
- expected_password = User.encrypted_password(password, self.salt)
- return self.hashed_password == expected_password
+ expected_password = User.encrypted_password(password, salt)
+ hashed_password == expected_password
end
# For use in to/from in email messages
def name_and_email
- return MailHandler.address_from_name_and_email(self.name, self.email)
- end
-
- # The "internal admin" is a special user for internal use.
- def User.internal_admin_user
- u = User.find_by_email(AlaveteliConfiguration::contact_email)
- if u.nil?
- password = PostRedirect.generate_random_token
- u = User.new(
- :name => 'Internal admin user',
- :email => AlaveteliConfiguration::contact_email,
- :password => password,
- :password_confirmation => password
- )
- u.save!
- end
-
- return u
+ MailHandler.address_from_name_and_email(name, email)
end
# Returns list of requests which the user hasn't described (and last
# changed more than a day ago)
def get_undescribed_requests
- self.info_requests.find(
+ info_requests.find(
:all,
:conditions => [ 'awaiting_description = ? and ' + InfoRequest.last_event_time_clause + ' < ?',
true, Time.now() - 1.day
@@ -223,7 +275,7 @@ class User < ActiveRecord::Base
# Can the user make new requests, without having to describe state of (most) existing ones?
def can_leave_requests_undescribed?
# TODO: should be flag in database really
- if self.url_name == "heather_brooke" || self.url_name == "heather_brooke_2"
+ if url_name == "heather_brooke" || url_name == "heather_brooke_2"
return true
end
return false
@@ -232,140 +284,102 @@ class User < ActiveRecord::Base
# Does the user magically gain powers as if they owned every request?
# e.g. Can classify it
def owns_every_request?
- self.super?
+ super?
end
# Does this user have extraordinary powers?
def super?
- self.admin_level == 'super'
- end
-
- def User.owns_every_request?(user)
- !user.nil? && user.owns_every_request?
- end
-
- # Can the user see every request, response, and outgoing message, even hidden ones?
- def User.view_hidden?(user)
- !user.nil? && user.super?
- end
-
- # Should the user be kept logged into their own account
- # if they follow a /c/ redirect link belonging to another user?
- def User.stay_logged_in_on_redirect?(user)
- !user.nil? && user.super?
+ admin_level == 'super'
end
# Does the user get "(admin)" links on each page on the main site?
def admin_page_links?
- self.super?
+ super?
end
# Is it public that they are banned?
def public_banned?
- !self.ban_text.empty?
+ !ban_text.empty?
end
# Various ways the user can be banned, and text to describe it if failed
def can_file_requests?
- self.ban_text.empty? && !self.exceeded_limit?
+ ban_text.empty? && !exceeded_limit?
end
def exceeded_limit?
# Some users have no limit
- return false if self.no_limit
+ return false if no_limit
# Batch request users don't have a limit
- return false if self.can_make_batch_requests?
+ return false if can_make_batch_requests?
# Has the user issued as many as MAX_REQUESTS_PER_USER_PER_DAY requests in the past 24 hours?
- return false if AlaveteliConfiguration::max_requests_per_user_per_day.blank?
- recent_requests = InfoRequest.count(:conditions => ["user_id = ? and created_at > now() - '1 day'::interval", self.id])
+ return false if AlaveteliConfiguration.max_requests_per_user_per_day.blank?
+ recent_requests = InfoRequest.count(:conditions => ["user_id = ? and created_at > now() - '1 day'::interval", id])
- return (recent_requests >= AlaveteliConfiguration::max_requests_per_user_per_day)
+ recent_requests >= AlaveteliConfiguration.max_requests_per_user_per_day
end
def next_request_permitted_at
- return nil if self.no_limit
+ return nil if no_limit
- n_most_recent_requests = InfoRequest.all(:conditions => ["user_id = ? and created_at > now() - '1 day'::interval", self.id], :order => "created_at DESC", :limit => AlaveteliConfiguration::max_requests_per_user_per_day)
+ n_most_recent_requests = InfoRequest.all(:conditions => ["user_id = ? and created_at > now() - '1 day'::interval", id],
+ :order => "created_at DESC",
+ :limit => AlaveteliConfiguration::max_requests_per_user_per_day)
return nil if n_most_recent_requests.size < AlaveteliConfiguration::max_requests_per_user_per_day
nth_most_recent_request = n_most_recent_requests[-1]
- return nth_most_recent_request.created_at + 1.day
+ nth_most_recent_request.created_at + 1.day
end
def can_make_followup?
- self.ban_text.empty?
+ ban_text.empty?
end
def can_make_comments?
- self.ban_text.empty?
+ ban_text.empty?
end
def can_contact_other_users?
- self.ban_text.empty?
+ ban_text.empty?
end
def can_fail_html
if ban_text
- text = self.ban_text.strip
+ text = ban_text.strip
else
raise "Unknown reason for ban"
end
text = CGI.escapeHTML(text)
text = MySociety::Format.make_clickable(text, :contract => 1)
text = text.gsub(/\n/, '<br>')
- return text.html_safe
+ text.html_safe
end
# Returns domain part of user's email address
def email_domain
- return PublicBody.extract_domain_from_email(self.email)
+ PublicBody.extract_domain_from_email(email)
end
# A photograph of the user (to make it all more human)
def set_profile_photo(new_profile_photo)
ActiveRecord::Base.transaction do
- if !self.profile_photo.nil?
- self.profile_photo.destroy
- end
+ profile_photo.destroy unless profile_photo.nil?
self.profile_photo = new_profile_photo
- self.save
+ save
end
end
- # Used for default values of last_daily_track_email
- def User.random_time_in_last_day
- earliest_time = Time.now() - 1.day
- latest_time = Time.now
- return earliest_time + rand(latest_time - earliest_time).seconds
- end
-
- # Alters last_daily_track_email for every user, so alerts will be sent
- # spread out fairly evenly throughout the day, balancing load on the
- # server. This is intended to be called by hand from the Ruby console. It
- # will mean quite a few users may get more than one email alert the day you
- # do it, so have a care and run it rarely.
- #
- # This SQL statement is useful for seeing how spread out users are at the moment:
- # select extract(hour from last_daily_track_email) as h, count(*) from users group by extract(hour from last_daily_track_email) order by h;
- def User.spread_alert_times_across_day
- for user in self.find(:all)
- user.last_daily_track_email = User.random_time_in_last_day
- user.save!
- end
- nil # so doesn't print all users on console
- end
-
# Return about me text for display as HTML
# TODO: Move this to a view helper
def get_about_me_for_html_display
- text = self.about_me.strip
+ text = about_me.strip
text = CGI.escapeHTML(text)
text = MySociety::Format.make_clickable(text, :contract => 1)
text = text.gsub(/\n/, '<br>')
- return text.html_safe
+ text.html_safe
end
def json_for_api
- return {
- :id => self.id,
- :url_name => self.url_name,
- :name => self.name,
- :ban_text => self.ban_text,
- :about_me => self.about_me,
+ {
+ :id => id,
+ :url_name => url_name,
+ :name => name,
+ :ban_text => ban_text,
+ :about_me => about_me,
# :profile_photo => self.profile_photo # ought to have this, but too hard to get URL out for now
# created_at / updated_at we only show the year on the main page for privacy reasons, so don't put here
}
@@ -374,40 +388,41 @@ class User < ActiveRecord::Base
def record_bounce(message)
self.email_bounced_at = Time.now
self.email_bounce_message = message
- self.save!
+ save!
end
def should_be_emailed?
- return (self.email_confirmed && self.email_bounced_at.nil?)
+ email_confirmed && email_bounced_at.nil?
end
def indexed_by_search?
- return self.email_confirmed
+ email_confirmed
end
def for_admin_column(complete = false)
if complete
columns = self.class.content_columns
else
- columns = self.class.content_columns.map{|c| c if %w(created_at updated_at admin_level email_confirmed).include?(c.name) }.compact
+ columns = self.class.content_columns.map do |c|
+ c if %w(created_at updated_at admin_level email_confirmed).include?(c.name)
+ end.compact
end
columns.each do |column|
- yield(column.human_name, self.send(column.name), column.type.to_s, column.name)
+ yield(column.human_name, send(column.name), column.type.to_s, column.name)
end
end
- ## Private instance methods
private
def create_new_salt
- self.salt = self.object_id.to_s + rand.to_s
+ self.salt = object_id.to_s + rand.to_s
end
def set_defaults
- if self.admin_level.nil?
+ if admin_level.nil?
self.admin_level = 'none'
end
- if self.new_record?
+ if new_record?
# make alert emails go out at a random time for each new user, so
# overall they are spread out throughout the day.
self.last_daily_track_email = User.random_time_in_last_day
@@ -415,35 +430,16 @@ class User < ActiveRecord::Base
end
def email_and_name_are_valid
- if self.email != "" && !MySociety::Validate.is_valid_email(self.email)
+ if email != "" && !MySociety::Validate.is_valid_email(email)
errors.add(:email, _("Please enter a valid email address"))
end
- if MySociety::Validate.is_valid_email(self.name)
+ if MySociety::Validate.is_valid_email(name)
errors.add(:name, _("Please enter your name, not your email address, in the name field."))
end
end
- ## Class methods
- def User.encrypted_password(password, salt)
- string_to_hash = password + salt # TODO: need to add a secret here too?
- Digest::SHA1.hexdigest(string_to_hash)
- end
-
- def User.record_bounce_for_email(email, message)
- user = User.find_user_by_email(email)
- return false if user.nil?
-
- if user.email_bounced_at.nil?
- user.record_bounce(message)
- end
- return true
- end
-
- after_save(:purge_in_cache)
- def purge_in_cache
- if self.name_changed?
- self.info_requests.each {|x| x.purge_in_cache}
- end
+ def purge_in_cache
+ info_requests.each { |x| x.purge_in_cache } if name_changed?
end
end