|
@@ -3,19 +3,27 @@
|
|
|
|
|
|
|
|
namespace App\Http\Middleware;
|
|
namespace App\Http\Middleware;
|
|
|
|
|
|
|
|
-use App\Cache\UserCache;
|
|
|
|
|
use App\Consts\ErrorConst;
|
|
use App\Consts\ErrorConst;
|
|
|
use App\Libs\Utils;
|
|
use App\Libs\Utils;
|
|
|
use App\Facade\Site;
|
|
use App\Facade\Site;
|
|
|
-use App\Models\Channel\Channel;
|
|
|
|
|
use Closure;
|
|
use Closure;
|
|
|
use App\Exceptions\ApiException;
|
|
use App\Exceptions\ApiException;
|
|
|
-use Illuminate\Support\Facades\Log;
|
|
|
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
|
|
|
|
class CheckCompany
|
|
class CheckCompany
|
|
|
{
|
|
{
|
|
|
/**
|
|
/**
|
|
|
|
|
+ * 请求参数资源归属校验(公司维度)
|
|
|
|
|
+ *
|
|
|
|
|
+ * 规则:
|
|
|
|
|
+ * - 参数带资源 ID 时,校验该资源是否属于当前登录用户所在公司(cpid);
|
|
|
|
|
+ * - 同一公司内不同用户默认视为共享,不校验 user_id;
|
|
|
|
|
+ * - superadmin 等平台角色跳过校验(需要跨公司操作);
|
|
|
|
|
+ * - 参数未传时跳过,必填校验由业务层负责。
|
|
|
|
|
+ *
|
|
|
|
|
+ * 校验项由 config/param_ownership.php 配置,支持参数名映射、
|
|
|
|
|
+ * 批量参数(数组/逗号分隔/JSON)以及按路由覆盖。
|
|
|
|
|
+ *
|
|
|
* @param $request
|
|
* @param $request
|
|
|
* @param Closure $next
|
|
* @param Closure $next
|
|
|
* @return mixed
|
|
* @return mixed
|
|
@@ -30,31 +38,180 @@ class CheckCompany
|
|
|
if (!$token) Utils::throwError(ErrorConst::NOT_LOGIN);
|
|
if (!$token) Utils::throwError(ErrorConst::NOT_LOGIN);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (env('CHECK_COMPANY')) {
|
|
|
|
|
- $cpid = Site::getCpid();
|
|
|
|
|
-
|
|
|
|
|
- $script_id = getProp($params, 'script_id');
|
|
|
|
|
- if ($script_id) {
|
|
|
|
|
- if (!DB::table('mp_scripts')->where('id', $script_id)->where('cpid', $cpid)->value('id')) {
|
|
|
|
|
- Utils::throwError(ErrorConst::NOT_ACCESS);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ $config = config('param_ownership', []);
|
|
|
|
|
+ if (empty($config['enabled'])) {
|
|
|
|
|
+ return $next($request);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 平台角色需要跨公司操作,跳过归属校验
|
|
|
|
|
+ if (in_array(Site::getRole(), getProp($config, 'skip_roles', []), true)) {
|
|
|
|
|
+ return $next($request);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $cpid = Site::getCpid();
|
|
|
|
|
+ if (!$cpid) {
|
|
|
|
|
+ Utils::throwError(ErrorConst::NOT_ACCESS);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 内部公司白名单:灰度期间先放过自己人,避免误拦
|
|
|
|
|
+ if (in_array($cpid, getProp($config, 'skip_cpids', []), true)) {
|
|
|
|
|
+ return $next($request);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $ownerColumn = getProp($config, 'owner_column', 'cpid');
|
|
|
|
|
+ $path = $request->path();
|
|
|
|
|
+ $routeMap = getProp($config, 'routes', []);
|
|
|
|
|
+
|
|
|
|
|
+ // 汇总全部校验项:普通 / 批量 / 链式 / 链式批量 / 按路由覆盖
|
|
|
|
|
+ $paramMap = getProp($config, 'params', []);
|
|
|
|
|
+
|
|
|
|
|
+ foreach (getProp($config, 'array_params', []) as $key => $define) {
|
|
|
|
|
+ $define['batch'] = true;
|
|
|
|
|
+ $paramMap[$key] = $define;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ foreach (getProp($config, 'linked_params', []) as $key => $define) {
|
|
|
|
|
+ $paramMap[$key] = $define;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ foreach (getProp($config, 'linked_array_params', []) as $key => $define) {
|
|
|
|
|
+ $define['batch'] = true;
|
|
|
|
|
+ $paramMap[$key] = $define;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (isset($routeMap[$path]) && is_array($routeMap[$path])) {
|
|
|
|
|
+ foreach ($routeMap[$path] as $key => $define) {
|
|
|
|
|
+ $paramMap[$key] = $define;
|
|
|
}
|
|
}
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- $anime_id = getProp($params, 'anime_id');
|
|
|
|
|
- if ($anime_id) {
|
|
|
|
|
- if (!DB::table('mp_animes')->where('id', $anime_id)->where('cpid', $cpid)->value('id')) {
|
|
|
|
|
- Utils::throwError(ErrorConst::NOT_ACCESS);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ foreach ($paramMap as $key => $define) {
|
|
|
|
|
+ $this->checkOwnership($params, $key, $define, $cpid, $ownerColumn);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return $next($request);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 校验参数所指向资源的归属公司
|
|
|
|
|
+ *
|
|
|
|
|
+ * 支持两种模式:
|
|
|
|
|
+ * - 直接校验:目标表自带归属列(cpid);
|
|
|
|
|
+ * - 链式校验:目标表无归属列,先取记录再用外键到父表校验。
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param array $params 请求参数
|
|
|
|
|
+ * @param string $key 参数名
|
|
|
|
|
+ * @param array $define 配置(table / key / type / batch / via)
|
|
|
|
|
+ * @param int $cpid 当前公司ID
|
|
|
|
|
+ * @param string $ownerColumn 归属列名
|
|
|
|
|
+ * @throws ApiException
|
|
|
|
|
+ */
|
|
|
|
|
+ private function checkOwnership(array $params, string $key, array $define, int $cpid, string $ownerColumn): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $table = getProp($define, 'table');
|
|
|
|
|
+ if (!$table || !array_key_exists($key, $params)) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $keyColumn = getProp($define, 'key', 'id');
|
|
|
|
|
+ $type = getProp($define, 'type', 'int');
|
|
|
|
|
+ $batch = (bool)getProp($define, 'batch', false);
|
|
|
|
|
+ $via = getProp($define, 'via', null);
|
|
|
|
|
+
|
|
|
|
|
+ foreach ($this->toIdList($params[$key], $batch, $type) as $id) {
|
|
|
|
|
+ $owned = $via
|
|
|
|
|
+ ? $this->ownedVia($table, $keyColumn, $id, $via, $cpid, $ownerColumn)
|
|
|
|
|
+ : DB::table($table)->where($keyColumn, $id)->where($ownerColumn, $cpid)->exists();
|
|
|
|
|
+
|
|
|
|
|
+ if (!$owned) {
|
|
|
|
|
+ dLog('checkCompany')->info('资源归属校验未通过', [
|
|
|
|
|
+ 'path' => request()->path(),
|
|
|
|
|
+ 'param' => $key,
|
|
|
|
|
+ 'id' => $id,
|
|
|
|
|
+ 'table' => $table,
|
|
|
|
|
+ 'cpid' => $cpid,
|
|
|
|
|
+ 'uid' => Site::getUid(),
|
|
|
|
|
+ ]);
|
|
|
|
|
+ Utils::throwError(ErrorConst::NOT_ACCESS);
|
|
|
}
|
|
}
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 链式归属校验:按主键取记录,再用记录中的外键去父表校验归属
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param string $table 目标表
|
|
|
|
|
+ * @param string $keyColumn 目标表主键列
|
|
|
|
|
+ * @param mixed $id 主键值
|
|
|
|
|
+ * @param array $via ['local' => 外键列, 'table' => 父表, 'key' => 父表主键列]
|
|
|
|
|
+ * @param int $cpid 当前公司ID
|
|
|
|
|
+ * @param string $ownerColumn 归属列名
|
|
|
|
|
+ * @return bool
|
|
|
|
|
+ */
|
|
|
|
|
+ private function ownedVia(string $table, string $keyColumn, $id, array $via, int $cpid, string $ownerColumn): bool
|
|
|
|
|
+ {
|
|
|
|
|
+ $local = getProp($via, 'local');
|
|
|
|
|
+ $refTable = getProp($via, 'table');
|
|
|
|
|
+ $refKey = getProp($via, 'key', 'id');
|
|
|
|
|
+ if (!$local || !$refTable) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- $episode_id = getProp($params, 'episode_id');
|
|
|
|
|
- if ($episode_id) {
|
|
|
|
|
- if (!DB::table('mp_anime_episodes')->where('id', $episode_id)->where('cpid', $cpid)->value('id')) {
|
|
|
|
|
- Utils::throwError(ErrorConst::NOT_ACCESS);
|
|
|
|
|
|
|
+ // 资源不存在时按无权限处理(fail-closed)
|
|
|
|
|
+ $row = DB::table($table)->where($keyColumn, $id)->first([$local]);
|
|
|
|
|
+ if (!$row) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $refValue = getProp($row, $local);
|
|
|
|
|
+ if ($refValue === null || $refValue === '') {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return DB::table($refTable)->where($refKey, $refValue)->where($ownerColumn, $cpid)->exists();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 统一转换为 ID 列表(兼容单值、数组、逗号分隔、JSON 数组)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param mixed $value
|
|
|
|
|
+ * @param bool $batch 是否允许逗号分隔 / JSON 数组
|
|
|
|
|
+ * @param string $type 主键类型:int(默认)/ string
|
|
|
|
|
+ * @return array
|
|
|
|
|
+ */
|
|
|
|
|
+ private function toIdList($value, bool $batch, string $type = 'int'): array
|
|
|
|
|
+ {
|
|
|
|
|
+ if (is_array($value)) {
|
|
|
|
|
+ $items = $value;
|
|
|
|
|
+ } elseif ($batch && is_string($value)) {
|
|
|
|
|
+ $decoded = json_decode($value, true);
|
|
|
|
|
+ $items = is_array($decoded) ? $decoded : explode(',', $value);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $items = [$value];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $ids = [];
|
|
|
|
|
+ foreach ($items as $item) {
|
|
|
|
|
+ if (is_array($item)) {
|
|
|
|
|
+ $ids = array_merge($ids, $this->toIdList($item, $batch, $type));
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if ($type === 'string') {
|
|
|
|
|
+ // 字符串主键(如 mp_episode_segments.segment_id)需保留原值
|
|
|
|
|
+ $val = trim((string)$item);
|
|
|
|
|
+ if ($val !== '') {
|
|
|
|
|
+ $ids[] = $val;
|
|
|
}
|
|
}
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $id = (int)$item;
|
|
|
|
|
+ if ($id > 0) {
|
|
|
|
|
+ $ids[] = $id;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return $next($request);
|
|
|
|
|
|
|
+ return array_values(array_unique($ids));
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|