/lib/Thruk/Controller/login.pm

http://github.com/sni/Thruk · Perl · 262 lines · 214 code · 33 blank · 15 comment · 48 complexity · eb890195f99aacca2d32e41520659e6f MD5 · raw file

  1. package Thruk::Controller::login;
  2. use strict;
  3. use warnings;
  4. =head1 NAME
  5. Thruk::Controller::login - Thruk Controller
  6. =head1 DESCRIPTION
  7. Thruk Controller.
  8. =head1 METHODS
  9. =head2 index
  10. =cut
  11. sub index {
  12. my ( $c ) = @_;
  13. $c->stats->profile(begin => "login::index");
  14. if(!$c->config->{'login_modules_loaded'}) {
  15. require Thruk::Utils::CookieAuth;
  16. $c->config->{'login_modules_loaded'} = 1;
  17. }
  18. $c->stash->{'navigation'} = 'off'; # would be useless here, so set it non-empty, otherwise AddDefaults::end would read it again
  19. $c->stash->{'no_auto_reload'} = 1;
  20. $c->stash->{'theme'} = $c->config->{'default_theme'} unless defined $c->stash->{'theme'};
  21. $c->stash->{'page'} = 'splashpage';
  22. $c->stash->{'loginurl'} = $c->stash->{'url_prefix'}."cgi-bin/login.cgi";
  23. $c->stash->{'template'} = 'login.tt';
  24. $c->stash->{'title'} = 'Login';
  25. my $product_prefix = $c->config->{'product_prefix'};
  26. my $cookie_path = $c->stash->{'cookie_path'};
  27. my $cookie_domain = _get_cookie_domain($c);
  28. my $keywords = $c->req->uri->query;
  29. my $logoutref;
  30. if($keywords and $keywords =~ m/^logout(\/.*)/mx) {
  31. $keywords = 'logout';
  32. $logoutref = $1;
  33. }
  34. if($c->req->url =~ m/\/\Q$product_prefix\E\/cgi\-bin\/login\.cgi\?logout(\/.*)/mx) {
  35. $keywords = 'logout';
  36. $logoutref = $1;
  37. }
  38. my $login = $c->req->parameters->{'login'} || '';
  39. my $pass = $c->req->parameters->{'password'} || '';
  40. my $submit = $c->req->parameters->{'submit'} || '';
  41. my $referer = $c->req->parameters->{'referer'} || '';
  42. $referer =~ s#^//#/#gmx; # strip double slashes
  43. $referer =~ s#.*/nocookie$##gmx; # strip nocookie
  44. $referer = $c->stash->{'url_prefix'} unless $referer;
  45. # append slash for omd sites, IE and chrome wont send the login cookie otherwise
  46. if(($ENV{'OMD_SITE'} and $referer eq '/'.$ENV{'OMD_SITE'})
  47. or ($referer eq $c->stash->{'url_prefix'})) {
  48. $referer =~ s/\/*$//gmx;
  49. $referer = $referer.'/';
  50. }
  51. $referer =~ s/%3f/?/mx;
  52. # add trailing slash if referer ends with the product prefix and nothing else
  53. if($referer =~ m|\Q/$product_prefix\E$|mx) {
  54. $referer = $referer.'/';
  55. }
  56. if(defined $keywords) {
  57. if($keywords eq 'logout') {
  58. _invalidate_current_session($c, $cookie_path);
  59. Thruk::Utils::set_message( $c, 'success_message', 'logout successful' );
  60. return $c->redirect_to($logoutref) if $logoutref;
  61. return $c->redirect_to($c->stash->{'url_prefix'}."cgi-bin/login.cgi");
  62. }
  63. if($keywords eq 'nocookie') {
  64. my $hint = '';
  65. if($cookie_domain) {
  66. $hint = ' (cookie domain is set to: '.$cookie_domain.')';
  67. }
  68. Thruk::Utils::set_message( $c, 'fail_message', 'login not possible without accepting cookies'.$hint );
  69. }
  70. if($keywords =~ /^expired\&(.*)$/mx or $keywords eq 'expired') {
  71. _invalidate_current_session($c, $cookie_path);
  72. Thruk::Utils::set_message( $c, 'fail_message', 'session has expired' );
  73. }
  74. if($keywords =~ /^invalid\&(.*)$/mx or $keywords eq 'invalid') {
  75. _invalidate_current_session($c, $cookie_path);
  76. Thruk::Utils::set_message( $c, 'fail_message', 'session is not valid (anymore)' );
  77. }
  78. if($keywords =~ /^problem\&(.*)$/mx or $keywords eq 'problem') {
  79. # don't remove all sessions when there is a (temporary) technical problem
  80. #_invalidate_current_session($c, $cookie_path);
  81. Thruk::Utils::set_message( $c, 'fail_message', 'technical problem during login, please have a look at the logfiles.' );
  82. }
  83. if($keywords =~ /^locked\&(.*)$/mx or $keywords eq 'locked') {
  84. _invalidate_current_session($c, $cookie_path);
  85. Thruk::Utils::set_message( $c, 'fail_message', 'account is locked, please contact an administrator' );
  86. }
  87. if($keywords =~ /^setsession\&(.*)$/mx or $keywords eq 'setsession') {
  88. $c->authenticate();
  89. return $c->redirect_to($referer) if $referer;
  90. return $c->redirect_to($c->stash->{'url_prefix'}."cgi-bin/login.cgi");
  91. }
  92. }
  93. # make lowercase username
  94. $login = lc($login) if $c->config->{'make_auth_user_lowercase'};
  95. if($submit ne '' || $login ne '') {
  96. my $testcookie = $c->cookie('thruk_test');
  97. $c->cookie('thruk_test' => '', {
  98. expires => 0,
  99. path => $cookie_path,
  100. domain => $cookie_domain,
  101. });
  102. if( (!defined $testcookie || !$testcookie->value)
  103. && (!defined $c->req->header('user-agent') || $c->req->header('user-agent') !~ m/wget/mix)) {
  104. return $c->redirect_to($c->stash->{'url_prefix'}."cgi-bin/login.cgi?nocookie");
  105. } else {
  106. my $userdata = Thruk::Utils::get_user_data($c, $login);
  107. if($userdata->{'login'}->{'locked'}) {
  108. return $c->redirect_to($c->stash->{'url_prefix'}."cgi-bin/login.cgi?locked&".$referer);
  109. }
  110. $c->stats->profile(begin => "login::external_authentication");
  111. my $success = Thruk::Utils::CookieAuth::external_authentication($c->config, $login, $pass, $c->req->address, $c->stats);
  112. $c->stats->profile(end => "login::external_authentication");
  113. if($success eq '-1') {
  114. return $c->redirect_to($c->stash->{'url_prefix'}."cgi-bin/login.cgi?problem&".$referer);
  115. }
  116. elsif($success) {
  117. $c->stash->{'remote_user'} = $login;
  118. $c->cookie('thruk_auth' => $success, {
  119. path => $cookie_path,
  120. domain => $cookie_domain,
  121. httponly => 1,
  122. });
  123. # clean failed logins
  124. my $userdata = Thruk::Utils::get_user_data($c, $login);
  125. if($userdata->{'login'}) {
  126. if($userdata->{'login'}->{'failed'}) {
  127. Thruk::Utils::set_message( $c, 'warn_message',
  128. sprintf("There had been %d failed login attempts. (Date: %s - IP: %s%s)",
  129. $userdata->{'login'}->{'failed'},
  130. Thruk::Utils::Filter::date_format($c, $userdata->{'login'}->{'last_failed'}->{'time'}),
  131. $userdata->{'login'}->{'last_failed'}->{'ip'},
  132. $userdata->{'login'}->{'last_failed'}->{'forwarded_for'} ? ' ('.$userdata->{'login'}->{'last_failed'}->{'forwarded_for'} : '',
  133. ));
  134. }
  135. delete $userdata->{'login'};
  136. Thruk::Utils::store_user_data($c, $userdata, $login);
  137. }
  138. # call a script hook after successful login?
  139. if($c->config->{'cookie_auth_login_hook'}) {
  140. Thruk::Utils::IO::cmd($c, $c->config->{'cookie_auth_login_hook'}.' >/dev/null 2>&1 &');
  141. }
  142. return $c->redirect_to($referer);
  143. } else {
  144. $c->log->info(sprintf("login failed for %s on %s from %s%s",
  145. $login,
  146. $referer,
  147. $c->req->address,
  148. ($c->env->{'HTTP_X_FORWARDED_FOR'} ? ' ('.$c->env->{'HTTP_X_FORWARDED_FOR'}.')' :''),
  149. ));
  150. Thruk::Utils::set_message( $c, 'fail_message', 'login failed' );
  151. if($c->config->{cookie_auth_disable_after_failed_logins}) {
  152. # increase failed login counter and disable account if it exceeds
  153. my $userdata = Thruk::Utils::get_user_data($c, $login);
  154. $userdata->{'login'}->{'failed'}++;
  155. $userdata->{'login'}->{'last_failed'} = { time => time(), ip => $c->req->address, forwarded_for => $c->env->{'HTTP_X_FORWARDED_FOR'} };
  156. if($userdata->{'login'}->{'failed'} >= $c->config->{cookie_auth_disable_after_failed_logins}) {
  157. $userdata->{'login'}->{'locked'} = 1;
  158. }
  159. # only update user data if already exist, otherwise we would end up with a new file for each failed login
  160. my $file = $c->config->{'var_path'}."/users/".$login;
  161. Thruk::Utils::store_user_data($c, $userdata, $login) if -s $file;
  162. if($userdata->{'login'}->{'locked'}) {
  163. return $c->redirect_to($c->stash->{'url_prefix'}."cgi-bin/login.cgi?locked&".$referer);
  164. }
  165. }
  166. return $c->redirect_to($c->stash->{'url_prefix'}."cgi-bin/login.cgi?".$referer);
  167. }
  168. }
  169. }
  170. Thruk::Utils::ssi_include($c, 'login');
  171. # set test cookie
  172. $c->cookie('thruk_test' => '****', {
  173. path => $cookie_path,
  174. domain => $cookie_domain,
  175. });
  176. if($c->config->{'cookie_auth_domain'} && $cookie_domain ne $c->config->{'cookie_auth_domain'}) {
  177. Thruk::Utils::set_message( $c, 'warn_message', 'using '.$cookie_domain.' instead of the configured cookie_auth_domain '.$c->config->{'cookie_auth_domain'});
  178. }
  179. $c->stash->{'cookie_domain'} = $cookie_domain;
  180. $c->stats->profile(end => "login::index");
  181. $c->res->code(401);
  182. if(($keywords && $keywords =~ m|/thruk/r/|mx) || $c->want_json_response()) {
  183. # respond with json error for the rest api
  184. my $details = $c->stash->{'thruk_message_details'} || "no or invalid credentials used.";
  185. $details =~ s/^.*~~//mx;
  186. return $c->render(json => {
  187. failed => Cpanel::JSON::XS::true,
  188. message => $c->stash->{'thruk_message'} || "login required",
  189. details => $details,
  190. description => "no or invalid credentials used.",
  191. code => 401,
  192. });
  193. }
  194. return 1;
  195. }
  196. ##########################################################
  197. sub _invalidate_current_session {
  198. my($c, $cookie_path) = @_;
  199. my $cookie = $c->cookie('thruk_auth');
  200. $c->cookie('thruk_auth' => '', {
  201. expires => 0,
  202. path => $cookie_path,
  203. domain => _get_cookie_domain($c),
  204. httponly => 1,
  205. });
  206. if(defined $cookie and defined $cookie->value) {
  207. my $session_data = Thruk::Utils::CookieAuth::retrieve_session(config => $c->config, id => $cookie->value);
  208. if($session_data) {
  209. unlink($session_data->{'file'});
  210. }
  211. }
  212. return;
  213. }
  214. ##########################################################
  215. sub _get_cookie_domain {
  216. my($c) = @_;
  217. my $domain = $c->config->{'cookie_auth_domain'};
  218. return "" unless $domain;
  219. my $http_host = $c->req->env->{'HTTP_HOST'};
  220. # remove port
  221. $http_host =~ s/:\d+$//gmx;
  222. $domain =~ s/\.$//gmx;
  223. if($http_host !~ m/\Q$domain\E$/mx) {
  224. return($http_host);
  225. }
  226. return $domain;
  227. }
  228. ##########################################################
  229. 1;