Subversion Repositories oidplus

Rev

Rev 1293 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. <?php
  2.  
  3. /*
  4.  * OIDplus 2.0
  5.  * Copyright 2019 - 2023 Daniel Marschall, ViaThinkSoft
  6.  *
  7.  * Licensed under the Apache License, Version 2.0 (the "License");
  8.  * you may not use this file except in compliance with the License.
  9.  * You may obtain a copy of the License at
  10.  *
  11.  *     http://www.apache.org/licenses/LICENSE-2.0
  12.  *
  13.  * Unless required by applicable law or agreed to in writing, software
  14.  * distributed under the License is distributed on an "AS IS" BASIS,
  15.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16.  * See the License for the specific language governing permissions and
  17.  * limitations under the License.
  18.  */
  19.  
  20. namespace ViaThinkSoft\OIDplus;
  21.  
  22. // phpcs:disable PSR1.Files.SideEffects
  23. \defined('INSIDE_OIDPLUS') or die;
  24. // phpcs:enable PSR1.Files.SideEffects
  25.  
  26. class OIDplusPagePublicLogin extends OIDplusPagePluginPublic {
  27.  
  28.         /**
  29.          * @param array $params
  30.          * @return array
  31.          * @throws OIDplusException
  32.          */
  33.         private function action_RaLogin(array $params): array {
  34.                 OIDplus::getActiveCaptchaPlugin()->captchaVerify($params, 'captcha');
  35.  
  36.                 _CheckParamExists($params, 'email');
  37.                 _CheckParamExists($params, 'password');
  38.  
  39.                 $email = $params['email'];
  40.                 $ra = new OIDplusRA($email);
  41.  
  42.                 if (empty($email)) {
  43.                         throw new OIDplusException(_L('Please enter a valid email address'));
  44.                 }
  45.  
  46.                 if ($ra->checkPassword($params['password'])) {
  47.                         OIDplus::authUtils()->raLoginEx($email, 'Regular login');
  48.  
  49.                         $authInfo = OIDplus::authUtils()->raGeneratePassword($params['password']);
  50.  
  51.                         // Rehash, so that we always have the latest default auth plugin and params
  52.                         // Note that we do it every time (unlike PHPs recommended password_needs_rehash),
  53.                         // because we are not sure which auth plugin created the hash (there might be multiple
  54.                         // auth plugins that can verify this hash). So we just rehash on every login!
  55.                         $new_authkey = $authInfo->getAuthKey();
  56.  
  57.                         OIDplus::db()->query("UPDATE ###ra set last_login = ".OIDplus::db()->sqlDate().", authkey = ? where email = ?", array($new_authkey, $email));
  58.  
  59.                         return array("status" => 0);
  60.                 } else {
  61.                         if (OIDplus::config()->getValue('log_failed_ra_logins', false)) {
  62.                                 if ($ra->existing()) {
  63.                                         OIDplus::logger()->log("V2:[WARN]A", "Failed login to RA account '%1' (wrong password)", $email);
  64.                                 } else {
  65.                                         OIDplus::logger()->log("V2:[WARN]A", "Failed login to RA account '%1' (RA not existing)", $email);
  66.                                 }
  67.                         }
  68.                         throw new OIDplusException(_L('Wrong password or user not registered'));
  69.                 }
  70.         }
  71.  
  72.         /**
  73.          * @param array $params
  74.          * @return array
  75.          * @throws OIDplusException
  76.          */
  77.         private function action_RaLogout(array $params): array {
  78.                 _CheckParamExists($params, 'email');
  79.  
  80.                 $email = $params['email'];
  81.  
  82.                 OIDplus::authUtils()->raLogoutEx($email);
  83.  
  84.                 return array("status" => 0);
  85.         }
  86.  
  87.         /**
  88.          * @param array $params
  89.          * @return array
  90.          * @throws OIDplusException
  91.          */
  92.         private function action_AdminLogin(array $params): array {
  93.                 OIDplus::getActiveCaptchaPlugin()->captchaVerify($params, 'captcha');
  94.  
  95.                 _CheckParamExists($params, 'password');
  96.                 if (OIDplus::authUtils()->adminCheckPassword($params['password'])) {
  97.                         OIDplus::authUtils()->adminLoginEx('Regular login');
  98.  
  99.                         // TODO: Write a "last login" entry in config table?
  100.  
  101.                         return array("status" => 0);
  102.                 } else {
  103.                         if (OIDplus::config()->getValue('log_failed_admin_logins', false)) {
  104.                                 OIDplus::logger()->log("V2:[WARN]A", "Failed login to admin account");
  105.                         }
  106.                         throw new OIDplusException(_L('Wrong password'));
  107.                 }
  108.         }
  109.  
  110.         /**
  111.          * @param array $params
  112.          * @return array
  113.          * @throws OIDplusException
  114.          */
  115.         private function action_AdminLogout(array $params): array {
  116.                 OIDplus::authUtils()->adminLogoutEx();
  117.  
  118.                 return array("status" => 0);
  119.         }
  120.  
  121.         /**
  122.          * @param string $actionID
  123.          * @param array $params
  124.          * @return array
  125.          * @throws OIDplusException
  126.          */
  127.         public function action(string $actionID, array $params): array {
  128.                 if ($actionID == 'ra_login') {
  129.                         return $this->action_RaLogin($params);
  130.                 } else if ($actionID == 'ra_logout') {
  131.                         return $this->action_RaLogout($params);
  132.                 } else if ($actionID == 'admin_login') {
  133.                         return $this->action_AdminLogin($params);
  134.                 }  else if ($actionID == 'admin_logout') {
  135.                         return $this->action_AdminLogout($params);
  136.                 } else {
  137.                         return parent::action($actionID, $params);
  138.                 }
  139.         }
  140.  
  141.         /**
  142.          * @param bool $html
  143.          * @return void
  144.          * @throws OIDplusException
  145.          */
  146.         public function init(bool $html=true) {
  147.                 OIDplus::config()->prepareConfigKey('log_failed_ra_logins', 'Log failed RA logins', '0', OIDplusConfig::PROTECTION_EDITABLE, function($value) {
  148.                         if (!is_numeric($value) || (($value != 0) && (($value != 1)))) {
  149.                                 throw new OIDplusException(_L('Valid values: 0 (off) or 1 (on).'));
  150.                         }
  151.                 });
  152.                 OIDplus::config()->prepareConfigKey('log_failed_admin_logins', 'Log failed Admin logins', '0', OIDplusConfig::PROTECTION_EDITABLE, function($value) {
  153.                         if (!is_numeric($value) || (($value != 0) && (($value != 1)))) {
  154.                                 throw new OIDplusException(_L('Valid values: 0 (off) or 1 (on).'));
  155.                         }
  156.                 });
  157.         }
  158.  
  159.         /**
  160.          * @param string $id
  161.          * @param array $out
  162.          * @param bool $handled
  163.          * @return void
  164.          * @throws OIDplusException
  165.          */
  166.         public function gui(string $id, array &$out, bool &$handled) {
  167.                 $ary = explode('$', $id);
  168.                 $desired_ra = '';
  169.                 if (isset($ary[1])) {
  170.                         $id = $ary[0];
  171.                         $tab = $ary[1];
  172.                         if (isset($ary[2])) {
  173.                                 $desired_ra = $ary[2];
  174.                         }
  175.                 } else {
  176.                         $tab = 'ra';
  177.                 }
  178.                 if ($id === 'oidplus:login') {
  179.                         $handled = true;
  180.                         $out['title'] = _L('Login');
  181.                         $out['icon']  = OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/login_icon.png';
  182.  
  183.                         $out['text']  = '<noscript>';
  184.                         $out['text'] .= '<p><font color="red">'._L('You need to enable JavaScript to use the login area.').'</font></p>';
  185.                         $out['text'] .= '</noscript>';
  186.  
  187.                         $out['text'] .= '<div id="loginArea" style="visibility: hidden"><div id="loginTab" class="container" style="width:100%;">';
  188.  
  189.                         $out['text'] .= OIDplus::getActiveCaptchaPlugin()->captchaGenerate(_L('Before logging in, please solve the following CAPTCHA'));
  190.                         $out['text'] .= '<br>';
  191.  
  192.                         // ---------------- Tab control
  193.                         $out['text'] .= OIDplus::gui()->tabBarStart();
  194.                         $out['text'] .= OIDplus::gui()->tabBarElement('ra',    _L('Login as RA'),            $tab === 'ra');
  195.                         $out['text'] .= OIDplus::gui()->tabBarElement('admin', _L('Login as administrator'), $tab === 'admin');
  196.                         $out['text'] .= OIDplus::gui()->tabBarEnd();
  197.                         $out['text'] .= OIDplus::gui()->tabContentStart();
  198.                         // ---------------- "RA" tab
  199.                         $tabcont = '<h2>'._L('Login as RA').'</h2>';
  200.                         $login_list = OIDplus::authUtils()->loggedInRaList();
  201.                         if (count($login_list) > 0) {
  202.                                 foreach ($login_list as $x) {
  203.                                         $tabcont .= '<p>'._L('You are logged in as %1','<b>'.$x->raEmail().'</b>').' (<a href="#" onclick="return OIDplusPagePublicLogin.raLogout('.js_escape($x->raEmail()).');">'._L('Logout').'</a>)</p>';
  204.                                 }
  205.                                 $tabcont .= '<p>'._L('If you have more accounts, you can log in with another account here.').'</p>';
  206.                         } else {
  207.                                 $tabcont .= '<p>'._L('Enter your email address and your password to log in as Registration Authority.').'</p>';
  208.                         }
  209.                         $tabcont .= '<form action="javascript:void(0);" onsubmit="return OIDplusPagePublicLogin.raLoginOnSubmit(this);">';
  210.                         $tabcont .= '<div><label class="padding_label">'._L('E-Mail').':</label><input type="text" name="email" value="'.htmlentities($desired_ra).'" id="raLoginEMail"></div>';
  211.                         $tabcont .= '<div><label class="padding_label">'._L('Password').':</label><input type="password" name="password" value="" id="raLoginPassword"></div>';
  212.                         $tabcont .= '<br><input type="submit" value="'._L('Login').'"><br><br>';
  213.                         $tabcont .= '</form>';
  214.                         $tabcont .= '<p><a '.OIDplus::gui()->link('oidplus:forgot_password').'>'._L('Forgot password?').'</a><br>';
  215.  
  216.                         $invitePlugin = OIDplus::getPluginByOid('1.3.6.1.4.1.37476.2.5.2.4.2.92'); // OIDplusPageRaInvite
  217.                         if (!is_null($invitePlugin) && OIDplus::config()->getValue('ra_invitation_enabled')) {
  218.                                 $tabcont .= '<p><b>'._L('How to register?').'</b> '._L('To receive login data, the superior RA needs to send you an invitation. After creating or updating your OID, the system will ask them if they want to send you an invitation. If they accept, you will receive an email with an activation link. Alternatively, the system admin can create your account manually in the administrator control panel.').'</p>';
  219.                         } else {
  220.                                 $tabcont .= '<p><b>'._L('How to register?').'</b> '._L('Since invitations are disabled at this OIDplus system, the system administrator needs to create your account manually in the administrator control panel.').'</p>';
  221.                         }
  222.  
  223.                         if ($tab === 'ra') {
  224.                                 $alt_logins_html = array();
  225.                                 foreach (OIDplus::getAllPlugins() as $plugin) {
  226.                                         if ($plugin instanceof INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_5) {
  227.                                                 $logins = $plugin->alternativeLoginMethods();
  228.                                                 foreach ($logins as $data) {
  229.                                                         if (isset($data[2]) && !empty($data[2])) {
  230.                                                                 $img = '<img src="'.$data[2].'" alt="'.htmlentities($data[1]).'"> ';
  231.                                                         } else {
  232.                                                                 $img = '';
  233.                                                         }
  234.                                                         $alt_logins_html[] = $img.'<a '.OIDplus::gui()->link($data[0]).'>'.htmlentities($data[1]).'</a>';
  235.                                                 }
  236.                                         }
  237.                                 }
  238.                                 if (count($alt_logins_html) > 0) {
  239.                                         $tabcont .= '<p>'._L('Alternative login methods').':<br>';
  240.                                         foreach ($alt_logins_html as $alt_login) {
  241.                                                 $tabcont .= $alt_login.'<br>';
  242.                                         }
  243.                                         $tabcont .= '</p>';
  244.                                 }
  245.                         }
  246.  
  247.                         $out['text'] .= OIDplus::gui()->tabContentPage('ra', $tabcont, $tab === 'ra');
  248.                         // ---------------- "Administrator" tab
  249.                         $tabcont = '<h2>'._L('Login as administrator').'</h2>';
  250.                         if (OIDplus::authUtils()->isAdminLoggedIn()) {
  251.                                 $tabcont .= '<p>'._L('You are logged in as administrator.').'</p>';
  252.                                 $tabcont .= '<a href="#" onclick="return OIDplusPagePublicLogin.adminLogout();">'._L('Logout').'</a>';
  253.                         } else {
  254.                                 $tabcont .= '<form action="javascript:void(0);" onsubmit="return OIDplusPagePublicLogin.adminLoginOnSubmit(this);">';
  255.                                 $tabcont .= '<div><label class="padding_label">'._L('Password').':</label><input type="password" name="password" value="" id="adminLoginPassword"></div>';
  256.                                 $tabcont .= '<br><input type="submit" value="'._L('Login').'"><br><br>';
  257.                                 $tabcont .= '</form>';
  258.                                 $tabcont .= '<p><a '.OIDplus::gui()->link('oidplus:forgot_password_admin').'>'._L('Forgot password?').'</a><br>';
  259.                         }
  260.                         $out['text'] .= OIDplus::gui()->tabContentPage('admin', $tabcont, $tab === 'admin');
  261.                         $out['text'] .= OIDplus::gui()->tabContentEnd();
  262.                         // ---------------- Tab control END
  263.  
  264.                         $out['text'] .= '</div><br>';
  265.  
  266.                         $out['text'] .= '<p><font size="-1">'._L('<i>Privacy information</i>: By using the login functionality, you are accepting that a "session cookie" is temporarily stored in your browser. The session cookie is a small text file that is sent to this website every time you visit it, to identify you as an already logged in user. It does not track any of your online activities outside OIDplus. The cookie will be destroyed when you log out.');
  267.                         $privacy_document_file = 'OIDplus/privacy_documentation.html';
  268.                         $resourcePlugin = OIDplus::getPluginByOid('1.3.6.1.4.1.37476.2.5.2.4.1.500'); // OIDplusPagePublicResources
  269.                         if (!is_null($resourcePlugin) && file_exists(OIDplus::localpath().'res/'.$privacy_document_file)) {
  270.                                 $out['text'] .= ' <a '.OIDplus::gui()->link('oidplus:resources$'.$privacy_document_file.'#cookies').'>'._L('More information about the cookies used').'</a>';
  271.                         }
  272.                         $out['text'] .= '</font></p></div>';
  273.  
  274.                         $out['text'] .= '<script>$("#loginArea")[0].style.visibility = "visible";</script>';
  275.                 }
  276.         }
  277.  
  278.         /**
  279.          * @param array $out
  280.          * @return void
  281.          */
  282.         public function publicSitemap(array &$out) {
  283.                 $out[] = 'oidplus:login';
  284.         }
  285.  
  286.         /**
  287.          * @param array $json
  288.          * @param string|null $ra_email
  289.          * @param bool $nonjs
  290.          * @param string $req_goto
  291.          * @return bool
  292.          * @throws OIDplusConfigInitializationException
  293.          * @throws OIDplusException
  294.          */
  295.         public function tree(array &$json, string $ra_email=null, bool $nonjs=false, string $req_goto=''): bool {
  296.                 $loginChildren = array();
  297.  
  298.                 if (OIDplus::authUtils()->isAdminLoggedIn()) {
  299.                         $ra_roots = array();
  300.  
  301.                         foreach (OIDplus::getPagePlugins() as $plugin) {
  302.                                 if (is_subclass_of($plugin, OIDplusPagePluginAdmin::class)) {
  303.                                         $plugin->tree($ra_roots);
  304.                                 }
  305.                         }
  306.  
  307.                         $ra_roots[] = array(
  308.                                 'id'       => 'oidplus:logout$admin',
  309.                                 'icon'     => OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/logout_icon16.png',
  310.                                 'conditionalselect' => 'OIDplusPagePublicLogin.adminLogout(); false;',
  311.                                 'text'     => _L('Log out')
  312.                         );
  313.                         $loginChildren[] = array(
  314.                                 'id'       => 'oidplus:dummy$'.md5((string)rand()),
  315.                                 'text'     => _L("Logged in as <b>admin</b>"),
  316.                                 'icon'     => OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/admin_icon16.png',
  317.                                 'conditionalselect' => 'false', // dummy node that can't be selected
  318.                                 'state'    => array("opened" => true),
  319.                                 'children' => $ra_roots
  320.                         );
  321.                 }
  322.  
  323.                 foreach (OIDplus::authUtils()->loggedInRaList() as $ra) {
  324.                         $ra_email = $ra->raEmail();
  325.                         $ra_roots = array();
  326.  
  327.                         foreach (OIDplus::getPagePlugins() as $plugin) {
  328.                                 if (is_subclass_of($plugin, OIDplusPagePluginRa::class)) {
  329.                                         $plugin->tree($ra_roots, $ra_email);
  330.                                 }
  331.                         }
  332.  
  333.                         $ra_roots[] = array(
  334.                                 'id'       => 'oidplus:logout$'.$ra_email,
  335.                                 'conditionalselect' => 'OIDplusPagePublicLogin.raLogout('.js_escape($ra_email).'); false;',
  336.                                 'icon'     => OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/logout_icon16.png',
  337.                                 'text'     => _L('Log out')
  338.                         );
  339.                         foreach (OIDplusObject::getRaRoots($ra_email) as $loc_root) {
  340.                                 $ico = $loc_root->getIcon();
  341.                                 $ra_roots[] = array(
  342.                                         'id' => 'oidplus:raroot$'.$loc_root->nodeId(),
  343.                                         'text' => _L('Jump to RA root %1',$loc_root->objectTypeTitleShort().' '.$loc_root->crudShowId(OIDplusObject::parse($loc_root::root()))),
  344.                                         'conditionalselect' => 'openOidInPanel('.js_escape($loc_root->nodeId()).', true); false;',
  345.                                         'icon' => !is_null($ico) ? $ico : OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/link_icon16.png'
  346.                                 );
  347.                         }
  348.                         $ra_email_or_name = (new OIDplusRA($ra_email))->raName();
  349.                         if ($ra_email_or_name == '') {
  350.                                 $ra_email_html = htmlentities($ra_email);
  351.                                 $ra_email_or_name = '<b>'.$ra_email_html.'</b>';
  352.                         } else {
  353.                                 $ra_email_html = htmlentities($ra_email);
  354.                                 $ra_email_or_name_html = htmlentities($ra_email_or_name);
  355.                                 $ra_email_or_name = "<b>$ra_email_or_name_html</b> ($ra_email_html)";
  356.                         }
  357.                         $loginChildren[] = array(
  358.                                 'id'       => 'oidplus:dummy$'.md5((string)rand()),
  359.                                 'text'     => _L('Logged in as %1',$ra_email_or_name),
  360.                                 'icon'     => OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/ra_icon16.png',
  361.                                 'conditionalselect' => 'false', // dummy node that can't be selected
  362.                                 'state'    => array("opened" => true),
  363.                                 'children' => $ra_roots
  364.                         );
  365.                 }
  366.  
  367.                 $json[] = array(
  368.                         'id'       => 'oidplus:login',
  369.                         'icon'     => OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/login_icon16.png',
  370.                         'text'     => _L('Login'),
  371.                         'state'    => array("opened" => count($loginChildren)>0),
  372.                         'children' => $loginChildren
  373.                 );
  374.  
  375.                 return true;
  376.         }
  377.  
  378.         /**
  379.          * @param string $request
  380.          * @return array|false
  381.          */
  382.         public function tree_search(string $request) {
  383.                 return false;
  384.         }
  385. }
  386.