PHPIndex

This page lists files in the current directory. You can view content, get download/execute commands for Wget, Curl, or PowerShell, or filter the list using wildcards (e.g., `*.sh`).

attachment.class.php
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/filter/attachment.class.php'
View Content
<?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 filterAttachment extends Controller{
	function __construct(){
		parent::__construct();
	}
	
	// 自动绑定处理;
	public function bind(){
		Hook::bind('admin.notice.add.after',array($this,'doNoticeLink'));
		Hook::bind('admin.notice.edit.after',array($this,'doNoticeLinkEdit'));
		Hook::bind('admin.notice.remove.after',array($this,'doNoticeClear'));
		
		Hook::bind('comment.index.add.after',array($this,'doCommentLink'));
		Hook::bind('comment.index.edit.after',array($this,'doNoticeLinkEdit'));
		Hook::bind('comment.index.remove.after',array($this,'doCommentClear'));
	}
	public function doNoticeLink($json){Action('explorer.attachment')->noticeLink($json['info']);}
	public function doNoticeLinkEdit($json){Action('explorer.attachment')->noticeLink($this->in['id']);}
	public function doNoticeClear($json){Action('explorer.attachment')->noticeClear($this->in['id']);}
	public function doCommentLink($json){Action('explorer.attachment')->commentLink($json['data']);}
	public function doCommentClear($json){Action('explorer.attachment')->commentClear($this->in['id']);}
}
fileOut.class.php
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/filter/fileOut.class.php'
View Content
<?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
*/

/**
 * fileOut数据过滤; fileOutBy 相对路径处理:js-import; css-import.src处理;
 */
class filterFileOut extends Controller{
	function __construct(){
		parent::__construct();
	}
	// 自动绑定处理;
	public function bind(){
		$action = strtolower(ACTION);
		$disableCookie = array(
			'explorer.index.fileoutby',
			'explorer.share.fileoutby',
			'explorer.index.filedownload',
			'explorer.index.fileout',
			'explorer.share.filedownload',
			'explorer.share.fileout',
		);
		$allowViewToken = array(
			'explorer.index.fileoutby',
			'explorer.share.fileoutby',
		);
		
		if(in_array($action,$disableCookie)){
			Cookie::disable(true);allowCROS();
		}
		
		// 仅限当前ip使用的token,有效期1天(该token仅限文件相对路径获取使用)
		if(in_array($action,$allowViewToken)){
			$token = isset($_REQUEST['viewToken']) ? $_REQUEST['viewToken']:'';
			if($token && strlen($token) < 500){
				$pass = substr(md5('safe_'.get_client_ip().Model('SystemOption')->get('systemPassword')),0,15);
				$sessionSign = Mcrypt::decode($token,$pass);
				if($sessionSign){Session::sign($sessionSign);}
				
				$parse = kodIO::parse($this->in['path']);// 不允许以相对路径获取php扩展名文件;避免管理员被钓鱼攻击;
				$pathAdd  = kodIO::pathUrlClear(rawurldecode($this->in['add']));
				$distPath = kodIO::pathTrue($parse['path'].'/../'.$pathAdd);
				if(get_path_ext($distPath) == 'php'){show_json('not allow',false);}
				// header("Status: 404 Not Found [viewToken]");exit;
			}
			Hook::bind('PathDriverBase.fileOut.before',array($this,'fileOut'));
		}
	}
	
	public function fileOut($file,$fileSize,$filename,$ext){
		// write_log(REQUEST_METHOD.":".$filename.';'.$file,'test');
		if(!isset($this->in['replaceType']) || !$this->in['replaceType']){
			if($ext != 'js'){return;}
			$this->outputJs(IO::getContent($file));
			return;
		}
		if(!$filename || $fileSize >= 10*1024*1024 || !in_array($ext,array('css','js')) ){return;}

		$content = IO::getContent($file);
		if($ext == 'css' && $this->in['replaceType'] == 'css-import'){$this->cssParse($content);}
		if($ext == 'js'  && $this->in['replaceType'] == 'script-import'){$this->scriptParse($content);}
		if($ext == 'js'  && $this->in['replaceType'] == 'script-wasm'){$this->scriptParseWasm($content);}
	}
	
	private function cssParse($content){
		$self = $this;
		$content = preg_replace_callback("/url\s*\(\s*['\"]*(.*?)['\"]*\s*\)/",function($matchs) use($self){
			return 'url("'.$self->urlFilter($matchs[1]).'")';
		},$content);
		$content = preg_replace_callback("/@import\s+['\"](.*\.css)['\"]/u",function($matchs) use($self){
			return '@import "'.$self->urlFilter($matchs[1]).'"';
		},$content);
		$this->output($content);
	}
	private function scriptParse($content){
		$self = $this;$contentOld = $content;
		$content = preg_replace_callback("/importScripts\s*\(\s*([`'\"].*?[`'\"])\s*\)/",function($matchs) use($self){
			$char = substr($matchs[1],0,1);$url = substr($matchs[1],1,strlen($matchs[1])-2);
			return 'importScripts('.$char.$self->urlFilter($url).$char.')';
		},$content);
		$content = preg_replace_callback("/\s+from\s+([`'\"].*?['\"`])/",function($matchs) use($self){
			$char = substr($matchs[1],0,1);$url = substr($matchs[1],1,strlen($matchs[1])-2);
			return ' from '.$char.$self->urlFilter($url).$char;
		},$content);
		$content = preg_replace_callback("/import\s+(['\"`].*?['\"`])/",function($matchs) use($self){
			$char = substr($matchs[1],0,1);$url = substr($matchs[1],1,strlen($matchs[1])-2);
			return 'import '.$char.$self->urlFilter($url).$char;
		},$content);
		
		// await import( `./Sidebar.Geometry.${ geometry.type }.js` ); 该情况兼容;
		$content = preg_replace_callback("/import\s*\(\s*(['\"`].*?['\"`])\s*\)/",function($matchs) use($self){
			$char = substr($matchs[1],0,1);$url = substr($matchs[1],1,strlen($matchs[1])-2);
			return 'import('.$char.$self->urlFilter($url).$char.')';
		},$content);
		// var_dump($contentOld,$content);exit;
		$this->outputJs($content);
	}
	private function outputJs($content){
		// window.location处理; 统一替换为_location_;解决跨域问题;
		$locationHas = "(hash|host|hostname|href|orgin|pathname|port|protocol|reload|replace|search)";
		$content = preg_replace("/(^|[^\w_.])window\.location($|[^\w_])/","$1window._location_$2",$content);
		$content = preg_replace("/(^|[^\w_.])location\.".$locationHas."($|[^\w_])/","$1_location_.$2$3",$content);
		$this->output($content);
	}
	private function scriptParseWasm($content){
		$self = $this;
		$content = preg_replace_callback("/=\s*\"([\w\.\-\_]+\.wasm)\"/",function($matchs) use($self){
			return '="'.$self->urlFilter($matchs[1]).'"';
		},$content);
		$this->output($content);
	}
	
