authController.php 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. <?php
  2. /**
  3. * This controller handles action about authentication.
  4. */
  5. class FreshRSS_auth_Controller extends Minz_ActionController {
  6. /**
  7. * This action handles the login page.
  8. *
  9. * It forwards to the correct login page (form or Persona) or main page if
  10. * the user is already connected.
  11. */
  12. public function loginAction() {
  13. if (FreshRSS_Auth::hasAccess()) {
  14. Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true);
  15. }
  16. $auth_type = Minz_Configuration::authType();
  17. switch ($auth_type) {
  18. case 'form':
  19. Minz_Request::forward(array('c' => 'auth', 'a' => 'formLogin'));
  20. break;
  21. case 'persona':
  22. Minz_Request::forward(array('c' => 'auth', 'a' => 'personaLogin'));
  23. break;
  24. case 'http_auth':
  25. case 'none':
  26. // It should not happened!
  27. Minz_Error::error(404);
  28. default:
  29. // TODO load plugin instead
  30. Minz_Error::error(404);
  31. }
  32. }
  33. /**
  34. * This action handles form login page.
  35. *
  36. * If this action is reached through a POST request, username and password
  37. * are compared to login the current user.
  38. *
  39. * Parameters are:
  40. * - nonce (default: false)
  41. * - username (default: '')
  42. * - challenge (default: '')
  43. * - keep_logged_in (default: false)
  44. */
  45. public function formLoginAction() {
  46. invalidateHttpCache();
  47. $file_mtime = @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js');
  48. Minz_View::appendScript(Minz_Url::display('/scripts/bcrypt.min.js?' . $file_mtime));
  49. if (Minz_Request::isPost()) {
  50. $nonce = Minz_Session::param('nonce');
  51. $username = Minz_Request::param('username', '');
  52. $challenge = Minz_Request::param('challenge', '');
  53. try {
  54. $conf = new FreshRSS_Configuration($username);
  55. } catch(Minz_Exception $e) {
  56. // $username is not a valid user, nor the configuration file!
  57. Minz_Log::warning('Login failure: ' . $e->getMessage());
  58. Minz_Request::bad(_t('invalid_login'),
  59. array('c' => 'auth', 'a' => 'login'));
  60. }
  61. $ok = FreshRSS_FormAuth::checkCredentials(
  62. $username, $conf->passwordHash, $nonce, $challenge
  63. );
  64. if ($ok) {
  65. // Set session parameter to give access to the user.
  66. Minz_Session::_param('currentUser', $username);
  67. Minz_Session::_param('passwordHash', $conf->passwordHash);
  68. FreshRSS_Auth::giveAccess();
  69. // Set cookie parameter if nedded.
  70. if (Minz_Request::param('keep_logged_in')) {
  71. FreshRSS_FormAuth::makeCookie($username, $conf->passwordHash);
  72. } else {
  73. FreshRSS_FormAuth::deleteCookie();
  74. }
  75. // All is good, go back to the index.
  76. Minz_Request::good(_t('login'),
  77. array('c' => 'index', 'a' => 'index'));
  78. } else {
  79. Minz_Log::warning('Password mismatch for' .
  80. ' user=' . $username .
  81. ', nonce=' . $nonce .
  82. ', c=' . $challenge);
  83. Minz_Request::bad(_t('invalid_login'),
  84. array('c' => 'auth', 'a' => 'login'));
  85. }
  86. }
  87. }
  88. /**
  89. * This action handles Persona login page.
  90. *
  91. * If this action is reached through a POST request, assertion from Persona
  92. * is verificated and user connected if all is ok.
  93. *
  94. * Parameter is:
  95. * - assertion (default: false)
  96. *
  97. * @todo: Persona system should be moved to a plugin
  98. */
  99. public function personaLoginAction() {
  100. $this->view->res = false;
  101. if (Minz_Request::isPost()) {
  102. $this->view->_useLayout(false);
  103. $assert = Minz_Request::param('assertion');
  104. $url = 'https://verifier.login.persona.org/verify';
  105. $params = 'assertion=' . $assert . '&audience=' .
  106. urlencode(Minz_Url::display(null, 'php', true));
  107. $ch = curl_init();
  108. $options = array(
  109. CURLOPT_URL => $url,
  110. CURLOPT_RETURNTRANSFER => TRUE,
  111. CURLOPT_POST => 2,
  112. CURLOPT_POSTFIELDS => $params
  113. );
  114. curl_setopt_array($ch, $options);
  115. $result = curl_exec($ch);
  116. curl_close($ch);
  117. $res = json_decode($result, true);
  118. $login_ok = false;
  119. $reason = '';
  120. if ($res['status'] === 'okay') {
  121. $email = filter_var($res['email'], FILTER_VALIDATE_EMAIL);
  122. if ($email != '') {
  123. $persona_file = DATA_PATH . '/persona/' . $email . '.txt';
  124. if (($current_user = @file_get_contents($persona_file)) !== false) {
  125. $current_user = trim($current_user);
  126. try {
  127. $conf = new FreshRSS_Configuration($current_user);
  128. $login_ok = strcasecmp($email, $conf->mail_login) === 0;
  129. } catch (Minz_Exception $e) {
  130. //Permission denied or conf file does not exist
  131. $reason = 'Invalid configuration for user ' .
  132. '[' . $current_user . '] ' . $e->getMessage();
  133. }
  134. }
  135. } else {
  136. $reason = 'Invalid email format [' . $res['email'] . ']';
  137. }
  138. } else {
  139. $reason = $res['reason'];
  140. }
  141. if ($login_ok) {
  142. Minz_Session::_param('currentUser', $current_user);
  143. Minz_Session::_param('mail', $email);
  144. FreshRSS_Auth::giveAccess();
  145. invalidateHttpCache();
  146. } else {
  147. Minz_Log::error($reason);
  148. $res = array();
  149. $res['status'] = 'failure';
  150. $res['reason'] = _t('invalid_login');
  151. }
  152. header('Content-Type: application/json; charset=UTF-8');
  153. $this->view->res = $res;
  154. }
  155. }
  156. /**
  157. * This action removes all accesses of the current user.
  158. */
  159. public function logoutAction() {
  160. invalidateHttpCache();
  161. FreshRSS_Auth::removeAccess();
  162. Minz_Request::good(_t('disconnected'),
  163. array('c' => 'index', 'a' => 'index'));
  164. }
  165. /**
  166. * This action resets the authentication system.
  167. *
  168. * After reseting, form auth is set by default.
  169. */
  170. public function resetAction() {
  171. Minz_View::prependTitle(_t('auth_reset') . ' · ');
  172. Minz_View::appendScript(Minz_Url::display(
  173. '/scripts/bcrypt.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js')
  174. ));
  175. $this->view->no_form = false;
  176. // Enable changement of auth only if Persona!
  177. if (Minz_Configuration::authType() != 'persona') {
  178. $this->view->message = array(
  179. 'status' => 'bad',
  180. 'title' => _t('damn'),
  181. 'body' => _t('auth_not_persona')
  182. );
  183. $this->view->no_form = true;
  184. return;
  185. }
  186. $conf = new FreshRSS_Configuration(Minz_Configuration::defaultUser());
  187. // Admin user must have set its master password.
  188. if (!$conf->passwordHash) {
  189. $this->view->message = array(
  190. 'status' => 'bad',
  191. 'title' => _t('damn'),
  192. 'body' => _t('auth_no_password_set')
  193. );
  194. $this->view->no_form = true;
  195. return;
  196. }
  197. invalidateHttpCache();
  198. if (Minz_Request::isPost()) {
  199. $nonce = Minz_Session::param('nonce');
  200. $username = Minz_Request::param('username', '');
  201. $challenge = Minz_Request::param('challenge', '');
  202. $ok = FreshRSS_FormAuth::checkCredentials(
  203. $username, $conf->passwordHash, $nonce, $challenge
  204. );
  205. if ($ok) {
  206. Minz_Configuration::_authType('form');
  207. $ok = Minz_Configuration::writeFile();
  208. if ($ok) {
  209. Minz_Request::good(_t('auth_form_set'));
  210. } else {
  211. Minz_Request::bad(_t('auth_form_not_set'),
  212. array('c' => 'auth', 'a' => 'reset'));
  213. }
  214. } else {
  215. Minz_Log::warning('Password mismatch for' .
  216. ' user=' . $username .
  217. ', nonce=' . $nonce .
  218. ', c=' . $challenge);
  219. Minz_Request::bad(_t('invalid_login'),
  220. array('c' => 'auth', 'a' => 'reset'));
  221. }
  222. }
  223. }
  224. }