PageRenderTime 27ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/SMMID/Login.pm

https://github.com/solgenomics/SMMID
Perl | 669 lines | 557 code | 76 blank | 36 comment | 27 complexity | aa21d1d8141017b1890d351b5fd0e092 MD5 | raw file
  1. =head1 NAME
  2. SMMID::Login - deal with browser site login
  3. =head1 DESCRIPTION
  4. This is an object which handles logging users in and out of our sites.
  5. =head1 EXAMPLES
  6. #example 1
  7. #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.
  8. #if they are logged in, you will get their person id and your code will continue to execute.
  9. my $person_id=CXGN::Login->new()->verify_session();
  10. #example 2
  11. #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.
  12. #if they are logged in, you will get their person id and user type and your code will continue to execute.
  13. my($person_id,$user_type)=CXGN::Login->new($dbh)->verify_session();
  14. #example 3
  15. #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
  16. #continue execution after this line no matter what.
  17. my $person_id=CXGN::Login->new($dbh)->has_session();
  18. #example 4
  19. #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.
  20. #your code will continue execution after this line no matter what.
  21. my($person_id,$user_type)=CXGN::Login->new($dbh)->has_session();
  22. =head1 AUTHOR
  23. John Binns <zombieite@gmail.com>
  24. =cut
  25. package SMMID::Login;
  26. use Moose;
  27. use Digest::MD5 qw(md5);
  28. use String::Random;
  29. use DateTime;
  30. use DateTime::Format::ISO8601;
  31. our $LOGIN_COOKIE_NAME = 'smmid_session_id';
  32. our $LOGIN_PAGE = '/user/login';
  33. our $LOGIN_TIMEOUT = 7200; #seconds for login to timeout
  34. our $DBH;
  35. our $EXCHANGE_DBH = 1;
  36. has 'schema' => ( isa => 'Ref', is => 'rw');
  37. has 'disable_login' => ( isa => 'Bool', is =>'rw' );
  38. has 'is_mirror' => ( isa => 'Bool', is => 'rw' );
  39. has 'cookie_string' => (isa => 'Str', is => 'rw');
  40. has 'login_info' => (isa => 'HashRef', is => 'rw');
  41. has 'login_cookie' => (isa => 'Str', is => 'rw');
  42. =head2 constructor new()
  43. Usage: my $login = SMMID::Login->new( { schema => $schema, cookie_string )
  44. Desc: creates a new login object
  45. Ret:
  46. Args: a database handle
  47. Side Effects: connects to database
  48. Example:
  49. =cut
  50. =head2 get_login_status
  51. Usage: my %logged_in_status = $login -> get_login_status();
  52. Desc: a member function. This was changed on 5/1/2009.
  53. Ret: a hash with user_type as a key and count of logins as a value
  54. Args: none
  55. Side Effects: accesses the database
  56. Example:
  57. =cut
  58. sub get_login_status {
  59. my $self = shift;
  60. my $sth = $self->get_sql("stats_aggregate");
  61. $sth->execute($LOGIN_TIMEOUT);
  62. my %logins = ();
  63. while ( my ( $user_type, $count ) = $sth->fetchrow_array() ) {
  64. $logins{$user_type} = $count;
  65. }
  66. if ( !$logins{curator} ) { $logins{curator} = "none"; }
  67. if ( !$logins{submitter} ) { $logins{submitter} = "none"; }
  68. if ( !$logins{user} ) { $logins{user} = "none"; }
  69. $sth = $self->get_sql("stats_private");
  70. $sth->execute($LOGIN_TIMEOUT);
  71. $logins{detailed} = {};
  72. while ( my ( $user_type, $username, $contact_email ) =
  73. $sth->fetchrow_array() )
  74. {
  75. $logins{detailed}->{$user_type}->{$username}->{contact_email} =
  76. $contact_email;
  77. }
  78. if (wantarray) {
  79. return %logins;
  80. }
  81. else {
  82. return \%logins;
  83. }
  84. }
  85. # =head2 get_login_info
  86. # Usage: $login->get_login_info()
  87. # Desc:
  88. # Ret:
  89. # Args:
  90. # Side Effects:
  91. # Example:
  92. # =cut
  93. # sub get_login_info {
  94. # my $self = shift;
  95. # return $self->{login_info};
  96. # }
  97. =head2 verify_session
  98. Usage: $login->verify_session($user_type)
  99. Desc: checks whether a user is logged in currently and
  100. is of the minimum user type $user_type.
  101. user types have the following precedence:
  102. user < submitter < sequencer < curator
  103. Ret: the person_id, if a session exists
  104. Args: a minimum user type required to access the page
  105. Side Effects: redirects the website to the login page if no login
  106. is currently defined.
  107. Example:
  108. =cut
  109. sub verify_session {
  110. my $self = shift;
  111. my ($user_must_be_type) = @_;
  112. my ( $person_id, $user_type ) = $self->has_session();
  113. if ($person_id) { #if they have a session
  114. if ($user_must_be_type)
  115. { #if there is a type that they must be to view this page
  116. if ( $user_must_be_type ne $user_type )
  117. { #if they are not the required type, send them away
  118. return;
  119. }
  120. }
  121. }
  122. else { #else they do not have a session, so send them away
  123. return;
  124. }
  125. if (wantarray)
  126. { #if they are trying to get both pieces of info, give it to them, in array context
  127. return ( $person_id, $user_type );
  128. }
  129. else { #else they just care about the login id
  130. return $person_id;
  131. }
  132. }
  133. =head2 has_session ()
  134. if the user is not logged in, the return value is false;
  135. else it's the person ID if in scalar context, or (person ID, user type) in array context
  136. =cut
  137. sub has_session {
  138. my $self = shift;
  139. print STDERR "has_session()...\n";
  140. #if people are not allowed to be logged in, return
  141. if ( !$self->login_allowed() ) {
  142. print STDERR "LOGIN NOT ALLOWED.\n";
  143. return;
  144. }
  145. #if they have no cookie, they are not logged in
  146. unless ($self->cookie_string()) {
  147. print STDERR "NO COOKIE\n";
  148. return;
  149. }
  150. else {
  151. print STDERR "We have a cookie (".$self->cookie_string().")!!!\n";
  152. }
  153. my ( $dbuser_id, $user_type, $user_prefs, $expired ) =
  154. $self->query_from_cookie($self->cookie_string());
  155. #if cookie string is not found, they are not logged in
  156. unless ( $dbuser_id ) {
  157. print STDERR "We have no person id and user type( $dbuser_id)\n";
  158. return;
  159. }
  160. #if their cookie is good but their timestamp is old, they are not logged in
  161. if ($expired) {
  162. print STDERR "The cookie is expired. Sorry!\n";
  163. return;
  164. }
  165. ################################
  166. # Ok, they are logged in! yay! #
  167. ################################
  168. my $login_info = {
  169. person_id => $dbuser_id,
  170. cookie_string => $self->cookie_string(),
  171. user_type => $user_type,
  172. };
  173. #$self->set_login_info($login_info);
  174. $self->update_timestamp($dbuser_id);
  175. #if they are trying to get both pieces of info, give it to them, in array context
  176. if (wantarray) {
  177. return ( $dbuser_id, $user_type );
  178. }
  179. #or they just care about the login id
  180. else {
  181. return $dbuser_id;
  182. }
  183. }
  184. sub query_from_cookie {
  185. my $self = shift;
  186. my $cookie_string = shift;
  187. my @result = (undef, undef, undef, undef);
  188. my $row = $self->user_from_cookie_string();
  189. my $expired = 0;
  190. if ($row && $self->cookie_string()) {
  191. @result = ($row->dbuser_id(), $row->user_type(), $row->user_prefs(), $row->last_access_time());
  192. if ($result[2]) {
  193. my $iso8601 = DateTime::Format::ISO8601->new;
  194. my $last_access_time = $iso8601->parse_datetime( $result[2] );
  195. my $current_time = DateTime->now();
  196. my $seconds_since_last_login = $current_time->epoch()-$last_access_time->epoch();
  197. print STDERR "SECONDS SINCE LAST LOGIN : $seconds_since_last_login\n";
  198. if ($seconds_since_last_login > $LOGIN_TIMEOUT) {
  199. print STDERR "LOGOUT IS EXPIRED!\n";
  200. $expired =1;
  201. }
  202. }
  203. }
  204. if (wantarray) {
  205. return ($result[0], $result[1], $result[2], $expired);
  206. }
  207. else {
  208. return $row->dbuser_id();
  209. }
  210. }
  211. sub user_from_cookie_string {
  212. my $self = shift;
  213. my $row = $self->schema()->resultset('SMIDDB::Result::Dbuser')->find( { cookie_string => $self->cookie_string() } );
  214. if (!$row) { return; }
  215. else {
  216. return $row;
  217. }
  218. }
  219. sub user_from_credentials {
  220. my $self = shift;
  221. my $username = shift;
  222. my $password = shift;
  223. if ($username && $password) {
  224. my $user_h = $self->schema()
  225. ->storage
  226. ->dbh()
  227. ->prepare("SELECT dbuser_id FROM dbuser WHERE username=? and password=crypt(?, password)");
  228. $user_h->execute($username, $password);
  229. if (my ($user_id) = $user_h->fetchrow_array()) {
  230. my $row = $self->schema()->resultset("SMIDDB::Result::Dbuser")->find( { dbuser_id => $user_id } );
  231. return $row;
  232. }
  233. }
  234. return undef;
  235. }
  236. sub exists_user {
  237. my $self = shift;
  238. my $username = shift;
  239. if ($username) {
  240. my $row = $self->schema()->resultset("SMIDDB::Result::Dbuser")->find( { username => { ilike => $username } } );
  241. if ($row) {
  242. return $row;
  243. }
  244. else {
  245. return 0;
  246. }
  247. }
  248. return 0;
  249. }
  250. sub login_allowed {
  251. my $self = shift;
  252. #conditions for allowing logins:
  253. #
  254. # 1. configuration 'disable_login' must be 0 or undef
  255. # 2. configuration 'is_mirror' must be 0 or undef
  256. # 3. configuration 'dbname' must not be 'sandbox' if configuration 'production_server' is 1
  257. # -- the reason for this is that if users can log in, they must be able to log in to the REAL database,
  258. # not some mirror or some sandbox, because logged-in users can CHANGE data in the database and we
  259. # don't want to lose or ignore those changes.
  260. if (
  261. !$self->disable_login()
  262. and !$self->is_mirror()
  263. #we haven't decided whether it's a good idea to comment this next line by default -- Evan
  264. # and !(
  265. # $self->{conf_object}->get_conf('dbname') =~ /sandbox/
  266. # and $self->{conf_object}->get_conf('production_server')
  267. # )
  268. )
  269. {
  270. return 1;
  271. }
  272. else {
  273. return 0;
  274. }
  275. }
  276. =head2 login_user
  277. Usage: $login->login_user($username, $password);
  278. Desc:
  279. Ret:
  280. Args:
  281. Side Effects:
  282. Example:
  283. =cut
  284. sub login_user {
  285. my $self = shift;
  286. my $username = shift;
  287. my $password = shift;
  288. print STDERR "Logging in user $username with password XXXXXXXX\n";
  289. my $login_info; #information about whether login succeeded, and if not, why not
  290. if ( ! $username) {
  291. $login_info->{error} = "Please provide a username.";
  292. }
  293. elsif (! $password) {
  294. $login_info->{error} = "Please provide a password.";
  295. }
  296. else {
  297. my $row = $self->user_from_credentials($username, $password);
  298. print STDERR "NOW LOGGING IN USER $username\n";
  299. #my $num_rows = $sth->execute( $username, $password );
  300. if (! $row) {
  301. $login_info->{error} = "Incorrect password or user information.";
  302. return $login_info;
  303. }
  304. #my ( $person_id, $disabled, $user_prefs, $first_name, $last_name ) = $sth->fetchrow_array();
  305. my ( $person_id, $disabled, $user_prefs, $first_name, $last_name ) = (
  306. $row->dbuser_id,
  307. $row->disabled,
  308. $row->first_name,
  309. $row->user_prefs,
  310. $row->last_name,
  311. );
  312. print STDERR "FOUND: $person_id\n";
  313. # if ( $num_rows > 1 ) {
  314. # die "Duplicate entries found for username '$username'";
  315. # }
  316. if ($disabled) {
  317. $login_info->{account_disabled} = $disabled;
  318. }
  319. else {
  320. print STDERR "Generating new login cookie...\n";
  321. $login_info->{user_prefs} = $user_prefs;
  322. if ($person_id) {
  323. my $new_cookie_string =
  324. String::Random->new()
  325. ->randpattern(
  326. "ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
  327. );
  328. my $row = $self->schema()->resultset("SMIDDB::Result::Dbuser")->find( { dbuser_id => $person_id });
  329. $row->update(
  330. {
  331. cookie_string => $new_cookie_string
  332. });
  333. $login_info->{person_id} = $person_id;
  334. $login_info->{first_name} = $first_name;
  335. $login_info->{last_name} = $last_name;
  336. $login_info->{cookie_string} = $new_cookie_string;
  337. }
  338. else {
  339. $login_info->{incorrect_password} = 1;
  340. }
  341. }
  342. }
  343. $self->{login_info} = $login_info;
  344. return $login_info;
  345. }
  346. =head2 function logout_user()
  347. Usage: $login->logout_user();
  348. Desc: log out the current logged in user
  349. Ret: nothing
  350. Args: none
  351. Side Effects: resets the cookie to empty
  352. Example:
  353. =cut
  354. sub logout_user {
  355. my $self = shift;
  356. my $cookie = $self->cookie_string();
  357. if ($cookie) {
  358. my $row = $self->schema()->resultset("Dbuser")->find( { cookie_string => $cookie });
  359. $row->update( { cookie_string => "", last_access_time => $self->now() });
  360. # controller needs to set the cookie
  361. ###CXGN::Cookie::set_cookie( $LOGIN_COOKIE_NAME, "" );
  362. }
  363. }
  364. =head2 update_timestamp
  365. Usage: $login->update_timestamp();
  366. Desc: updates the timestamp, such that users don't
  367. get logged out when they are active on the site.
  368. Ret: nothing
  369. Args: none
  370. Side Effects: accesses the database to change the timeout status.
  371. Example:
  372. =cut
  373. sub update_timestamp {
  374. my $self = shift;
  375. my $dbuser_id = shift;
  376. my $cookie = $self->cookie_string();
  377. if ($cookie) {
  378. # my $sth = $self->get_sql("refresh_cookie");
  379. # " UPDATE
  380. # sgn_people.sp_person
  381. # SET
  382. # last_access_time=current_timestamp
  383. # WHERE
  384. # cookie_string=?",
  385. my $row = $self->schema()->resultset("SMIDDB::Result::Dbuser")->find( { dbuser_id => $dbuser_id });
  386. $row->update( { last_access_time => $self->now() });
  387. }
  388. }
  389. sub now {
  390. my $self = shift;
  391. my $now = DateTime->now();
  392. return $now->ymd()."T".$now->hms();
  393. }
  394. # =head2 get_login_cookie
  395. # Usage: my $cookie = $login->get_login_cookie();
  396. # Desc: returns the cookie for the current login
  397. # Args: none
  398. # Side Effects:
  399. # Example:
  400. # =cut
  401. # sub get_login_cookie {
  402. # my $self = shift;
  403. # return CXGN::Cookie::get_cookie($LOGIN_COOKIE_NAME);
  404. # }
  405. =head2 login_page_and_exit
  406. ##DEPRECATED: redirect should happen in a catalyst controller, not in an object like CXGN::Login
  407. Usage: $login->login_page_and_exit();
  408. Desc: redirects to the login page.
  409. Ret:
  410. Args:
  411. Side Effects:
  412. Example:
  413. =cut
  414. #sub login_page_and_exit {
  415. # my $self = shift;
  416. #CGI redirect crashes server when used from a catalyst controller.
  417. #Redirecting should happen in controller, not in an object like CXGN::Login
  418. #print CGI->new->redirect( -uri => $LOGIN_PAGE, -status => 302 );
  419. #exit;
  420. #}
  421. ###
  422. ### helper function. SQL should probably be moved to the CXGN::People::Login class
  423. ###
  424. sub set_sql {
  425. my $self = shift;
  426. $self->{queries} = {
  427. user_from_cookie => #send: session_time_in_secs, cookiestring
  428. " SELECT
  429. sp_person_id,
  430. sgn_people.sp_roles.name as user_type,
  431. user_prefs,
  432. extract (epoch FROM current_timestamp-last_access_time)>? AS expired
  433. FROM
  434. sgn_people.sp_person JOIN sgn_people.sp_person_roles using(sp_person_id) join sgn_people.sp_roles using(sp_role_id)
  435. WHERE
  436. cookie_string=?
  437. ORDER BY sp_role_id
  438. LIMIT 1",
  439. user_from_uname_pass =>
  440. " SELECT
  441. sp_person_id, disabled, user_prefs, first_name, last_name
  442. FROM
  443. sgn_people.sp_person
  444. WHERE
  445. UPPER(username)=UPPER(?)
  446. AND (sp_person.password = crypt(?, sp_person.password))",
  447. cookie_string_exists =>
  448. " SELECT
  449. cookie_string
  450. FROM
  451. sgn_people.sp_person
  452. WHERE
  453. cookie_string=?",
  454. login => #send: cookie_string, sp_person_id
  455. " UPDATE
  456. sgn_people.sp_person
  457. SET
  458. cookie_string=?,
  459. last_access_time=current_timestamp
  460. WHERE
  461. sp_person_id=?",
  462. logout => #send: cookie_string
  463. " UPDATE
  464. sgn_people.sp_person
  465. SET
  466. cookie_string=null,
  467. last_access_time=current_timestamp
  468. WHERE
  469. cookie_string=?",
  470. refresh_cookie => #send: cookie_string (updates the timestamp)
  471. " UPDATE
  472. sgn_people.sp_person
  473. SET
  474. last_access_time=current_timestamp
  475. WHERE
  476. cookie_string=?",
  477. stats_aggregate => #send: session_timeout_in_secs (gets aggregate login data)
  478. " SELECT
  479. sp_roles.name, count(*)
  480. FROM
  481. sgn_people.sp_person
  482. JOIN sgn_people.sp_person_roles USING(sp_person_id)
  483. JOIN sgn_people.sp_roles USING(sp_role_id)
  484. WHERE
  485. last_access_time IS NOT NULL
  486. AND cookie_string IS NOT NULL
  487. AND extract(epoch from now()-last_access_time)<?
  488. GROUP BY
  489. sp_roles.name",
  490. stats_private => #send: session_timeout_in_secs (gets all logged-in users)
  491. " SELECT
  492. sp_roles.name as user_type, username, contact_email
  493. FROM
  494. sgn_people.sp_person JOIN sgn_people.sp_person_roles using(sp_person_id) JOIN sgn_people.sp_roles using (sp_role_id)
  495. WHERE
  496. last_access_time IS NOT NULL
  497. AND cookie_string IS NOT NULL
  498. AND extract(epoch from now()-last_access_time)<?",
  499. };
  500. while ( my ( $name, $sql ) = each %{ $self->{queries} } ) {
  501. $self->{query_handles}->{$name} = $self->get_dbh()->prepare($sql);
  502. }
  503. }
  504. sub get_sql {
  505. my $self = shift;
  506. my $name = shift;
  507. return $self->{query_handles}->{$name};
  508. }
  509. ###
  510. 1; #do not remove
  511. ###