	private function urlFilter($url){
		if(substr($url,0,5) == 'http:' || substr($url,0,6) == 'https:'){return $url;} // 外部链接;
		$url = kodIO::pathUrlClear($url);
		$path = rawurldecode($_GET['path']);// $this->in['path'], 外链分享时可能被替换;
		// 采用相对路径重新计算; 确保多个位置import 最后引用路径一致; 
		// 路径有变化时js多个地方import同一个文件,但url路径不一致,时会导致重复执行;
		$addNew  = kodIO::pathTrue(kodIO::pathUrlClear($this->in['add']).'/../'.$url);
		
		// 路径中不替换部分; 兼容js中url字符串带`${v}`变量情况;
		$addNew  = str_replace(array('%24','%20','%7B','%7D'),array('$',' ','{','}'),rawurlencode($addNew)); 
		$shareID = isset($this->in['shareID']) ? '&shareID='.$this->in['shareID']:'';
		$param   = '&viewToken='.$this->in['viewToken'].$shareID.'&replaceType='.$this->in['replaceType'];
		$url = APP_HOST.'index.php?'.str_replace('.','/',ACTION); // chrome xhr跨域options目录预检处理;需要带上index.php
		return $url.$param.'&path='.rawurlencode($path).'&add='.$addNew;
	}
	private function output($content){
		header('HTTP/1.1 200 OK');
		header('Content-Encoding: none');
		header('Content-Length:'.strlen($content));
		echo $content;exit;
	}
}
html.class.php
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/filter/html.class.php'
View Content
<?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 filterHtml extends Controller{
	function __construct(){
		parent::__construct();
	}
	
	// 自动绑定处理;
	public function bind(){
		$action = strtolower(ACTION);
		$check  = array(
			'admin.notice.add' 	=> array('content'=>'htmlFilter'),
			'admin.notice.edit' => array('content'=>'htmlFilter'),
			'comment.index.add' => array('content'=>'htmlOnlyImage','title'=>'htmlClear')
		);
		if(!isset($check[$action])) return;
		$this->filter($check[$action]);
	}
	
	public function filter($item){
		$in = $this->in;
		foreach ($item as $key => $method) {
			if(!isset($in[$key]) || !$in[$key]) continue;
			
			switch($method){
				case 'htmlFilter':$this->in[$key] = Html::clean($in[$key]);break;
				case 'htmlOnlyImage':$this->in[$key] = Html::onlyImage($in[$key]);break;
				case 'htmlClear':$this->in[$key] = clear_html($in[$key]);break;
				case 'json':$this->in[$key] = json_decode($in[$key]);break;
				default:break;
			}
		}
	}
}
index.class.php
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/filter/index.class.php'
View Content
<?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
 */

/**
 * 动作hook;
 * 
 * 注意: 事件绑定需要在事件触发之前;(之后会造成触发时没有回调情况)
 * 1. 插件注册bind事件: 在pluginModel初始化之后依次绑定  eg:LDAP  @ user.index.loginsubmit.before
 * 2. 插件中trigger的事件: 需要在pluginModel之前绑定好;  eg:webdav@ user.index.userInfo)
 */
class filterIndex extends Controller{
	function __construct() {
		parent::__construct();
	}
	
	public function bindBefore(){
		Action("filter.fileOut")->bind();
	}
	public function bind(){
		Action("filter.userRequest")->bind();
		Action("filter.userCheck")->bind();
		Action("filter.userLoginState")->bind();
		Action("filter.attachment")->bind();
		Action("filter.html")->bind();
		Action("filter.template")->bind();
	}

	public function trigger(){
		Action("filter.post")->check();
		Action("filter.userGroup")->check();
		Action("filter.limit")->check();
		Action("explorer.seo")->check();
		
		Hook::trigger(strtolower(ACTION).'.before',array());
		Hook::bind('show_json',array($this,'eventAfter'));
	}
	public function eventAfter($data){
		if(!$data['code']) return $data;
		$action = strtolower(ACTION).'.after';
		if($action == 'user.view.options.after') return $data;//手动调用过

		$returnData = Hook::trigger($action,$data);
		return is_array($returnData) ? $returnData:$data;
	}
}
limit.class.php
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/filter/limit.class.php'
View Content
<?php 

class filterLimit extends Controller{
	function __construct(){
		parent::__construct();
    }
    
    public function check(){
        $action  = strtolower(ACTION);
        switch($action) {
            case 'explorer.index.pathcopyto':$this->checkShareCopy();break;
            case 'explorer.usershare.edit':$this->checkShareLink();break;
            case 'explorer.index.unzip':$this->checkUnzip();break;
            case 'explorer.index.zip':
            case 'explorer.index.zipdownload':$this->checkZip();break;
			case 'explorer.share.fileupload':
            case 'explorer.upload.fileupload':$this->checkUpload();break;
            default: break;
        }
    }

    /**
     * 分享转存个数限制
     * @return void
     */
    private function checkShareCopy(){
        $list  = json_decode($this->in['dataArr'],true);
		$list  = is_array($list) ? $list : array();
		$storeMax = intval($this->config['settings']['storeFileNumberMax']);
		if(!$storeMax) return;
		
        for ($i=0; $i < count($list); $i++) {
			$path = $list[$i]['path'];
            $pathParse= KodIO::parse($path);
			if($pathParse['type'] != KodIO::KOD_SHARE_LINK) continue;
			if(!$info = Action('explorer.share')->sharePathInfo($path)){
				show_json($GLOBALS['explorer.sharePathInfo.error'], false);
			}
			if($info['type'] == 'folder' && $storeMax && $info['children']['fileNum'] > $storeMax){
                show_json(LNG('explorer.filter.shareCopyLimit').$storeMax, false);
			}
		}
    }

    /**
     * 分享文件/夹大小限制
     * @return void
     */
    private function checkShareLink(){
        $data = Input::getArray(array(
			"shareID"	=> array("check"=>"int"),
			"isLink"	=> array("check"=>"bool", "default"=>0),
		));
        if($data['isLink'] == 1){
			$shareMax    = floatval($this->config['settings']['shareLinkSizeMax'])*1024*1024*1024;
            $shareInfo = Model('Share')->getInfo($data['shareID']);
            if($shareMax && floatval($shareInfo['sourceInfo']['size']) > $shareMax){
                $sizeShow = size_format($shareMax);
                show_json(LNG('explorer.filter.shareSizeLimit').$sizeShow, false);
            }
        }
    }

    /**
     * 解压缩大小限制
     * @return void
     */
    private function checkUnzip(){
        $path = Input::get('path', 'require');
        $info = IO::info($path);
        if(!$info) return;
        $unzipMax  = floatval($this->config['settings']['unzipFileSizeMax'])*1024*1024*1024;
        if($unzipMax && floatval($info['size']) > $unzipMax){
            $sizeShow = size_format($unzipMax);
            show_json(LNG('explorer.filter.unzipSizeLimit').$sizeShow, false);
        }
    }

    /**
     * 压缩大小限制
     * @return void
     */
    private function checkZip(){
        $list  = json_decode($this->in['dataArr'],true);
        $size = 0;
        foreach($list as $item) {
            $info = IO::infoSimple($item['path']);
            $size += (float) $info['size'];
        }
		$zipMax  = floatval($this->config['settings']['zipFileSizeMax'])*1024*1024*1024;
        if($zipMax && $size > $zipMax){
            $sizeShow = size_format($zipMax);
            show_json(LNG('explorer.filter.zipSizeLimit').$sizeShow, false);
        }
    }

    /**
     * 上传大小限制
     * @return void
     */
    private function checkUpload(){
        $size = Input::get('size', null, 0);
		$uploadMax  = floatval($this->config['settings']['ignoreFileSize'])*1024*1024*1024;
        if($size && $uploadMax && floatval($size) > $uploadMax){
            $sizeShow = size_format($uploadMax);
            show_json(LNG('explorer.filter.uploadSizeLimit').$sizeShow, false);
        }
    }
}
post.class.php
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/filter/post.class.php'
View Content
<?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
*/

