OWNCLOUD源码改动分析---登录模块

来源:互联网 发布:当红网络主播 英语 编辑:程序博客网 时间:2024/06/06 04:06
<span style="font-family:SimSun;"><span style="font-size:12px;"><span style="line-height: 21px;"></span></span></span> 

最近项目需求要了解owncloud这个开源的软件。来自开源中国上的介绍wnCloud 是一个来自 KDE 社区开发的免费软件,提供私人的 Web 服务。当前主要功能包括文件管理(内建文件分享)、音乐、日历、联系人等等,可在PC和服务器上运行。简单来说就是一个基于Php的自建网盘。基本上是私人使用这样,因为直到现在开发版本也没有暴露注册功能。

现在分析一下登录过程,因为看了好久的源码,里边全都是PHP面向对象的写法,自己是个菜鸟,看不懂,只能蒙着找。大概理请了一下思路:

1.首先从index.php着手,因为我们用浏览器访问目录的时候默认就是这个。看一下源码:

<?php/** * @author Frank Karlitschek <frank@owncloud.org> * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Lukas Reschke <lukas@owncloud.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <icewind@owncloud.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @copyright Copyright (c) 2015, ownCloud, Inc. * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program 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, version 3, * along with this program.  If not, see <http://www.gnu.org/licenses/> * */// Show warning if a PHP version below 5.4.0 is used, this has to happen here// because base.php will already use 5.4 syntax.if (version_compare(PHP_VERSION, '5.4.0') === -1) {echo 'This version of ownCloud requires at least PHP 5.4.0<br/>';echo 'You are currently running ' . PHP_VERSION . '. Please update your PHP version.';return;}try {require_once 'lib/base.php';OC::handleRequest();} catch(\OC\ServiceUnavailableException $ex) {\OCP\Util::logException('index', $ex);//show the user a detailed error pageOC_Response::setStatus(OC_Response::STATUS_SERVICE_UNAVAILABLE);OC_Template::printExceptionErrorPage($ex);} catch (\OC\HintException $ex) {OC_Response::setStatus(OC_Response::STATUS_SERVICE_UNAVAILABLE);OC_Template::printErrorPage($ex->getMessage(), $ex->getHint());} catch (Exception $ex) {\OCP\Util::logException('index', $ex);//show the user a detailed error pageOC_Response::setStatus(OC_Response::STATUS_INTERNAL_SERVER_ERROR);OC_Template::printExceptionErrorPage($ex);}
看到上边的代码主要是在这两句代码。其中包含了一个lib里边的base.php文件类,这里边是创建的一个OC类,里边 有很多功能,看的不是很明白,里边大概是一些初始化的内容,类里边的具体代码就不粘贴了,base.php里边会自己初始化类里边的一些函数,重点是index.php里边如何加载出来页面的。这里边是用到了框架,具体是啥框架没看出来。
require_once 'lib/base.php';OC::handleRequest();
但是用到了模版,登录界面的模版login.php是在/core/templates/login.php。打开这个文件我们就可以看到登录页面了,但是发现这里边采用表单方式提交用户信息,可是表单里边却没有action选项,这就意味着表单提交的时候在将数据提交在本页,因为采用POST方法提交,我们就可以在index本页利用$_POST[]方式来获取数据。当时不知道数据提交在哪,找了好久发现就是用户将信息输入后,点击提交,将用户名密码都提交到了inde.php本页。代码再获取就可以了。

现在说一下如何加载的模版:主要方法在OC::handleRequest()函数中,我们看一下源码:

