modified key
[sgn.git] / lib / CXGN / Login.pm
blob50288586ff884d206e8ec3ecf29a5cc208dfe81e
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;
46 use CXGN::Cookie;
48 use CatalystX::GlobalContext '$c';
50 use base qw | CXGN::DB::Object |;
52 our $LOGIN_COOKIE_NAME = 'sgn_session_id';
53 our $LOGIN_PAGE = '/user/login';
54 our $LOGIN_TIMEOUT = 7200; #seconds for login to timeout
55 our $DBH;
56 our $EXCHANGE_DBH = 1;
58 =head2 constructor new()
60 Usage: my $login = CXGN::Login->new($dbh)
61 Desc: creates a new login object
62 Ret:
63 Args: a database handle
64 Side Effects: connects to database
65 Example:
67 =cut
69 sub new {
70 my $class = shift;
71 my $dbh = shift;
72 my $self = $class->SUPER::new($dbh);
73 $self->set_sql()
74 ; #### This SQL should really be in the CXGN::People::Person object!
76 foreach (@_) {
77 if ( ref($_) eq "HASH" ) {
79 #Process hash args here
80 $self->{no_redirect} = $_->{NO_REDIRECT};
81 last;
84 $self->{conf_object} = $c || do{ require SGN::Context; SGN::Context->new };
85 return $self;
88 =head2 get_login_status
90 Usage: my %logged_in_status = $login -> get_login_status();
91 Desc: a member function. This was changed on 5/1/2009.
92 Ret: a hash with user_type as a key and count of logins as a value
93 Args: none
94 Side Effects: accesses the database
95 Example:
97 =cut
99 sub get_login_status {
100 my $self = shift;
102 my $sth = $self->get_sql("stats_aggregate");
103 $sth->execute($LOGIN_TIMEOUT);
105 my %logins = ();
106 while ( my ( $user_type, $count ) = $sth->fetchrow_array() ) {
107 $logins{$user_type} = $count;
109 if ( !$logins{curator} ) { $logins{curator} = "none"; }
110 if ( !$logins{submitter} ) { $logins{submitter} = "none"; }
111 if ( !$logins{user} ) { $logins{user} = "none"; }
113 $sth = $self->get_sql("stats_private");
114 $sth->execute($LOGIN_TIMEOUT);
116 $logins{detailed} = {};
117 while ( my ( $user_type, $username, $contact_email ) =
118 $sth->fetchrow_array() )
120 $logins{detailed}->{$user_type}->{$username}->{contact_email} =
121 $contact_email;
124 if (wantarray) {
125 return %logins;
127 else {
128 return \%logins;
132 =head2 get_login_info
134 Usage: $login->get_login_info()
135 Desc:
136 Ret:
137 Args:
138 Side Effects:
139 Example:
141 =cut
143 sub get_login_info {
144 my $self = shift;
145 if ($self->has_session()) {
146 return $self->{login_info};
148 else {
149 return {};
153 =head2 verify_session
155 Usage: $login->verify_session($user_type)
156 Desc: checks whether a user is logged in currently and
157 is of the minimum user type $user_type.
158 user types have the following precedence:
159 user < submitter < sequencer < curator
160 Ret: the person_id, if a session exists
161 Args: a minimum user type required to access the page
162 Side Effects: redirects the website to the login page if no login
163 is currently defined.
164 Example:
166 =cut
168 sub verify_session {
169 my $self = shift;
170 my ($user_must_be_type) = @_;
171 my ( $person_id, $user_type ) = $self->has_session();
172 if ($person_id) { #if they have a session
173 if ($user_must_be_type)
174 { #if there is a type that they must be to view this page
176 if ( $user_must_be_type ne $user_type )
177 { #if they are not the required type, send them away
179 return;;
183 else { #else they do not have a session, so send them away
185 return;
187 if (wantarray)
188 { #if they are trying to get both pieces of info, give it to them, in array context
190 return ( $person_id, $user_type );
192 else { #else they just care about the login id
194 return $person_id;
198 =head2 has_session ()
200 if the user is not logged in, the return value is false;
201 else it's the person ID if in scalar context, or (person ID, user type) in array context
203 =cut
205 sub has_session {
206 my $self = shift;
208 #if people are not allowed to be logged in, return
209 if ( !$self->login_allowed() ) {
210 return;
213 my $cookie = $self->get_login_cookie();
215 #if they have no cookie, they are not logged in
216 unless ($cookie) {
217 return;
220 my ( $person_id, $user_type, $user_prefs, $expired ) =
221 $self->query_from_cookie($cookie);
223 #if cookie string is not found, they are not logged in
224 unless ( $person_id and $user_type ) {
225 return;
228 #if their cookie is good but their timestamp is old, they are not logged in
229 if ($expired) {
230 return;
233 ################################
234 # Ok, they are logged in! yay! #
235 ################################
237 $self->{login_info}->{person_id} = $person_id;
238 $self->{login_info}->{cookie_string} = $cookie;
239 $self->{login_info}->{user_type} = $user_type;
240 $self->{login_info}->{user_prefs} = $user_prefs;
241 $self->update_timestamp();
243 #if they are trying to get both pieces of info, give it to them, in array context
244 if (wantarray) {
245 return ( $person_id, $user_type );
248 #or they just care about the login id
249 else {
250 return $person_id;
254 sub query_from_cookie {
255 my $self = shift;
256 my $cookie_string = shift;
258 my $sth = $self->get_sql("user_from_cookie");
259 return undef unless $sth;
260 if ( !$sth->execute( $LOGIN_TIMEOUT, $cookie_string ) ) {
261 print STDERR "Cookie Query Error: " . $DBH->errstr;
262 return undef;
264 my @result = $sth->fetchrow_array();
266 return undef unless scalar(@result);
268 #if TWO rows are found with the SAME cookie_string, scream!
269 if ( scalar(@result) && $sth->fetchrow_array() ) {
271 "Duplicate cookie_string entries found for cookie string '$cookie_string'";
274 #Return info, or just the person_id, depending on array/scalar context of function
275 if (wantarray) {
276 return @result;
278 else {
279 return $result[0];
283 sub login_allowed {
284 my $self = shift;
286 #conditions for allowing logins:
288 # 1. configuration 'disable_login' must be 0 or undef
289 # 2. configuration 'is_mirror' must be 0 or undef
290 # 3. configuration 'dbname' must not be 'sandbox' if configuration 'production_server' is 1
291 # -- the reason for this is that if users can log in, they must be able to log in to the REAL database,
292 # not some mirror or some sandbox, because logged-in users can CHANGE data in the database and we
293 # don't want to lose or ignore those changes.
294 if (
295 !$self->{conf_object}->get_conf('disable_login')
296 and !$self->{conf_object}->get_conf('is_mirror')
298 #we haven't decided whether it's a good idea to comment this next line by default -- Evan
299 and !(
300 $self->{conf_object}->get_conf('dbname') =~ /sandbox/
301 and $self->{conf_object}->get_conf('production_server')
305 return 1;
307 else {
308 print STDERR "Login is disabled if dbname contains 'sandbox' and production_server is set to 1\n";
309 return 0;
313 =head2 login_user
315 Usage: $login->login_user($username, $password);
316 Desc:
317 Ret:
318 Args:
319 Side Effects:
320 Example:
322 =cut
324 sub login_user {
325 my $self = shift;
326 my ( $username, $password ) = @_;
327 my $login_info
328 ; #information about whether login succeeded, and if not, why not
329 if ( $self->login_allowed() ) {
330 my $sth = $self->get_sql("user_from_uname_pass");
332 print STDERR "NOW LOGGING IN USER $username\n";
333 my $num_rows = $sth->execute( $username, $password );
335 my ( $person_id, $disabled, $user_prefs, $first_name, $last_name ) = $sth->fetchrow_array();
337 print STDERR "FOUND: $person_id\n";
338 if ( $num_rows > 1 ) {
339 die "Duplicate entries found for username '$username'";
341 if ($disabled) {
342 $login_info->{account_disabled} = $disabled;
345 else {
346 $login_info->{user_prefs} = $user_prefs;
347 if ($person_id) {
348 my $new_cookie_string =
349 String::Random->new()
350 ->randpattern(
351 "ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
353 $sth = $self->get_sql("cookie_string_exists");
354 $sth->execute($new_cookie_string);
355 if ( $sth->fetchrow_array()
356 ) #very unlikely--or we need a new random string generator
358 $login_info->{duplicate_cookie_string} = 1;
360 else {
362 $sth = $self->get_sql("login");
363 $sth->execute( $new_cookie_string, $person_id );
364 CXGN::Cookie::set_cookie( $LOGIN_COOKIE_NAME,
365 $new_cookie_string );
366 CXGN::Cookie::set_cookie( "user_prefs", $user_prefs );
367 $login_info->{person_id} = $person_id;
368 $login_info->{first_name} = $first_name;
369 $login_info->{last_name} = $last_name;
370 $login_info->{cookie_string} = $new_cookie_string;
373 else {
374 $login_info->{incorrect_password} = 1;
378 else {
379 $login_info->{logins_disabled} = 1;
381 $self->{login_info} = $login_info;
382 return $login_info;
385 =head2 function logout_user()
387 Usage: $login->logout_user();
388 Desc: log out the current logged in user
389 Ret: nothing
390 Args: none
391 Side Effects: resets the cookie to empty
392 Example:
394 =cut
396 sub logout_user {
397 my $self = shift;
398 my $cookie = $self->get_login_cookie();
399 if ($cookie) {
400 my $sth = $self->get_sql("logout");
401 $sth->execute($cookie);
402 CXGN::Cookie::set_cookie( $LOGIN_COOKIE_NAME, "" );
406 =head2 update_timestamp
408 Usage: $login->update_timestamp();
409 Desc: updates the timestamp, such that users don't
410 get logged out when they are active on the site.
411 Ret: nothing
412 Args: none
413 Side Effects: accesses the database to change the timeout status.
414 Example:
416 =cut
418 sub update_timestamp {
419 my $self = shift;
420 my $cookie = $self->get_login_cookie();
421 if ($cookie) {
422 my $sth = $self->get_sql("refresh_cookie");
423 $sth->execute($cookie);
427 =head2 get_login_cookie
429 Usage: my $cookie = $login->get_login_cookie();
430 Desc: returns the cookie for the current login
431 Args: none
432 Side Effects:
433 Example:
435 =cut
437 sub get_login_cookie {
438 my $self = shift;
439 return CXGN::Cookie::get_cookie($LOGIN_COOKIE_NAME);
442 =head2 login_page_and_exit
443 ##DEPRECATED: redirect should happen in a catalyst controller, not in an object like CXGN::Login
445 Usage: $login->login_page_and_exit();
446 Desc: redirects to the login page.
447 Ret:
448 Args:
449 Side Effects:
450 Example:
452 =cut
454 #sub login_page_and_exit {
455 # my $self = shift;
456 #CGI redirect crashes server when used from a catalyst controller.
457 #Redirecting should happen in controller, not in an object like CXGN::Login
458 #print CGI->new->redirect( -uri => $LOGIN_PAGE, -status => 302 );
459 #exit;
463 ### helper function. SQL should probably be moved to the CXGN::People::Login class
466 sub set_sql {
467 my $self = shift;
469 $self->{queries} = {
471 user_from_cookie => #send: session_time_in_secs, cookiestring
473 " SELECT
474 sp_token.sp_person_id,
475 sgn_people.sp_roles.name as user_type,
476 user_prefs,
477 extract (epoch FROM current_timestamp-sp_token.last_access_time)>? AS expired
478 FROM
479 sgn_people.sp_person JOIN sgn_people.sp_person_roles using(sp_person_id) join sgn_people.sp_roles using(sp_role_id) JOIN sgn_people.sp_token on(sgn_people.sp_person.sp_person_id = sgn_people.sp_token.sp_person_id)
480 WHERE
481 sp_token.cookie_string=?
482 ORDER BY sp_role_id
483 LIMIT 1",
485 user_from_uname_pass =>
487 " SELECT
488 sp_person_id, disabled, user_prefs, first_name, last_name
489 FROM
490 sgn_people.sp_person
491 WHERE
492 UPPER(username)=UPPER(?)
493 AND (sp_person.password = crypt(?, sp_person.password))",
495 cookie_string_exists =>
497 " SELECT
498 sgn_people.sp_token.cookie_string
499 FROM
500 sgn_people.sp_person JOIN sgn_people.sp_token using(sp_person_id)
501 WHERE
502 sp_token.cookie_string=?",
504 login => #send: cookie_string, sp_person_id
506 " INSERT INTO
507 sgn_people.sp_token(cookie_string, sp_person_id, last_access_time)
508 VALUES (
511 current_timestamp
515 logout => #send: cookie_string
517 " UPDATE
518 sgn_people.sp_token
519 SET
520 cookie_string=null,
521 last_access_time=current_timestamp
522 WHERE
523 cookie_string=?",
525 refresh_cookie => #send: cookie_string (updates the timestamp)
527 " UPDATE
528 sgn_people.sp_token
529 SET
530 last_access_time=current_timestamp
531 WHERE
532 cookie_string=?",
534 stats_aggregate => #send: session_timeout_in_secs (gets aggregate login data)
536 " SELECT
537 sp_roles.name, count(*)
538 FROM
539 sgn_people.sp_person
540 JOIN sgn_people.sp_person_roles USING(sp_person_id)
541 JOIN sgn_people.sp_roles USING(sp_role_id)
542 JOIN sgn_people.sp_token on(sgn_people.sp_person.sp_person_id=sgn_people.sp_token.sp_person_id)
544 WHERE
545 sp_token.last_access_time IS NOT NULL
546 AND sp_token.cookie_string IS NOT NULL
547 AND extract(epoch from now()-sp_token.last_access_time)<?
548 GROUP BY
549 sp_roles.name",
551 stats_private => #send: session_timeout_in_secs (gets all logged-in users)
553 " SELECT
554 sp_roles.name as user_type, username, contact_email
555 FROM
556 sgn_people.sp_person JOIN sgn_people.sp_person_roles using(sp_person_id) JOIN sgn_people.sp_roles using (sp_role_id) JOIN sgn_people.sp_token on (sgn_people.sp_person.sp_person_id=sgn_people.sp_token.sp_person_id)
557 WHERE
558 sp_token.last_access_time IS NOT NULL
559 AND sp_token.cookie_string IS NOT NULL
560 AND extract(epoch from now()-sp_token.last_access_time)<?",
564 while ( my ( $name, $sql ) = each %{ $self->{queries} } ) {
565 $self->{query_handles}->{$name} = $self->get_dbh()->prepare($sql);
570 sub get_sql {
571 my $self = shift;
572 my $name = shift;
573 return $self->{query_handles}->{$name};
577 1; #do not remove