/**
 * method过滤;
 * 
 * csrf防护:(外部js或内部链接构造越权接口请求)
 * 1. refer白名单;
 * 2. 一律post请求; get允许的控制器白名单; 插件处理;
 * 3. hash校验;(UA不为app和pc客户端);
 */
class filterPost extends Controller{
	function __construct(){
		parent::__construct();
	}

	// 一律post请求; get请求白名单; 插件处理;
	public function check(){
		if( Model('SystemOption')->get('csrfProtect') != '1') return;
		$ua = strtolower($_SERVER['HTTP_USER_AGENT']);
		if( strstr($ua,'kodbox') || 
			strstr($ua,'okhttp') ||
			strstr($ua,'kodcloud')
		){return;}

		// if(GLOBAL_DEBUG) return;
		$theMod 	= strtolower(MOD);
		$theST 		= strtolower(ST);
		$theACT 	= strtolower(ACT);
		$theAction 	= strtolower(ACTION);
		$GLOBALS['config']['jsonpAllow'] = false; //全局禁用jsonp
		if($theMod == 'plugin'){
			$GLOBALS['config']['jsonpAllow'] = true;
			return; //插件内部自行处理;
		}
		
		// webdav 挂载kod; 当前开启了csrf防护,直接接口上传时不处理;
		if($theACT == 'fileupload' || $theMod.'.'.$theST == 'explorer.shareout'){
			allowCROS();// 允许跨域访问;外部联合分享, 前端直传;
			if($_SERVER['REQUEST_METHOD'] == 'OPTIONS'){exit;}
			if(isset($_POST['clientFrom']) && $_POST['clientFrom'] =='webdav-kodbox'){return;}
		}

		$allowGetArr = array(
			'explorer.fileview'	=> 'index',
			'explorer.history'	=> 'fileOut',
			'explorer.index'	=> 'fileOut,fileDownload,fileOutBy,fileDownloadRemove',
			'explorer.share'	=> 'fileOut,fileDownload,fileOutBy,zipDownload,fileDownloadRemove,file',
			'admin.setting'		=> 'get,server',
			'admin.repair'		=> '*',

			'install.index'	 	=> '*',
			'user.index' 		=> 'index,autoLogin,loginSubmit,logout',//accessTokenGet logout
			'user.view'	 		=> '*',
			'user.sso'			=> '*',
			'test'				=> '*',
			'sitemap'			=> '*',
		);
		$allowGet  = false;
		$ST_MOD = $theMod.'.'.$theST;
		if(isset($allowGetArr[$ST_MOD])){
			$methods = strtolower($allowGetArr[$ST_MOD]);
			$methodArr = explode(',',$methods);
			if($methods == '*' || in_array($theACT,$methodArr)){
				$allowGet = true;
			}
		}
		if(isset($allowGetArr[MOD])){$allowGet = true;}

		//必须使用POST的请求:统一检测csrfToken;
		if($allowGet) return;
		
		// 无需登录的接口不处理;
		$authNotNeedLogin = $this->config['authNotNeedLogin'];
		foreach ($authNotNeedLogin as &$val) {
			$val = strtolower($val);
		};unset($val);
		if(in_array($theAction,$authNotNeedLogin)) return;
		foreach ($authNotNeedLogin as $value) {
			$item = explode('.',$value); //MOD,ST,ACT
			if( count($item) == 2 && 
				$item[0] === $theMod && $item[1] === '*'){
				$allowGet = true;break;
			}
			if( count($item) == 3 && 
				$item[0] === $theMod && $item[1] === $theST  &&$item[2] === '*'){
				$allowGet = true;break;
			}
		}
		if($allowGet) return;
		
		$this->checkCsrfToken();
		if(REQUEST_METHOD != 'POST'){
			// csrf校验后, post不再是必须的;
			// show_json('REQUEST_METHOD must be POST!',false);
		}
	}
	
	// csrfToken检测; 允许UA为APP,PC客户端的情况;
	private function checkCsrfToken(){
		if(isset($_REQUEST['accessToken'])) return;
		if(!$this->in['CSRF_TOKEN'] || $this->in['CSRF_TOKEN'] != Cookie::get('CSRF_TOKEN')){
			$className	= substr(ACTION,0,strrpos(ACTION,'.'));
			if(!Action($className)){header('HTTP/1.1 404 Not Found');exit;}

			//write_log(array('CSRF_TOKEN error',$this->in,$_COOKIE,$_SERVER['HTTP_USER_AGENT']),'error');
			Cookie::remove('CSRF_TOKEN');// 部分手机浏览器异常情况(ios-夸克浏览器: 打开zip内视频,关闭后拉取文件列表)
			return show_json('CSRF_TOKEN error!',false);
		}
	}
}
template.class.php
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/filter/template.class.php'
View Content
<?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 filterTemplate extends Controller{
	function __construct(){
		parent::__construct();
	}
	
	// 自动绑定处理;
	public function bind(){
		Hook::bind('user.index.index',array($this,'lessCompile'));
	}
	
	
	/**
	less 自动编译处理;  缓存处理; 5s编译;
	*/
	public function lessCompile(){
		if(!STATIC_DEV) return;
		if(!$this->lessChange()) return;
		Action("test.debug")->less(false);
	}
	private function lessChange(){
		$path = BASIC_PATH.'static/style/skin_dev';
		$list = IO::listAll($path);
		$str  = '';
		foreach ($list as $item) {
			$str .= $item['path'].';'.$item['modifyTime'].';'.$item['size'];
		}
		$hashNew	= md5($str);
		$hashBefore = Cache::get('debug.lessCompile.key');
		if($hashNew == $hashBefore) return false;
		Cache::set('debug.lessCompile.key',$hashNew);
		return true;
	}
}
userCheck.class.php
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/filter/userCheck.class.php'
View Content
<?php

/**
 * 用户登录检测
 * 
 * 密码错误次数处理;
 * 登录ip白名单处理; 只检验拦截登录接口;
 */
class filterUserCheck extends Controller {
	function __construct() {
		parent::__construct();
	}
	public function bind(){
		$this->options = Model('systemOption')->get();
		if(isset($this->in['HTTP_X_PLATFORM_1'])){ // 新版APP端,对此参数做了编码处理;
			$this->in['HTTP_X_PLATFORM'] = base64_decode($this->in['HTTP_X_PLATFORM_1']);
			$_REQUEST['HTTP_X_PLATFORM'] = $this->in['HTTP_X_PLATFORM'];
		}
		$this->ipCheck();
		$this->setAppLang();		
		Hook::bind('user.index.loginSubmitBefore',array($this,'loginSubmitBefore'));
		Hook::bind('user.index.loginBefore',array($this,'loginBefore'));
	}

	public function loginBefore($user){
		return $this->userLoginCheck($user);
	}
	
	// 密码输入错误记录锁定;
	public function loginSubmitBefore($name,$user){
		return $this->userLoginLockCheck($name,$user);
	}

