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

<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 page
	OC_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 page
	OC_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 app
		OC::loadAppClassPaths();

		// Check if ownCloud is installed or in maintenance (update) mode
		if (!$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 upgrade
			self::checkMaintenanceMode();
			self::checkUpgrade();
		}

		// Always load authentication apps
		OC_App::loadApps(['authentication']);

		// Load minimum set of apps
		if (!self::checkUpgrade(false)
			&& !$systemConfig->getValue('maintenance', false)
			&& !\OCP\Util::needUpgrade()) {
			// For logged-in users: Load everything
			if(OC_User::isLoggedIn()) {
				OC_App::loadApps();
			} else {
				// For guests: Load only filesystem and logging
				OC_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 users
		if (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.com
			if (strpos($location, '@') === false) {
				header('Location: ' . $location);
				return;
			}
		}
		// Handle WebDAV
		if ($_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-42425583
		if(isset($_GET['logout']) && !OC_User::isLoggedIn()) {
			header("Location: " . OC::$WEBROOT.(empty(OC::$WEBROOT) ? '/' : ''));
			return;
		}

		// Someone is logged in
		if (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 empty
				header("Location: " . OC::$WEBROOT.(empty(OC::$WEBROOT) ? '/' : ''));
			} else {
				// Redirect to default application
				OC_Util::redirectToDefaultPage();
			}
		} else {
			// Not handled and not logged in
			self::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 login
			elseif (OC::tryRememberLogin()) {
				$error[] = 'invalidcookie';
			} // logon via web form
			elseif (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;
 }*/

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


    原文作者:newbird105
    原文地址: https://blog.csdn.net/newbird105/article/details/47839125
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