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

471 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
*/
/**
* 文档操作权限旁路拦截;
*
* 如下是配置单个文档操作统一path参数进行权限检测
* 其他拦截path为虚拟目录只支持列表模式 explorer.list.path;
*/
class explorerAuth extends Controller {
private $actionPathCheck;
function __construct() {
parent::__construct();
$this->isShowError = true; //检测时输出报错;
$this->actionPathCheck = array(
'show' => array("explorer.list"=>'path,listAll'),
'view' => array(
'explorer.index'=>'fileOut,fileOutBy,fileView,fileThumb',
'explorer.editor' =>'fileGet'
),
'download' => array('explorer.index'=>'fileDownload'),// 下载/复制;下载/复制/文件预览打印
'upload' => array(
'explorer.upload' =>'fileUpload,serverDownload',
'explorer.index' =>'mkdir,mkfile',
),
'edit' => array(
'explorer.index' =>'mkdir,mkfile,setDesc,fileSave,pathRename,pathPast,pathCopyTo,pathCuteTo,setMeta',
'explorer.editor' =>'fileSave'
),
// 'remove' => array('explorer.index'=>'pathDelete'), //批量中处理
'share' => array('explorer.userShare'=>'add'),// get,edit,del 在userShare中自行判断(允许协作者(拥有者时)修改协作成员及权限)
'comment' => array('comment.index'=>''),
'event' => array('explorer.index'=>'pathLog'),
'root' => array('explorer.index'=>'setAuth'),
);
$this->actionCheckSpace = array(//空间大小检测
'explorer.upload' => 'fileUpload,serverDownload',
'explorer.index' => 'mkdir,mkfile,fileSave,pathPast,pathCopyTo,pathCuteTo,unzip',
'explorer.editor' => 'fileSave',
'explorer.share' => 'fileUpload',
);
Hook::bind('SourceModel.createBefore','explorer.auth.checkSpaceOnCreate');
}
// 文档操作权限统一拦截检测
public function autoCheck(){
$theMod = strtolower(MOD);
$theAction = strtolower(ACTION);
if($theMod !== 'explorer') return;
$this->targetSpaceCheck();//空间大小检测
Action('explorer.listGroup')->pathRootCheck($theAction);
// 多个请求或者包含来源去向的,分别进行权限判别;
switch ($theAction) {//小写
//自行判断权限,兼容文件关联附件获取信息情况;
// case 'explorer.index.pathinfo':$this->checkAuthArray('show');break;
case 'explorer.index.zip':$this->checkAuthArray('edit');break;
case 'explorer.index.zipdownload':$this->checkAuthArray('download');break;
case 'explorer.index.unzip':
$this->canRead($this->in['path']);
$this->canWrite($this->in['pathTo']);
break;
case 'explorer.index.pathdelete':$this->checkAuthArray('remove');break;
case 'explorer.index.pathcopy':$this->checkAuthArray('download');break;
case 'explorer.index.pathcute':
$this->checkAuthArray('remove');
break;
case 'explorer.index.pathcopyto':
$data = json_decode($this->in['dataArr'],true);
if(!is_array($data)){
return $this->errorMsg('param error:dataArr!');
}
// $this->checkAuthArray('download');
// 来源文档权限检测: 有编辑权限或者下载权限;
$this->isShowError = false;$errorNum = 0;
foreach ($data as $item) {
$result = $this->can($item['path'],'download') || $this->can($item['path'],'remove');
$errorNum = $result ? $errorNum: ($errorNum+1);
}
$this->isShowError = true;
if($errorNum){$this->errorMsg($this->lastError ? $this->lastError : LNG('explorer.noPermissionAction'),1007);}
$this->canWrite($this->in['path']);
break;
case 'explorer.index.pathcuteto':
$this->checkAuthArray('remove');
$this->canWrite($this->in['path']);
break;
default:
//直接检测定义在actionPathCheck中的方法参数为path直接检测
$actionType = $this->actionParse();
if(isset($actionType[$theAction])){
$authTypeArr = $actionType[$theAction];
$errorNum = 0; // 一个控制器对应多个权限点时; 所有都失败了才算失败;一个成功就算成功;
$this->isShowError = false;
for ($i=0; $i < count($authTypeArr); $i++) {
$result = $this->can($this->in['path'],$authTypeArr[$i]);
$errorNum = $result ? $errorNum: ($errorNum+1);
}
$this->isShowError = true;
if($errorNum == count($authTypeArr)){
$msg = $this->lastError ? $this->lastError : LNG('explorer.noPermissionAction');
$this->errorMsg($msg,1007);
}
}
break;
}
}
public function targetSpaceCheck(){
$actions = array();
foreach ($this->actionCheckSpace as $controller => $stActions) {
$stActions = explode(',',trim($stActions,','));
foreach ($stActions as $action) {
$actions[] = strtolower($controller.'.'.$action);
}
}
if(!in_array(strtolower(ACTION),$actions)) return;
if(!$this->spaceAllow($this->in['path'])){
show_json(LNG('explorer.spaceIsFull'),false);
}
}
public function spaceAllow($path){
$parse = KodIO::parse($path);
if($parse['isTruePath'] != true) return true;
if($parse['driverType'] == 'io') return true;
$info = IO::infoAuth($parse['pathBase']);//目标存储;
// 目标在回收站中: 不支持保存/上传/远程下载/粘贴/移动到此/新建文件/新建文件夹;
$fromDav = _get($GLOBALS,'requestFrom') == 'webdav';
if($info['isDelete'] == '1' && !$fromDav){
$msg = $info['type'] == 'file' ? LNG('explorer.pathInRecycleFile') : LNG("explorer.pathInRecycle");
show_json($msg,false);
}
$space = Action("explorer.list")->targetSpace($info);
if(!$space || $space['sizeMax']==0 ) return true; // 没有大小信息,或上限为0则放过;
$result = $space['sizeMax'] > $space['sizeUse'];
if(!$result){$this->lastError = LNG('explorer.spaceIsFull');}
return $result;
}
public function checkSpaceOnCreate($sourceInfo){
$space = false;
if($sourceInfo['targetType'] == SourceModel::TYPE_GROUP){
$space = $this->space('group',$sourceInfo['targetID']);
}else if($sourceInfo['targetType'] == SourceModel::TYPE_USER){
$space = $this->space('user',$sourceInfo['targetID']);
}
if(!$space || $space['sizeMax']==0 ) return true; // 没有大小信息,或上限为0则放过;
if($space['sizeMax'] <= $space['sizeUse']+ $sourceInfo['size'] ){
show_json(LNG('explorer.spaceIsFull'),false);
}
}
// 用户空间占用集中处理;
public function space($targetType,$targetID){
if($targetType == 'user'){//空间追加处理;
$target = Model('User')->getInfo($targetID);
}else{
$target = Model('Group')->getInfo($targetID);
}
$target['sizeUse'] = (!$target['sizeUse'] || $target['sizeUse'] <= 0 ) ? 0 : intval($target['sizeUse']);
$result = array(
'targetType' => $targetType,
'targetID' => $targetID,
'targetName' => $target['name'],
"sizeMax" => floatval($target['sizeMax'])*1024*1024*1024,
"sizeUse" => $target['sizeUse'],
);
$result = Hook::filter("explorer.targetSpace",$result);
return $result;
}
// 外部获取文件读写权限; Action("explorer.auth")->fileCanRead($path);
public function fileCanRead($file){
if($this->fileIsShareLink($file)){return $this->fileCan($file,'view');}
if(!ActionCall('user.authRole.authCanRead')) return false;
return $this->fileCan($file,'view');
}
public function fileCanDownload($file){
if($this->fileIsShareLink($file)){return $this->fileCan($file,'download');}
if(!ActionCall('user.authRole.authCanRead')) return false;
return $this->fileCan($file,'download');
}
public function fileCanWrite($file){
if($this->fileIsShareLink($file)){return $this->fileCan($file,'edit');}
if(!ActionCall('user.authRole.authCanEdit')) return false;
return $this->fileCan($file,'edit');
}
private function fileIsShareLink($file){
return strpos($file,'{shareItemLink:') === 0 ? true:false;
}
public function fileCan($file,$action){
if(request_url_safe($file)) return true;
$this->isShowError = false;
$result = $this->can($file,$action);
$this->isShowError = true;
return $result;
}
/**
* 检测文档权限,是否支持$action动作
* 目录解析:拦截只支持列目录,但当前方法为其他操作的
* 获取目录信息:自己的文档,则放过
*
* 权限判断:
* 1. 是不是:是不是自己的文档;是的话则跳过权限检测
* 2. 能不能:
* a. 是我所在的部门的文档:则通过权限&动作判别
* b. 是内部协作分享给我的:检测分享信息,通过权限&动作判别
* c. 其他情况:一律做权限拦截;
*
* 操作屏蔽remove不支持根目录用户根目录部门根目录分享根目录
*/
public function can($path,$action){
$isRoot = KodUser::isRoot();
$parse = KodIO::parse($path);
$ioType = $parse['type'];
if($ioType == KodIO::KOD_SHARE_LINK){
$shareInfo = Action('explorer.share')->sharePathInfo($path);
if($shareInfo && in_array($action,array('view','show'))) return true;
if($shareInfo && $action == 'download' && _get($shareInfo,'option.notDownload') !='1' ) return true;
if($shareInfo && $action == 'edit' && _get($shareInfo,'option.canEditSave') == '1' ){
if(Model('SystemOption')->get('shareLinkAllowEdit') == '0'){ // 全局开关;
return $this->errorMsg(LNG('explorer.pathNotSupport'),1108);
}
return true;
}
return $this->errorMsg(LNG('explorer.pathNotSupport'),1108);
}
if(!$isRoot && !Action('user.authRole')->canCheckRole($action)){
return $this->errorMsg(LNG('explorer.noPermissionAction'),1021);
}
// 物理路径 io路径拦截只有管理员且开启了访问才能做相关操作;
if( $ioType == KodIO::KOD_IO || $ioType == false ){
if( request_url_safe($path) ) return $action == 'view';
// 允许管理存储,即可访问系统回收站及挂载的存储.
$pathCanRead = Action('user.authRole')->authCan('admin.storage.edit');
if($pathCanRead && $this->config["ADMIN_ALLOW_IO"]) return true;
if($pathCanRead && $parse['id'] == 'systemRecycle') return true;
return $this->errorMsg(LNG('explorer.pathNotSupport'),1001);
}
//个人挂载目录;跨空间移动复制根据身份处理;
if( $ioType == KodIO::KOD_USER_DRIVER ) return true;
//不支持删除自己的桌面
if($action == 'remove'){
if(trim($path,'/') == trim(MY_DESKTOP,'/')){
return $this->errorMsg(LNG('explorer.desktopDelError'),1100);
}
if(trim($path,'/') == trim(MY_HOME,'/')){
return $this->errorMsg(LNG('explorer.pathNotSupport'),1100);
}
}
// 纯虚拟路径只能列表; 不支持其他任何操作;
if( $this->pathOnlyShow($path) ){
if($action == 'show') return true;
return $this->errorMsg(LNG('explorer.pathNotSupport'),1002);
}
//分享内容;分享子文档所属分享判别,操作分享权限判别;
if( $ioType == KodIO::KOD_SHARE_ITEM){
return $this->checkShare($parse['id'],trim($parse['param'],'/'),$action);
}
$pathInfo = IO::infoAuth($parse['pathBase']);
Hook::trigger("explorer.auth.can",$pathInfo,$action);
// 个人私密空间是否登录检测;
if($pathInfo && isset($pathInfo['sourceID']) && $pathInfo['targetType'] == 'user'){
$userSafeCheck = Action('explorer.listSafe')->authCheck($pathInfo,$action);
if($userSafeCheck){return $this->errorMsg($userSafeCheck,1101);}
}
// source 类型; 新建文件夹 {source:10}/新建文件夹; 去除
//文档类型检测:屏蔽用户和部门之外的类型;
if($this->allowRootSourceInfo($pathInfo)) return true;
$targetType = $pathInfo['targetType'];
// if(!$pathInfo) return true;
if(!$pathInfo){//不存在,不判断文档权限;
return $this->errorMsg(LNG('common.pathNotExists'),0);
}
if( $targetType != 'user' && $targetType != 'group' ){
return $this->errorMsg(LNG('explorer.noPermissionAction'),1003);
}
//个人文档;不属于自己
if( $targetType == 'user' && $pathInfo['targetID'] != USER_ID ){
return $this->errorMsg(LNG('explorer.noPermissionAction'),1004);
}
//部门文档:权限拦截;会自动匹配权限;我在的部门会有对应权限
if($targetType == 'group'){
$auth = $pathInfo['auth']['authValue'];
return $this->checkAuthMethod($auth,$action);
}
// 删除操作:拦截根文件夹;用户根文件夹,部门根文件夹
if( $pathInfo['parentID'] == '0' && $action=='remove' ){
return $this->errorMsg(LNG('explorer.noPermissionAction'),1100);
}
return true;
}
public function allowRootSourceInfo($pathInfo){
$isRoot = KodUser::isRoot();
if($isRoot && $this->config["ADMIN_ALLOW_SOURCE"]) return true;
if($isRoot && $pathInfo && $pathInfo['pathType'] == '{systemRecycle}') return true;
return false;
}
// 路径类型中: 检测目录是否可操作(属性,重命名,新建上传等); 纯虚拟路径只能列表;
public function pathOnlyShow($path){
$parse = KodIO::parse($path);
$truePath = array( // 可以操作的目录类型;
KodIO::KOD_IO,
KodIO::KOD_SOURCE,
KodIO::KOD_SHARE_ITEM,
);
$canAction = in_array($parse['type'],$truePath);
return $canAction? false:true;
}
private function errorMsg($msg,$code=false){
if($this->isShowError){
return show_json($msg,false,$code);
}
$this->lastError = $msg.$code;
return false;
}
public function getLastError(){return $this->lastError;}
public function canView($path){return $this->can($path,'view');}
public function canRead($path){return $this->can($path,'download');}
public function canWrite($path){return $this->can($path,'edit');}
private function checkAuthArray($action){
$data = json_decode($this->in['dataArr'],true);
if(!is_array($data)){
return $this->errorMsg('param error:dataArr!');
}
foreach ($data as $item) {
$this->can($item['path'],$action);
}
}
/**
* 根据权限值判别是否允许该动作
* $method: view,show,... AuthModel::authCheckShow...
*/
private function checkAuthMethod($auth,$method){
if(KodUser::isRoot() && $this->config["ADMIN_ALLOW_SOURCE"]) return true;
$auth = intval($auth);
if(!$auth || $auth == 0){
return $this->errorMsg(LNG('explorer.noPermissionAction'),1005);
}
//某文档有权限,打通上层文件夹通路;
if($method == 'show' && $auth === -1) return true;
$method = strtoupper(substr($method,0,1)).substr($method,1);
$method = 'authCheck'.$method;
$allow = Model('Auth')->$method($auth);
if(!$allow){
return $this->errorMsg(LNG('explorer.noPermissionAction'),1006);
}
return true;
}
/**
* 分享检测:
* 1. 分享存在;文档存在;
* 2. 文档属于该分享或该分享的子目录
* 2. 且自己在分享目标中; 权限不等于0 说明自己在该分享中;
* 3. 权限动作检测
*
* 分享根文件夹不支持删除操作;
*/
public function checkShare($shareID,$sourceID,$method){
$shareInfo = Model('Share')->getInfoAuth($shareID);
$sharePath = $shareInfo['sourceID'].'';
if(!$shareInfo || !$shareInfo['sourceInfo'] ){
return $this->errorMsg(LNG('explorer.share.notExist'));
}
if( $sharePath == $sourceID && $method =='remove' ){
return $this->errorMsg("source share root can't remove !");
}
// 自己协作分享的内容; 权限同自己拥有的权限;
if($shareInfo['userID'] == USER_ID){
$sourceInfo = $shareInfo['sourceInfo'];
if( $sourceInfo['targetType'] == 'user' && $sourceInfo['targetID'] == USER_ID ){
return Action('user.authRole')->canCheckRole($method);
}
if(!$shareInfo['sourceInfo']['auth']){ // 自己内部协作分享 io路径(拥有后台存储管理权限的角色)
return Action('explorer.authUser')->can($shareInfo['sourcePath'],$method,$shareInfo['userID']);
}
return $this->checkAuthMethod($shareInfo['sourceInfo']['auth']['authValue'],$method);
}
// 分享时间处理;
$timeout = intval(_get($shareInfo,'options.shareToTimeout',0));
if($timeout > 0 && $timeout < time()){
return $this->errorMsg(LNG('explorer.share.expiredTips'));
}
// 关闭协作分享的;
if($shareInfo['isShareTo'] == '0'){
return $this->errorMsg(LNG('explorer.share.notExist').'[status=0]');
}
// 内部协作分享有效性处理: 当分享者被禁用,没有分享权限,所在文件不再拥有分享权限时自动禁用外链分享;
if(!Action('explorer.authUser')->canShare($shareInfo)){
$userInfo = Model('User')->getInfoSimpleOuter($shareInfo['userID']);
$tips = '('.$userInfo['name'].' - '.LNG('common.noPermission').')';
return $this->errorMsg(LNG('explorer.share.notExist').$tips);
}
// 物理路径,io路径;
if($shareInfo['sourceID'] == '0'){
$sharePath = KodIO::clear($shareInfo['sourcePath']);
$thisPath = KodIO::clear($shareInfo['sourcePath'].$sourceID);
if(substr($thisPath,0,strlen($sharePath)) != $sharePath) return false;
return $this->checkAuthMethod($shareInfo['auth']['authValue'],$method);
}
if (!$sourceID) $sourceID = $shareInfo['sourceID'];
$sourceInfo = Model('Source')->sourceInfo($sourceID);
$parent = Model('Source')->parentLevelArray($sourceInfo['parentLevel']);
array_push($parent,$sourceID);
if(!$sourceInfo || !in_array($sourceID,$parent) ){
return $this->errorMsg(LNG('explorer.share.notExist'));
}
// 自己的分享,不判断权限;协作中添加了自己或自己所在的部门;
if( $sourceInfo['targetType'] == SourceModel::TYPE_USER &&
$sourceInfo['targetID'] == USER_ID ){
return true;
}
return $this->checkAuthMethod($shareInfo['auth']['authValue'],$method);
}
//解析上述配置到action列表统一转为小写;
private function actionParse(){
$actionArray = array();
foreach ($this->actionPathCheck as $authType => $modelActions) {
foreach ($modelActions as $controller => $stActions) {
if(!$stActions) continue;
$stActions = explode(',',trim($stActions,','));
foreach ($stActions as $action) {
$fullAction = strtolower($controller.'.'.$action);
if(!isset($actionArray[$fullAction])){
$actionArray[$fullAction] = array();
}
$actionArray[$fullAction][] = $authType;
}
}
}
// pr($actionArray,$this->actionPathCheck);exit;
return $actionArray;
}
}