├── .gitignore ├── Casbin.png ├── README.md ├── composer.json ├── config ├── casbin-rbac-model.conf ├── casbin-restful-model.conf └── casbin.php ├── database └── 20210218074218_create_rule_table.php └── src ├── Enforcer.php ├── Permission.php ├── adapters └── DatabaseAdapter.php └── watcher └── RedisWatcher.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/* 2 | /vendor/ 3 | nohup.out 4 | composer.lock -------------------------------------------------------------------------------- /Casbin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/teamones-open/casbin/09f4f6385929887323a2bbe200b06110b62ab694/Casbin.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # casbin 2 | 基于 php-casbin 的 casbin 封装 3 | 4 | 5 | # 架构 6 | 7 | ![image](Casbin.png) 8 | 9 | # 引用 10 | 11 | ``` 12 | composer require teamones/casbin 13 | ``` -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "teamones/casbin", 3 | "description": "Teamones框架 casbin sdk", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Weijer Cheng", 9 | "email": "weiwei163@foxmail.com" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=7.4", 14 | "casbin/casbin": "~3.1", 15 | "workerman/redis": "^1.0|^2.0" 16 | }, 17 | "autoload": { 18 | "psr-4": { 19 | "teamones\\casbin\\": "src/" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /config/casbin-rbac-model.conf: -------------------------------------------------------------------------------- 1 | [request_definition] 2 | r = sub, dom, obj, act 3 | 4 | [policy_definition] 5 | p = sub, dom, obj, act 6 | 7 | [role_definition] 8 | g = _, _, _ 9 | 10 | [policy_effect] 11 | e = some(where (p.eft == allow)) 12 | 13 | [matchers] 14 | m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act -------------------------------------------------------------------------------- /config/casbin-restful-model.conf: -------------------------------------------------------------------------------- 1 | [request_definition] 2 | r = sub, obj, act 3 | 4 | [policy_definition] 5 | p = sub, obj, act 6 | 7 | [policy_effect] 8 | e = some(where (p.eft == allow)) 9 | 10 | [matchers] 11 | m = r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act) -------------------------------------------------------------------------------- /config/casbin.php: -------------------------------------------------------------------------------- 1 | [ 6 | 'model' => [ 7 | 'config_type' => 'file', 8 | 'config_file_path' => config_path() . '/casbin-restful-model.conf', 9 | 'config_text' => '', 10 | ], 11 | 'adapter' => [ 12 | 'type' => 'model', // model or adapter 13 | 'class' => \app\model\Rule::class, 14 | ], 15 | ], 16 | ]; -------------------------------------------------------------------------------- /database/20210218074218_create_rule_table.php: -------------------------------------------------------------------------------- 1 | table('rule', ['id' => false, 'primary_key' => ['id'], 'engine' => 'InnoDB', 'collation' => 'utf8mb4_general_ci', 'comment' => '规则表']); 35 | 36 | //添加数据字段 37 | $table->addColumn('id', 'integer', ['identity' => true, 'signed' => false, 'limit' => 11, 'comment' => '主键ID']) 38 | ->addColumn('ptype', 'char', ['default' => '', 'limit' => 8, 'comment' => '规则类型']) 39 | ->addColumn('v0', 'string', ['default' => '', 'limit' => 128]) 40 | ->addColumn('v1', 'string', ['default' => '', 'limit' => 128]) 41 | ->addColumn('v2', 'string', ['default' => '', 'limit' => 128]) 42 | ->addColumn('v3', 'string', ['default' => '', 'limit' => 128]) 43 | ->addColumn('v4', 'string', ['default' => '', 'limit' => 128]) 44 | ->addColumn('v5', 'string', ['default' => '', 'limit' => 128]); 45 | 46 | //执行创建 47 | $table->create(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Enforcer.php: -------------------------------------------------------------------------------- 1 | loadModel($config[$type]['model']['config_file_path']); 82 | } elseif ('text' == $configType) { 83 | $model->loadModelFromText($config[$type]['model']['config_text']); 84 | } 85 | 86 | // 实例化casbin adapter 适配器 87 | if (empty($config[$type]['adapter']) && empty($config[$type]['adapter']['type']) && empty($config[$type]['adapter']['class']) && !class_exists($config[$type]['adapter']['class'])) { 88 | throw new InvalidArgumentException("Enforcer adapter is not defined."); 89 | } 90 | 91 | switch ($config[$type]['adapter']['type']) { 92 | case 'model': 93 | // 使用支持 think-orm 的适配器 94 | $ruleModel = new $config[$type]['adapter']['class'](); 95 | $adapter = new DatabaseAdapter($ruleModel); 96 | break; 97 | case 'adapter': 98 | // 使用自定义适配器 99 | $adapter = new $config[$type]['adapter']['class'](); 100 | break; 101 | default: 102 | throw new InvalidArgumentException("Only model and adapter are supported."); 103 | break; 104 | } 105 | 106 | self::$_instance = new BaseEnforcer($model, $adapter, false); 107 | } 108 | return self::$_instance; 109 | } 110 | 111 | /** 112 | * @param $name 113 | * @param $arguments 114 | * @return mixed 115 | * @throws \Casbin\Exceptions\CasbinException 116 | */ 117 | public static function __callStatic($name, $arguments) 118 | { 119 | return static::instance('default')->{$name}(... $arguments); 120 | } 121 | 122 | } -------------------------------------------------------------------------------- /src/Permission.php: -------------------------------------------------------------------------------- 1 | setWatcher($watcher); 27 | $watcher->setUpdateCallback(function () use ($driver) { 28 | static::$_manager[$driver]->loadPolicy(); 29 | }); 30 | return static::$_manager[$driver]; 31 | } 32 | 33 | /** 34 | * @desc: 获取所有驱动 35 | * @return Enforcer[] 36 | */ 37 | public static function getAllDriver(): array 38 | { 39 | return static::$_manager; 40 | } 41 | 42 | 43 | /** 44 | * @desc: 静态调用 45 | * @param string $method 46 | * @param $arguments 47 | * @return mixed 48 | * @throws CasbinException 49 | * @author Tinywan(ShaoBo Wan) 50 | */ 51 | public static function __callStatic(string $method, $arguments) 52 | { 53 | return self::driver()->{$method}(...$arguments); 54 | } 55 | } -------------------------------------------------------------------------------- /src/adapters/DatabaseAdapter.php: -------------------------------------------------------------------------------- 1 | eloquent = $ruleModel; 31 | } 32 | 33 | /** 34 | * savePolicyLine function. 35 | * 36 | * @param string $ptype 37 | * @param array $rule 38 | */ 39 | public function savePolicyLine(string $ptype, array $rule): void 40 | { 41 | $col['ptype'] = $ptype; 42 | foreach ($rule as $key => $value) { 43 | $col['v' . strval($key)] = $value; 44 | } 45 | 46 | $this->eloquent->create($col); 47 | } 48 | 49 | /** 50 | * loads all policy rules from the storage. 51 | * @param Model $model 52 | * @throws \think\db\exception\DataNotFoundException 53 | * @throws \think\db\exception\DbException 54 | * @throws \think\db\exception\ModelNotFoundException 55 | */ 56 | public function loadPolicy(Model $model): void 57 | { 58 | $rows = $this->eloquent->field('ptype,v0,v1,v2,v3,v4,v5') 59 | ->select() 60 | ->toArray(); 61 | 62 | foreach ($rows as $row) { 63 | $line = implode(', ', array_filter($row, function ($val) { 64 | return '' != $val && !is_null($val); 65 | })); 66 | $this->loadPolicyLine(trim($line), $model); 67 | } 68 | } 69 | 70 | /** 71 | * saves all policy rules to the storage. 72 | * 73 | * @param Model $model 74 | */ 75 | public function savePolicy(Model $model): void 76 | { 77 | foreach ($model['p'] as $ptype => $ast) { 78 | foreach ($ast->policy as $rule) { 79 | $this->savePolicyLine($ptype, $rule); 80 | } 81 | } 82 | 83 | foreach ($model['g'] as $ptype => $ast) { 84 | foreach ($ast->policy as $rule) { 85 | $this->savePolicyLine($ptype, $rule); 86 | } 87 | } 88 | } 89 | 90 | /** 91 | * adds a policy rule to the storage. 92 | * This is part of the Auto-Save feature. 93 | * 94 | * @param string $sec 95 | * @param string $ptype 96 | * @param array $rule 97 | */ 98 | public function addPolicy(string $sec, string $ptype, array $rule): void 99 | { 100 | $this->savePolicyLine($ptype, $rule); 101 | } 102 | 103 | /** 104 | * This is part of the Auto-Save feature. 105 | * @param string $sec 106 | * @param string $ptype 107 | * @param array $rule 108 | * @throws \think\db\exception\DataNotFoundException 109 | * @throws \think\db\exception\DbException 110 | * @throws \think\db\exception\ModelNotFoundException 111 | */ 112 | public function removePolicy(string $sec, string $ptype, array $rule): void 113 | { 114 | $instance = $this->eloquent->where('ptype', $ptype); 115 | 116 | foreach ($rule as $key => $value) { 117 | $instance->where('v' . strval($key), $value); 118 | } 119 | 120 | $modelRows = $instance->select()->toArray(); 121 | 122 | foreach ($modelRows as $model) { 123 | $this->eloquent->where('id', $model['id'])->delete(); 124 | } 125 | } 126 | 127 | /** 128 | * RemoveFilteredPolicy removes policy rules that match the filter from the storage. 129 | * This is part of the Auto-Save feature. 130 | * 131 | * @param string $sec 132 | * @param string $ptype 133 | * @param int $fieldIndex 134 | * @param string ...$fieldValues 135 | * @throws \think\db\exception\DataNotFoundException 136 | * @throws \think\db\exception\DbException 137 | * @throws \think\db\exception\ModelNotFoundException 138 | */ 139 | public function removeFilteredPolicy(string $sec, string $ptype, int $fieldIndex, string ...$fieldValues): void 140 | { 141 | $instance = $this->eloquent->where('ptype', $ptype); 142 | foreach (range(0, 5) as $value) { 143 | if ($fieldIndex <= $value && $value < $fieldIndex + count($fieldValues)) { 144 | if ('' != $fieldValues[$value - $fieldIndex]) { 145 | $instance->where('v' . strval($value), $fieldValues[$value - $fieldIndex]); 146 | } 147 | } 148 | } 149 | 150 | $modelRows = $instance->select()->toArray(); 151 | 152 | foreach ($modelRows as $model) { 153 | $this->eloquent->where('id', $model['id'])->delete(); 154 | } 155 | } 156 | 157 | } -------------------------------------------------------------------------------- /src/watcher/RedisWatcher.php: -------------------------------------------------------------------------------- 1 | '127.0.0.1', 24 | * 'password' => '', 25 | * 'port' => 6379, 26 | * 'database' => 0, 27 | * 'channel' => '/casbin', 28 | * ] 29 | */ 30 | public function __construct(array $config, string $driver) 31 | { 32 | $this->pubRedis = $this->createRedisClient($config); 33 | $this->subRedis = $this->createRedisClient($config); 34 | $this->channel = ($config['channel'] ?? '/casbin') . '/' . $driver; 35 | 36 | $this->subRedis->subscribe([$this->channel], function ($channel, $message) { 37 | if ($this->callback) { 38 | call_user_func($this->callback); 39 | } 40 | }); 41 | } 42 | 43 | /** 44 | * Sets the callback function that the watcher will call when the policy in DB has been changed by other instances. 45 | * A classic callback is loadPolicy() method of Enforcer class. 46 | * 47 | * @param Closure $func 48 | */ 49 | public function setUpdateCallback(Closure $func): void 50 | { 51 | $this->callback = $func; 52 | } 53 | 54 | /** 55 | * Update calls the update callback of other instances to synchronize their policy. 56 | * It is usually called after changing the policy in DB, like savePolicy() method of Enforcer class, 57 | * addPolicy(), removePolicy(), etc. 58 | */ 59 | public function update(): void 60 | { 61 | $this->pubRedis->publish($this->channel, 'casbin rules updated'); 62 | } 63 | 64 | /** 65 | * Close stops and releases the watcher, the callback function will not be called any more. 66 | */ 67 | public function close(): void 68 | { 69 | $this->pubRedis->close(); 70 | $this->subRedis->close(); 71 | } 72 | 73 | /** 74 | * Create redis client 75 | * 76 | * @param array $config 77 | * @return Client 78 | */ 79 | private function createRedisClient(array $config): Client 80 | { 81 | $redis = new Client('redis://' . ($config['host'] ?? '127.0.0.1') . ':' . ($config['port'] ?? 6379)); 82 | $redis->auth($config['password'] ?? ''); 83 | 84 | return $redis; 85 | } 86 | } --------------------------------------------------------------------------------