Show More
Commit Description:
merge
Commit Description:
merge
References:
File last commit:
Show/Diff file:
Action:
app/models/user.rb
| 420 lines
| 10.9 KiB
| text/x-ruby
| RubyLexer
|
|
r0 | require 'digest/sha1' | ||
r390 | require 'net/pop' | |||
r406 | require 'net/https' | |||
require 'net/http' | ||||
r396 | require 'json' | |||
|
r0 | |||
class User < ActiveRecord::Base | ||||
has_and_belongs_to_many :roles | ||||
r678 | ||||
#has_and_belongs_to_many :groups | ||||
r751 | has_many :groups_users, class_name: 'GroupUser' | |||
r678 | has_many :groups, :through => :groups_users | |||
|
r0 | |||
r748 | has_many :test_requests, -> {order(submitted_at: :desc)} | |||
|
r36 | |||
r748 | has_many :messages, -> { order(created_at: :desc) }, | |||
|
r102 | :class_name => "Message", | ||
r618 | :foreign_key => "sender_id" | |||
|
r102 | |||
r748 | has_many :replied_messages, -> { order(created_at: :desc) }, | |||
|
r102 | :class_name => "Message", | ||
r618 | :foreign_key => "receiver_id" | |||
|
r102 | |||
r833 | has_many :logins | |||
|
r247 | has_one :contest_stat, :class_name => "UserContestStat", :dependent => :destroy | ||
|
r217 | |||
|
r85 | belongs_to :site | ||
|
r106 | belongs_to :country | ||
|
r85 | |||
r753 | has_and_belongs_to_many :contests, -> { order(:name)} | |||
|
r268 | |||
r618 | scope :activated_users, -> {where activated: true} | |||
|
r157 | |||
|
r0 | validates_presence_of :login | ||
|
r157 | validates_uniqueness_of :login | ||
r617 | validates_format_of :login, :with => /\A[\_A-Za-z0-9]+\z/ | |||
|
r162 | validates_length_of :login, :within => 3..30 | ||
|
r157 | |||
|
r0 | validates_presence_of :full_name | ||
|
r23 | validates_length_of :full_name, :minimum => 1 | ||
|
r0 | |||
validates_presence_of :password, :if => :password_required? | ||||
|
r776 | validates_length_of :password, :within => 4..50, :if => :password_required? | ||
|
r0 | validates_confirmation_of :password, :if => :password_required? | ||
|
r162 | validates_format_of :email, | ||
:with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, | ||||
:if => :email_validation? | ||||
validate :uniqueness_of_email_from_activated_users, | ||||
:if => :email_validation? | ||||
validate :enough_time_interval_between_same_email_registrations, | ||||
:if => :email_validation? | ||||
|
r157 | |||
|
r162 | # these are for ytopc | ||
# disable for now | ||||
#validates_presence_of :province | ||||
|
r157 | |||
|
r0 | attr_accessor :password | ||
before_save :encrypt_new_password | ||||
|
r162 | before_save :assign_default_site | ||
|
r308 | before_save :assign_default_contest | ||
|
r0 | |||
|
r299 | # this is for will_paginate | ||
cattr_reader :per_page | ||||
@@per_page = 50 | ||||
|
r0 | def self.authenticate(login, password) | ||
user = find_by_login(login) | ||||
r396 | if user | |||
return user if user.authenticated?(password) | ||||
r833 | if user.authenticated_by_cucas?(password) | |||
user.password = password | ||||
user.save | ||||
return user | ||||
end | ||||
r390 | end | |||
|
r0 | end | ||
r833 | ||||
|
r0 | def authenticated?(password) | ||
|
r155 | if self.activated | ||
hashed_password == User.encrypt(password,self.salt) | ||||
else | ||||
false | ||||
end | ||||
|
r0 | end | ||
r833 | def authenticated_by_cucas?(password) | |||
url = URI.parse('https://www.cas.chula.ac.th/cas/api/?q=studentAuthenticate') | ||||
appid = '41508763e340d5858c00f8c1a0f5a2bb' | ||||
appsecret ='d9cbb5863091dbe186fded85722a1e31' | ||||
post_args = { | ||||
'appid' => appid, | ||||
'appsecret' => appsecret, | ||||
'username' => login, | ||||
'password' => password | ||||
} | ||||
#simple call | ||||
begin | ||||
http = Net::HTTP.new('www.cas.chula.ac.th', 443) | ||||
http.use_ssl = true | ||||
http.verify_mode = OpenSSL::SSL::VERIFY_NONE | ||||
result = [ ] | ||||
http.start do |http| | ||||
req = Net::HTTP::Post.new('/cas/api/?q=studentAuthenticate') | ||||
#req = Net::HTTP::Post.new('/appX/prod/?q=studentAuthenticate') | ||||
#req = Net::HTTP::Post.new('/app2/prod/api/?q=studentAuthenticate') | ||||
param = "appid=#{appid}&appsecret=#{appsecret}&username=#{login}&password=#{password}" | ||||
resp = http.request(req,param) | ||||
result = JSON.parse resp.body | ||||
puts result | ||||
end | ||||
return true if result["type"] == "beanStudent" | ||||
rescue => e | ||||
puts e | ||||
puts e.message | ||||
return false | ||||
end | ||||
return false | ||||
end | ||||
|
r0 | def admin? | ||
r757 | self.roles.where(name: 'admin').count > 0 | |||
|
r0 | end | ||
|
r18 | def email_for_editing | ||
|
r23 | if self.email==nil | ||
"(unknown)" | ||||
elsif self.email=='' | ||||
"(blank)" | ||||
else | ||||
|
r18 | self.email | ||
end | ||||
end | ||||
def email_for_editing=(e) | ||||
self.email=e | ||||
end | ||||
def alias_for_editing | ||||
|
r23 | if self.alias==nil | ||
"(unknown)" | ||||
elsif self.alias=='' | ||||
"(blank)" | ||||
else | ||||
|
r18 | self.alias | ||
end | ||||
end | ||||
def alias_for_editing=(e) | ||||
self.alias=e | ||||
end | ||||
|
r155 | def activation_key | ||
|
r158 | if self.hashed_password==nil | ||
encrypt_new_password | ||||
end | ||||
|
r155 | Digest::SHA1.hexdigest(self.hashed_password)[0..7] | ||
end | ||||
def verify_activation_key(key) | ||||
key == activation_key | ||||
end | ||||
|
r157 | def self.random_password(length=5) | ||
chars = 'abcdefghjkmnopqrstuvwxyz' | ||||
password = '' | ||||
length.times { password << chars[rand(chars.length - 1)] } | ||||
password | ||||
end | ||||
|
r200 | def self.find_non_admin_with_prefix(prefix='') | ||
r619 | users = User.all | |||
|
r200 | return users.find_all { |u| !(u.admin?) and u.login.index(prefix)==0 } | ||
end | ||||
|
r217 | # Contest information | ||
|
r297 | def self.find_users_with_no_contest() | ||
r619 | users = User.all | |||
|
r297 | return users.find_all { |u| u.contests.length == 0 } | ||
end | ||||
|
r217 | def contest_time_left | ||
|
r320 | if GraderConfiguration.contest_mode? | ||
|
r217 | return nil if site==nil | ||
return site.time_left | ||||
|
r320 | elsif GraderConfiguration.indv_contest_mode? | ||
time_limit = GraderConfiguration.contest_time_limit | ||||
|
r275 | if time_limit == nil | ||
return nil | ||||
end | ||||
|
r295 | if contest_stat==nil or contest_stat.started_at==nil | ||
|
r217 | return (Time.now.gmtime + time_limit) - Time.now.gmtime | ||
else | ||||
finish_time = contest_stat.started_at + time_limit | ||||
current_time = Time.now.gmtime | ||||
if current_time > finish_time | ||||
return 0 | ||||
else | ||||
return finish_time - current_time | ||||
end | ||||
end | ||||
else | ||||
return nil | ||||
end | ||||
end | ||||
def contest_finished? | ||||
|
r320 | if GraderConfiguration.contest_mode? | ||
|
r217 | return false if site==nil | ||
return site.finished? | ||||
|
r320 | elsif GraderConfiguration.indv_contest_mode? | ||
r753 | return false if self.contest_stat==nil | |||
|
r217 | return contest_time_left == 0 | ||
else | ||||
return false | ||||
end | ||||
end | ||||
def contest_started? | ||||
|
r320 | if GraderConfiguration.indv_contest_mode? | ||
|
r302 | stat = self.contest_stat | ||
return ((stat != nil) and (stat.started_at != nil)) | ||||
|
r320 | elsif GraderConfiguration.contest_mode? | ||
|
r217 | return true if site==nil | ||
return site.started | ||||
else | ||||
return true | ||||
end | ||||
end | ||||
|
r275 | def update_start_time | ||
stat = self.contest_stat | ||||
r585 | if stat.nil? or stat.started_at.nil? | |||
|
r295 | stat ||= UserContestStat.new(:user => self) | ||
stat.started_at = Time.now.gmtime | ||||
|
r275 | stat.save | ||
end | ||||
end | ||||
|
r282 | def problem_in_user_contests?(problem) | ||
problem_contests = problem.contests.all | ||||
if problem_contests.length == 0 # this is public contest | ||||
return true | ||||
end | ||||
contests.each do |contest| | ||||
if problem_contests.find {|c| c.id == contest.id } | ||||
return true | ||||
end | ||||
end | ||||
return false | ||||
end | ||||
|
r288 | def available_problems_group_by_contests | ||
contest_problems = [] | ||||
pin = {} | ||||
contests.enabled.each do |contest| | ||||
available_problems = contest.problems.available | ||||
contest_problems << { | ||||
:contest => contest, | ||||
:problems => available_problems | ||||
} | ||||
available_problems.each {|p| pin[p.id] = true} | ||||
end | ||||
other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0} | ||||
contest_problems << { | ||||
:contest => nil, | ||||
:problems => other_avaiable_problems | ||||
} | ||||
return contest_problems | ||||
end | ||||
r649 | def solve_all_available_problems? | |||
available_problems.each do |p| | ||||
u = self | ||||
sub = Submission.find_last_by_user_and_problem(u.id,p.id) | ||||
return false if !p or !sub or sub.points < p.full_score | ||||
end | ||||
return true | ||||
end | ||||
r682 | #get a list of available problem | |||
|
r288 | def available_problems | ||
r762 | # first, we check if this is normal mode | |||
|
r320 | if not GraderConfiguration.multicontests? | ||
r762 | ||||
#if this is a normal mode | ||||
#we show problem based on problem_group, if the config said so | ||||
r672 | if GraderConfiguration.use_problem_group? | |||
return available_problems_in_group | ||||
else | ||||
return Problem.available_problems | ||||
end | ||||
|
r288 | else | ||
r762 | #this is multi contest mode | |||
|
r288 | contest_problems = [] | ||
pin = {} | ||||
contests.enabled.each do |contest| | ||||
contest.problems.available.each do |problem| | ||||
if not pin.has_key? problem.id | ||||
contest_problems << problem | ||||
end | ||||
pin[problem.id] = true | ||||
end | ||||
end | ||||
other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0} | ||||
return contest_problems + other_avaiable_problems | ||||
end | ||||
end | ||||
r795 | # new feature, get list of available problem in all enabled group that the user belongs to | |||
r672 | def available_problems_in_group | |||
problem = [] | ||||
r795 | self.groups.where(enabled: true).each do |group| | |||
r672 | group.problems.where(available: true).each { |p| problem << p } | |||
end | ||||
r677 | problem.uniq! | |||
if problem | ||||
problem.sort! do |a,b| | ||||
case | ||||
when a.date_added < b.date_added | ||||
1 | ||||
when a.date_added > b.date_added | ||||
-1 | ||||
else | ||||
a.name <=> b.name | ||||
end | ||||
r675 | end | |||
r677 | return problem | |||
else | ||||
return [] | ||||
r675 | end | |||
r672 | end | |||
r795 | #check if the user has the right to view that problem | |||
#this also consider group based problem policy | ||||
|
r282 | def can_view_problem?(problem) | ||
r680 | return true if admin? | |||
return available_problems.include? problem | ||||
|
r282 | end | ||
r525 | def self.clear_last_login | |||
User.update_all(:last_ip => nil) | ||||
end | ||||
|
r18 | protected | ||
|
r0 | def encrypt_new_password | ||
return if password.blank? | ||||
self.salt = (10+rand(90)).to_s | ||||
|
r58 | self.hashed_password = User.encrypt(self.password,self.salt) | ||
|
r0 | end | ||
|
r162 | def assign_default_site | ||
# have to catch error when migrating (because self.site is not available). | ||||
begin | ||||
if self.site==nil | ||||
self.site = Site.find_by_name('default') | ||||
|
r190 | if self.site==nil | ||
self.site = Site.find(1) # when 'default has be renamed' | ||||
end | ||||
|
r162 | end | ||
rescue | ||||
end | ||||
end | ||||
|
r308 | def assign_default_contest | ||
# have to catch error when migrating (because self.site is not available). | ||||
begin | ||||
if self.contests.length == 0 | ||||
|
r320 | default_contest = Contest.find_by_name(GraderConfiguration['contest.default_contest_name']) | ||
|
r308 | if default_contest | ||
self.contests = [default_contest] | ||||
end | ||||
end | ||||
rescue | ||||
end | ||||
end | ||||
|
r0 | def password_required? | ||
|
r58 | self.hashed_password.blank? || !self.password.blank? | ||
|
r0 | end | ||
|
r58 | def self.encrypt(string,salt) | ||
|
r0 | Digest::SHA1.hexdigest(salt + string) | ||
end | ||||
|
r157 | |||
def uniqueness_of_email_from_activated_users | ||||
|
r158 | user = User.activated_users.find_by_email(self.email) | ||
if user and (user.login != self.login) | ||||
r347 | self.errors.add(:base,"Email has already been taken") | |||
|
r157 | end | ||
end | ||||
|
r158 | |||
def enough_time_interval_between_same_email_registrations | ||||
|
r160 | return if !self.new_record? | ||
|
r162 | return if self.activated | ||
|
r158 | open_user = User.find_by_email(self.email, | ||
:order => 'created_at DESC') | ||||
if open_user and open_user.created_at and | ||||
(open_user.created_at > Time.now.gmtime - 5.minutes) | ||||
r347 | self.errors.add(:base,"There are already unactivated registrations with this e-mail address (please wait for 5 minutes)") | |||
|
r158 | end | ||
end | ||||
|
r162 | |||
def email_validation? | ||||
begin | ||||
return VALIDATE_USER_EMAILS | ||||
rescue | ||||
return false | ||||
end | ||||
end | ||||
|
r0 | end | ||