2024-08-31 01:03:37 +08:00

506 lines
18 KiB
PHP
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/*
* @link http://kodcloud.com/
* @author warlee | e-mail:kodcloud@qq.com
* @copyright warlee 2014.(Shanghai)Co.,Ltd
* @license http://kodcloud.com/tools/license/license.txt
*/
class userIndex extends Controller {
private $user; //用户相关信息
function __construct() {
parent::__construct();
}
public function index(){
@ob_end_clean();
include(TEMPLATE.'user/index.html');
}
// 进入初始化; total=10ms左右;
public function init(){
Hook::trigger('globalRequestBefore');
Hook::bind('beforeShutdown','user.index.shutdownEvent');
if( !file_exists(USER_SYSTEM . 'install.lock') ){
return ActionCall('install.index.check');
}
if( !file_exists(BASIC_PATH . 'config/setting_user.php') || empty($GLOBALS['config']['database'])){
show_tips(LNG('explorer.sysSetUserError'));
}
$this->initDB(); //
Action('filter.index')->bindBefore();
$this->initSession(); //
$this->initSetting(); //
init_check_update(); // 升级检测处理;
KodIO::initSystemPath();
Action('filter.index')->bind();
$this->loginCheck();
Model('Plugin')->init();
Action('filter.index')->trigger();
if(!defined("USER_ID")){
$userID = Session::get("kodUser.userID");$userID = 0;
define("USER_ID",$userID ? $userID:0);
}
}
public function shutdownEvent(){
TaskQueue::addSubmit(); // 结束后有任务时批量加入
TaskRun::autoRun(); // 定期执行及延期任务处理;
CacheLock::unlockRuntime(); // 清空异常时退出,未解锁的加锁;
}
private function initDB(){
think_config($GLOBALS['config']['databaseDefault']);
think_config($GLOBALS['config']['database']);
}
private function initSession(){
$this->apiSignCheck();
// 入口不处理cookie,兼容服务器启用了全GET缓存情况(输出前一次用户登录的cookie,导致账号登录异常)
$action= strtolower(ACTION);
if( $action == 'user.index.index' || $action == 'user.view.call'){
Cookie::disable(true);
}
$systemPass = Model('SystemOption')->get('systemPassword');
if(isset($_REQUEST['accessToken'])){
$token = $_REQUEST['accessToken'];
if(!$token || strlen($token) > 500){show_json('token error!',false);}
$pass = substr(md5('kodbox_'.$systemPass),0,15);
$sessionSign = Mcrypt::decode($token,$pass);
if(!$sessionSign){show_json(LNG('common.loginTokenError'),ERROR_CODE_LOGOUT);}
if($action == 'user.index.index'){Cookie::disable(false);} // 带token的url跳转入口页面允许cookie输出;
Session::sign($sessionSign);
}
if(!$GLOBALS['disableSession'] && !Session::get('kod')){
Session::set('kod',1);
if(!Session::get('kod')){show_tips(LNG('explorer.sessionSaveError'));}
}
// 注意: Session设置sessionid的cookie;两个请求时间过于相近,可能导致删除cookie失败的问题;(又有sessionid请求覆盖)
// 设置csrf防护;
if(!Cookie::get('CSRF_TOKEN')){Cookie::set('CSRF_TOKEN',rand_string(16));}
}
private function initSetting(){
if(!defined('STATIC_PATH')){
define('STATIC_PATH',$GLOBALS['config']['settings']['staticPath']);
}
$sysOption = Model('SystemOption')->get();
$upload = &$GLOBALS['config']['settings']['upload'];
if(isset($sysOption['chunkSize'])){ //没有设置则使用默认;
$upload['chunkSize'] = floatval($sysOption['chunkSize']);
// 老版本升级,没有值情况处理;
if(isset($sysOption['ignoreName'])){$upload['ignoreName'] = trim($sysOption['ignoreName']);}
if(isset($sysOption['chunkRetry'])){$upload['chunkRetry'] = intval($sysOption['chunkRetry']);}
if(isset($sysOption['threads'])){$upload['threads'] = floatval($sysOption['threads']);}
if($upload['threads'] <= 0){$upload['threads'] = 1;}
// 上传限制扩展名,限制单文件大小;
$role = Action('user.authRole')->userRoleAuth();
if($role && $role['info']){
$roleInfo = $role['info'];
// if(isset($roleInfo['ignoreExt'])){
// $upload['ignoreExt'] = $roleInfo['ignoreExt'];
// }
if(isset($roleInfo['ignoreFileSize'])){
$upload['ignoreFileSize'] = $roleInfo['ignoreFileSize'];
}
}
if($sysOption['downloadSpeedOpen']){//限速大小;
$upload['downloadSpeed'] = floatval($sysOption['downloadSpeed']);
}
}
// 文件历史版本数量限制; 小于等于1则关闭,大于500则不限制;
if(isset($sysOption['fileHistoryMax'])){
$GLOBALS['config']['settings']['fileHistoryMax'] = intval($sysOption['fileHistoryMax']);
}
$upload['chunkSize'] = $upload['chunkSize']*1024*1024;
$upload['chunkSize'] = $upload['chunkSize'] <= 1024*1024*0.1 ? 1024*1024*0.4:$upload['chunkSize'];
$upload['chunkSize'] = intval($upload['chunkSize']);
}
/**
* 登录检测;并初始化数据状态
* 通过session或kodToken检测登录
*/
public function loginCheck() {
if( KodUser::isLogin() ){
return $this->userDataInit();
}
if(Session::get('kodUserLogoutTrigger')){return;} //主动设置退出,不自动登录;
$userID = Cookie::get('kodUserID');
$loginToken = Cookie::get('kodToken');
if ($userID && $loginToken ) {
$user = Model('User')->getInfoFull($userID);
if ($user && $user['status'] != '0' && $this->makeLoginToken($user['userID']) == $loginToken ) {
return $this->loginSuccess($user);
}
}
}
private function logoutError($msg,$code){
Session::destory();
Cookie::remove('kodToken');
show_json($msg,$code);
}
private function userDataInit() {
$this->user = Session::get('kodUser');
if($this->user){
$findUser = Model('User')->getInfoFull($this->user['userID']);
// 用户账号hash对比; 账号密码修改自动退出处理;
if($findUser['userHash'] != $this->user['userHash']){
$this->logoutError(LNG('common.loginTokenError').'(hash)',ERROR_CODE_LOGOUT);
}
//优化,避免频繁写入session(file缓存时容易造成并发锁); 变化时更新;或者超过10分钟写入一次;
$_lastTime = $this->user['_lastTime'];unset($this->user['_lastTime']);
if($this->user != $findUser || time() - $_lastTime > 600){
$findUser['_lastTime'] = time();
Session::set('kodUser',$findUser);
}
$this->user = $findUser;
}
if(!$this->user) {
$this->logoutError('user data error!',ERROR_CODE_LOGOUT);
}else if($this->user['status'] == 0) {
$this->logoutError(LNG('user.userEnabled'),ERROR_CODE_USER_INVALID);
}else if($this->user['roleID'] == '') {
$this->logoutError(LNG('user.roleError'),ERROR_CODE_LOGOUT);
}
$GLOBALS['isRoot'] = 0;
$role = Model('SystemRole')->listData($this->user['roleID']);
if($role['administrator'] == '1'){
$GLOBALS['isRoot'] = 1;
}
// 计划任务处理; 目录读写所有者为系统;
if( strtolower(ACTION) == 'user.view.call'){
define('USER_ID',0);
define('MY_HOME','');
define('MY_DESKTOP','');
return;
}
define('USER_ID',$this->user['userID']);
define('MY_HOME',KodIO::make($this->user['sourceInfo']['sourceID']));
define('MY_DESKTOP',KodIO::make($this->user['sourceInfo']['desktop']));
}
public function accessToken(){
$systemPass = Model('SystemOption')->get('systemPassword');
$pass = substr(md5('kodbox_'.$systemPass),0,15);
return Mcrypt::encode(Session::sign(),$pass,3600*24*30);
}
public function accessTokenGet(){
if(!KodUser::isLogin()){show_json('user not login!',ERROR_CODE_LOGOUT);}
show_json($this->accessToken(),true);
}
public function accessTokenCheck($token){
if(!$token || strlen($token) > 500) return false;
$systemPass = Model('SystemOption')->get('systemPassword');
$pass = substr(md5('kodbox_'.$systemPass),0,15);
$sessionSign = Mcrypt::decode($token,$pass);
if(!$sessionSign || $sessionSign != Session::sign()) return false;
return true;
}
// 登录校验并自动跳转 (已登录则直接跳转,未登录则登录成功后跳转)
public function autoLogin(){
$link = $this->in['link'];
if(!$link) return;
$errorTips = _get($this->in,'msg','');
$errorTips = $errorTips == '[API LOGIN]' ? '':$errorTips; // 未登录标记,不算做登录错误;
if(KodUser::isLogin() && !$errorTips){
$param = 'kodTokenApi='.$this->accessToken();
if($this->in['callbackToken'] == '1'){
$link .= strstr($link,'?') ? '&'.$param:'?'.$param;
}
header('Location:'.$link);exit;
}
$param = '#user/login&link='.rawurlencode($link);
$param .= isset($this->in['msg']) ? "&msg=".$this->in['msg']:'';
$param .= isset($this->in['callbackToken']) ? '&callbackToken=1':'';
header('Location:'.APP_HOST.$param);exit;
}
/**
* 根据用户名密码获取用户信息
* @param [type] $name
* @param [type] $password
*/
public function userInfo($name, $password){
$user = Model("User")->userLoginCheck($name,$password);
if(!is_array($user)) {
$userHook = Hook::trigger("user.index.userInfo",$name, $password);
if(is_array($userHook)) return $userHook;// 第三方登陆不做检测处理;
}
return Hook::trigger('user.index.loginSubmitBefore',$name,$user);
}
/**
* 退出处理
*/
public function logout() {
$user = Session::get('kodUser');
if(!is_array($user) || !$user['userID']){show_json('ok');}
Hook::trigger('user.index.logoutBefore',$user);
$lastLogin = time() - $GLOBALS['config']['cache']['sessionTime'] - 10;
Model('User')->userEdit($user['userID'],array("lastLogin"=>$lastLogin));
Session::destory();
Cookie::remove(SESSION_ID,true);
Cookie::remove('kodToken');
Action('user.sso')->logout(); // 单点登录同步跟随退出;清理缓存;
show_json('ok');
}
/**
* 登录数据提交处理;登录跳转:
*/
public function loginSubmit() {
$res = $this->loginWithToken();
if($res || $res !== false) return $res;
$res = $this->loginWithThird(); // app第三方账号登录
if($res || $res !== false) return $res;
$data = Input::getArray(array(
"name" => array("check"=>"require",'lengthMax'=>100),
"password" => array('check'=>"require",'lengthMax'=>100),
"salt" => array("default"=>false),
));
$checkCode = Input::get('checkCode', 'require', '');
if( need_check_code() && $data['name'] != 'guest'){
Action('user.setting')->checkImgCode($checkCode);
}
if ($data['salt']) {
$key = substr($data['password'], 0, 5) . "2&$%@(*@(djfhj1923";
$data['password'] = Mcrypt::decode(substr($data['password'], 5), $key);
}
$user = $this->userInfo($data['name'],$data['password']);
if (!is_array($user)){
show_json($this->loginErrMsg($user),false);
}
if(!$user['status']){
show_json(LNG('user.userEnabled'), ERROR_CODE_USER_INVALID);
}
$this->loginSuccessUpdate($user);
//自动登录跳转; http://xxx.com/?user/index/loginSubmit&name=guest&password=guest&auto=1
$this->loginAuto();
show_json('ok',true,$this->accessToken());
}
private function loginWithToken(){
if (!isset($this->in['loginToken'])) return false;
$apiToken = $this->config['settings']['apiLoginToken'];
$param = explode('|', $this->in['loginToken']);
if (strlen($apiToken) < 5 ||
count($param) != 2 || strlen($this->in['loginToken']) > 500 ||
md5(base64_decode($param[0]) . $apiToken) != $param[1]
) {
return show_json('API 接口参数错误!', false);
}
$name = base64_decode($param[0]);
$res = Model('User')->where(array('name' => $name))->field('userID')->find();
if(empty($res['userID'])) {
return show_json(LNG('user.pwdError'),false);
}
$user = Model('User')->getInfoFull($res['userID']);
$this->loginSuccessUpdate($user);
$this->loginAuto();
return show_json('ok',true,$this->accessToken());
}
// 更新登录时间
public function loginSuccessUpdate($user){
$this->loginSuccess($user);
Model('User')->userEdit($user['userID'],array("lastLogin"=>time()));
ActionCall('admin.log.loginLog'); // 登录日志
}
/**
* app第三方登录
*/
private function loginWithThird(){
if (!isset($this->in['third'])) return false;
$third = Input::get('third');
if(empty($third)) return false;
$third = is_array($third) ? $third : json_decode($third, true);
// 判断执行结果
if(isset($third['avatar'])) $third['avatar'] = rawurldecode($third['avatar']);
Hook::trigger('user.bind.withApp', $third);
$this->loginAuto();
return show_json('ok',true,$this->accessToken());
}
// 登录后自动跳转
private function loginAuto() {
if($this->in['auto'] != '1') return;
header('Location: '.APP_HOST);exit;
}
// 刷新用户信息;
public function refreshUser($userID){
Model('User')->clearCache($userID);
$user = Model('User')->getInfoFull($userID);
Session::set('kodUser', $user);
}
//前端及app找回密码
public function findPassword(){
return Action('user.setting')->findPassword();
}
/**
* app端请求
*/
private function findPwdWidthApp(){
// api直接填写手机/邮箱验证码、密码进行修改
$data = Input::getArray(array(
'type' => array('check' => 'in','default'=>'','param'=>array('phone','email')),
'input' => array('check' => 'require'),
'code' => array('check' => 'require'),
'password' => array('check' => 'require'),
));
$param = array(
'type' => 'regist',
'input' => $data['input']
);
Action('user.setting')->checkMsgCode($data['type'], $data['code'], $param);
$user = Model('User')->where(array($data['type'] => $data['input']))->find();
if (empty($user)) {
show_json(LNG('user.notBind'), false);
}
if (!Model('User')->userEdit($user['userID'], array('password' => $data['password']))) {
show_json(LNG('explorer.error'), false);
}
show_json(LNG('explorer.success'));
}
public function loginSuccess($user) {
// 登录管控
$result = Hook::trigger('user.index.loginBefore',$user);
if($result !== true) {
show_tips($this->loginErrMsg($result), APP_HOST);exit;
}
Hook::trigger('user.index.loginAfter',$user);
Session::set('kodUser', $user);
Cookie::set('kodUserID', $user['userID']);
Cookie::set('kodTokenUpdate','1');//更新token通知;//自动登录处理;
$kodToken = Cookie::get('kodToken');
if($kodToken){//已存在则延期
Cookie::setSafe('kodToken',$kodToken);
}
if (!empty($this->in['rememberPassword'])) {
$kodToken = $this->makeLoginToken($user['userID']);
Cookie::setSafe('kodToken',$kodToken);
}
$this->userDataInit($user);
Hook::trigger("user.loginSuccess",$user);
}
// 登录时模糊提示消息
private function loginErrMsg($code){
if (in_array($code, array('-1','-2'))) {
return LNG('user.pwdError');
}
$error = UserModel::errorLang($code);
return $error ? $error : LNG('user.pwdError');
}
/**
* 以当前用户身份临时授权访问接口请求构造
*
* 安全性: 不泄露 accessToken;
* 只能访问特定接口特定参数
* 一定时间内访问有效
*
* 可以按应用授权;维护{appKey:appSecret,...};
* 注:完全以当前身份访问, 权限以当前用户为准;
*/
public function apiSignMake($action,$args,$timeout=false,$appKey='',$uriIndex=false){
$appSecret = $this->appKeySecret($appKey);
if(!$appSecret || !USER_ID){ // 外链分享等情况
return APP_HOST.'index.php?'.$action.'&'.http_build_query($args);
}
$userID = Session::get('kodUser.userID');
$timeout = $timeout ? $timeout : 3600*24*3;
$param = '';
$keyList = array(strtolower($action));
$signArr = array(strtolower($action),$appSecret);
foreach($args as $key=>$val){
$keyList[] = strtolower($key);
$signArr[] = strtolower($key).'='.base64_encode($val);
$param .= $key.'='.rawurlencode($val).'&';
}
$signToken = md5(implode(';',$signArr));//解密时获取
$acionKey = hash_encode(implode(';',$keyList));
$actionToken = Mcrypt::encode(USER_ID,$signToken,0,md5($appSecret)); // 避免url变化,无法缓存问题;
$param .= 'actionToken='.$actionToken.'&actionKey='.$acionKey;
// 包含index.php; (跨域时浏览器请求options, 避免被nginx拦截提前处理)
if($uriIndex){return APP_HOST.'index.php?'.$action.'&'.$param;}
return urlApi($action,$param);
}
// 解析处理;
public function apiSignCheck(){
if(isset($_REQUEST['accessToken']) && $_REQUEST['accessToken']){return;} // token优先;
$actionToken = _get($this->in, 'actionToken', '');
$actionKey = _get($this->in, 'actionKey', '');
$appKey = _get($this->in, 'appKey', '');
$appSecret = $this->appKeySecret($appKey);
if(!$actionToken || !$actionKey || !$appSecret) return;
if(strlen($actionToken) > 500) return;
$action = str_replace('.','/',ACTION);
$keyList = explode(';',hash_decode($actionKey));
$signArr = array(strtolower($action),$appSecret);
if(strtolower($action) != $signArr[0]) return;
for ($i = 1; $i < count($keyList); $i++) {
$key = $keyList[$i];
$signArr[] = strtolower($key).'='.base64_encode($this->in[$key]);
}
$signToken = md5(implode(';',$signArr));//同上加密计算
$userID = Mcrypt::decode($actionToken,$signToken);
allowCROS();Cookie::disable(true);
// 当前为自己则默认使用当前session(是否登录保险箱等session共享;)
if($userID && Session::get('kodUser.userID') == $userID){return;}
$userInfo = $userID ? Model('User')->getInfoFull($userID):false;
if(!is_array($userInfo)) {show_json(LNG("explorer.systemError").'.[apiSignCheck]',false);};
// api临时访问接口; 不处理cookie; 不影响已登录用户session; 允许跨域
Session::$sessionSign = guid();
Session::set('kodUser', $userInfo);
unset($_REQUEST['accessToken']);
}
// 维护多个应用
public function appKeySecret($appKey=''){
if(!$appKey) return md5(Model('SystemOption')->get('systemPassword'));
$appList = Model('SystemOption')->get('appKeySecret');
if(!$appList || !is_array($appList[$appKey])) return '';
return $appList[$appKey]['appSecret'];
}
//登录token
private function makeLoginToken($userID) {
$pass = Model('SystemOption')->get('systemPassword');
$user = Model('User')->getInfo($userID);
if(!$user) return false;
return md5($user['password'] . $pass . $userID);
}
// 系统维护中
public function maintenance($update=false,$value=0){
// Model('SystemOption')->set('maintenance',0);exit;
if($update) return Model('SystemOption')->set('maintenance', $value);
// 管理员or未启动维护返回
if(KodUser::isRoot() || !Model('SystemOption')->get('maintenance')) return;
show_tips(LNG('common.maintenanceTips'), '','',LNG('common.tips'));
}
}