Subversion Repositories oidplus

Rev

Rev 1305 | 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 OIDplusPagePublicLoginLdap extends OIDplusPagePluginPublic
  27.         implements INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_5, /* alternativeLoginMethods */
  28.                    INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_8  /* getNotifications */
  29. {
  30.  
  31.         /**
  32.          * @param OIDplusRA $ra
  33.          * @param array $ldap_userinfo
  34.          * @return void
  35.          * @throws OIDplusException
  36.          */
  37.         private function registerRA(OIDplusRA $ra, array $ldap_userinfo) {
  38.                 $email = $ra->raEmail();
  39.  
  40.                 $ra->register_ra(null); // create a user account without password
  41.  
  42.                 /*
  43.                 OIDplus DB Field          ActiveDirectory field
  44.                 ------------------------------------------------
  45.                 ra_name                   cn
  46.                 personal_name             displayname (or: givenname + " " + sn)
  47.                 organization              company
  48.                 office                    physicaldeliveryofficename or department
  49.                 street                    streetaddress
  50.                 zip_town                  postalcode + " " + l
  51.                 country                   co (human-readable) or c (ISO country code)
  52.                 phone                     telephonenumber or homephone
  53.                 mobile                    mobile
  54.                 fax                       facsimiletelephonenumber
  55.                 (none)                    wwwhomepage
  56.                 */
  57.  
  58.                 $opuserdata = array();
  59.                 $opuserdata['ra_name'] = \VtsLDAPUtils::getString($ldap_userinfo,'cn');
  60.                 if (!empty(\VtsLDAPUtils::getString($ldap_userinfo,'displayname'))) {
  61.                         $opuserdata['personal_name'] = \VtsLDAPUtils::getString($ldap_userinfo,'displayname');
  62.                 } else {
  63.                         $opuserdata['personal_name'] = trim(\VtsLDAPUtils::getString($ldap_userinfo,'givenname').' '.\VtsLDAPUtils::getString($ldap_userinfo,'sn'));
  64.                 }
  65.                 $opuserdata['organization'] = \VtsLDAPUtils::getString($ldap_userinfo,'company');
  66.                 if (!empty(\VtsLDAPUtils::getString($ldap_userinfo,'physicaldeliveryofficename'))) {
  67.                         $opuserdata['office'] = \VtsLDAPUtils::getString($ldap_userinfo,'physicaldeliveryofficename');
  68.                 } else {
  69.                         $opuserdata['office'] = \VtsLDAPUtils::getString($ldap_userinfo,'department');
  70.                 }
  71.                 $opuserdata['street'] = \VtsLDAPUtils::getString($ldap_userinfo,'streetaddress');
  72.                 $opuserdata['zip_town'] = trim(\VtsLDAPUtils::getString($ldap_userinfo,'postalcode').' '.\VtsLDAPUtils::getString($ldap_userinfo,'l'));
  73.                 $opuserdata['country'] = \VtsLDAPUtils::getString($ldap_userinfo,'co'); // ISO country code: \VtsLDAPUtils::getString($ldap_userinfo,'c')
  74.                 $opuserdata['phone'] = \VtsLDAPUtils::getString($ldap_userinfo,'telephonenumber'); // homephone for private phone number
  75.                 $opuserdata['mobile'] = \VtsLDAPUtils::getString($ldap_userinfo,'mobile');
  76.                 $opuserdata['fax'] = \VtsLDAPUtils::getString($ldap_userinfo,'facsimiletelephonenumber');
  77.  
  78.                 foreach ($opuserdata as $dbfield => $val) {
  79.                         if (!empty($val)) {
  80.                                 OIDplus::db()->query("update ###ra set ".$dbfield." = ? where email = ?", array($val, $email));
  81.                         }
  82.                 }
  83.         }
  84.  
  85.         /**
  86.          * @param string $email
  87.          * @param array $ldap_userinfo
  88.          * @return void
  89.          * @throws OIDplusException
  90.          */
  91.         private function doLoginRA(string $email, array $ldap_userinfo) {
  92.                 $ra = new OIDplusRA($email);
  93.                 if (!$ra->existing()) {
  94.                         $this->registerRA($ra, $ldap_userinfo);
  95.                         OIDplus::logger()->log("V2:[INFO]RA(%1)", "RA '%1' was created because of successful LDAP login", $email);
  96.                 }
  97.  
  98.                 OIDplus::authUtils()->raLoginEx($email, 'LDAP');
  99.  
  100.                 OIDplus::db()->query("UPDATE ###ra set last_login = ".OIDplus::db()->sqlDate()." where email = ?", array($email));
  101.         }
  102.  
  103.         /**
  104.          * @param string $upn
  105.          * @return int
  106.          * @throws OIDplusException
  107.          */
  108.         private function getDomainNumber(string $upn): int {
  109.                 $numDomains = OIDplus::baseConfig()->getValue('LDAP_NUM_DOMAINS', 1);
  110.                 for ($i=1; $i<=$numDomains; $i++) {
  111.                         $cfgSuffix = $i == 1 ? '' : "__$i";
  112.                         $upnSuffix = OIDplus::baseConfig()->getValue('LDAP_UPN_SUFFIX'.$cfgSuffix, '');
  113.                         if (str_ends_with($upn, $upnSuffix)) return $i;
  114.                 }
  115.                 return -1;
  116.         }
  117.  
  118.         /**
  119.          * @param array $params
  120.          * @return array
  121.          * @throws OIDplusConfigInitializationException
  122.          * @throws OIDplusException
  123.          */
  124.         private function action_Login(array $params): array {
  125.                 if (!OIDplus::baseConfig()->getValue('LDAP_ENABLED', false)) {
  126.                         throw new OIDplusException(_L('LDAP authentication is disabled on this system.'));
  127.                 }
  128.  
  129.                 if (!function_exists('ldap_connect')) throw new OIDplusConfigInitializationException(_L('PHP extension "%1" not installed','LDAP'));
  130.  
  131.                 OIDplus::getActiveCaptchaPlugin()->captchaVerify($params, 'captcha');
  132.  
  133.                 _CheckParamExists($params, 'email');
  134.                 _CheckParamExists($params, 'password');
  135.  
  136.                 $upn = $params['email'];
  137.                 $password = $params['password'];
  138.  
  139.                 $domainNumber = $this->getDomainNumber($upn);
  140.                 if ($domainNumber <= 0) {
  141.                         throw new OIDplusException(_L('The server is not configured to handle this domain (the part behind the at-sign)'));
  142.                 }
  143.                 $cfgSuffix = $domainNumber == 1 ? '' : "__$domainNumber";
  144.  
  145.                 if (empty($upn)) {
  146.                         throw new OIDplusException(_L('Please enter a valid username'));
  147.                 }
  148.  
  149.                 $ldap = new \VtsLDAPUtils();
  150.  
  151.                 try {
  152.  
  153.                         $cfg_ldap_server      = OIDplus::baseConfig()->getValue('LDAP_SERVER'.$cfgSuffix);
  154.                         $cfg_ldap_port        = OIDplus::baseConfig()->getValue('LDAP_PORT'.$cfgSuffix, 389);
  155.                         $cfg_ldap_base_dn     = OIDplus::baseConfig()->getValue('LDAP_BASE_DN'.$cfgSuffix);
  156.  
  157.                         // Note: Will throw an Exception if connect fails
  158.                         $ldap->connect($cfg_ldap_server, $cfg_ldap_port);
  159.  
  160.                         if (!$ldap->login($upn, $password)) {
  161.                                 if (OIDplus::config()->getValue('log_failed_ra_logins', false)) {
  162.                                         OIDplus::logger()->log("V2:[WARN]A", "Failed login to RA account '%1' using LDAP", $upn);
  163.                                 }
  164.                                 throw new OIDplusException(_L('Wrong password or user not registered'));
  165.                         }
  166.  
  167.                         $ldap_userinfo = $ldap->getUserInfo($upn, $cfg_ldap_base_dn);
  168.  
  169.                         if (!$ldap_userinfo) {
  170.                                 throw new OIDplusException(_L('The LDAP login was successful, but the own user %1 cannot be found. Please check the base configuration setting %2 and %3', $upn, "LDAP_BASE_DN$cfgSuffix", "LDAP_UPN_SUFFIX$cfgSuffix"));
  171.                         }
  172.  
  173.                         $foundSomething = false;
  174.  
  175.                         // ---
  176.  
  177.                         $cfgAdminGroup = OIDplus::baseConfig()->getValue('LDAP_ADMIN_GROUP'.$cfgSuffix,'');
  178.                         if (!empty($cfgAdminGroup)) {
  179.                                 $isAdmin = $ldap->isMemberOfRec($ldap_userinfo, $cfgAdminGroup);
  180.                         } else {
  181.                                 $isAdmin = false;
  182.                         }
  183.                         if ($isAdmin) {
  184.                                 $foundSomething = true;
  185.                                 OIDplus::authUtils()->adminLoginEx('LDAP login');
  186.                         }
  187.  
  188.                         // ---
  189.  
  190.                         $cfgRaGroup = OIDplus::baseConfig()->getValue('LDAP_RA_GROUP'.$cfgSuffix,'');
  191.                         if (!empty($cfgRaGroup)) {
  192.                                 $isRA = $ldap->isMemberOfRec($ldap_userinfo, $cfgRaGroup);
  193.                         } else {
  194.                                 $isRA = true;
  195.                         }
  196.                         if ($isRA) {
  197.                                 if (OIDplus::baseConfig()->getValue('LDAP_AUTHENTICATE_UPN'.$cfgSuffix,true)) {
  198.                                         $mail = \VtsLDAPUtils::getString($ldap_userinfo, 'userprincipalname');
  199.                                         $foundSomething = true;
  200.                                         $this->doLoginRA($mail, $ldap_userinfo);
  201.                                 }
  202.                                 if (OIDplus::baseConfig()->getValue('LDAP_AUTHENTICATE_EMAIL'.$cfgSuffix,false)) {
  203.                                         $mails = \VtsLDAPUtils::getArray($ldap_userinfo, 'mail');
  204.                                         foreach ($mails as $mail) {
  205.                                                 $foundSomething = true;
  206.                                                 $this->doLoginRA($mail, $ldap_userinfo);
  207.                                         }
  208.                                 }
  209.                         }
  210.  
  211.                 } finally {
  212.                         $ldap->disconnect();
  213.                         $ldap = null;
  214.                 }
  215.  
  216.                 if (!$foundSomething) {
  217.                         throw new OIDplusException(_L("Error: These credentials cannot be used with OIDplus. Please check the base configuration."));
  218.                 }
  219.  
  220.                 return array("status" => 0);
  221.         }
  222.  
  223.         /**
  224.          * @param string $actionID
  225.          * @param array $params
  226.          * @return array
  227.          * @throws OIDplusConfigInitializationException
  228.          * @throws OIDplusException
  229.          */
  230.         public function action(string $actionID, array $params): array {
  231.                 if ($actionID == 'ra_login_ldap') {
  232.                         return $this->action_Login($params);
  233.                 } else {
  234.                         return parent::action($actionID, $params);
  235.                 }
  236.         }
  237.  
  238.         /**
  239.          * @param bool $html
  240.          * @return void
  241.          */
  242.         public function init(bool $html=true) {
  243.                 // Nothing
  244.         }
  245.  
  246.         /**
  247.          * @param string $id
  248.          * @param array $out
  249.          * @param bool $handled
  250.          * @return void
  251.          * @throws OIDplusException
  252.          */
  253.         public function gui(string $id, array &$out, bool &$handled) {
  254.                 if ($id === 'oidplus:login_ldap') {
  255.                         $handled = true;
  256.                         $out['title'] = _L('Login using LDAP / ActiveDirectory');
  257.                         $out['icon']  = OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/main_icon.png';
  258.  
  259.                         if (!OIDplus::baseConfig()->getValue('LDAP_ENABLED', false)) {
  260.                                 throw new OIDplusException(_L('LDAP authentication is disabled on this system.'), $out['title']);
  261.                         }
  262.  
  263.                         if (!function_exists('ldap_connect')) {
  264.                                 throw new OIDplusException(_L('PHP extension "%1" not installed','LDAP'), $out['title']);
  265.                         }
  266.  
  267.                         $out['text']  = '<noscript>';
  268.                         $out['text'] .= '<p><font color="red">'._L('You need to enable JavaScript to use this feature.').'</font></p>';
  269.                         $out['text'] .= '</noscript>';
  270.  
  271.                         $out['text'] .= '<div id="loginLdapArea" style="visibility: hidden">';
  272.  
  273.                         $out['text'] .= OIDplus::getActiveCaptchaPlugin()->captchaGenerate(_L('Before logging in, please solve the following CAPTCHA'));
  274.                         $out['text'] .= '<br>';
  275.  
  276.                         $out['text'] .= '<p><a '.OIDplus::gui()->link('oidplus:login').'><img src="img/arrow_back.png" width="16" alt="'._L('Go back').'"> '._L('Regular login method').'</a></p>';
  277.  
  278.                         $out['text'] .= '<h2>'._L('Login as RA').'</h2>';
  279.  
  280.                         $login_list = OIDplus::authUtils()->loggedInRaList();
  281.                         if (count($login_list) > 0) {
  282.                                 foreach ($login_list as $x) {
  283.                                         $out['text'] .= '<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>';
  284.                                 }
  285.                                 $out['text'] .= '<p>'._L('If you have more accounts, you can log in with another account here.').'</p>';
  286.                         } else {
  287.                                 $out['text'] .= '<p>'._L('Enter your domain username and your password to log in as Registration Authority.').'</p>';
  288.                         }
  289.                         $out['text'] .= '<form onsubmit="return OIDplusPagePublicLoginLDAP.raLoginLdapOnSubmit(this);">';
  290.                         $out['text'] .= '<div><label class="padding_label">'._L('Username').':</label><input type="text" name="username" value="" id="raLoginLdapUsername">';
  291.                         $out['text'] .= '&nbsp;&nbsp;';
  292.                         $out['text'] .= '<select id="ldapUpnSuffix" name="upnSuffix">';
  293.  
  294.                         $numDomains = OIDplus::baseConfig()->getValue('LDAP_NUM_DOMAINS', 1);
  295.                         for ($i=1; $i<=$numDomains; $i++) {
  296.                                 $cfgSuffix = $i == 1 ? '' : "__$i";
  297.                                 $upnSuffix = OIDplus::baseConfig()->getValue('LDAP_UPN_SUFFIX'.$cfgSuffix, '');
  298.                                 if ($upnSuffix == '') throw new OIDplusException(_L('Invalid base configuration setting: %1 is missing or empty', 'LDAP_UPN_SUFFIX'.$cfgSuffix));
  299.                                 $out['text'] .= '<option value="'.htmlentities($upnSuffix).'">'.htmlentities($upnSuffix).'</option>';
  300.                         }
  301.  
  302.                         $out['text'] .= '</select>';
  303.                         $out['text'] .= '</div>';
  304.                         $out['text'] .= '<div><label class="padding_label">'._L('Password').':</label><input type="password" name="password" value="" id="raLoginLdapPassword"></div>';
  305.                         $out['text'] .= '<br><input type="submit" value="'._L('Login').'"><br><br>';
  306.                         $out['text'] .= '</form>';
  307.  
  308.                         $invitePlugin = OIDplus::getPluginByOid('1.3.6.1.4.1.37476.2.5.2.4.2.92'); // OIDplusPageRaInvite
  309.                         $out['text'] .= '<p><b>'._L('How to register?').'</b> '._L('You don\'t need to register. Just enter your Windows/Company credentials.').'</p>';
  310.  
  311.                         $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.');
  312.                         $privacy_document_file = 'OIDplus/privacy_documentation.html';
  313.                         $resourcePlugin = OIDplus::getPluginByOid('1.3.6.1.4.1.37476.2.5.2.4.1.500'); // OIDplusPagePublicResources
  314.                         if (!is_null($resourcePlugin) && file_exists(OIDplus::localpath().'res/'.$privacy_document_file)) {
  315.                                 $out['text'] .= ' <a '.OIDplus::gui()->link('oidplus:resources$'.$privacy_document_file.'#cookies').'>'._L('More information about the cookies used').'</a>';
  316.                         }
  317.                         $out['text'] .= '</font></p></div>';
  318.  
  319.                         $out['text'] .= '<script>$("#loginLdapArea")[0].style.visibility = "visible";</script>';
  320.                 }
  321.         }
  322.  
  323.         /**
  324.          * @param array $out
  325.          * @return void
  326.          */
  327.         public function publicSitemap(array &$out) {
  328.                 $out[] = 'oidplus:login_ldap';
  329.         }
  330.  
  331.         /**
  332.          * @param array $json
  333.          * @param string|null $ra_email
  334.          * @param bool $nonjs
  335.          * @param string $req_goto
  336.          * @return bool
  337.          */
  338.         public function tree(array &$json, string $ra_email=null, bool $nonjs=false, string $req_goto=''): bool {
  339.                 return true;
  340.         }
  341.  
  342.         /**
  343.          * @param string $request
  344.          * @return array|false
  345.          */
  346.         public function tree_search(string $request) {
  347.                 return false;
  348.         }
  349.  
  350.         /**
  351.          * Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_5
  352.          * @return array
  353.          * @throws OIDplusException
  354.          */
  355.         public function alternativeLoginMethods(): array {
  356.                 $logins = array();
  357.                 if (OIDplus::baseConfig()->getValue('LDAP_ENABLED', false)) {
  358.                         $logins[] = array(
  359.                                 'oidplus:login_ldap',
  360.                                 _L('Login using LDAP / ActiveDirectory'),
  361.                                 OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/main_icon16.png'
  362.                         );
  363.                 }
  364.                 return $logins;
  365.         }
  366.  
  367.         /**
  368.          * Implements interface INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_8
  369.          * @param string|null $user
  370.          * @return array
  371.          * @throws OIDplusException
  372.          */
  373.         public function getNotifications(string $user=null): array {
  374.                 $notifications = array();
  375.                 if ((!$user || ($user == 'admin')) && OIDplus::authUtils()->isAdminLoggedIn()) {
  376.                         if (OIDplus::baseConfig()->getValue('LDAP_ENABLED', false)) {
  377.                                 if (!function_exists('ldap_connect')) {
  378.                                         $title = _L('LDAP Login');
  379.                                         $notifications[] = new OIDplusNotification('ERR', _L('OIDplus plugin "%1" is enabled, but the required PHP extension "%2" is not installed.', htmlentities($title), 'php_ldap'));
  380.                                 }
  381.                         }
  382.                 }
  383.                 return $notifications;
  384.         }
  385.  
  386. }
  387.