/** * Handle the request */public static function handleRequest() {\OC::$server->getEventLogger()->start('handle_request', 'Handle request');$systemConfig = \OC::$server->getSystemConfig();// load all the classpaths from the enabled apps so they are available// in the routing files of each appOC::loadAppClassPaths();// Check if ownCloud is installed or in maintenance (update) modeif (!$systemConfig->getValue('installed', false)) {\OC::$server->getSession()->clear();$setupHelper = new OC\Setup(\OC::$server->getConfig(), \OC::$server->getIniWrapper(), \OC::$server->getL10N('lib'), new \OC_Defaults());$controller = new OC\Core\Setup\Controller($setupHelper);$controller->run($_POST);exit();}$request = \OC::$server->getRequest()->getPathInfo();if (substr($request, -3) !== '.js') { // we need these files during the upgradeself::checkMaintenanceMode();self::checkUpgrade();}// Always load authentication appsOC_App::loadApps(['authentication']);// Load minimum set of appsif (!self::checkUpgrade(false)&& !$systemConfig->getValue('maintenance', false)&& !\OCP\Util::needUpgrade()) {// For logged-in users: Load everythingif(OC_User::isLoggedIn()) {OC_App::loadApps();} else {// For guests: Load only filesystem and loggingOC_App::loadApps(array('filesystem', 'logging'));\OC_User::tryBasicAuthLogin();}}if (!self::$CLI and (!isset($_GET["logout"]) or ($_GET["logout"] !== 'true'))) {try {if (!$systemConfig->getValue('maintenance', false) && !\OCP\Util::needUpgrade()) {OC_App::loadApps(array('filesystem', 'logging'));OC_App::loadApps();}self::checkSingleUserMode();OC_Util::setupFS();OC::$server->getRouter()->match(\OC::$server->getRequest()->getRawPathInfo());return;} catch (Symfony\Component\Routing\Exception\ResourceNotFoundException $e) {//header('HTTP/1.0 404 Not Found');} catch (Symfony\Component\Routing\Exception\MethodNotAllowedException $e) {OC_Response::setStatus(405);return;}}// Handle redirect URL for logged in usersif (isset($_REQUEST['redirect_url']) && OC_User::isLoggedIn()) {$location = OC_Helper::makeURLAbsolute(urldecode($_REQUEST['redirect_url']));// Deny the redirect if the URL contains a @// This prevents unvalidated redirects like ?redirect_url=:user@domain.comif (strpos($location, '@') === false) {header('Location: ' . $location);return;}}// Handle WebDAVif ($_SERVER['REQUEST_METHOD'] == 'PROPFIND') {// not allowed any more to prevent people// mounting this root directly.// Users need to mount remote.php/webdav instead.header('HTTP/1.1 405 Method Not Allowed');header('Status: 405 Method Not Allowed');return;}// Redirect to index if the logout link is accessed without valid session// this is needed to prevent "Token expired" messages while login if a session is expired// @see https://github.com/owncloud/core/pull/8443#issuecomment-42425583if(isset($_GET['logout']) && !OC_User::isLoggedIn()) {header("Location: " . OC::$WEBROOT.(empty(OC::$WEBROOT) ? '/' : ''));return;}// Someone is logged inif (OC_User::isLoggedIn()) {OC_App::loadApps();OC_User::setupBackends();OC_Util::setupFS();if (isset($_GET["logout"]) and ($_GET["logout"])) {OC_JSON::callCheck();if (isset($_COOKIE['oc_token'])) {\OC::$server->getConfig()->deleteUserValue(OC_User::getUser(), 'login_token', $_COOKIE['oc_token']);}OC_User::logout();// redirect to webroot and add slash if webroot is emptyheader("Location: " . OC::$WEBROOT.(empty(OC::$WEBROOT) ? '/' : ''));} else {// Redirect to default applicationOC_Util::redirectToDefaultPage();}} else {// Not handled and not logged inself::handleLogin();}}

以上的代码大部分都是用来验证登录和设置一些其他的东西,就是登录了会做一些事情,没有登录会做一些事情。太复杂了,经过调试,发现登录页面载入就是最后一句代码,我们从注释也能看出,没有处理和没有登录的话 调用类本身的的handleLoin()方法。这里边的调用层级比较多,耐心点吧。接下来看一下这个函数(看源码建议用IDE工具,可以直接找到定义的函数位置,之前傻呼呼的用记事本看。。。真心傻)

protected static function handleLogin() {OC_App::loadApps(array('prelogin'));$error = array();$messages = [];try {// auth possible via apache module?if (OC::tryApacheAuth()) {$error[] = 'apacheauthfailed';} // remember was checked after last loginelseif (OC::tryRememberLogin()) {$error[] = 'invalidcookie';} // logon via web formelseif (OC::tryFormLogin()) {$error[] = 'invalidpassword';}} catch (\OC\User\LoginException $e) {$messages[] = $e->getMessage();} catch (\Exception $ex) {\OCP\Util::logException('handleLogin', $ex);// do not disclose information. show generic error$error[] = 'internalexception';}OC_Util::displayLoginPage(array_unique($error), $messages);}

这个函数就比较清爽了,try里边有三层验证,先判断是否apache登录,其次判断是否cookie登录,最后判断是否是表单登录,因为我们要做的是单点登录,绕过owncloud的登录界面,所以要修改表单的提交方式。接下来代码是有异常处理异常,最后将模版页面打印出来调用的函数是 
OC_Util::displayLoginPage(array_unique($error), $messages);
上边的逻辑就是如果在if判断中用户成功登录,则跳转到相应的页面,就不会继续执行了(要看相应的判断登录函数,继续一层一层找下去)。如果登录失败出错,则返回设置$error[]数组信息,就会继续执行
OC_Util::displayLoginPage(array_unique($error), $messages);
这句代码。效果就是登录成功就跳转到主页面,如何登录失败就会跳转在登录页面。因为我们要自定义页面,所以在这里将以上代码屏蔽掉。添加一句跳转语句:

header("Location:http://xxxxx.php");这里添加我们自己要跳转的页面。

接下来说说如何从我们自己的登录页面传值过去,现在有两种方式:一种是POST过去,通过AJAX也好,表单提交也好,直接向index.php页面传递就好,但是POST过后,不知道怎么能直接进入主页面,就是用户名密码都传递过去,验证通过他会自动跳转,但是从A页面POST值过去到index.php页面后,还是留在A页面不会跳转进去;所以我用了方法2:采用GET方法,执行url+user="admin"+password="abc",直接访问这个页面就可以跳转了。如果验证成功就可以进去,如果验证失败的话,就会跳转到当前登录页面。这里要重新写一个GET登录方法,其实就是将tryFormLogin()方法全都复制过来,将里边的帐号密码获取的变量由 $_POST[]改为$_GET[]接收方式就可以了。如下:也是写在base.php里边的。

        protected static function tryGetLogin() {                if (!isset($_GET["user"]) || !isset($_GET['password'])) {                        return false;                }                /*if(!OC_Util::isCallRegistered()) {                        return false;                }*/                OC_App::loadApps();                //setup extra user backends                OC_User::setupBackends();                if (OC_User::login((string)$_GET["user"], (string)$_GET["password"])) {                        $userId = OC_User::getUser();                        // setting up the time zone                        if (isset($_POST['timezone-offset'])) {                                self::$server->getSession()->set('timezone', (string)$_POST['timezone-offset']);                                self::$server->getConfig()->setUserValue($userId, 'core', 'timezone', (string)$_POST['timezone']);                        }                        self::cleanupLoginTokens($userId);                        if (!empty($_POST["remember_login"])) {                                if (defined("DEBUG") && DEBUG) {                                        self::$server->getLogger()->debug('Setting remember login to cookie', array('app' => 'core'));                                }                                $token = \OC::$server->getSecureRandom()->getMediumStrengthGenerator()->generate(32);                                self::$server->getConfig()->setUserValue($userId, 'login_token', $token, time());                                OC_User::setMagicInCookie($userId, $token);                        } else {                                OC_User::unsetMagicInCookie();                        }                        OC_Util::redirectToDefaultPage();                        exit();                }                return true;        }

接下来说一下,GET过来后,登录验证有个地方过不去,是因为owncloud里边采用了很多安全性的验证:下边的代码会进行是否是跨站脚本攻击,具体是如何运作的,我也没看懂,但是屏蔽掉了就可以通过了,可能是从其他地方GET过去请求,认为是跨站脚本攻击吧。

 /*if(!OC_Util::isCallRegistered()) {
        return false;
 }*/

登录这部分看的不是很细 ,先记录下来,大概就这样实现了功能。


1 0