added sol100 and chado cvterm pages to validate_all.t
[sgn.git] / lib / CXGN / Login.pm
blob5d0b08a71dbb2bdf50aafeec3f2e321599b186b6
2 =head1 NAME
4 CXGN::Login - deal with browser site login
6 =head1 DESCRIPTION
8 This is an object which handles logging users in and out of our sites.
10 This class inherits from L<CXGN::DB::Object>.
12 =head1 EXAMPLES
14 #example 1
15 #kick user out if they are not logged in. if they are not logged in, your code will exit here and they will be sent to the login page.
16 #if they are logged in, you will get their person id and your code will continue to execute.
17 my $person_id=CXGN::Login->new()->verify_session();
19 #example 2
20 #kick user out if they are not logged in. if they are not logged in, your code will exit here and they will be sent to the login page.
21 #if they are logged in, you will get their person id and user type and your code will continue to execute.
22 my($person_id,$user_type)=CXGN::Login->new($dbh)->verify_session();
24 #example 3
25 #let everyone view this page, but if they are logged in, get their person id so you can give them a customized page. your code will
26 #continue execution after this line no matter what.
27 my $person_id=CXGN::Login->new($dbh)->has_session();
29 #example 4
30 #let everyone view this page, but if they are logged in, get their person id and user type so you can give them a customized page.
31 #your code will continue execution after this line no matter what.
32 my($person_id,$user_type)=CXGN::Login->new($dbh)->has_session();
34 =head1 AUTHOR
36 John Binns <zombieite@gmail.com>
38 =cut
40 package CXGN::Login;
41 use strict;
43 use Apache2::RequestRec;
44 use Apache2::SubRequest;
45 #use base qw/CXGN::Class::DBI/;
46 use CXGN::Cookie;
47 use SGN::Context;
48 use Digest::MD5 qw(md5);
49 use String::Random;
51 use base qw | CXGN::DB::Object |;
53 our $LOGIN_COOKIE_NAME='sgn_session_id';
54 our $LOGIN_PAGE='/solpeople/login.pl';
55 our $LOGIN_TIMEOUT=7200;#seconds for login to timeout
56 our $DBH;
57 our $EXCHANGE_DBH = 1;
59 =head2 constructor new()
61 Usage: my $login = CXGN::Login->new($dbh)
62 Desc: creates a new login object
63 Ret:
64 Args: a database handle
65 Side Effects: connects to database
66 Example:
68 =cut
70 sub new {
71 my $class=shift;
72 my $dbh = shift;
73 my $self = $class->SUPER::new($dbh);
74 $self->set_sql(); #### This SQL should really be in the CXGN::People::Person object!
76 foreach(@_){
77 if(ref($_) eq "HASH") {
78 #Process hash args here
79 $self->{no_redirect} = $_->{NO_REDIRECT};
80 last;
83 $self->{conf_object}=SGN::Context->new;
84 return $self;
87 =head2 get_login_status
89 Usage: my %logged_in_status = $login -> get_login_status();
90 Desc: a member function. This was changed on 5/1/2009.
91 Ret: a hash with user_type as a key and count of logins as a value
92 Args: none
93 Side Effects: accesses the database
94 Example:
96 =cut
98 sub get_login_status {
99 my $self = shift;
101 my $sth = $self->get_sql("stats_aggregate");
102 $sth->execute($LOGIN_TIMEOUT);
104 my %logins = ();
105 while (my ($user_type, $count) = $sth->fetchrow_array()) {
106 $logins{$user_type}=$count;
108 if (!$logins{curator}) { $logins{curator}="none"; }
109 if (!$logins{submitter}) { $logins{submitter}="none"; }
110 if (!$logins{user}) { $logins{user} = "none"; }
112 $sth = $self->get_sql("stats_private");
113 $sth->execute($LOGIN_TIMEOUT);
115 $logins{detailed} = {};
116 while(my ($user_type, $username, $contact_email) = $sth->fetchrow_array()) {
117 $logins{detailed}->{$user_type}->{$username}->{contact_email} = $contact_email;
120 if(wantarray){
121 return %logins;
123 else {
124 return \%logins;
129 =head2 get_login_info
131 Usage: $login->get_login_info()
132 Desc:
133 Ret:
134 Args:
135 Side Effects:
136 Example:
138 =cut
140 sub get_login_info {
141 my $self = shift;
142 return $self->{login_info};
146 =head2 verify_session
148 Usage: $login->verify_session($user_type)
149 Desc: checks whether a user is logged in currently and
150 is of the minimum user type $user_type.
151 user types have the following precedence:
152 user < submitter < sequencer < curator
153 Ret: the person_id, if a session exists
154 Args: a minimum user type required to access the page
155 Side Effects: redirects the website to the login page if no login
156 is currently defined.
157 Example:
159 =cut
161 sub verify_session {
162 my $self=shift;
163 my($user_must_be_type)=@_;
164 my($person_id,$user_type)=$self->has_session();
165 if($person_id) { #if they have a session
166 if($user_must_be_type) { #if there is a type that they must be to view this page
168 if($user_must_be_type ne $user_type) {#if they are not the required type, send them away
170 $self->login_page_and_exit();
174 else { #else they do not have a session, so send them away
176 $self->login_page_and_exit();
178 if(wantarray) {#if they are trying to get both pieces of info, give it to them, in array context
180 return($person_id,$user_type);
182 else { #else they just care about the login id
184 return $person_id;
188 =head2 has_session ()
190 if the user is not logged in, the return value is false;
191 else it's the person ID if in scalar context, or (person ID, user type) in array context
193 =cut
195 sub has_session {
196 my $self=shift;
198 #if people are not allowed to be logged in, return
199 if(!$self->login_allowed()){
200 return;
203 my $cookie=$self->get_login_cookie();
205 #if they have no cookie, they are not logged in
206 unless($cookie){
207 return;
210 my ($person_id,$user_type,$user_prefs,$expired)= $self->query_from_cookie($cookie);
212 #if cookie string is not found, they are not logged in
213 unless($person_id and $user_type){
214 return;
217 #if their cookie is good but their timestamp is old, they are not logged in
218 if($expired) {
219 return;
222 ################################
223 # Ok, they are logged in! yay! #
224 ################################
226 $self->{login_info}->{person_id} = $person_id;
227 $self->{login_info}->{cookie_string} = $cookie;
228 $self->{login_info}->{user_type} = $user_type;
229 $self->{login_info}->{user_prefs} = $user_prefs;
230 $self->update_timestamp();
232 #if they are trying to get both pieces of info, give it to them, in array context
233 if (wantarray) {
234 return ($person_id, $user_type);
236 #or they just care about the login id
237 else {
238 return $person_id;
242 sub query_from_cookie {
243 my $self = shift;
244 my $cookie_string = shift;
246 my $sth = $self->get_sql("user_from_cookie");
247 return undef unless $sth;
248 if(!$sth->execute($LOGIN_TIMEOUT, $cookie_string)){
249 print STDERR "Cookie Query Error: " . $DBH->errstr;
250 return undef;
252 my @result = $sth->fetchrow_array();
254 return undef unless scalar(@result);
256 #if TWO rows are found with the SAME cookie_string, scream!
257 if(scalar(@result) && $sth->fetchrow_array()){
258 die "Duplicate cookie_string entries found for cookie string '$cookie_string'";
261 #Return info, or just the person_id, depending on array/scalar context of function
262 if(wantarray){
263 return @result;
265 else {
266 return $result[0];
270 sub login_allowed {
271 my $self=shift;
272 #conditions for allowing logins:
274 # 1. configuration 'disable_login' must be 0 or undef
275 # 2. configuration 'is_mirror' must be 0 or undef
276 # 3. configuration 'dbname' must not be 'sandbox' if configuration 'production_server' is 1
277 # -- the reason for this is that if users can log in, they must be able to log in to the REAL database,
278 # not some mirror or some sandbox, because logged-in users can CHANGE data in the database and we
279 # don't want to lose or ignore those changes.
282 !$self->{conf_object}->get_conf('disable_login')
283 and !$self->{conf_object}->get_conf('is_mirror')
284 #we haven't decided whether it's a good idea to comment this next line by default -- Evan
285 and !($self->{conf_object}->get_conf('dbname')=~/sandbox/ and $self->{conf_object}->get_conf('production_server'))
288 return 1;
290 else {
291 return 0;
295 sub login_user {
296 my $self=shift;
297 my($username,$password)=@_;
298 my $login_info;#information about whether login succeeded, and if not, why not
299 if($self->login_allowed())
301 my $sth = $self->get_sql("user_from_uname_pass");
302 my $num_rows = $sth->execute($username,$password);
304 my($person_id,$disabled,$user_prefs)=$sth->fetchrow_array();
305 if($num_rows > 1){die "Duplicate entries found for username '$username'";}
306 if($disabled)
308 $login_info->{account_disabled}=$disabled;
310 else {
311 $login_info->{user_prefs} = $user_prefs;
312 if($person_id) {
313 my $new_cookie_string=String::Random->new()->randpattern("ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc");
314 $sth = $self->get_sql("cookie_string_exists");
315 $sth->execute($new_cookie_string);
316 if($sth->fetchrow_array())#very unlikely--or we need a new random string generator
318 $login_info->{duplicate_cookie_string}=1;
320 else {
321 $sth = $self->get_sql("login");
322 $sth->execute($new_cookie_string,$person_id);
323 CXGN::Cookie::set_cookie($LOGIN_COOKIE_NAME,$new_cookie_string);
324 CXGN::Cookie::set_cookie("user_prefs", $user_prefs);
325 $login_info->{person_id}=$person_id;
326 $login_info->{cookie_string} = $new_cookie_string;
329 else {
330 $login_info->{incorrect_password}=1;
334 else {
335 $login_info->{logins_disabled}=1;
337 $self->{login_info} = $login_info;
338 return $login_info;
341 =head2 function logout_user()
343 Usage: $login->logout_user();
344 Desc: log out the current logged in user
345 Ret: nothing
346 Args: none
347 Side Effects: resets the cookie to empty
348 Example:
350 =cut
352 sub logout_user {
353 my $self=shift;
354 my $cookie=$self->get_login_cookie();
355 if($cookie) {
356 my $sth = $self->get_sql("logout");
357 $sth->execute($cookie);
358 CXGN::Cookie::set_cookie($LOGIN_COOKIE_NAME,"");
362 =head2 update_timestamp
364 Usage: $login->update_timestamp();
365 Desc: updates the timestamp, such that users don't
366 get logged out when they are active on the site.
367 Ret: nothing
368 Args: none
369 Side Effects: accesses the database to change the timeout status.
370 Example:
372 =cut
374 sub update_timestamp {
375 my $self=shift;
376 my $cookie=$self->get_login_cookie();
377 if($cookie) {
378 my $sth = $self->get_sql("refresh_cookie");
379 $sth->execute($cookie);
383 =head2 get_login_cookie
385 Usage: my $cookie = $login->get_login_cookie();
386 Desc: returns the cookie for the current login
387 Args: none
388 Side Effects:
389 Example:
391 =cut
393 sub get_login_cookie {
394 my $self=shift;
395 return CXGN::Cookie::get_cookie($LOGIN_COOKIE_NAME);
398 =head2 login_page_and_exit
400 Usage: $login->login_page_and_exit();
401 Desc: redirects to the login page.
402 Ret:
403 Args:
404 Side Effects:
405 Example:
407 =cut
409 sub login_page_and_exit {
410 my $self=shift;
411 Apache2::RequestUtil->request()->internal_redirect($LOGIN_PAGE) unless $self->{no_redirect};
412 exit 0;
416 ### helper function. SQL should probably be moved to the CXGN::People::Login class
419 sub set_sql {
420 my $self =shift;
422 $self->{queries} = {
424 user_from_cookie => #send: session_time_in_secs, cookiestring
426 " SELECT
427 sp_person_id,
428 user_type,
429 user_prefs,
430 extract (epoch FROM current_timestamp-last_access_time)>? AS expired
431 FROM
432 sgn_people.sp_person
433 WHERE
434 cookie_string=?",
436 user_from_uname_pass =>
438 " SELECT
439 sp_person_id, disabled, user_prefs
440 FROM
441 sgn_people.sp_person
442 WHERE
443 UPPER(username)=UPPER(?)
444 AND password=?",
446 cookie_string_exists =>
448 " SELECT
449 cookie_string
450 FROM
451 sgn_people.sp_person
452 WHERE
453 cookie_string=?",
455 login => #send: cookie_string, sp_person_id
457 " UPDATE
458 sgn_people.sp_person
459 SET
460 cookie_string=?,
461 last_access_time=current_timestamp
462 WHERE
463 sp_person_id=?",
465 logout => #send: cookie_string
467 " UPDATE
468 sgn_people.sp_person
469 SET
470 cookie_string=null,
471 last_access_time=current_timestamp
472 WHERE
473 cookie_string=?",
475 refresh_cookie => #send: cookie_string (updates the timestamp)
477 " UPDATE
478 sgn_people.sp_person
479 SET
480 last_access_time=current_timestamp
481 WHERE
482 cookie_string=?",
484 stats_aggregate => #send: session_timeout_in_secs (gets aggregate login data)
486 " SELECT
487 user_type, count(*)
488 FROM
489 sgn_people.sp_person
490 WHERE
491 last_access_time IS NOT NULL
492 AND cookie_string IS NOT NULL
493 AND extract(epoch from now()-last_access_time)<?
494 GROUP BY
495 user_type ",
497 stats_private => #send: session_timeout_in_secs (gets all logged-in users)
499 " SELECT
500 user_type, username, contact_email
501 FROM
502 sgn_people.sp_person
503 WHERE
504 last_access_time IS NOT NULL
505 AND cookie_string IS NOT NULL
506 AND extract(epoch from now()-last_access_time)<?",
511 while(my($name,$sql) = each %{$self->{queries}}){
512 $self->{query_handles}->{$name} = $self->get_dbh()->prepare($sql);
517 sub get_sql {
518 my $self =shift;
519 my $name = shift;
520 return $self->{query_handles}->{$name};
524 1;#do not remove