diff --git a/core/src/core/classes/class.AJXP_Utils.php b/core/src/core/classes/class.AJXP_Utils.php index a39d2f76b8..c9f483e278 100644 --- a/core/src/core/classes/class.AJXP_Utils.php +++ b/core/src/core/classes/class.AJXP_Utils.php @@ -1622,9 +1622,6 @@ public static function parseStandardFormParameters(&$repDef, &$options, $userId $options[substr($key, strlen($prefix))] = $value; unset($repDef[$key]); } else { - if ($key == "DISPLAY") { - $value = SystemTextEncoding::fromUTF8(AJXP_Utils::securePath($value)); - } $repDef[$key] = $value; } } diff --git a/core/src/core/classes/class.AuthService.php b/core/src/core/classes/class.AuthService.php index b52bb76cb7..760ad36160 100644 --- a/core/src/core/classes/class.AuthService.php +++ b/core/src/core/classes/class.AuthService.php @@ -370,15 +370,13 @@ public static function logUser($user_id, $pwd, $bypass_pwd = false, $cookieLogin if ($authDriver->isAjxpAdmin($user_id)) { $user->setAdmin(true); } + if(self::$useSession) $_SESSION["AJXP_USER"] = $user; + else self::$currentUser = $user; + if ($user->isAdmin()) { $user = self::updateAdminRights($user); - } else { - if (!$user->hasParent() && $user_id != "guest") { - //$user->setAcl("ajxp_shared", "rw"); - } + self::updateUser($user); } - if(self::$useSession) $_SESSION["AJXP_USER"] = $user; - else self::$currentUser = $user; if ($authDriver->autoCreateUser() && !$user->storageExists()) { $user->save("superuser"); // make sure update rights now diff --git a/core/src/core/classes/class.ConfService.php b/core/src/core/classes/class.ConfService.php index 57e4d5aea8..20f7e14650 100644 --- a/core/src/core/classes/class.ConfService.php +++ b/core/src/core/classes/class.ConfService.php @@ -215,6 +215,11 @@ public static function getAuthDriverImpl() return AJXP_PluginsService::getInstance()->getPluginById("core.auth")->getAuthImpl(); } + /** + * @param AbstractAjxpUser $loggedUser + * @param String|int $parameterId + * @return bool + */ public static function switchUserToActiveRepository($loggedUser, $parameterId = -1) { if (isSet($_SESSION["PENDING_REPOSITORY_ID"]) && isSet($_SESSION["PENDING_FOLDER"])) { diff --git a/core/src/core/classes/class.SystemTextEncoding.php b/core/src/core/classes/class.SystemTextEncoding.php index d08a8a06a2..ebc72c90c4 100644 --- a/core/src/core/classes/class.SystemTextEncoding.php +++ b/core/src/core/classes/class.SystemTextEncoding.php @@ -163,4 +163,34 @@ public static function isUtf8($string) | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 )*$%xs', $string); } + /** + * Transform a string from current Storage charset to utf8 + * @static + * @param string $filesystemElement + * @param bool $test Test if it's already UTF8 or not, to avoid double-encoding + * @return string + */ + public static function fromStorageEncoding($filesystemElement, $test = true) + { + if ($test && SystemTextEncoding::isUtf8($filesystemElement)) { + return $filesystemElement; + } + $enc = SystemTextEncoding::getEncoding(); + return SystemTextEncoding::changeCharset($enc, "UTF-8", $filesystemElement); + } + /** + * Decode a string from UTF8 to current Storage Charset + * @static + * @param string $filesystemElement + * @param bool $test Try to detect if it's really utf8 or not + * @return string + */ + public static function toStorageEncoding($filesystemElement, $test = false) + { + if ($test && !SystemTextEncoding::isUtf8($filesystemElement)) { + return $filesystemElement; + } + $enc = SystemTextEncoding::getEncoding(); + return SystemTextEncoding::changeCharset("UTF-8", $enc, $filesystemElement); + } } diff --git a/core/src/plugins/access.ajxp_conf/class.ajxp_confAccessDriver.php b/core/src/plugins/access.ajxp_conf/class.ajxp_confAccessDriver.php index c016c1d0fa..de0ee180df 100644 --- a/core/src/plugins/access.ajxp_conf/class.ajxp_confAccessDriver.php +++ b/core/src/plugins/access.ajxp_conf/class.ajxp_confAccessDriver.php @@ -1326,7 +1326,7 @@ public function switchAction($action, $httpVars, $fileVars) $repo = ConfService::getRepositoryById($repId); $res = 0; if (isSet($httpVars["newLabel"])) { - $newLabel = AJXP_Utils::decodeSecureMagic($httpVars["newLabel"]); + $newLabel = AJXP_Utils::sanitize(AJXP_Utils::securePath($httpVars["newLabel"]), AJXP_SANITIZE_HTML); if ($this->repositoryExists($newLabel)) { AJXP_XMLWriter::header(); AJXP_XMLWriter::sendMessage(null, $mess["ajxp_conf.50"]); diff --git a/core/src/plugins/access.fs/class.fsAccessDriver.php b/core/src/plugins/access.fs/class.fsAccessDriver.php index fd135e81a3..02d5d29255 100644 --- a/core/src/plugins/access.fs/class.fsAccessDriver.php +++ b/core/src/plugins/access.fs/class.fsAccessDriver.php @@ -54,7 +54,7 @@ public function initRepository() ConfService::setConf("PROBE_REAL_SIZE", $this->getFilteredOption("PROBE_REAL_SIZE", $this->repository->getId())); } $create = $this->repository->getOption("CREATE"); - $path = $this->repository->getOption("PATH"); + $path = SystemTextEncoding::toStorageEncoding($this->repository->getOption("PATH")); $recycle = $this->repository->getOption("RECYCLE_BIN"); $chmod = $this->repository->getOption("CHMOD_VALUE"); $wrapperData = $this->detectStreamWrapper(true); @@ -2022,7 +2022,7 @@ public function makePublicletOptions($filePath, $password, $expire, $downloadlim public function makeSharedRepositoryOptions($httpVars, $repository) { $newOptions = array( - "PATH" => $repository->getOption("PATH").AJXP_Utils::decodeSecureMagic($httpVars["file"]), + "PATH" => SystemTextEncoding::toStorageEncoding($repository->getOption("PATH")).AJXP_Utils::decodeSecureMagic($httpVars["file"]), "CREATE" => isSet($httpVars["inherit_recycle"])? $repository->getOption("CREATE") : false, "RECYCLE_BIN" => isSet($httpVars["inherit_recycle"])? $repository->getOption("RECYCLE_BIN") : "", "DEFAULT_RIGHTS" => ""); diff --git a/core/src/plugins/access.fs/class.fsAccessWrapper.php b/core/src/plugins/access.fs/class.fsAccessWrapper.php index 6813d62d78..e08831e9f3 100644 --- a/core/src/plugins/access.fs/class.fsAccessWrapper.php +++ b/core/src/plugins/access.fs/class.fsAccessWrapper.php @@ -93,7 +93,7 @@ protected static function initPath($path, $streamType, $storeOpenContext = false $tmpFileName = $tmpDir.DIRECTORY_SEPARATOR.basename($localPath); AJXP_Logger::debug(__CLASS__,__FUNCTION__,"Tmp file $tmpFileName"); register_shutdown_function(array("fsAccessWrapper", "removeTmpFile"), $tmpDir, $tmpFileName); - $crtZip = new PclZip(AJXP_Utils::securePath(realpath($repoObject->getOption("PATH")).$repoObject->resolveVirtualRoots($zipPath))); + $crtZip = new PclZip(AJXP_Utils::securePath(realpath(SystemTextEncoding::toStorageEncoding($repoObject->getOption("PATH"))).$repoObject->resolveVirtualRoots($zipPath))); $content = $crtZip->listContent(); foreach ($content as $item) { $fName = AJXP_Utils::securePath($item["stored_filename"]); @@ -116,7 +116,7 @@ protected static function initPath($path, $streamType, $storeOpenContext = false } } } else { - $crtZip = new PclZip(AJXP_Utils::securePath(realpath($repoObject->getOption("PATH")).$repoObject->resolveVirtualRoots($zipPath))); + $crtZip = new PclZip(AJXP_Utils::securePath(realpath(SystemTextEncoding::toStorageEncoding($repoObject->getOption("PATH"))).$repoObject->resolveVirtualRoots($zipPath))); $liste = $crtZip->listContent(); if($storeOpenContext) self::$crtZip = $crtZip; $folders = array(); $files = array();$builtFolders = array(); @@ -178,7 +178,7 @@ protected static function initPath($path, $streamType, $storeOpenContext = false return -1; } } - return realpath($repoObject->getOption("PATH")).$repoObject->resolveVirtualRoots($url["path"]); + return realpath(SystemTextEncoding::toStorageEncoding($repoObject->getOption("PATH"))).$repoObject->resolveVirtualRoots($url["path"]); } } diff --git a/core/src/plugins/action.share/class.ShareCenter.php b/core/src/plugins/action.share/class.ShareCenter.php index 53530c5d02..b909aca0c5 100644 --- a/core/src/plugins/action.share/class.ShareCenter.php +++ b/core/src/plugins/action.share/class.ShareCenter.php @@ -1797,6 +1797,7 @@ public function createSharedRepository($httpVars, $repository, $accessDriver, $u $newRepo->setGroupPath($gPath); } $newRepo->setDescription($description); + $newRepo->options["PATH"] = SystemTextEncoding::fromStorageEncoding($newRepo->options["PATH"]); if(isSet($httpVars["filter_nodes"])){ $newRepo->setContentFilter(new ContentFilter($httpVars["filter_nodes"])); } diff --git a/core/src/plugins/authfront.cyphered/class.CypheredAuthFrontend.php b/core/src/plugins/authfront.cyphered/class.CypheredAuthFrontend.php new file mode 100644 index 0000000000..ed134dfea1 --- /dev/null +++ b/core/src/plugins/authfront.cyphered/class.CypheredAuthFrontend.php @@ -0,0 +1,167 @@ + + * This file is part of Pydio. + * + * Pydio is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Pydio is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Pydio. If not, see . + * + * The latest code can be found at . + */ +defined('AJXP_EXEC') or die( 'Access not allowed'); + + +class CypheredAuthFrontend extends AbstractAuthFrontend { + + function detectVar(&$httpVars, $varName){ + if(isSet($httpVars[$varName])) return $httpVars[$varName]; + if(isSet($_SERVER["HTTP_PYDIO_".strtoupper($varName)])) return $_SERVER["HTTP_".strtoupper($varName)]; + return ""; + } + + function getLastKeys(){ + $file = $this->getPluginWorkDir(false)."/last_inc"; + if(!is_file($file)) return array(); + $content = file_get_contents($file); + if(empty($content)) return array(); + $data = unserialize($content); + if(is_array($data)) return $data; + return array(); + } + + function storeLastKeys($data){ + $file = $this->getPluginWorkDir(true)."/last_inc"; + file_put_contents($file, serialize($data)); + } + + /** + * decrypt AES 256 + * + * @param string $password + * @param data $edata + * @return dencrypted data + */ + public function decrypt($password, $edata) { + $data = base64_decode($edata); + $salt = substr($data, 8, 8); + $ct = substr($data, 16); + /** + * From https://github.com/mdp/gibberish-aes + * + * Number of rounds depends on the size of the AES in use + * 3 rounds for 256 + * 2 rounds for the key, 1 for the IV + * 2 rounds for 128 + * 1 round for the key, 1 round for the IV + * 3 rounds for 192 since it's not evenly divided by 128 bits + */ + $rounds = 3; + $data00 = $password.$salt; + $md5_hash = array(); + $md5_hash[0] = md5($data00, true); + $result = $md5_hash[0]; + for ($i = 1; $i < $rounds; $i++) { + $md5_hash[$i] = md5($md5_hash[$i - 1].$data00, true); + $result .= $md5_hash[$i]; + } + $key = substr($result, 0, 32); + $iv = substr($result, 32,16); + + return openssl_decrypt($ct, 'aes-256-cbc', $key, true, $iv); + } + + /** + * crypt AES 256 + * + * @param string $password + * @param string $data + * @return string encrypted data + */ + public function crypt($password, $data) { + // Set a random salt + $salt = openssl_random_pseudo_bytes(8); + + $salted = ''; + $dx = ''; + // Salt the key(32) and iv(16) = 48 + while (strlen($salted) < 48) { + $dx = md5($dx.$password.$salt, true); + $salted .= $dx; + } + + $key = substr($salted, 0, 32); + $iv = substr($salted, 32,16); + + $encrypted_data = openssl_encrypt($data, 'aes-256-cbc', $key, true, $iv); + return base64_encode('Salted__' . $salt . $encrypted_data); + } + + + + function tryToLogUser(&$httpVars, $isLast = false){ + + $checkNonce = $this->pluginConf["CHECK_NONCE"] === true; + $token = $this->detectVar($httpVars, "cyphered_token"); + $tokenInc = $this->detectVar($httpVars, "cyphered_token_inc"); + if(empty($token) || ($checkNonce && empty($tokenInc))){ + return false; + } + + if(!$checkNonce){ + $decoded = $this->decrypt($this->pluginConf["PRIVATE_KEY"], $token); + }else{ + $decoded = $this->decrypt($this->pluginConf["PRIVATE_KEY"].":".$tokenInc, $token); + } + if($decoded == null){ + return false; + } + $data = unserialize($decoded); + if(empty($data) || !is_array($data) || !isset($data["user_id"]) || !isset($data["user_pwd"])){ + $this->logDebug(__FUNCTION__, "Cyphered Token found but wrong deserizalized data"); + return false; + } + if(AuthService::getLoggedUser() != null){ + $currentUser = AuthService::getLoggedUser()->getId(); + if($currentUser != $data["user_id"]){ + AuthService::disconnect(); + } + } + $this->logDebug(__FUNCTION__, "Trying to log user ".$data["user_id"]." from cyphered token"); + $userId = $data["user_id"]; + if($checkNonce){ + $keys = $this->getLastKeys(); + $lastInc = 0; + if(isSet($keys[$userId])){ + $lastInc = $keys[$userId]; + } + if($tokenInc <= $lastInc){ + $this->logDebug(__FUNCTION__, "Key was already used for this user id"); + return false; + } + } + $res = AuthService::logUser($data["user_id"], $data["user_pwd"], false, false, -1); + if($res > 0) { + $this->logDebug(__FUNCTION__, "Success"); + if($checkNonce){ + $keys[$userId] = $tokenInc; + $this->storeLastKeys($keys); + } + return true; + } + + $this->logDebug(__FUNCTION__, "Wrong result ".$res); + return false; + + } + +} \ No newline at end of file diff --git a/core/src/plugins/authfront.cyphered/manifest.xml b/core/src/plugins/authfront.cyphered/manifest.xml new file mode 100644 index 0000000000..ce01f36fe7 --- /dev/null +++ b/core/src/plugins/authfront.cyphered/manifest.xml @@ -0,0 +1,16 @@ + + + + + + + + + + \ No newline at end of file diff --git a/core/src/plugins/authfront.cyphered/plugin_doc.html b/core/src/plugins/authfront.cyphered/plugin_doc.html new file mode 100644 index 0000000000..f6b4e897a0 --- /dev/null +++ b/core/src/plugins/authfront.cyphered/plugin_doc.html @@ -0,0 +1,47 @@ +This is plugin aims at passing credentials directly through a POST to Pydio.
+User ID and PWD are expected to be passed in an encrypted token using the standard Open SSL functions (openssl extension must be enabled). +A simple incremental "nonce" is used to make sure the token can never be replayed.
+
+Here is the sample PHP code to use to cypher the password: +
+    /**
+     * crypt AES 256
+     *
+     * @param string $password
+     * @param string $data
+     * @return string Base64 encoded encrypted data.
+     */
+    function PYDIO_crypt($password, $data) {
+        // Set a random salt
+        $salt = openssl_random_pseudo_bytes(8);
+
+        $salted = '';
+        $dx = '';
+        // Salt the key(32) and iv(16) = 48
+        while (strlen($salted) < 48) {
+            $dx = md5($dx.$password.$salt, true);
+            $salted .= $dx;
+        }
+
+        $key = substr($salted, 0, 32);
+        $iv  = substr($salted, 32,16);
+
+        $encrypted_data = openssl_encrypt($data, 'aes-256-cbc', $key, true, $iv);
+        return base64_encode('Salted__' . $salt . $encrypted_data);
+    }
+
+
+    $testUser = "USER_ID";
+    $testPwd = "USER_PASSWORD";
+    $privateKey = "YOUR_PRIVATE_KEY_AS_CONFIGURED_IN_PLUGIN";
+    // IF REPLAY CHECK OPTION IS ENABLED
+    $tokenInc = 1; // IMPORTANT: INCREMENT THIS AT EACH CALL
+
+    $serial = serialize(array("user_id" => $testUser, "user_pwd" => $testPwd));
+    $token = urlencode(PYDIO_crypt(isSet($tokenInc)?$privateKey.":".$tokenInc:$privateKey, $serial));
+
+    // BUILD AN URL WITH HIDDEN POSTS, OR GET:
+    // SKIP cyphered_token_inc if REPLAY_CHECK is not enabled.
+    $URL = "https://yourserver/path/?cyphered_token=$token&cyphered_token_inc=$tokenInc";
+
+
diff --git a/core/src/plugins/meta.mount/class.FilesystemMounter.php b/core/src/plugins/meta.mount/class.FilesystemMounter.php index 4384159b7c..866b23787e 100644 --- a/core/src/plugins/meta.mount/class.FilesystemMounter.php +++ b/core/src/plugins/meta.mount/class.FilesystemMounter.php @@ -150,7 +150,7 @@ public function mountFS() $output = shell_exec($cmd1); $success = !empty($output); }else{ - $success = ($res == 0); + $success = ($res == 0 || $res == 32); } if (!$success) { throw new Exception("Error while mounting file system!");