	/**
	 * 密码强度校验;
	 * 
	 * none:不限制,默认;
	 * strong: 中等强度, 长度大于6; 必须同时包含英文和数字;
	 * strongMore: 高强度, 长度大于8; 必须同时包含数字,大写英文,小写英文; =>[数字、大、小写字母、特殊字符]至少含3种
	 * 
	 * 检测点: 用户注册;用户修改密码;管理员添加用户;管理员修改用户;导入用户;
	 * 前端点: 登录成功后:如果密码规则不匹配当前强度,则提示修改密码;[提示点:注册密码,修改密码,编辑用户设置密码,添加用户设置密码]
	 */
	public function password($password,$out=false){
		$type = $this->options['passwordRule'];
		if( !$type || $type == 'none') return true;
		$length			= strlen($password);
		$hasNumber 		= preg_match('/\d/',$password);
		$hasChar   		= preg_match('/[A-Za-z]/',$password);
		$hasCharBig 	= preg_match('/[A-Z]/',$password);
		$hasCharSmall  	= preg_match('/[a-z]/',$password);
		$hasCharOthers  = preg_match('/[~!@#$%^&*]/',$password);

		if( $type == 'strong' && $length >= 6 && $hasNumber && $hasChar){
			return true;
		}else if( $type == 'strongMore' && $length >= 8){
			$classCnt = 0;
			if($hasNumber)		$classCnt++;
			if($hasCharBig) 	$classCnt++;
			if($hasCharSmall) 	$classCnt++;
			if($hasCharOthers) 	$classCnt++;
			if ($classCnt >= 3) return true;
		}
		return false;
	}
	public function passwordTips(){
		$type = $this->options['passwordRule'];
		$desc = array(
			'strong'	 => LNG('admin.setting.passwordRuleStrongDesc'),
			'strongMore' => LNG('admin.setting.passwordRuleStrongMoreDesc')
		);
		$error = LNG('user.passwordCheckError');
		$errorMore = isset($desc[$type]) ? ";<br/>".$desc[$type]:'';
		return show_json($error.$errorMore,false);
	}
	
	
	/**
	 * ip黑名单限制处理;
	 * 仅判断:IP+设备 (仅限所有人时情况,指定用户时忽略,在登录时判断);
	 */
	private function ipCheck(){	
		$this->_checkConfig();
		if(_get($this->config,'loginIpCheckIgnore') == '1') return true;// 手动关闭ip白名单检测;
		if(!_get($this->options,'loginCheckAllow')) return true;

		$ip  		= get_client_ip();
		$serverIP 	= get_server_ip();
		$device 	= $this->getDevice();
		$checkList 	= json_decode($this->options['loginCheckAllow'],true);
		if(!$checkList) return true;
		if($ip == 'unknown' || $ip == $serverIP || $ip == '127.0.0.1') return true;
		
		foreach ($checkList as $item){
			if($item['loginIpCheck'] != '2') continue;
			$userSelect = json_decode($item['userSelect'],true);
			if(! isset($userSelect['all']) || $userSelect['all'] == '0') continue;
			$allowDevice = $this->checkDevice($device,$item['device']);
			if( $allowDevice && $this->checkIP($ip,$item['disableIp']) ){
				$error = UserModel::errorLang(UserModel::ERROR_IP_NOT_ALLOW);
				if($device['type'] == 'app'){
					show_json($error."; IP: ".$ip,false);
				}else{
					show_tips($error."<br>IP: ".$ip);
				}
				exit;
			}
		}
		return true;
	}
	
	
	// app多语言自动识别处理;
	private function setAppLang(){
		$ua = $_SERVER['HTTP_USER_AGENT'].';';
		if(!strstr($ua,'kodCloud-System:iOS')) return;

		$lang = match_text($ua,"Language:(.*);");
		$langMap = array('zh-Hans'=>'zh-CN','zh-Hant'=>'zh-TW');
		$setLang = $langMap[$lang] ? $langMap[$lang] : $lang;
		$GLOBALS['config']['settings']['language'] = $setLang;
	}
		
	/**
	 * 用户登录限制管控
	 * 
	 * 可以配置多个: 用户/部门/权限组 + 设备类型 + ip白名单; 组合的限制策略;
	 * 根据规则列表顺序依次进行过滤; 所有都能通过才算放过;
	 */
	private function userLoginCheck($user){
		if($this->config['loginIpCheckIgnore'] == '1') return true;// 手动关闭ip白名单检测;
		if(!$this->options['loginCheckAllow']) return true;
		
		$ip 		= get_client_ip();
		$serverIP 	= get_server_ip();
		$device 	= $this->getDevice();
		$checkList 	= json_decode($this->options['loginCheckAllow'],true);
		if(!$checkList) return true;
		// if($ip == 'unknown' || $ip == $serverIP || $ip == '127.0.0.1') return true;

		$error = UserModel::ERROR_IP_NOT_ALLOW;// 您当前ip不在允许登录的ip白名单里,请联系管理员!;
		$rootIpAdd = "
		10.0.0.0-10.255.255.255
		192.168.0.0-192.168.255.255";
		foreach ($checkList as $item){
			if(!Action('user.authPlugin')->checkAuthValue($item['userSelect'],$user)) continue;
			$allowIp = true;
			$allowDevice = $this->checkDevice($device,$item['device']);
			if($item['loginIpCheck'] == '1'){
				// 系统管理员允许内网登录
				$role = Model('SystemRole')->listData($user['roleID']);
				if($role['administrator'] == '1'){
					$item['loginIpAllow'] .= $rootIpAdd;
				}
				$allowIp = $this->checkIP($ip,$item['loginIpAllow']);
			}else if($item['loginIpCheck'] == '2'){
				// 限制ip黑名单, 当前ip符合时, 设备符合指定设备则不允许登录;
				if( $this->checkIP($ip,$item['disableIp']) ){
					return $allowDevice ? $error:true;
				}
			}
			return ($allowDevice && $allowIp) ? true:$error;
			// 检测所有规则, 规则包含当前用户,不符合则不允许, 所有都通过才算通过;
			// if(!$allowDevice || !$allowIp) return $error; 
		}
		return true;
	}
	
	/**
	 * ip检测支持规则; 逗号或换行隔开多个规则;
	 * 
	 * 单行为ip: 相等则匹配
	 * 单行为ip前缀: ip以前缀为开头则匹配;
	 * ip区间: 两个ip以中划线进行分割; ip在该区间内则匹配;
	 */
	private function checkIP($ip,$check){
		$ipLong  = ip2long($ip);
		if(!$ip || !$ipLong) return false;
		$check   = str_replace(array(',',"\r",'|'),"\n",$check);
		$allowIp = explode("\n",trim($check));
		foreach ($allowIp as $line) {
			$line = trim($line);
			if(!$line) continue;
			if( $ip == $line ) return true;
			if( count(explode('.',$line)) != 4 &&
				substr($ip,0,strlen($line)) == $line ){
				return true;
			}
			
			$ipRange = explode('-',$line);
			if(count($ipRange) != 2) continue;
			if( $ipLong >= ip2long($ipRange[0]) && 
				$ipLong <= ip2long($ipRange[1]) ){
				return true;
			}
		}
		return false;
	}
	
