4 CXGN::Login - deal with browser site login
8 This is an object which handles logging users in and out of our sites.
10 This class inherits from L<CXGN::DB::Object>.
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();
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();
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();
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();
36 John Binns <zombieite@gmail.com>
44 use Digest
::MD5
qw(md5);
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
56 our $EXCHANGE_DBH = 1;
58 =head2 constructor new()
60 Usage: my $login = CXGN::Login->new($dbh)
61 Desc: creates a new login object
63 Args: a database handle
64 Side Effects: connects to database
72 my $self = $class->SUPER::new
($dbh);
74 ; #### This SQL should really be in the CXGN::People::Person object!
77 if ( ref($_) eq "HASH" ) {
79 #Process hash args here
80 $self->{no_redirect
} = $_->{NO_REDIRECT
};
84 $self->{conf_object
} = $c || do{ require SGN
::Context
; SGN
::Context
->new };
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
94 Side Effects: accesses the database
99 sub get_login_status
{
102 my $sth = $self->get_sql("stats_aggregate");
103 $sth->execute($LOGIN_TIMEOUT);
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
} =
132 =head2 get_login_info
134 Usage: $login->get_login_info()
145 return $self->{login_info
};
148 =head2 verify_session
150 Usage: $login->verify_session($user_type)
151 Desc: checks whether a user is logged in currently and
152 is of the minimum user type $user_type.
153 user types have the following precedence:
154 user < submitter < sequencer < curator
155 Ret: the person_id, if a session exists
156 Args: a minimum user type required to access the page
157 Side Effects: redirects the website to the login page if no login
158 is currently defined.
165 my ($user_must_be_type) = @_;
166 my ( $person_id, $user_type ) = $self->has_session();
167 if ($person_id) { #if they have a session
168 if ($user_must_be_type)
169 { #if there is a type that they must be to view this page
171 if ( $user_must_be_type ne $user_type )
172 { #if they are not the required type, send them away
178 else { #else they do not have a session, so send them away
183 { #if they are trying to get both pieces of info, give it to them, in array context
185 return ( $person_id, $user_type );
187 else { #else they just care about the login id
193 =head2 has_session ()
195 if the user is not logged in, the return value is false;
196 else it's the person ID if in scalar context, or (person ID, user type) in array context
203 #if people are not allowed to be logged in, return
204 if ( !$self->login_allowed() ) {
208 my $cookie = $self->get_login_cookie();
210 #if they have no cookie, they are not logged in
215 my ( $person_id, $user_type, $user_prefs, $expired ) =
216 $self->query_from_cookie($cookie);
218 #if cookie string is not found, they are not logged in
219 unless ( $person_id and $user_type ) {
223 #if their cookie is good but their timestamp is old, they are not logged in
228 ################################
229 # Ok, they are logged in! yay! #
230 ################################
232 $self->{login_info
}->{person_id
} = $person_id;
233 $self->{login_info
}->{cookie_string
} = $cookie;
234 $self->{login_info
}->{user_type
} = $user_type;
235 $self->{login_info
}->{user_prefs
} = $user_prefs;
236 $self->update_timestamp();
238 #if they are trying to get both pieces of info, give it to them, in array context
240 return ( $person_id, $user_type );
243 #or they just care about the login id
249 sub query_from_cookie
{
251 my $cookie_string = shift;
253 my $sth = $self->get_sql("user_from_cookie");
254 return undef unless $sth;
255 if ( !$sth->execute( $LOGIN_TIMEOUT, $cookie_string ) ) {
256 print STDERR
"Cookie Query Error: " . $DBH->errstr;
259 my @result = $sth->fetchrow_array();
261 return undef unless scalar(@result);
263 #if TWO rows are found with the SAME cookie_string, scream!
264 if ( scalar(@result) && $sth->fetchrow_array() ) {
266 "Duplicate cookie_string entries found for cookie string '$cookie_string'";
269 #Return info, or just the person_id, depending on array/scalar context of function
281 #conditions for allowing logins:
283 # 1. configuration 'disable_login' must be 0 or undef
284 # 2. configuration 'is_mirror' must be 0 or undef
285 # 3. configuration 'dbname' must not be 'sandbox' if configuration 'production_server' is 1
286 # -- the reason for this is that if users can log in, they must be able to log in to the REAL database,
287 # not some mirror or some sandbox, because logged-in users can CHANGE data in the database and we
288 # don't want to lose or ignore those changes.
290 !$self->{conf_object
}->get_conf('disable_login')
291 and !$self->{conf_object
}->get_conf('is_mirror')
293 #we haven't decided whether it's a good idea to comment this next line by default -- Evan
295 $self->{conf_object
}->get_conf('dbname') =~ /sandbox/
296 and $self->{conf_object
}->get_conf('production_server')
303 print STDERR
"Login is disabled if dbname contains 'sandbox' and production_server is set to 1\n";
310 Usage: $login->login_user($username, $password);
321 my ( $username, $password ) = @_;
323 ; #information about whether login succeeded, and if not, why not
324 if ( $self->login_allowed() ) {
325 my $sth = $self->get_sql("user_from_uname_pass");
327 print STDERR
"NOW LOGGING IN USER $username\n";
328 my $num_rows = $sth->execute( $username, $password );
330 my ( $person_id, $disabled, $user_prefs, $first_name, $last_name ) = $sth->fetchrow_array();
332 print STDERR
"FOUND: $person_id\n";
333 if ( $num_rows > 1 ) {
334 die "Duplicate entries found for username '$username'";
337 $login_info->{account_disabled
} = $disabled;
341 $login_info->{user_prefs
} = $user_prefs;
343 my $new_cookie_string =
344 String
::Random
->new()
346 "ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
348 $sth = $self->get_sql("cookie_string_exists");
349 $sth->execute($new_cookie_string);
350 if ( $sth->fetchrow_array()
351 ) #very unlikely--or we need a new random string generator
353 $login_info->{duplicate_cookie_string
} = 1;
356 $sth = $self->get_sql("login");
357 $sth->execute( $new_cookie_string, $person_id );
358 CXGN
::Cookie
::set_cookie
( $LOGIN_COOKIE_NAME,
359 $new_cookie_string );
360 CXGN
::Cookie
::set_cookie
( "user_prefs", $user_prefs );
361 $login_info->{person_id
} = $person_id;
362 $login_info->{first_name
} = $first_name;
363 $login_info->{last_name
} = $last_name;
364 $login_info->{cookie_string
} = $new_cookie_string;
368 $login_info->{incorrect_password
} = 1;
373 $login_info->{logins_disabled
} = 1;
375 $self->{login_info
} = $login_info;
379 =head2 function logout_user()
381 Usage: $login->logout_user();
382 Desc: log out the current logged in user
385 Side Effects: resets the cookie to empty
392 my $cookie = $self->get_login_cookie();
394 my $sth = $self->get_sql("logout");
395 $sth->execute($cookie);
396 CXGN
::Cookie
::set_cookie
( $LOGIN_COOKIE_NAME, "" );
400 =head2 update_timestamp
402 Usage: $login->update_timestamp();
403 Desc: updates the timestamp, such that users don't
404 get logged out when they are active on the site.
407 Side Effects: accesses the database to change the timeout status.
412 sub update_timestamp
{
414 my $cookie = $self->get_login_cookie();
416 my $sth = $self->get_sql("refresh_cookie");
417 $sth->execute($cookie);
421 =head2 get_login_cookie
423 Usage: my $cookie = $login->get_login_cookie();
424 Desc: returns the cookie for the current login
431 sub get_login_cookie
{
433 return CXGN
::Cookie
::get_cookie
($LOGIN_COOKIE_NAME);
436 =head2 login_page_and_exit
437 ##DEPRECATED: redirect should happen in a catalyst controller, not in an object like CXGN::Login
439 Usage: $login->login_page_and_exit();
440 Desc: redirects to the login page.
448 #sub login_page_and_exit {
450 #CGI redirect crashes server when used from a catalyst controller.
451 #Redirecting should happen in controller, not in an object like CXGN::Login
452 #print CGI->new->redirect( -uri => $LOGIN_PAGE, -status => 302 );
457 ### helper function. SQL should probably be moved to the CXGN::People::Login class
465 user_from_cookie
=> #send: session_time_in_secs, cookiestring
468 sp_token.sp_person_id,
469 sgn_people.sp_roles.name as user_type,
471 extract (epoch FROM current_timestamp-sp_token.last_access_time)>? AS expired
473 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)
475 sp_token.cookie_string=?
479 user_from_uname_pass
=>
482 sp_person_id, disabled, user_prefs, first_name, last_name
486 UPPER(username)=UPPER(?)
487 AND (sp_person.password = crypt(?, sp_person.password))",
489 cookie_string_exists
=>
492 sgn_people.sp_token.cookie_string
494 sgn_people.sp_person JOIN sgn_people.sp_token using(sp_person_id)
496 sp_token.cookie_string=?",
498 login
=> #send: cookie_string, sp_person_id
501 sgn_people.sp_token(cookie_string, sp_person_id, last_access_time)
509 logout
=> #send: cookie_string
515 last_access_time=current_timestamp
519 refresh_cookie
=> #send: cookie_string (updates the timestamp)
524 last_access_time=current_timestamp
528 stats_aggregate
=> #send: session_timeout_in_secs (gets aggregate login data)
531 sp_roles.name, count(*)
534 JOIN sgn_people.sp_person_roles USING(sp_person_id)
535 JOIN sgn_people.sp_roles USING(sp_role_id)
536 JOIN sgn_people.sp_token on(sgn_people.sp_person.sp_person_id=sgn_people.sp_token.sp_person_id)
539 sp_token.last_access_time IS NOT NULL
540 AND sp_token.cookie_string IS NOT NULL
541 AND extract(epoch from now()-sp_token.last_access_time)<?
545 stats_private
=> #send: session_timeout_in_secs (gets all logged-in users)
548 sp_roles.name as user_type, username, contact_email
550 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)
552 sp_token.last_access_time IS NOT NULL
553 AND sp_token.cookie_string IS NOT NULL
554 AND extract(epoch from now()-sp_token.last_access_time)<?",
558 while ( my ( $name, $sql ) = each %{ $self->{queries
} } ) {
559 $self->{query_handles
}->{$name} = $self->get_dbh()->prepare($sql);
567 return $self->{query_handles
}->{$name};