Subversion Repositories oidplus

Rev

Rev 585 | Rev 592 | 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 - 2021 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. if (!defined('INSIDE_OIDPLUS')) die();
  21.  
  22. class OIDplusAuthUtils {
  23.  
  24.         // Useful functions
  25.  
  26.         public static function getRandomBytes($len) {
  27.                 if (function_exists('openssl_random_pseudo_bytes')) {
  28.                         $a = openssl_random_pseudo_bytes($len);
  29.                         if ($a) return $a;
  30.                 }
  31.  
  32.                 if (function_exists('mcrypt_create_iv')) {
  33.                         $a = bin2hex(mcrypt_create_iv($len, MCRYPT_DEV_URANDOM));
  34.                         if ($a) return $a;
  35.                 }
  36.  
  37.                 if (function_exists('random_bytes')) {
  38.                         $a = random_bytes($len);
  39.                         if ($a) return $a;
  40.                 }
  41.  
  42.                 // Fallback to non-secure RNG
  43.                 $a = '';
  44.                 while (strlen($a) < $len*2) {
  45.                         $a .= sha1(uniqid(mt_rand(), true));
  46.                 }
  47.                 $a = substr($a, 0, $len*2);
  48.                 return hex2bin($a);
  49.         }
  50.  
  51.         // Content provider
  52.  
  53.         public function getAuthMethod() {
  54.                 $acs = $this->getAuthContentStore();
  55.                 if (is_null($acs)) return 'null';
  56.                 return get_class($acs);
  57.         }
  58.  
  59.         protected function getAuthContentStore() {
  60.                 // Logged in via JWT
  61.                 $tmp = OIDplusAuthContentStoreJWT::getActiveProvider();
  62.                 if ($tmp) return $tmp;
  63.  
  64.                 // Normal login via web-browser
  65.                 // Cookie will only be created once content is stored
  66.                 $tmp = OIDplusAuthContentStoreSession::getActiveProvider();
  67.                 if ($tmp) return $tmp;
  68.  
  69.                 // No active session and no JWT token available. User is not logged in.
  70.                 return null;
  71.         }
  72.  
  73.         public function getExtendedAttribute($name, $default=NULL) {
  74.                 $acs = $this->getAuthContentStore();
  75.                 if (is_null($acs)) return $default;
  76.                 return $acs->getValue($name, $default);
  77.         }
  78.  
  79.         // RA authentication functions
  80.  
  81.         public function raLogin($email) {
  82.                 $acs = $this->getAuthContentStore();
  83.                 if (is_null($acs)) return;
  84.                 return $acs->raLogin($email);
  85.         }
  86.  
  87.         public function raLogout($email) {
  88.                 $acs = $this->getAuthContentStore();
  89.                 if (is_null($acs)) return;
  90.                 return $acs->raLogout($email);
  91.         }
  92.  
  93.         public function raCheckPassword($ra_email, $password) {
  94.                 $ra = new OIDplusRA($ra_email);
  95.  
  96.                 $authInfo = $ra->getAuthInfo();
  97.                 if (!$authInfo) return false; // user not found
  98.  
  99.                 $plugins = OIDplus::getAuthPlugins();
  100.                 if (count($plugins) == 0) {
  101.                         throw new OIDplusException(_L('No RA authentication plugins found'));
  102.                 }
  103.                 foreach ($plugins as $plugin) {
  104.                         if ($plugin->verify($authInfo, $password)) return true;
  105.                 }
  106.  
  107.                 return false;
  108.         }
  109.  
  110.         public function raNumLoggedIn() {
  111.                 $acs = $this->getAuthContentStore();
  112.                 if (is_null($acs)) return 0;
  113.                 return $acs->raNumLoggedIn();
  114.         }
  115.  
  116.         public function loggedInRaList() {
  117.                 if ($this->forceAllLoggedOut()) {
  118.                         return array();
  119.                 } else {
  120.                         $acs = $this->getAuthContentStore();
  121.                         if (is_null($acs)) return array();
  122.                         return $acs->loggedInRaList();
  123.                 }
  124.         }
  125.  
  126.         public function isRaLoggedIn($email) {
  127.                 $acs = $this->getAuthContentStore();
  128.                 if (is_null($acs)) return false;
  129.                 return $acs->isRaLoggedIn($email);
  130.         }
  131.  
  132.         // "High level" function including logging and checking for valid JWT alternations
  133.         public function raLoginEx($email, $remember_me, $origin) {
  134.                 $loginfo = '';
  135.                 $acs = $this->getAuthContentStore();
  136.                 if (!is_null($acs)) {
  137.                         $acs->raLoginEx($email, $loginfo);
  138.                         $acs->activate();
  139.                 } else {
  140.                         if ($remember_me) {
  141.                                 if (!OIDplus::baseConfig()->getValue('JWT_ALLOW_LOGIN_USER', true)) {
  142.                                         throw new OIDplusException(_L('The administrator has disabled this feature. (Base configuration setting %1).','JWT_ALLOW_LOGIN_USER'));
  143.                                 }
  144.                                 $ttl = OIDplus::baseConfig()->getValue('JWT_TTL_LOGIN_USER', 10*365*24*60*60);
  145.                                 $authSimulation = new OIDplusAuthContentStoreJWT();
  146.                                 $authSimulation->raLoginEx($email, $loginfo);
  147.                                 $authSimulation->setValue('oidplus_generator', OIDplusAuthContentStoreJWT::JWT_GENERATOR_LOGIN);
  148.                                 $authSimulation->setValue('exp', time()+$ttl); // JWT "exp" attribute
  149.                                 $authSimulation->activate();
  150.                         } else {
  151.                                 $authSimulation = new OIDplusAuthContentStoreSession();
  152.                                 $authSimulation->raLoginEx($email, $loginfo);
  153.                                 $authSimulation->activate();
  154.                         }
  155.                 }
  156.                 $logmsg = "RA '$email' logged in";
  157.                 if ($origin != '') $logmsg .= " via $origin";
  158.                 if ($loginfo != '') $logmsg .= " ($loginfo)";
  159.                 OIDplus::logger()->log("[OK]RA($email)!", $logmsg);
  160.         }
  161.  
  162.         public function raLogoutEx($email) {
  163.                 $loginfo = '';
  164.  
  165.                 $acs = $this->getAuthContentStore();
  166.                 if (is_null($acs)) return;
  167.                 $res = $acs->raLogoutEx($email, $loginfo);
  168.  
  169.                 OIDplus::logger()->log("[OK]RA($email)!", "RA '$email' logged out ($loginfo)");
  170.  
  171.                 if (($this->raNumLoggedIn() == 0) && (!$this->isAdminLoggedIn())) {
  172.                         // Nobody logged in anymore. Destroy session cookie to make GDPR people happy
  173.                         $acs->destroySession();
  174.                 } else {
  175.                         // Get a new token for the remaining users
  176.                         $acs->activate();
  177.                 }
  178.  
  179.                 return $res;
  180.         }
  181.  
  182.         // Admin authentication functions
  183.  
  184.         public function adminLogin() {
  185.                 $acs = $this->getAuthContentStore();
  186.                 if (is_null($acs)) return;
  187.                 return $acs->adminLogin();
  188.         }
  189.  
  190.         public function adminLogout() {
  191.                 $acs = $this->getAuthContentStore();
  192.                 if (is_null($acs)) return;
  193.                 return $acs->adminLogout();
  194.         }
  195.  
  196.         public function adminCheckPassword($password) {
  197.                 $passwordData = OIDplus::baseConfig()->getValue('ADMIN_PASSWORD', '');
  198.                 if (empty($passwordData)) {
  199.                         throw new OIDplusException(_L('No admin password set in %1','userdata/baseconfig/config.inc.php'));
  200.                 }
  201.  
  202.                 if (strpos($passwordData, '$') !== false) {
  203.                         if ($passwordData[0] == '$') {
  204.                                 // Version 3: BCrypt
  205.                                 return password_verify($password, $passwordData);
  206.                         } else {
  207.                                 // Version 2: SHA3-512 with salt
  208.                                 list($s_salt, $hash) = explode('$', $passwordData, 2);
  209.                         }
  210.                 } else {
  211.                         // Version 1: SHA3-512 without salt
  212.                         $s_salt = '';
  213.                         $hash = $passwordData;
  214.                 }
  215.                 return strcmp(sha3_512($s_salt.$password, true), base64_decode($hash)) === 0;
  216.         }
  217.  
  218.         public function isAdminLoggedIn() {
  219.                 if ($this->forceAllLoggedOut()) {
  220.                         return false;
  221.                 } else {
  222.                         $acs = $this->getAuthContentStore();
  223.                         if (is_null($acs)) return false;
  224.                         return $acs->isAdminLoggedIn();
  225.                 }
  226.         }
  227.  
  228.         // "High level" function including logging and checking for valid JWT alternations
  229.         public function adminLoginEx($remember_me, $origin) {
  230.                 $loginfo = '';
  231.                 $acs = $this->getAuthContentStore();
  232.                 if (!is_null($acs)) {
  233.                         $acs->adminLoginEx($loginfo);
  234.                         $acs->activate();
  235.                 } else {
  236.                         if ($remember_me) {
  237.                                 if (!OIDplus::baseConfig()->getValue('JWT_ALLOW_LOGIN_ADMIN', true)) {
  238.                                         throw new OIDplusException(_L('The administrator has disabled this feature. (Base configuration setting %1).','JWT_ALLOW_LOGIN_ADMIN'));
  239.                                 }
  240.                                 $ttl = OIDplus::baseConfig()->getValue('JWT_TTL_LOGIN_ADMIN', 10*365*24*60*60);
  241.                                 $authSimulation = new OIDplusAuthContentStoreJWT();
  242.                                 $authSimulation->adminLoginEx($loginfo);
  243.                                 $authSimulation->setValue('oidplus_generator', OIDplusAuthContentStoreJWT::JWT_GENERATOR_LOGIN);
  244.                                 $authSimulation->setValue('exp', time()+$ttl); // JWT "exp" attribute
  245.                                 $authSimulation->activate();
  246.                         } else {
  247.                                 $authSimulation = new OIDplusAuthContentStoreSession();
  248.                                 $authSimulation->adminLoginEx($loginfo);
  249.                                 $authSimulation->activate();
  250.                         }
  251.                 }
  252.                 $logmsg = "Admin logged in";
  253.                 if ($origin != '') $logmsg .= " via $origin";
  254.                 if ($loginfo != '') $logmsg .= " ($loginfo)";
  255.                 OIDplus::logger()->log("[OK]A!", $logmsg);
  256.         }
  257.  
  258.         public function adminLogoutEx() {
  259.                 $loginfo = '';
  260.  
  261.                 $acs = $this->getAuthContentStore();
  262.                 if (is_null($acs)) return;
  263.                 $res = $acs->adminLogoutEx($loginfo);
  264.  
  265.                 if ($this->raNumLoggedIn() == 0) {
  266.                         // Nobody here anymore. Destroy the cookie to make GDPR people happy
  267.                         $acs->destroySession();
  268.                 } else {
  269.                         // Get a new token for the remaining users
  270.                         $acs->activate();
  271.                 }
  272.  
  273.                 OIDplus::logger()->log("[OK]A!", "Admin logged out ($loginfo)");
  274.                 return $res;
  275.         }
  276.  
  277.         // Authentication keys for validating arguments (e.g. sent by mail)
  278.  
  279.         public static function makeAuthKey($data) {
  280.                 $data = OIDplus::baseConfig()->getValue('SERVER_SECRET') . '/AUTHKEY/' . $data;
  281.                 $calc_authkey = sha3_512($data, false);
  282.                 return $calc_authkey;
  283.         }
  284.  
  285.         public static function validateAuthKey($data, $auth_key) {
  286.                 return strcmp(self::makeAuthKey($data), $auth_key) === 0;
  287.         }
  288.  
  289.         // "Veto" functions to force logout state
  290.  
  291.         protected function forceAllLoggedOut() {
  292.                 if (isset($_SERVER['SCRIPT_FILENAME']) && (basename($_SERVER['SCRIPT_FILENAME']) == 'sitemap.php')) {
  293.                         // The sitemap may not contain any confidential information,
  294.                         // even if the user is logged in, because the admin could
  295.                         // accidentally copy-paste the sitemap to a
  296.                         // search engine control panel while they are logged in
  297.                         return true;
  298.                 } else {
  299.                         return false;
  300.                 }
  301.         }
  302.  
  303.         // CSRF functions
  304.  
  305.         private $enable_csrf = true;
  306.  
  307.         public function enableCSRF() {
  308.                 $this->enable_csrf = true;
  309.         }
  310.  
  311.         public function disableCSRF() {
  312.                 $this->enable_csrf = false;
  313.         }
  314.  
  315.         public function genCSRFToken() {
  316.                 return bin2hex(self::getRandomBytes(64));
  317.         }
  318.  
  319.         public function checkCSRF() {
  320.                 if (!$this->enable_csrf) return;
  321.                 if (!isset($_REQUEST['csrf_token']) || !isset($_COOKIE['csrf_token']) || ($_REQUEST['csrf_token'] !== $_COOKIE['csrf_token'])) {
  322.                         throw new OIDplusException(_L('Wrong CSRF Token'));
  323.                 }
  324.         }
  325.  
  326.         // Generate RA passwords
  327.  
  328.         public static function raGeneratePassword($password): OIDplusRAAuthInfo {
  329.                 $def_method = OIDplus::config()->getValue('default_ra_auth_method');
  330.  
  331.                 $plugins = OIDplus::getAuthPlugins();
  332.                 foreach ($plugins as $plugin) {
  333.                         if (basename($plugin->getPluginDirectory()) === $def_method) {
  334.                                 return $plugin->generate($password);
  335.                         }
  336.                 }
  337.                 throw new OIDplusException(_L('Default RA auth method/plugin "%1" not found',$def_method));
  338.         }
  339.  
  340.         // Generate admin password
  341.  
  342.         /* Nothing here; the admin password will be generated in setup_base.js , purely in the web-browser */
  343.  
  344. }
  345.