elfinder = $elfinder; $this->args = $args; } /** * Return boolean that this plugin is enabled. * * @return bool */ public function enabled() { return true; } /** * Return boolean that $name method is allowed. * * @param string $name * * @return bool */ public function isAllowedMethod($name) { $checker = array_flip($this->allowed); return isset($checker[$name]); } /** * Return $this->args value of the key * * @param string $key target key * @param string $empty empty value * * @return mixed */ public function argValue($key, $empty = '') { return isset($this->args[$key]) ? $this->args[$key] : $empty; } /** * Create callback state and return token params for a callback request. * * @param string $method * @param string $hash * @param string $secret * @param array $meta * @param int $ttl * * @return array|false */ protected function createCallbackState($method, $hash, $secret, $meta = array(), $ttl = null) { $this->gcCallbackStates(); $token = $this->createCallbackStateToken(); $expires = time() + max(60, is_null($ttl) ? (int)$this->callbackStateTtl : (int)$ttl); $state = array( 'editor' => get_class($this), 'method' => (string)$method, 'token' => $token, 'hash' => $hash, 'expires' => $expires, 'meta' => is_array($meta) ? $meta : array() ); if (!$this->writeCallbackState($token, $state)) { return false; } return array( 'token' => $token, 'expires' => $expires, 'sig' => $this->createCallbackSignature($method, $token, $expires, $secret) ); } /** * Verify callback request and return state. * * @param string $method * @param array $post * @param string $secret * @param string $tokenKey * @param string $expiresKey * @param string $sigKey * * @return array|false */ protected function verifyCallbackRequest($method, $post, $secret, $tokenKey = 'token', $expiresKey = 'expires', $sigKey = 'sig') { $this->gcCallbackStates(); if (!is_array($post)) { return false; } $token = isset($post[$tokenKey]) ? (string)$post[$tokenKey] : ''; $expires = isset($post[$expiresKey]) ? (string)$post[$expiresKey] : ''; $sig = isset($post[$sigKey]) ? (string)$post[$sigKey] : ''; if ($token === '' || $expires === '' || $sig === '' || !ctype_digit($expires)) { return false; } $expires = (int)$expires; if ($expires < time()) { $this->deleteCallbackState($token); return false; } $expectedSig = $this->createCallbackSignature($method, $token, $expires, $secret); if (!$this->hashEquals($expectedSig, $sig)) { return false; } $state = $this->readCallbackState($token); if (!$state) { return false; } if (empty($state['editor']) || !$this->hashEquals($state['editor'], get_class($this)) || empty($state['method']) || !$this->hashEquals($state['method'], (string)$method) || empty($state['token']) || !$this->hashEquals($state['token'], $token) || !isset($state['expires']) || (int)$state['expires'] !== $expires || empty($state['hash'])) { return false; } return $state; } /** * Consume callback state. * * @param string $token * * @return void */ protected function consumeCallbackState($token) { $this->deleteCallbackState($token); } /** * Garbage collect expired callback states. * * @return void */ protected function gcCallbackStates() { $dir = $this->getCallbackStateDir(false); if (!$dir) { return; } $files = glob($dir . DIRECTORY_SEPARATOR . '*.json'); if (!$files) { return; } $now = time(); foreach ($files as $path) { if (!is_file($path)) { continue; } $remove = false; $json = file_get_contents($path); if ($json === false || $json === '') { $remove = true; } else { $state = json_decode($json, true); if (!is_array($state) || empty($state['expires']) || (int)$state['expires'] < $now) { $remove = true; } } if ($remove) { @unlink($path); } } } /** * Return callback state directory. * * @param bool $create * * @return string|false */ protected function getCallbackStateDir($create = false) { $base = elFinder::getCommonTempPath(); if (!$base) { return false; } $dir = $base . DIRECTORY_SEPARATOR . $this->callbackStateDirName; if (!is_dir($dir)) { if (!$create || !@mkdir($dir, 0700, true)) { return false; } } return is_writable($dir) ? $dir : false; } /** * Return callback state file path. * * @param string $token * @param bool $create * * @return string|false */ protected function getCallbackStatePath($token, $create = false) { $dir = $this->getCallbackStateDir($create); if (!$dir || !is_string($token) || $token === '') { return false; } return $dir . DIRECTORY_SEPARATOR . hash('sha256', $token) . '.json'; } /** * Persist callback state. * * @param string $token * @param array $state * * @return bool */ protected function writeCallbackState($token, $state) { $path = $this->getCallbackStatePath($token, true); if (!$path) { return false; } $json = json_encode($state); if ($json === false) { return false; } return file_put_contents($path, $json, LOCK_EX) !== false; } /** * Read callback state. * * @param string $token * * @return array|false */ protected function readCallbackState($token) { $path = $this->getCallbackStatePath($token, false); if (!$path || !is_file($path)) { return false; } $json = file_get_contents($path); if ($json === false || $json === '') { return false; } $state = json_decode($json, true); return is_array($state) ? $state : false; } /** * Delete callback state. * * @param string $token * * @return void */ protected function deleteCallbackState($token) { $path = $this->getCallbackStatePath($token, false); if ($path && is_file($path)) { @unlink($path); } } /** * Create callback signature. * * @param string $method * @param string $token * @param int $expires * @param string $secret * * @return string */ protected function createCallbackSignature($method, $token, $expires, $secret) { $payload = implode('|', array(get_class($this), (string)$method, (string)$token, (string)$expires)); return hash_hmac('sha256', $payload, (string)$secret); } /** * Create random callback state token. * * @return string */ protected function createCallbackStateToken() { if (function_exists('random_bytes')) { return bin2hex(random_bytes(32)); } if (function_exists('openssl_random_pseudo_bytes')) { $bytes = openssl_random_pseudo_bytes(32); if ($bytes !== false) { return bin2hex($bytes); } } return md5(uniqid(mt_rand(), true)) . md5(uniqid(mt_rand(), true)); } /** * Constant-time string comparison. * * @param string $known * @param string $user * * @return bool */ protected function hashEquals($known, $user) { if (function_exists('hash_equals')) { return hash_equals((string)$known, (string)$user); } $known = (string)$known; $user = (string)$user; if (strlen($known) !== strlen($user)) { return false; } $result = 0; $length = strlen($known); for ($i = 0; $i < $length; $i++) { $result |= ord($known[$i]) ^ ord($user[$i]); } return $result === 0; } }