	private function checkDevice($device,$check){
		$all = 'web,pc-windows,pc-mac,app-android,app-ios,Webdav';
		if($check == $all) return true;
		
		$system = $device['system'] ? '-'.$device['system'] : '';
		$currentType = $device['type'].$system;
		if (strstr($check, $currentType)) return true;
		return false;
	}
	private function _checkConfig(){
		$nowSize=_get($_SERVER,'_afileSize','');$enSize=_get($_SERVER,'_afileSizeIn','');
		if(function_exists('_kodDe') && (!$nowSize || !$enSize || $nowSize != $enSize)){exit;}
	}
	/**
	 * pc-mac:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) kodcloud/0.2.1 Chrome/69.0.3497.106 Electron/4.0.1 Safari/537.36
	 * pc-win:Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) kodcloud/0.1.5 Chrome/69.0.3497.106 Electron/4.0.1 Safari/537.36
	 * 
	 * app: okhttp/3.10.0; HTTP_X_PLATFORM:
	 * 	android:{"brand":"OPPO","deviceId":"sm6150","bundleID":"com.kodcloud.kodbox","menufacturer":"OPPO","system":"Android"...
	 *	ios:{"brand":"Apple","deviceId":"iPhone9,4","bundleID":"com.kodcloud.kodbox","menufacturer":"Apple","system":"iOS"...
		iosApp:'kodCloud-System:iOS;Device:iPhone 7 Plus;softwareVerison:15.0.2;AppVersion:2.0.0;Language:zh-Hans'
	 */
	public function getDevice(){
		static $_device = false;
		if($_device){return $_device;}
		
		$ua = $_SERVER['HTTP_USER_AGENT'].';';
		$platform 	= isset($this->in['HTTP_X_PLATFORM']) ? json_decode($this->in['HTTP_X_PLATFORM'],1):false;
		$device 	= array(
			'type' 			=> 'web',	//平台类型: pc/app/others; 默认为web:浏览器端
			'system'		=> '',		//操作系统: windows/mac/android/ios
			'systemVersion' => '',		//系统版本: ...
			'appVersion'	=> '',		//平台版本
		);
		
		$webdavAuth = HttpAuth::get();
		if($webdavAuth && $webdavAuth['user']){
			$device['type'] = 'Webdav';
		}

		// pc:windows,mac;
		if(stristr($ua,'kodcloud') && stristr($ua,'Electron')){
			$device['type'] = 'pc';
			$device['system'] = stristr($ua,'Mac OS') ? 'mac':'';
			$device['system'] = stristr($ua,'Windows') ? 'windows':$device['system'];
			$device['appVersion'] = match_text($ua,"kodcloud\/([\d.]+) ");
		}
		// ios APP 原生;
		if(strstr($ua,'kodCloud-System:iOS')){
			$device['type'] = 'app';
			$device['system'] = 'ios';
			$device['systemVersion'] = match_text($ua,"softwareVerison:(.*);");
			$device['appVersion'] 	 = match_text($ua,"AppVersion:(.*);");
		}

		// app:ios,android;
		if(is_array($platform)){
			$device['type'] 	= 'app';
			$device['system'] 	= strtolower($platform['system']);
			$device['systemVersion'] = strtolower($platform['systemVersion']);
			$device['appVersion'] 	 = strtolower($platform['appVersion']);
			$device['moreInfo'] 	 = $platform;
			if (stripos($device['system'], 'android') !== false) {	// android 12
				$device['system'] = 'android';
			}
		}
		$device = Hook::filter('filter.getDevice',$device);
		$_device = $device;
		return $device;
	}
	
	
	/**
	 * 密码输入错误自动锁定该账号; =根据ip进行识别;不区分ip;
	 * 连续错误5次; 则锁定30秒; [1分钟内最多校验10次,600次/h/账号]
	 */
	private function userLoginLockCheck($name,$user){
		if($this->options['passwordErrorLock'] =='0') return $user;
		$findUser = Model("User")->userLoginFind($name);
		if(!$findUser) return $user;
		
		$lockErrorNum   = intval(_get($this->options,'passwordLockNumber',6));	//错误n次后锁定账号;
		$lockTime 		= intval(_get($this->options,'passwordLockTime',60)); 	//锁定n秒;
		$key = 'user_login_lock_'.$findUser['userID'].'_'.get_client_ip();		//按IP隔离;
		$arr = Cache::get($key);
		$item = is_array($arr)?$arr:array(); //不区分ip;
		// Cache::remove($key);return $user;//debug;
		
		if( count($item) >= $lockErrorNum && 
			time() - $item[count($item)-1] <= $lockTime
		){
			return UserModel::ERROR_USER_LOGIN_LOCK;
		}
		
		if(is_array($user)){
			Cache::remove($key);
			return $user;
		}
		
		// 最后时间超出锁定时间;则从头计算;
		if(time() - $item[count($item)-1] > $lockTime){$item = array();}
		if(count($item) >= $lockErrorNum){array_shift($item);}
		$item[] = time();
		Cache::set($key,$item,600);
		return $user;
	}
}
userGroup.class.php
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/filter/userGroup.class.php'
View Content
<?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 filterUserGroup extends Controller{
	function __construct(){
		parent::__construct();
	}

	public function check(){
		if(KodUser::isRoot()){
			if($this->config["ADMIN_ALLOW_ALL_ACTION"]){return;}
			//三权分立,限制系统管理员设置用户角色及所在部门权限; 还原设置数据;
			return $this->userAuthEditCheck();
		}
		$this->checkUser();
		$this->checkGroup();
		$this->checkRole();
	}
	
	// 用户列表获取,
	private function checkUser(){
		$paramMap = array(
			'admin.member.get'			=> array('group'=>'groupID','read'=>'allow','error'=>'list'),
			'admin.member.search'		=> array('group'=>'parentGroup','read'=>'allow','error'=>'list'),
			
			'admin.member.add'			=> array('groupArray'=>'groupInfo','userRole'=>'roleID'),
			'admin.member.addgroup'		=> array('groupArray'=>'groupInfo','user'=>'userID'),
			'admin.member.removegroup'	=> array('group'=>'groupID','user'=>'userID'),
			'admin.member.switchgroup'	=> array('group'=>'to','user'=>'userID'),
					
			'admin.member.edit'			=> array('groupArray'=>'groupInfo','user'=>'userID','userRole'=>'roleID'),
			'admin.member.status'		=> array('user'=>'userID'),
			'admin.member.remove'		=> array('user'=>'userID'),
		);
		$this->checkItem($paramMap);
	}
	
	private function checkGroup(){
		$paramMap = array(
			'admin.group.get' 		=> array('group'=>'parentID','read'=>'allow','error'=>'list'),
			'admin.group.search' 	=> array('group'=>'parentGroup','read'=>'allow','error'=>'list'),

			'admin.group.add' 		=> array('group'=>'parentID'),
			'admin.group.edit' 		=> array('group'=>'groupID'),
			'admin.group.status' 	=> array('group'=>'groupID'),
			'admin.group.remove' 	=> array('group'=>'groupID'),
			'admin.group.sort' 		=> array('group'=>'groupID'),
			'admin.group.switchgroup' => array('group'=>'from','group'=>'to'),
			
			// 部门公共标签: 获取,修改;
			'explorer.taggroup.set' => array('group'=>'groupID'),
		);
		$this->checkItem($paramMap);
	}
	private function checkRole(){
		$paramMap = array(
			'admin.role.add' 		=> array('roleAuth'=>'auth'),
			'admin.role.edit' 		=> array('roleAuth'=>'auth'),
			'admin.role.remove' 	=> array('userRole'=>'id'),
		);
		$this->checkItem($paramMap);
	}
		
	private function checkItem($actions){
		$action = strtolower(ACTION);
		if(!isset($actions[$action])) return;
		if(!Session::get("kodUser")){show_json(LNG('user.loginFirst'),ERROR_CODE_LOGOUT);}
		$check = $actions[$action];
		
		if($check['read'] == 'allow'){			
			// 部门管理员,从后台搜索部门; 仅限在有权限的部门中搜索;
			$isFromAdmin = isset($this->in['requestFromType']) && $this->in['requestFromType'] == 'admin';
			$adminGroup = $this->userGroupAdmin();
			$isSearch    = in_array($action,array('admin.member.search','admin.group.search'));
			if( $isSearch && $isFromAdmin && !isset($this->in[$check['group']])){
				$this->in[$check['group']] = implode(',',$adminGroup);
			}
			return $this->checkItemRead($check,$action);
		}

		$allow = true;
		if($allow && $check['group']){
			$groupID = $this->in[$check['group']];
			$allow   = $this->allowChangeGroup($groupID);
			$adminGroup = $this->userGroupAdmin();
			// 自己为管理员的根部门: 禁用编辑与删除;
			$disableAction = array('admin.group.edit','admin.group.remove');
			if(in_array($action,$disableAction) && in_array($groupID,$adminGroup)){$allow = false;}
		}
		
		$err = $allow ? -1:0;
		if($allow){$allow = $this->userAuthEditCheck();$err=$allow?$err:1;}
		if($allow && $check['user']){ $allow = $this->allowChangeUser($this->in[$check['user']]);$err=$allow?$err:2;}
		if($allow && $check['userRole']){$allow = $this->allowChangeUserRole($this->in[$check['userRole']]);$err=$allow?$err:3;}
		if($allow && $check['roleAuth'] && isset($this->in['roleID'])){$allow = $this->roleActionAllow($this->in[$check['roleAuth']]);$err=$allow?$err:4;}
		if($allow && $check['groupArray']){$allow = $this->allowChangeGroupArray($check);$err=$allow?$err:5;}
		// trace_log([$err,$allow,$check,$this->in,'GET:',$_REQUEST]);
		if($allow) return true;
		$this->checkError($check);
	}
	
	private function checkItemRead($check,$action){
		$groupList = Session::get("kodUser.groupInfo");
		if(!$groupList || count($groupList) == 0){ // 不在任何部门则不支持用户及部门查询;
			return $this->checkError($check);
		}
		$groupArray = $this->userGroupRootShow();
		$groupID = $this->in[$check['group']];
		$userID  = $this->in[$check['user']];
		if($groupID && ($groupID == 'root' || $groupID == 'rootOuter')){$groupID = '';}		
		
		if(!$check['group']) return;
		if(!$groupID || $this->allowViewGroup($groupArray,$groupID)) return;
		$this->checkError($check);
	}
	
	private function checkError($check){
		if(!$check || !$check['error']){show_json(LNG('explorer.noPermissionAction'),false);}

		$result = array('list'=>array(),'pageInfo'=>array("page"=>1,"totalNum"=>0,"pageTotal"=>0));
		if($check['error'] == 'listSimple'){$result = array();}
		show_json($result,true,'empty');
	}
	
	// 用户编辑,用户角色设置权限处理;  三权分立系统管理员; 
	private function userAuthEditCheck(){
		$action = strtolower(ACTION);
		$allowUserAuth 	= Action('user.authRole')->authCan('admin.member.userAuth');
		$allowUserEdit 	= Action('user.authRole')->authCan('admin.member.userEdit');
		if(KodUser::isRoot()){
			$allowUserEdit = true;
			$allowUserAuth = $this->config["ADMIN_ALLOW_ALL_ACTION"] == 1 ? true:false;
		}
		if($allowUserEdit && $allowUserAuth){return true;}
		if(!$allowUserEdit && !$allowUserAuth){return true;}
		
		$userCheckActions = array(
			'admin.member.add'			=> array('groupArray'=>'groupInfo','userRole'=>'roleID'),
			//'admin.member.addgroup'		=> array('groupArray'=>'groupInfo'), // 单独检测;
			'admin.member.removegroup'	=> array(),
			'admin.member.switchgroup'	=> array(),
			'admin.member.edit'			=> array('groupArray'=>'groupInfo','userRole'=>'roleID'),
			'admin.member.status'		=> array(),
			'admin.member.remove'		=> array(),
		);
		
		if(!is_array($userCheckActions[$action])){return true;}
		$userCheck = $userCheckActions[$action];
		$groupInfoKey = isset($userCheck['groupArray']) ? $userCheck['groupArray'] : '';

		// 不允许编辑用户,仅允许设置用户权限(安全保密员角色); 只能设置用户角色和用户所在部门权限,不能设置用户所在部门
		if(!$allowUserEdit && $allowUserAuth){
			if($action != 'admin.member.edit'){return false;} 	// 只允许调用edit;其他方法禁用			
			$keepKey  = array('userID','groupInfo','roleID','HTTP_X_PLATFORM','HTTP_X_PLATFORM_1','CSRF_TOKEN','URLrouter','URLremote');
			foreach ($this->in as $key=>$v) {
				if(!in_array($key,$keepKey)){unset($this->in[$key]);unset($_REQUEST[$key]);}
			}
			return $this->userAuthEditKeepGroupInfo($groupInfoKey,$this->in['userID'],'onlyAuth');
		}
		
		// 允许编辑用户,不允许设置用户权限(用户管理员; 系统管理员-启用三权分立时)
		// 可以编辑,删除,添加用户; 不能设置用户角色(添加用户:最小普通用户角色),能设置用户所在部门,不能设置用户所在部门权限(最小权限-不可见);
		if($allowUserEdit && !$allowUserAuth){
			// 用户角色权限默认移除;
			if(isset($this->in['roleID']) ){unset($this->in['roleID']);}
			if($action == 'admin.member.add'){
				$this->in['roleID'] = Model('SystemRole')->findRoleDefault();
			}
			return $this->userAuthEditKeepGroupInfo($groupInfoKey,$this->in['userID'],'onlyEdit');
		}
	}
	
	private function userAuthEditKeepGroupInfo($groupInfoKey,$userID,$type='onlyEdit'){
		if(!$groupInfoKey || !isset($this->in[$groupInfoKey])){return true;}
		$authSet 	= json_decode($this->in[$groupInfoKey],true);
		$authSet 	= is_array($authSet) ? $authSet : array();$authSetOld = $authSet;
		$userInfo 	= $userID ? Model('User')->getInfo($userID):array();
		$groupAuth 	= array_to_keyvalue($userInfo['groupInfo'],'groupID','auth');
		$defaultAuth= Model('Auth')->findAuthMinDefault();

		if($type == 'onlyAuth'){
			// 只能修改在某部门的权限(设置部门不在已有部门中-移除; 移除已有部门kv-保留);
			foreach ($authSet as $groupID => $auth){
				if(!isset($groupAuth[$groupID])){unset($authSet[$groupID]);}
			}
			foreach ($groupAuth as $groupID => $auth){
				if(!isset($authSet[$groupID])){$authSet[$groupID] = $auth['id'];}
			}
		}else if($type == 'onlyEdit'){
			$isGroupAppend = isset($this->in['groupInfoAppend']) && $this->in['groupInfoAppend'] == '1';
			if(strtolower(ACTION) == 'admin.member.addgroup'){$isGroupAppend = true;}

			// 只能修改用户所在部门, 不能修改所在部门权限,允许移除所在部门(新添加部门使用默认最小权限)
			foreach ($authSet as $groupID => $auth){
				$authSet[$groupID] = isset($groupAuth[$groupID]) ? $groupAuth[$groupID]['id']:$defaultAuth;
			}
			// 仅添加时,用户所在部门不在设置范围内则自动加入;
			foreach ($groupAuth as $groupID => $auth){
				if($isGroupAppend && !isset($authSet[$groupID])){$authSet[$groupID] = $auth['id'];}
			}
		}
		$this->in[$groupInfoKey] = json_encode($authSet);
		return true;
	}
	
	
	// 多个部门信息检测; 修改所在部门及权限时检测(不允许删除/添加用户在自己非部门管理员的部门)
	public function allowChangeGroupArray($check){
		$groupInfoKey = $check['groupArray'];
		if(!$groupInfoKey || !isset($this->in[$groupInfoKey])){return true;} // 没有传入groupInfo 代表不修改, 对应不检测;
		
		$authSet = json_decode($this->in[$groupInfoKey],true);
		$authSet = is_array($authSet) ? $authSet : array();
		$userInfo  = $this->in[$check['user']] ? Model('User')->getInfo($this->in[$check['user']]):array();
		$groupAuth = array_to_keyvalue($userInfo['groupInfo'],'groupID','auth');$allow = true;
		
		foreach ($authSet as $groupID => $auth){
			if($userInfo && $groupAuth[$groupID]['id'] == $auth) continue;//其他部门权限,不允许修改;
			if(!$this->allowChangeGroup($groupID)){$allow = false;break;}
		}
		if(!$userInfo || !$groupAuth) return $allow;
		
		// 追加用户所在部门;// 该值启用时,已存在所在部门权限继续保持,仅追加新的;
		$isGroupAppend = isset($this->in['groupInfoAppend']) && $this->in['groupInfoAppend'] == '1';
		if(strtolower(ACTION) == 'admin.member.addgroup'){$isGroupAppend = true;}

		// 其他自己无权限部门: 不允许删除,不允许修改;
		foreach ($groupAuth as $groupID => $authInfo){
			if($isGroupAppend && !$authSet[$groupID]){$authSet[$groupID] = $authInfo['id'];}
			if($this->allowChangeGroup($groupID)) continue;
			// if(!$authSet[$groupID]){$allow = false;break;} // 部门自己没有管理权限,报错;
			if(!$authSet[$groupID]){$authSet[$groupID] = $authInfo['id'];} // 去除部门自己没有管理权限,则默认自动加上;
		}
		foreach ($authSet as $groupID => $auth){
			if($this->allowChangeGroup($groupID)) continue;
			if($groupAuth[$groupID]['id'] != $auth){$allow = false;break;}
		}
		$this->in[$groupInfoKey] = json_encode($authSet);
		return $allow;
	}
	
	// 当前用户是否有操作该部门的权限;
	public function allowChangeGroup($groupID){return $this->allowViewGroup($this->userGroupAdmin(),$groupID);}
	public function allowChangeUser($userID){return $this->allowViewUser($this->userGroupAdmin(),$userID);}
	
	// 检测自己是否有权限获取指定用户信息;$returnAllow为true,则返回有权限访问的部分用户id;
	public function allowViewUser($selfGroup,$users,$returnAllow=false){
		if(!$selfGroup || count($selfGroup) == 0) return false;
		$allowAll   = true;$allowHas = array();
		$valueArray = explode(',',trim($users.'',','));//默认多个,逗号分隔;全部都有权限才通过
		foreach ($valueArray as $theID){
			$userInfo  = Model('User')->getInfo($theID);
			$groupList = $userInfo ? $userInfo['groupInfo']:array();
			$groups    = implode(',',array_to_keyvalue($groupList,'','groupID'));
			if($this->allowViewGroup($selfGroup,$groups,true)){
				$allowHas[] = $theID;
			}else{
				$allowAll = false;
				if(!$returnAllow){return $allowAll;}
			}
		}
		return $returnAllow ? implode(',',$allowHas) : $allowAll;
	}
	
	// 检测自己是否有权限获取指定部门信息;$returnAllow为true,则返回有权限访问的部分部门id;
	public function allowViewGroup($selfGroup,$groups,$returnAllow=false){
		if(!$selfGroup || count($selfGroup) == 0) return false;
		$allowAll   = true;$allowHas = array();
		$valueArray = explode(',',trim($groups.'',','));//默认多个,逗号分隔; 全部都有权限才通过
		foreach ($valueArray as $theID){
			if(Model('Group')->parentInGroup($theID,$selfGroup)){
				$allowHas[] = $theID;
			}else{
				$allowAll = false;
				if(!$returnAllow){return $allowAll;}
			}
		}
		return $returnAllow ? implode(',',$allowHas) : $allowAll;
	}

	// 权限修改删除范围处理: 只能操作权限包含内容小于等于自己权限包含内容的类型;  设置用户权限也以此为标准;
	public function allowChangeUserRole($roleID){
		if(KodUser::isRoot() || !$roleID) return true;
		$authInfo = Model('SystemRole')->listData($roleID);
		if($authInfo && $authInfo['administrator'] == 1) return false; // 系统管理员不允许非系统管理员获取,设置
		if(!$this->config["ADMIN_ALLOW_ALL_ACTION"]){return true;} // 启用了三权分立,安全保密员允许获取,或设置用户的角色;
		
		return $this->roleActionAllow($authInfo['auth']);
	}
	private function roleActionAllow($actions){
		$userInfo 		= Session::get("kodUser");
		$authInfo 		= Model('SystemRole')->listData($userInfo['roleID']);
		$actions   		= $actions ? explode(',',$actions) : array('--');
		$selfActions  = isset($authInfo['auth']) ? explode(',',$authInfo['auth']):array('-error-');
		foreach ($actions as $action){
			if($action && !in_array($action,$selfActions)) return false;
		}//$selfActions 包含$actions;
		return true;
	}
	
	// 自己所在为管理员的部门;
	public function userGroupAdmin(){
		static $groupArray = null;
		if($groupArray !== null) return $groupArray;
		
		$groupList 	= Session::get("kodUser.groupInfo");$groupArray = array();
		foreach ($groupList as $group){
			if(!AuthModel::authCheckRoot($group['auth']['auth'])) continue;
			$groupArray[] = $group['groupID'].'';
		}
		$groupArray = Model('Group')->groupMerge($groupArray);
		return $groupArray;
	}
	// 自己所在部门()
	public function userGroupAt(){
		static $groupArray = null;
		if($groupArray !== null) return $groupArray;

		$groupList 	= Session::get("kodUser.groupInfo");
		$groupArray = Model('Group')->groupMerge(array_to_keyvalue($groupList,'','groupID'));
		return $groupArray;
	}
	
	public function  userGroupRoot(){
		return $this->userGroupRootShow();
	}
	// 自己可见的部门; 所在的部门向上回溯;
	public function userGroupRootShow(){
		static $groupArray = null;
		if($groupArray !== null) return $groupArray;

		$groupCompany = $GLOBALS['config']['settings']['groupCompany'];
		$groupList 	= Session::get("kodUser.groupInfo");$groupArray = array();
		foreach ($groupList as $group){
			$groupRoot = Model('Group')->groupShowRoot($group['groupID'],$groupCompany);
			$groupArray = array_merge($groupArray,$groupRoot);
		}
		$groupArray = Model('Group')->groupMerge($groupArray);
		return $groupArray;
	}
}
userLoginState.class.php
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/filter/userLoginState.class.php'
View Content
<?php

