fix to years pheno download
[sgn.git] / lib / CXGN / Login.pm
blobe3d8b97b31606a8de55e084c9e1ed97e920c87ae
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;
42 use warnings;
44 use Digest::MD5 qw(md5);
45 use String::Random;
47 use CXGN::Cookie;
49 use CatalystX::GlobalContext '$c';
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()
75 ; #### This SQL should really be in the CXGN::People::Person object!
77 foreach (@_) {
78 if ( ref($_) eq "HASH" ) {
80 #Process hash args here
81 $self->{no_redirect} = $_->{NO_REDIRECT};
82 last;
85 $self->{conf_object} = $c || do{ require SGN::Context; SGN::Context->new };
86 return $self;
89 =head2 get_login_status
91 Usage: my %logged_in_status = $login -> get_login_status();
92 Desc: a member function. This was changed on 5/1/2009.
93 Ret: a hash with user_type as a key and count of logins as a value
94 Args: none
95 Side Effects: accesses the database
96 Example:
98 =cut
100 sub get_login_status {
101 my $self = shift;
103 my $sth = $self->get_sql("stats_aggregate");
104 $sth->execute($LOGIN_TIMEOUT);
106 my %logins = ();
107 while ( my ( $user_type, $count ) = $sth->fetchrow_array() ) {
108 $logins{$user_type} = $count;
110 if ( !$logins{curator} ) { $logins{curator} = "none"; }
111 if ( !$logins{submitter} ) { $logins{submitter} = "none"; }
112 if ( !$logins{user} ) { $logins{user} = "none"; }
114 $sth = $self->get_sql("stats_private");
115 $sth->execute($LOGIN_TIMEOUT);
117 $logins{detailed} = {};
118 while ( my ( $user_type, $username, $contact_email ) =
119 $sth->fetchrow_array() )
121 $logins{detailed}->{$user_type}->{$username}->{contact_email} =
122 $contact_email;
125 if (wantarray) {
126 return %logins;
128 else {
129 return \%logins;
133 =head2 get_login_info
135 Usage: $login->get_login_info()
136 Desc:
137 Ret:
138 Args:
139 Side Effects:
140 Example:
142 =cut
144 sub get_login_info {
145 my $self = shift;
146 return $self->{login_info};
149 =head2 verify_session
151 Usage: $login->verify_session($user_type)
152 Desc: checks whether a user is logged in currently and
153 is of the minimum user type $user_type.
154 user types have the following precedence:
155 user < submitter < sequencer < curator
156 Ret: the person_id, if a session exists
157 Args: a minimum user type required to access the page
158 Side Effects: redirects the website to the login page if no login
159 is currently defined.
160 Example:
162 =cut
164 sub verify_session {
165 my $self = shift;
166 my ($user_must_be_type) = @_;
167 my ( $person_id, $user_type ) = $self->has_session();
168 if ($person_id) { #if they have a session
169 if ($user_must_be_type)
170 { #if there is a type that they must be to view this page
172 if ( $user_must_be_type ne $user_type )
173 { #if they are not the required type, send them away
175 $self->login_page_and_exit();
179 else { #else they do not have a session, so send them away
181 $self->login_page_and_exit();
183 if (wantarray)
184 { #if they are trying to get both pieces of info, give it to them, in array context
186 return ( $person_id, $user_type );
188 else { #else they just care about the login id
190 return $person_id;
194 =head2 has_session ()
196 if the user is not logged in, the return value is false;
197 else it's the person ID if in scalar context, or (person ID, user type) in array context
199 =cut
201 sub has_session {
202 my $self = shift;
204 #if people are not allowed to be logged in, return
205 if ( !$self->login_allowed() ) {
206 return;
209 my $cookie = $self->get_login_cookie();
211 #if they have no cookie, they are not logged in
212 unless ($cookie) {
213 return;
216 my ( $person_id, $user_type, $user_prefs, $expired ) =
217 $self->query_from_cookie($cookie);
219 #if cookie string is not found, they are not logged in
220 unless ( $person_id and $user_type ) {
221 return;
224 #if their cookie is good but their timestamp is old, they are not logged in
225 if ($expired) {
226 return;
229 ################################
230 # Ok, they are logged in! yay! #
231 ################################
233 $self->{login_info}->{person_id} = $person_id;
234 $self->{login_info}->{cookie_string} = $cookie;
235 $self->{login_info}->{user_type} = $user_type;
236 $self->{login_info}->{user_prefs} = $user_prefs;
237 $self->update_timestamp();
239 #if they are trying to get both pieces of info, give it to them, in array context
240 if (wantarray) {
241 return ( $person_id, $user_type );
244 #or they just care about the login id
245 else {
246 return $person_id;
250 sub query_from_cookie {
251 my $self = shift;
252 my $cookie_string = shift;
254 my $sth = $self->get_sql("user_from_cookie");
255 return undef unless $sth;
256 if ( !$sth->execute( $LOGIN_TIMEOUT, $cookie_string ) ) {
257 print STDERR "Cookie Query Error: " . $DBH->errstr;
258 return undef;
260 my @result = $sth->fetchrow_array();
262 return undef unless scalar(@result);
264 #if TWO rows are found with the SAME cookie_string, scream!
265 if ( scalar(@result) && $sth->fetchrow_array() ) {
267 "Duplicate cookie_string entries found for cookie string '$cookie_string'";
270 #Return info, or just the person_id, depending on array/scalar context of function
271 if (wantarray) {
272 return @result;
274 else {
275 return $result[0];
279 sub login_allowed {
280 my $self = shift;
282 #conditions for allowing logins:
284 # 1. configuration 'disable_login' must be 0 or undef
285 # 2. configuration 'is_mirror' must be 0 or undef
286 # 3. configuration 'dbname' must not be 'sandbox' if configuration 'production_server' is 1
287 # -- the reason for this is that if users can log in, they must be able to log in to the REAL database,
288 # not some mirror or some sandbox, because logged-in users can CHANGE data in the database and we
289 # don't want to lose or ignore those changes.
290 if (
291 !$self->{conf_object}->get_conf('disable_login')
292 and !$self->{conf_object}->get_conf('is_mirror')
294 #we haven't decided whether it's a good idea to comment this next line by default -- Evan
295 and !(
296 $self->{conf_object}->get_conf('dbname') =~ /sandbox/
297 and $self->{conf_object}->get_conf('production_server')
301 return 1;
303 else {
304 return 0;
308 sub login_user {
309 my $self = shift;
310 my ( $username, $password ) = @_;
311 my $login_info
312 ; #information about whether login succeeded, and if not, why not
313 if ( $self->login_allowed() ) {
314 my $sth = $self->get_sql("user_from_uname_pass");
315 my $num_rows = $sth->execute( $username, $password );
317 my ( $person_id, $disabled, $user_prefs, $first_name, $last_name ) = $sth->fetchrow_array();
318 if ( $num_rows > 1 ) {
319 die "Duplicate entries found for username '$username'";
321 if ($disabled) {
322 $login_info->{account_disabled} = $disabled;
324 else {
325 $login_info->{user_prefs} = $user_prefs;
326 if ($person_id) {
327 my $new_cookie_string =
328 String::Random->new()
329 ->randpattern(
330 "ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
332 $sth = $self->get_sql("cookie_string_exists");
333 $sth->execute($new_cookie_string);
334 if ( $sth->fetchrow_array()
335 ) #very unlikely--or we need a new random string generator
337 $login_info->{duplicate_cookie_string} = 1;
339 else {
340 $sth = $self->get_sql("login");
341 $sth->execute( $new_cookie_string, $person_id );
342 CXGN::Cookie::set_cookie( $LOGIN_COOKIE_NAME,
343 $new_cookie_string );
344 CXGN::Cookie::set_cookie( "user_prefs", $user_prefs );
345 $login_info->{person_id} = $person_id;
346 $login_info->{first_name} = $first_name;
347 $login_info->{last_name} = $last_name;
348 $login_info->{cookie_string} = $new_cookie_string;
351 else {
352 $login_info->{incorrect_password} = 1;
356 else {
357 $login_info->{logins_disabled} = 1;
359 $self->{login_info} = $login_info;
360 return $login_info;
363 =head2 function logout_user()
365 Usage: $login->logout_user();
366 Desc: log out the current logged in user
367 Ret: nothing
368 Args: none
369 Side Effects: resets the cookie to empty
370 Example:
372 =cut
374 sub logout_user {
375 my $self = shift;
376 my $cookie = $self->get_login_cookie();
377 if ($cookie) {
378 my $sth = $self->get_sql("logout");
379 $sth->execute($cookie);
380 CXGN::Cookie::set_cookie( $LOGIN_COOKIE_NAME, "" );
384 =head2 update_timestamp
386 Usage: $login->update_timestamp();
387 Desc: updates the timestamp, such that users don't
388 get logged out when they are active on the site.
389 Ret: nothing
390 Args: none
391 Side Effects: accesses the database to change the timeout status.
392 Example:
394 =cut
396 sub update_timestamp {
397 my $self = shift;
398 my $cookie = $self->get_login_cookie();
399 if ($cookie) {
400 my $sth = $self->get_sql("refresh_cookie");
401 $sth->execute($cookie);
405 =head2 get_login_cookie
407 Usage: my $cookie = $login->get_login_cookie();
408 Desc: returns the cookie for the current login
409 Args: none
410 Side Effects:
411 Example:
413 =cut
415 sub get_login_cookie {
416 my $self = shift;
417 return CXGN::Cookie::get_cookie($LOGIN_COOKIE_NAME);
420 =head2 login_page_and_exit
422 Usage: $login->login_page_and_exit();
423 Desc: redirects to the login page.
424 Ret:
425 Args:
426 Side Effects:
427 Example:
429 =cut
431 sub login_page_and_exit {
432 my $self = shift;
433 print CGI->new->redirect( -uri => $LOGIN_PAGE, -status => 302 );
434 exit;
438 ### helper function. SQL should probably be moved to the CXGN::People::Login class
441 sub set_sql {
442 my $self = shift;
444 $self->{queries} = {
446 user_from_cookie => #send: session_time_in_secs, cookiestring
448 " SELECT
449 sp_person_id,
450 sgn_people.sp_roles.name as user_type,
451 user_prefs,
452 extract (epoch FROM current_timestamp-last_access_time)>? AS expired
453 FROM
454 sgn_people.sp_person JOIN sgn_people.sp_person_roles using(sp_person_id) join sgn_people.sp_roles using(sp_role_id)
455 WHERE
456 cookie_string=?
457 ORDER BY sp_role_id
458 LIMIT 1",
460 user_from_uname_pass =>
462 " SELECT
463 sp_person_id, disabled, user_prefs, first_name, last_name
464 FROM
465 sgn_people.sp_person
466 WHERE
467 UPPER(username)=UPPER(?)
468 AND password=?",
470 cookie_string_exists =>
472 " SELECT
473 cookie_string
474 FROM
475 sgn_people.sp_person
476 WHERE
477 cookie_string=?",
479 login => #send: cookie_string, sp_person_id
481 " UPDATE
482 sgn_people.sp_person
483 SET
484 cookie_string=?,
485 last_access_time=current_timestamp
486 WHERE
487 sp_person_id=?",
489 logout => #send: cookie_string
491 " UPDATE
492 sgn_people.sp_person
493 SET
494 cookie_string=null,
495 last_access_time=current_timestamp
496 WHERE
497 cookie_string=?",
499 refresh_cookie => #send: cookie_string (updates the timestamp)
501 " UPDATE
502 sgn_people.sp_person
503 SET
504 last_access_time=current_timestamp
505 WHERE
506 cookie_string=?",
508 stats_aggregate => #send: session_timeout_in_secs (gets aggregate login data)
510 " SELECT
511 sp_roles.name, count(*)
512 FROM
513 sgn_people.sp_person
514 JOIN sgn_people.sp_person_roles USING(sp_person_id)
515 JOIN sgn_people.sp_roles USING(sp_role_id)
517 WHERE
518 last_access_time IS NOT NULL
519 AND cookie_string IS NOT NULL
520 AND extract(epoch from now()-last_access_time)<?
521 GROUP BY
522 sp_roles.name",
524 stats_private => #send: session_timeout_in_secs (gets all logged-in users)
526 " SELECT
527 sp_roles.name as user_type, username, contact_email
528 FROM
529 sgn_people.sp_person JOIN sgn_people.sp_person_roles using(sp_person_id) JOIN sgn_people.sp_roles using (sp_role_id)
530 WHERE
531 last_access_time IS NOT NULL
532 AND cookie_string IS NOT NULL
533 AND extract(epoch from now()-last_access_time)<?",
537 while ( my ( $name, $sql ) = each %{ $self->{queries} } ) {
538 $self->{query_handles}->{$name} = $self->get_dbh()->prepare($sql);
543 sub get_sql {
544 my $self = shift;
545 my $name = shift;
546 return $self->{query_handles}->{$name};
550 1; #do not remove