Description:
add authentication by CU-CAS from p' krerk
Commit status:
[Not Reviewed]
References:
Comments:
0 Commit comments 0 Inline Comments
Unresolved TODOs:
There are no unresolved TODOs
Add another comment

r396:4b88edeab117 - - 1 file changed: 31 inserted, 6 deleted

@@ -1,342 +1,367
1 require 'digest/sha1'
1 require 'digest/sha1'
2 require 'net/pop'
2 require 'net/pop'
3 + require 'json'
3
4
4 class User < ActiveRecord::Base
5 class User < ActiveRecord::Base
5
6
6 has_and_belongs_to_many :roles
7 has_and_belongs_to_many :roles
7
8
8 has_many :test_requests, :order => "submitted_at DESC"
9 has_many :test_requests, :order => "submitted_at DESC"
9
10
10 has_many :messages,
11 has_many :messages,
11 :class_name => "Message",
12 :class_name => "Message",
12 :foreign_key => "sender_id",
13 :foreign_key => "sender_id",
13 :order => 'created_at DESC'
14 :order => 'created_at DESC'
14
15
15 has_many :replied_messages,
16 has_many :replied_messages,
16 :class_name => "Message",
17 :class_name => "Message",
17 :foreign_key => "receiver_id",
18 :foreign_key => "receiver_id",
18 :order => 'created_at DESC'
19 :order => 'created_at DESC'
19
20
20 has_one :contest_stat, :class_name => "UserContestStat", :dependent => :destroy
21 has_one :contest_stat, :class_name => "UserContestStat", :dependent => :destroy
21
22
22 belongs_to :site
23 belongs_to :site
23 belongs_to :country
24 belongs_to :country
24
25
25 has_and_belongs_to_many :contests, :uniq => true, :order => 'name'
26 has_and_belongs_to_many :contests, :uniq => true, :order => 'name'
26
27
27 scope :activated_users, :conditions => {:activated => true}
28 scope :activated_users, :conditions => {:activated => true}
28
29
29 validates_presence_of :login
30 validates_presence_of :login
30 validates_uniqueness_of :login
31 validates_uniqueness_of :login
31 validates_format_of :login, :with => /^[\_A-Za-z0-9]+$/
32 validates_format_of :login, :with => /^[\_A-Za-z0-9]+$/
32 validates_length_of :login, :within => 3..30
33 validates_length_of :login, :within => 3..30
33
34
34 validates_presence_of :full_name
35 validates_presence_of :full_name
35 validates_length_of :full_name, :minimum => 1
36 validates_length_of :full_name, :minimum => 1
36
37
37 validates_presence_of :password, :if => :password_required?
38 validates_presence_of :password, :if => :password_required?
38 validates_length_of :password, :within => 4..20, :if => :password_required?
39 validates_length_of :password, :within => 4..20, :if => :password_required?
39 validates_confirmation_of :password, :if => :password_required?
40 validates_confirmation_of :password, :if => :password_required?
40
41
41 validates_format_of :email,
42 validates_format_of :email,
42 :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i,
43 :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i,
43 :if => :email_validation?
44 :if => :email_validation?
44 validate :uniqueness_of_email_from_activated_users,
45 validate :uniqueness_of_email_from_activated_users,
45 :if => :email_validation?
46 :if => :email_validation?
46 validate :enough_time_interval_between_same_email_registrations,
47 validate :enough_time_interval_between_same_email_registrations,
47 :if => :email_validation?
48 :if => :email_validation?
48
49
49 # these are for ytopc
50 # these are for ytopc
50 # disable for now
51 # disable for now
51 #validates_presence_of :province
52 #validates_presence_of :province
52
53
53 attr_accessor :password
54 attr_accessor :password
54
55
55 before_save :encrypt_new_password
56 before_save :encrypt_new_password
56 before_save :assign_default_site
57 before_save :assign_default_site
57 before_save :assign_default_contest
58 before_save :assign_default_contest
58
59
59 # this is for will_paginate
60 # this is for will_paginate
60 cattr_reader :per_page
61 cattr_reader :per_page
61 @@per_page = 50
62 @@per_page = 50
62
63
63 def self.authenticate(login, password)
64 def self.authenticate(login, password)
64 user = find_by_login(login)
65 user = find_by_login(login)
65 - return user if user && user.authenticated?(password)
66 + if user
66 - if user && user.authenticated_by_pop3?(password)
67 + return user if user.authenticated?(password)
67 - user.password = password
68 + if user.authenticated_by_cucas?(password) or user.authenticated_by_pop3?(password)
68 - user.save
69 + user.password = password
69 - return user
70 + user.save
71 + return user
72 + end
70 end
73 end
71 end
74 end
72
75
73 def authenticated?(password)
76 def authenticated?(password)
74 if self.activated
77 if self.activated
75 hashed_password == User.encrypt(password,self.salt)
78 hashed_password == User.encrypt(password,self.salt)
76 else
79 else
77 false
80 false
78 end
81 end
79 end
82 end
80
83
81 def authenticated_by_pop3?(password)
84 def authenticated_by_pop3?(password)
82 Net::POP3.enable_ssl
85 Net::POP3.enable_ssl
83 pop = Net::POP3.new('pops.it.chula.ac.th')
86 pop = Net::POP3.new('pops.it.chula.ac.th')
84 authen = true
87 authen = true
85 begin
88 begin
86 - pop.start(login, password) # (1)
89 + pop.start(login, password)
87 pop.finish
90 pop.finish
88 return true
91 return true
89 rescue
92 rescue
90 return false
93 return false
91 end
94 end
92 end
95 end
93
96
97 + def authenticated_by_cucas?(password)
98 + url = URI.parse('https://www.cas.chula.ac.th/cas/api/?q=studentAuthenticate')
99 + appid = '41508763e340d5858c00f8c1a0f5a2bb'
100 + appsecret ='d9cbb5863091dbe186fded85722a1e31'
101 + post_args = {
102 + 'appid' => appid,
103 + 'appsecret' => appsecret,
104 + 'username' => login,
105 + 'password' => password
106 + }
107 +
108 + #simple call
109 + begin
110 + resp = Net::HTTP.post_form(url, post_args)
111 + result = JSON.parse resp.body
112 + return true if result["type"] == "beanStudent"
113 + rescue
114 + return false
115 + end
116 + return false
117 + end
118 +
94 def admin?
119 def admin?
95 self.roles.detect {|r| r.name == 'admin' }
120 self.roles.detect {|r| r.name == 'admin' }
96 end
121 end
97
122
98 def email_for_editing
123 def email_for_editing
99 if self.email==nil
124 if self.email==nil
100 "(unknown)"
125 "(unknown)"
101 elsif self.email==''
126 elsif self.email==''
102 "(blank)"
127 "(blank)"
103 else
128 else
104 self.email
129 self.email
105 end
130 end
106 end
131 end
107
132
108 def email_for_editing=(e)
133 def email_for_editing=(e)
109 self.email=e
134 self.email=e
110 end
135 end
111
136
112 def alias_for_editing
137 def alias_for_editing
113 if self.alias==nil
138 if self.alias==nil
114 "(unknown)"
139 "(unknown)"
115 elsif self.alias==''
140 elsif self.alias==''
116 "(blank)"
141 "(blank)"
117 else
142 else
118 self.alias
143 self.alias
119 end
144 end
120 end
145 end
121
146
122 def alias_for_editing=(e)
147 def alias_for_editing=(e)
123 self.alias=e
148 self.alias=e
124 end
149 end
125
150
126 def activation_key
151 def activation_key
127 if self.hashed_password==nil
152 if self.hashed_password==nil
128 encrypt_new_password
153 encrypt_new_password
129 end
154 end
130 Digest::SHA1.hexdigest(self.hashed_password)[0..7]
155 Digest::SHA1.hexdigest(self.hashed_password)[0..7]
131 end
156 end
132
157
133 def verify_activation_key(key)
158 def verify_activation_key(key)
134 key == activation_key
159 key == activation_key
135 end
160 end
136
161
137 def self.random_password(length=5)
162 def self.random_password(length=5)
138 chars = 'abcdefghjkmnopqrstuvwxyz'
163 chars = 'abcdefghjkmnopqrstuvwxyz'
139 password = ''
164 password = ''
140 length.times { password << chars[rand(chars.length - 1)] }
165 length.times { password << chars[rand(chars.length - 1)] }
141 password
166 password
142 end
167 end
143
168
144 def self.find_non_admin_with_prefix(prefix='')
169 def self.find_non_admin_with_prefix(prefix='')
145 users = User.find(:all)
170 users = User.find(:all)
146 return users.find_all { |u| !(u.admin?) and u.login.index(prefix)==0 }
171 return users.find_all { |u| !(u.admin?) and u.login.index(prefix)==0 }
147 end
172 end
148
173
149 # Contest information
174 # Contest information
150
175
151 def self.find_users_with_no_contest()
176 def self.find_users_with_no_contest()
152 users = User.find(:all)
177 users = User.find(:all)
153 return users.find_all { |u| u.contests.length == 0 }
178 return users.find_all { |u| u.contests.length == 0 }
154 end
179 end
155
180
156
181
157 def contest_time_left
182 def contest_time_left
158 if GraderConfiguration.contest_mode?
183 if GraderConfiguration.contest_mode?
159 return nil if site==nil
184 return nil if site==nil
160 return site.time_left
185 return site.time_left
161 elsif GraderConfiguration.indv_contest_mode?
186 elsif GraderConfiguration.indv_contest_mode?
162 time_limit = GraderConfiguration.contest_time_limit
187 time_limit = GraderConfiguration.contest_time_limit
163 if time_limit == nil
188 if time_limit == nil
164 return nil
189 return nil
165 end
190 end
166 if contest_stat==nil or contest_stat.started_at==nil
191 if contest_stat==nil or contest_stat.started_at==nil
167 return (Time.now.gmtime + time_limit) - Time.now.gmtime
192 return (Time.now.gmtime + time_limit) - Time.now.gmtime
168 else
193 else
169 finish_time = contest_stat.started_at + time_limit
194 finish_time = contest_stat.started_at + time_limit
170 current_time = Time.now.gmtime
195 current_time = Time.now.gmtime
171 if current_time > finish_time
196 if current_time > finish_time
172 return 0
197 return 0
173 else
198 else
174 return finish_time - current_time
199 return finish_time - current_time
175 end
200 end
176 end
201 end
177 else
202 else
178 return nil
203 return nil
179 end
204 end
180 end
205 end
181
206
182 def contest_finished?
207 def contest_finished?
183 if GraderConfiguration.contest_mode?
208 if GraderConfiguration.contest_mode?
184 return false if site==nil
209 return false if site==nil
185 return site.finished?
210 return site.finished?
186 elsif GraderConfiguration.indv_contest_mode?
211 elsif GraderConfiguration.indv_contest_mode?
187 return false if self.contest_stat(true)==nil
212 return false if self.contest_stat(true)==nil
188 return contest_time_left == 0
213 return contest_time_left == 0
189 else
214 else
190 return false
215 return false
191 end
216 end
192 end
217 end
193
218
194 def contest_started?
219 def contest_started?
195 if GraderConfiguration.indv_contest_mode?
220 if GraderConfiguration.indv_contest_mode?
196 stat = self.contest_stat
221 stat = self.contest_stat
197 return ((stat != nil) and (stat.started_at != nil))
222 return ((stat != nil) and (stat.started_at != nil))
198 elsif GraderConfiguration.contest_mode?
223 elsif GraderConfiguration.contest_mode?
199 return true if site==nil
224 return true if site==nil
200 return site.started
225 return site.started
201 else
226 else
202 return true
227 return true
203 end
228 end
204 end
229 end
205
230
206 def update_start_time
231 def update_start_time
207 stat = self.contest_stat
232 stat = self.contest_stat
208 if stat == nil or stat.started_at == nil
233 if stat == nil or stat.started_at == nil
209 stat ||= UserContestStat.new(:user => self)
234 stat ||= UserContestStat.new(:user => self)
210 stat.started_at = Time.now.gmtime
235 stat.started_at = Time.now.gmtime
211 stat.save
236 stat.save
212 end
237 end
213 end
238 end
214
239
215 def problem_in_user_contests?(problem)
240 def problem_in_user_contests?(problem)
216 problem_contests = problem.contests.all
241 problem_contests = problem.contests.all
217
242
218 if problem_contests.length == 0 # this is public contest
243 if problem_contests.length == 0 # this is public contest
219 return true
244 return true
220 end
245 end
221
246
222 contests.each do |contest|
247 contests.each do |contest|
223 if problem_contests.find {|c| c.id == contest.id }
248 if problem_contests.find {|c| c.id == contest.id }
224 return true
249 return true
225 end
250 end
226 end
251 end
227 return false
252 return false
228 end
253 end
229
254
230 def available_problems_group_by_contests
255 def available_problems_group_by_contests
231 contest_problems = []
256 contest_problems = []
232 pin = {}
257 pin = {}
233 contests.enabled.each do |contest|
258 contests.enabled.each do |contest|
234 available_problems = contest.problems.available
259 available_problems = contest.problems.available
235 contest_problems << {
260 contest_problems << {
236 :contest => contest,
261 :contest => contest,
237 :problems => available_problems
262 :problems => available_problems
238 }
263 }
239 available_problems.each {|p| pin[p.id] = true}
264 available_problems.each {|p| pin[p.id] = true}
240 end
265 end
241 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
266 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
242 contest_problems << {
267 contest_problems << {
243 :contest => nil,
268 :contest => nil,
244 :problems => other_avaiable_problems
269 :problems => other_avaiable_problems
245 }
270 }
246 return contest_problems
271 return contest_problems
247 end
272 end
248
273
249 def available_problems
274 def available_problems
250 if not GraderConfiguration.multicontests?
275 if not GraderConfiguration.multicontests?
251 return Problem.find_available_problems
276 return Problem.find_available_problems
252 else
277 else
253 contest_problems = []
278 contest_problems = []
254 pin = {}
279 pin = {}
255 contests.enabled.each do |contest|
280 contests.enabled.each do |contest|
256 contest.problems.available.each do |problem|
281 contest.problems.available.each do |problem|
257 if not pin.has_key? problem.id
282 if not pin.has_key? problem.id
258 contest_problems << problem
283 contest_problems << problem
259 end
284 end
260 pin[problem.id] = true
285 pin[problem.id] = true
261 end
286 end
262 end
287 end
263 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
288 other_avaiable_problems = Problem.available.find_all {|p| pin[p.id]==nil and p.contests.length==0}
264 return contest_problems + other_avaiable_problems
289 return contest_problems + other_avaiable_problems
265 end
290 end
266 end
291 end
267
292
268 def can_view_problem?(problem)
293 def can_view_problem?(problem)
269 if not GraderConfiguration.multicontests?
294 if not GraderConfiguration.multicontests?
270 return problem.available
295 return problem.available
271 else
296 else
272 return problem_in_user_contests? problem
297 return problem_in_user_contests? problem
273 end
298 end
274 end
299 end
275
300
276 protected
301 protected
277 def encrypt_new_password
302 def encrypt_new_password
278 return if password.blank?
303 return if password.blank?
279 self.salt = (10+rand(90)).to_s
304 self.salt = (10+rand(90)).to_s
280 self.hashed_password = User.encrypt(self.password,self.salt)
305 self.hashed_password = User.encrypt(self.password,self.salt)
281 end
306 end
282
307
283 def assign_default_site
308 def assign_default_site
284 # have to catch error when migrating (because self.site is not available).
309 # have to catch error when migrating (because self.site is not available).
285 begin
310 begin
286 if self.site==nil
311 if self.site==nil
287 self.site = Site.find_by_name('default')
312 self.site = Site.find_by_name('default')
288 if self.site==nil
313 if self.site==nil
289 self.site = Site.find(1) # when 'default has be renamed'
314 self.site = Site.find(1) # when 'default has be renamed'
290 end
315 end
291 end
316 end
292 rescue
317 rescue
293 end
318 end
294 end
319 end
295
320
296 def assign_default_contest
321 def assign_default_contest
297 # have to catch error when migrating (because self.site is not available).
322 # have to catch error when migrating (because self.site is not available).
298 begin
323 begin
299 if self.contests.length == 0
324 if self.contests.length == 0
300 default_contest = Contest.find_by_name(GraderConfiguration['contest.default_contest_name'])
325 default_contest = Contest.find_by_name(GraderConfiguration['contest.default_contest_name'])
301 if default_contest
326 if default_contest
302 self.contests = [default_contest]
327 self.contests = [default_contest]
303 end
328 end
304 end
329 end
305 rescue
330 rescue
306 end
331 end
307 end
332 end
308
333
309 def password_required?
334 def password_required?
310 self.hashed_password.blank? || !self.password.blank?
335 self.hashed_password.blank? || !self.password.blank?
311 end
336 end
312
337
313 def self.encrypt(string,salt)
338 def self.encrypt(string,salt)
314 Digest::SHA1.hexdigest(salt + string)
339 Digest::SHA1.hexdigest(salt + string)
315 end
340 end
316
341
317 def uniqueness_of_email_from_activated_users
342 def uniqueness_of_email_from_activated_users
318 user = User.activated_users.find_by_email(self.email)
343 user = User.activated_users.find_by_email(self.email)
319 if user and (user.login != self.login)
344 if user and (user.login != self.login)
320 self.errors.add_to_base("Email has already been taken")
345 self.errors.add_to_base("Email has already been taken")
321 end
346 end
322 end
347 end
323
348
324 def enough_time_interval_between_same_email_registrations
349 def enough_time_interval_between_same_email_registrations
325 return if !self.new_record?
350 return if !self.new_record?
326 return if self.activated
351 return if self.activated
327 open_user = User.find_by_email(self.email,
352 open_user = User.find_by_email(self.email,
328 :order => 'created_at DESC')
353 :order => 'created_at DESC')
329 if open_user and open_user.created_at and
354 if open_user and open_user.created_at and
330 (open_user.created_at > Time.now.gmtime - 5.minutes)
355 (open_user.created_at > Time.now.gmtime - 5.minutes)
331 self.errors.add_to_base("There are already unactivated registrations with this e-mail address (please wait for 5 minutes)")
356 self.errors.add_to_base("There are already unactivated registrations with this e-mail address (please wait for 5 minutes)")
332 end
357 end
333 end
358 end
334
359
335 def email_validation?
360 def email_validation?
336 begin
361 begin
337 return VALIDATE_USER_EMAILS
362 return VALIDATE_USER_EMAILS
338 rescue
363 rescue
339 return false
364 return false
340 end
365 end
341 end
366 end
342 end
367 end
You need to be logged in to leave comments. Login now