/**
 * 同一账号限制同时登录数;0=不限制;
 * guest/admin不限制  不限制=最多50个登录设备;
 * 
 * 对外方法:
 * Action("filter.userLoginState")->userListLoad(); // 当前账号在线设备列表;
 * Action("filter.userLoginState")->userLogoutTrigger($userID,$sid);// 主动踢下线某个设备;
 */
class filterUserLoginState extends Controller {
	function __construct() {
		parent::__construct();
	}
	public function bind(){
		Hook::bind('user.index.loginBefore',array($this,'checkLimit'));
		Hook::bind('user.index.logoutBefore',array($this,'logoutBefore'));
	}

	// 同时登录限制处理; (只在登录时检测在线的session)
	// 暂不支持通过accessToken的共享session方式登录(app扫描登录, 外部accessToken打开文件或网页); 
	// 		有一点点门槛;同时一个点退出其他所有设备都会退出;
	public function checkLimit($user){
		//排除webdav header直接登录方式;	
		if(isset($_SERVER['HTTP_AUTHORIZATION']) && $_SERVER['HTTP_AUTHORIZATION']) return;
		
		$limitMax = 500;
		$limit = $GLOBALS['config']['settings']['userLoginLimit'];
		if($limit == 0) {$limit = $limitMax;};
		
		// 权限检测;guest/admin不限制 [guest按没有写入权限及新建权限判断]
		$role 		= Model('SystemRole')->listData($user['roleID']);
		$roleAuth 	= explode(',',trim($role['auth'],','));
		$isRoot  	= $role['administrator'] == '1';
		$isGuest 	= !in_array('explorer.add',$roleAuth) && !in_array('explorer.upload',$roleAuth);
		if($isRoot || $isGuest){$limit = $limitMax;}
		
		$sid = Session::sign();
		$loginList = $this->userListLoad($user['userID']);
		unset($loginList[$sid]);
		
		// 已经有序, 保留数组后$limit - 1项; 前面的退出处理(更早登录的);
		$indexFrom = count($loginList) - ($limit - 1);
		$indexFrom = $indexFrom <= 0 ? 0 : $indexFrom;
		$loginListNew = array();$index = 0;
		foreach($loginList as $item){
			if($index >= $indexFrom){
				$loginListNew[$item['sid']] = $item;
				$index++;continue;
			}
			$this->userLogoutSession($item['sid']);
			$index++;
		}
		$loginListNew[$sid] = array(
			'time' 	=> timeFloat(),
			'sid'	=> $sid,
			'ip' 	=> get_client_ip(),
			'ua'	=> $_SERVER['HTTP_USER_AGENT'].';',
			'device'=> Action("filter.userCheck")->getDevice()
		);
		if(isset($this->in['HTTP_X_PLATFORM'])){
			$loginListNew[$sid]['HTTP_X_PLATFORM'] = $this->in['HTTP_X_PLATFORM'];
		}
		$this->userListSet($user['userID'],$loginListNew);
	}
	
