container = $container; // Initialise from the $config array $knownKeys = [ 'timeStep', 'totpKey', 'cryptoKey', 'basicAuthUsername', 'queryParam', 'queryParamUsername', 'queryParamPassword', 'logoutOnExit', ]; foreach ($knownKeys as $key) { if (isset($config[$key])) { $this->$key = $config[$key]; } } if (isset($config['authenticationMethods'])) { $this->authenticationMethods = $this->parseAuthenticationMethods($config['authenticationMethods']); } } /** * Get the enabled authentication methods * * @return int[] * * @codeCoverageIgnore */ public function getAuthenticationMethods(): array { return $this->authenticationMethods; } /** * Set the enabled authentication methods * * @param int[] $authenticationMethods * * @codeCoverageIgnore */ public function setAuthenticationMethods(array $authenticationMethods): void { $this->authenticationMethods = []; foreach ($authenticationMethods as $method) { $this->addAuthenticationMethod($method); } } /** * Enable an authentication method * * @param integer $method */ public function addAuthenticationMethod(int $method): void { $validMethods = [ self::Auth_HTTPBasicAuth_Plaintext, self::Auth_HTTPBasicAuth_TOTP, self::Auth_QueryString_Plaintext, self::Auth_QueryString_TOTP, self::Auth_SplitQueryString_Plaintext, ]; if (!in_array($method, $validMethods)) { throw new \InvalidArgumentException(Text::sprintf('LIB_FOF40_TRANSPARENTAUTHENTICATION_INVALIDAUTHMETHOD', $method)); } if (!in_array($method, $this->authenticationMethods)) { $this->authenticationMethods[] = $method; } } /** * Disable an authentication method * * @param integer $method */ public function removeAuthenticationMethod(int $method): void { if (in_array($method, $this->authenticationMethods)) { $key = array_search($method, $this->authenticationMethods); unset($this->authenticationMethods[$key]); } } /** * Get the required username for the HTTP Basic Authentication with TOTP method * * @return string * * @codeCoverageIgnore */ public function getBasicAuthUsername(): string { return $this->basicAuthUsername; } /** * Set the required username for the HTTP Basic Authentication with TOTP method * * @param string $basicAuthUsername * * @codeCoverageIgnore */ public function setBasicAuthUsername(string $basicAuthUsername): void { $this->basicAuthUsername = $basicAuthUsername; } /** * Get the query parameter for the Auth_QueryString_TOTP method * * @return string * * @codeCoverageIgnore */ public function getQueryParam(): string { return $this->queryParam; } /** * Set the query parameter for the Auth_QueryString_TOTP method * * @param string $queryParam * * @codeCoverageIgnore */ public function setQueryParam(string $queryParam): void { $this->queryParam = $queryParam; } /** * Get the query string for the password in the Auth_SplitQueryString_Plaintext method * * @return string * * @codeCoverageIgnore */ public function getQueryParamPassword(): string { return $this->queryParamPassword; } /** * Set the query string for the password in the Auth_SplitQueryString_Plaintext method * * @param string $queryParamPassword * * @codeCoverageIgnore */ public function setQueryParamPassword(string $queryParamPassword): void { $this->queryParamPassword = $queryParamPassword; } /** * Get the query string for the username in the Auth_SplitQueryString_Plaintext method * * @return string * * @codeCoverageIgnore */ public function getQueryParamUsername(): string { return $this->queryParamUsername; } /** * Set the query string for the username in the Auth_SplitQueryString_Plaintext method * * @param string $queryParamUsername * * @codeCoverageIgnore */ public function setQueryParamUsername(string $queryParamUsername): void { $this->queryParamUsername = $queryParamUsername; } /** * Get the time step in seconds for the TOTP in the Auth_HTTPBasicAuth_TOTP method * * @return int * * @codeCoverageIgnore */ public function getTimeStep(): int { return $this->timeStep; } /** * Set the time step in seconds for the TOTP in the Auth_HTTPBasicAuth_TOTP method * * @param int $timeStep * * @codeCoverageIgnore */ public function setTimeStep(int $timeStep): void { $this->timeStep = $timeStep; } /** * Get the secret key for the TOTP in the Auth_HTTPBasicAuth_TOTP method * * @return string * * @codeCoverageIgnore */ public function getTotpKey(): string { return $this->totpKey; } /** * Set the secret key for the TOTP in the Auth_HTTPBasicAuth_TOTP method * * @param string $totpKey * * @codeCoverageIgnore */ public function setTotpKey(string $totpKey): void { $this->totpKey = $totpKey; } /** * Should I log out when the dispatcher finishes? * * @return boolean * * @codeCoverageIgnore */ public function getLogoutOnExit(): bool { return $this->logoutOnExit; } /** * Set the log out on exit flag (for testing) * * @param boolean $logoutOnExit * * @codeCoverageIgnore */ public function setLogoutOnExit(bool $logoutOnExit): void { $this->logoutOnExit = $logoutOnExit; } /** * Tries to get the transparent authentication credentials from the request * * @return array|null */ public function getTransparentAuthenticationCredentials(): ?array { $return = null; // Always run onFOFGetTransparentAuthenticationCredentials. These methods take precedence over anything else. $this->container->platform->importPlugin('user'); $this->container->platform->importPlugin('fof'); $pluginResults = $this->container->platform->runPlugins('onFOFGetTransparentAuthenticationCredentials', [$this->container]); foreach ($pluginResults as $result) { if (empty($result)) { continue; } if (is_array($result)) { return $result; } } // Make sure there are enabled transparent authentication methods if (empty($this->authenticationMethods)) { return $return; } $input = $this->container->input; foreach ($this->authenticationMethods as $method) { switch ($method) { case self::Auth_HTTPBasicAuth_TOTP: if (empty($this->totpKey)) { continue 2; } if (empty($this->basicAuthUsername)) { continue 2; } if (!isset($_SERVER['PHP_AUTH_USER'])) { continue 2; } if (!isset($_SERVER['PHP_AUTH_PW'])) { continue 2; } if ($_SERVER['PHP_AUTH_USER'] != $this->basicAuthUsername) { continue 2; } $encryptedData = $_SERVER['PHP_AUTH_PW']; return $this->decryptWithTOTP($encryptedData); case self::Auth_QueryString_TOTP: if (empty($this->queryParam)) { continue 2; } $encryptedData = $input->get($this->queryParam, '', 'raw'); if (empty($encryptedData)) { continue 2; } $return = $this->decryptWithTOTP($encryptedData); if (!is_null($return)) { return $return; } break; case self::Auth_HTTPBasicAuth_Plaintext: if (!isset($_SERVER['PHP_AUTH_USER'])) { continue 2; } if (!isset($_SERVER['PHP_AUTH_PW'])) { continue 2; } return [ 'username' => $_SERVER['PHP_AUTH_USER'], 'password' => $_SERVER['PHP_AUTH_PW'], ]; case self::Auth_QueryString_Plaintext: if (empty($this->queryParam)) { continue 2; } $jsonEncoded = $input->get($this->queryParam, '', 'raw'); if (empty($jsonEncoded)) { continue 2; } $authInfo = json_decode($jsonEncoded, true); if (!is_array($authInfo)) { continue 2; } if (!array_key_exists('username', $authInfo) || !array_key_exists('password', $authInfo)) { continue 2; } return $authInfo; case self::Auth_SplitQueryString_Plaintext: if (empty($this->queryParamUsername)) { continue 2; } if (empty($this->queryParamPassword)) { continue 2; } $username = $input->get($this->queryParamUsername, '', 'raw'); $password = $input->get($this->queryParamPassword, '', 'raw'); if (empty($username)) { continue 2; } if (empty($password)) { continue 2; } return [ 'username' => $username, 'password' => $password, ]; } } return $return; } /** * Parses a list of transparent authentication methods (array or comma separated list of integers or method names) * and converts it into an array of integers this class understands. * * @param string|int[]|string[] $methods * * @return int[] */ protected function parseAuthenticationMethods($methods): array { if (empty($methods)) { return []; } if (!is_array($methods)) { $methods = explode(',', $methods); } $return = []; foreach ($methods as $method) { if (empty($method)) { continue; } $method = trim($method); if ((int) $method == $method) { $return[] = (int) $method; } switch ($method) { case 'HTTPBasicAuth_TOTP': $return[] = 1; break; case 'QueryString_TOTP': $return[] = 2; break; case 'HTTPBasicAuth_Plaintext': $return[] = 3; break; case 'QueryString_Plaintext': $return[] = 4; break; case 'SplitQueryString_Plaintext': $return[] = 5; } } return $return; } /** * Decrypts a transparent authentication message using a TOTP * * @param string $encryptedData The encrypted data * * @return array|null The decrypted data */ private function decryptWithTOTP(string $encryptedData): ?array { if (empty($this->totpKey)) { $this->cryptoKey = null; return null; } $totp = new Totp($this->timeStep); $period = $totp->getPeriod(); $period--; for ($i = 0; $i <= 2; $i++) { $time = ($period + $i) * $this->timeStep; $otp = $totp->getCode($this->totpKey, $time); $this->cryptoKey = hash('sha256', $this->totpKey . $otp); $aes = new Aes(); $aes->setPassword($this->cryptoKey); try { $ret = $aes->decryptString($encryptedData); } catch (\Exception $e) { continue; } $ret = rtrim($ret, "\000"); $ret = json_decode($ret, true); if (!is_array($ret)) { continue; } if (!array_key_exists('username', $ret)) { continue; } if (!array_key_exists('password', $ret)) { continue; } // Successful decryption! return $ret; } // Obviously if we're here we could not decrypt anything. Bail out. $this->cryptoKey = null; return null; } }