	public function logoutBefore($user){
		if(!is_array($user)) return;
		$this->userLogoutTrigger($user['userID'],Session::sign());
	}
	
	// 踢出登录用户(根据sessionID)
	public function userLogoutTrigger($userID,$sid){
		$loginList = $this->userListLoad($userID);
		if(!is_array($loginList[$sid])) return;

		$this->userLogoutSession($sid);
		unset($loginList[$sid]);
		$this->userListSet($userID,$loginList);
	}
	private function userLogoutSession($sid){
		$session = Session::getBySign($sid);
		if(!is_array($session['kodUser'])) return;
		$session['kodUser'] = false;
		$session['kodUserLogoutTrigger'] = true;
		Session::setBySign($sid,$session);
	}
	
	// 获取当前用户在线列表; 自动清理不在线的设备;
	public function userListLoad($userID){
		$key = 'userLoginList_'.$userID;
		$loginList = Cache::get($key);
		$loginList = is_array($loginList) ? $loginList : array();
		if(count($loginList) == 0) return $loginList;
		
		$loginListNew = array();
		foreach($loginList as $loginInfo){
			$session = Session::getBySign($loginInfo['sid']);
			if(!$session || !is_array($session['kodUser'])) continue;
			$loginListNew[$loginInfo['sid']] = $loginInfo;
		}
		$loginListNew = array_sort_by($loginListNew,'time');
		$this->userListSet($userID,$loginListNew);
		return $loginListNew;
	}
	private function userListSet($userID,$loginList){
		$key = 'userLoginList_'.$userID;
		Cache::set($key,$loginList,3600*24*30);
		// write_log($key.';count='.count($loginList).';'.ACTION);
	}
}
userRequest.class.php
wget 'https://sme10.lists2.roe3.org/kodbox/app/controller/filter/userRequest.class.php'
View Content
<?php

/**
 * url请求限制处理;
 * 
 * 限制用户请求过于频繁处理;
 * 限制用户同时进行中的长任务数量;
 */
class filterUserRequest extends Controller {
	function __construct() {
		parent::__construct();
	}
	public function bind(){
		$this->checkRequestMany();
		Hook::bind('Task.init',array($this,'taskCheck'));
	}
	public function taskCheck(){
		$taskAllowMax = $this->config['systemOption']['userTaskAllowMax'];
		$userID = Session::get('kodUser.userID');
		if(!$taskAllowMax || KodUser::isRoot() || !$userID) return;
		
		$result  = Task::listData($userID);
		if(count($result) > $taskAllowMax){
			$error = "Task is too many! (".$taskAllowMax.")";
			Task::log($error.';user='.$userID.';');
			show_json($error,false);
		}
	}
	
	/**
	 * 防止用户恶意请求;
	 */
	public function checkRequestMany(){
		//每分钟最大请求数; 300个则每秒5个,每5秒25个, 25个内小于5s
		if(KodUser::isRoot()) return;
		$requestPerMinuteMax = $this->config['systemOption']['requestPerMinuteMax'];
		$requestAllowPerMinuteMax = $this->config['systemOption']['requestAllowPerMinuteMax'];
		if(!$requestPerMinuteMax || !$requestAllowPerMinuteMax) return;
		$ingoreActions = array(
			'explorer.index.mkdir',
			'explorer.list.path',
			'explorer.index.mkfile',
			'explorer.upload.fileupload',
			
			'explorer.index.fileout',
			'explorer.share.file',
			'explorer.share.fileout',
			'explorer.share.fileupload',
			'user.setting.taskaction',
		);
		if(in_array(strtolower(ACTION),$ingoreActions)){
			$this->checkRequestTimer('userRequestAllow',$requestAllowPerMinuteMax);//
		}else{
			$this->checkRequestTimer('userRequest',$requestPerMinuteMax);//
		}
	}
	
	// 常规请求与高频词请求,分开处理;
	private function checkRequestTimer($key,$requestMax){
		$timeList = Session::get($key);
		$timeList = $timeList ? $timeList:array();
		$timeList[] = timeFloat();
		$seconds    = 5;// 检测间隔
		$lastNumber = intval($requestMax * ($seconds / 60));
		$count      = count($timeList);	
		if($count > $lastNumber){
			$timeUse = $timeList[$count - 1] - $timeList[$count - $lastNumber + 1];
			$timeList = array_slice($timeList,1,$lastNumber);
			if($timeUse < $seconds){
				Session::set($key,$timeList);
				// usleep(50000);//50ms;
				// write_log([$key,timeFloat(),$timeUse,$seconds,$requestMax,$timeList],'task');
				show_json("request too many!",false);exit;
			}
		}
		Session::set($key,$timeList);
	}
}